灵符lingfu

lingmodeler

↓ 0 downloads

LingModeler: box modeling with quads and tris, rigging + weight painting, 2D vector illustration and animation, procedural textures and game assets; .lmesh / .ling native formats, OBJ, glTF and SVG import / export

Windows · x86-64Linux · x86-64LingOS · x86-64LingOS · Pi 5 ARM64Web · WASM
Keywords 3d, modeling, vector, svg, gltf, obj, rigging, animation, textures, ling
lingfu install lingmodeler
LicenseApache-2.0 OR MIT
Authorsling-lin
READMECode / FilesVersionsDependency GraphDependentsSecurityBuildsSettings

Code / Files (67)

ling/ds.ling
# ━━━ ds.ling — Ling design system toolkit (shared with LingStationX) ━━━
# Line work only (outline + crosshatch), square corners, tile lettering.
# Colour keys (what each colour means on a panel):
#   asm red = sources / danger   bind yellow = presets   ctl green = time, animation
#   call blue = in / out, selection   func indigo = transforms   mod violet = identity   ink grey = structure
# Colours are lifted 22% toward white so they read on the black ground.

fn ds_col(key) {
    if key == "asm" { return c_mix(c_rgb(229.0, 56.0, 59.0), 16777215.0, 0.22) }
    if key == "lit" { return c_mix(c_rgb(247.0, 127.0, 0.0), 16777215.0, 0.22) }
    if key == "bind" { return c_mix(c_rgb(209.0, 155.0, 0.0), 16777215.0, 0.22) }
    if key == "ctl" { return c_mix(c_rgb(42.0, 157.0, 73.0), 16777215.0, 0.22) }
    if key == "call" { return c_mix(c_rgb(31.0, 111.0, 214.0), 16777215.0, 0.22) }
    if key == "func" { return c_mix(c_rgb(75.0, 63.0, 196.0), 16777215.0, 0.22) }
    if key == "mod" { return c_mix(c_rgb(155.0, 63.0, 201.0), 16777215.0, 0.22) }
    return c_mix(c_rgb(86.0, 101.0, 122.0), 16777215.0, 0.22)
}

# clip to a rectangle without losing the surrounding scroll clip
fn ds_clip_to(x, y, w, h) {
    bind x0 = x
    bind y0 = y
    bind x1 = x + w
    bind y1 = y + h
    if st_get("clip_on", 0.0) > 0.5 {
        bind x0 = max(x0, st_get("clip_x0", 0.0))
        bind y0 = max(y0, st_get("clip_y0", 0.0))
        bind x1 = min(x1, st_get("clip_x1", 100000.0))
        bind y1 = min(y1, st_get("clip_y1", 100000.0))
    }
    px_clip(x0, y0, max(0.0, x1 - x0), max(0.0, y1 - y0))
}
fn ds_clip_end() {
    if st_get("clip_on", 0.0) > 0.5 {
        px_clip(st_get("clip_x0", 0.0), st_get("clip_y0", 0.0), st_get("clip_x1", 0.0) - st_get("clip_x0", 0.0), st_get("clip_y1", 0.0) - st_get("clip_y0", 0.0))
    } else { px_unclip() }
}

# parallel hatch lines at `ang` degrees (45 = rising to the right), `sp` px apart, clipped to the box
fn ds_hatch(x, y, w, h, col, ang, sp, a) {
    if w < 1.0 || h < 1.0 { return 0.0 }
    if sp < 1.0 { bind sp = 1.0 }
    bind r = ang * 3.14159265 / 180.0
    bind dx = cos(r)
    bind dy = 0.0 - sin(r)
    bind nx = sin(r)
    bind ny = cos(r)
    bind p0 = x * nx + y * ny
    bind p1 = (x + w) * nx + y * ny
    bind p2 = x * nx + (y + h) * ny
    bind p3 = (x + w) * nx + (y + h) * ny
    bind lo = min(min(p0, p1), min(p2, p3))
    bind hi = max(max(p0, p1), max(p2, p3))
    bind ln = w + h
    bind s0 = (x + w * 0.5) * dx + (y + h * 0.5) * dy
    ds_clip_to(x, y, w, h)
    bind t = lo + sp * 0.5
    while t < hi {
        px_line(nx * t + dx * (s0 - ln), ny * t + dy * (s0 - ln), nx * t + dx * (s0 + ln), ny * t + dy * (s0 + ln), col, a)
        bind t = t + sp
    }
    ds_clip_end()
}
# crosshatch: 45 and -45
fn ds_cross(x, y, w, h, col, sp, a) {
    ds_hatch(x, y, w, h, col, 45.0, sp, a)
    ds_hatch(x, y, w, h, col, 135.0, sp, a)
}

# corner brackets
fn ds_brackets(x, y, w, h, n, col) {
    px_hline(x, y, n, col)
    px_vline(x, y, n, col)
    px_hline(x + w - n, y, n, col)
    px_vline(x + w - 1.0, y, n, col)
    px_hline(x, y + h - 1.0, n, col)
    px_vline(x, y + h - n, n, col)
    px_hline(x + w - n, y + h - 1.0, n, col)
    px_vline(x + w - 1.0, y + h - n, n, col)
}

# one letter in a square tile (glyph fitted to 60% of the tile)
fn ds_tile(x, y, s, ch, col, on) {
    px_outline(x, y, s, s, col, 0.7)
    ds_brackets(x, y, s, s, max(2.0, floor(s * 0.28)), col)
    bind fs = s * 0.8
    bind gw = g_text_w(fs, ch)
    px_text(st_get("font", 0.0), x + (s - gw) * 0.5, y + (s - fs) * 0.5 - 1.0, fs, sel(on, 0.0, col), ch)
}
fn ds_tile_fill(x, y, s, col) {
    ds_cross(x + 1.0, y + 1.0, s - 2.0, s - 2.0, col, 3.0, 0.55)
}
# a word, one letter per tile; `on` = hatched. Returns the width.
fn ds_word(x, y, s, text, col, on) {
    bind n = str_len(text)
    bind i = 0.0
    while i < n {
        bind tx = x + i * (s + 1.0)
        if on { ds_hatch(tx + 1.0, y + 1.0, s - 2.0, s - 2.0, col, 45.0, 3.0, 0.7) }
        ds_tile(tx, y, s, str_slice(text, i, 1.0), col, false)
        bind i = i + 1.0
    }
    return n * (s + 1.0)
}
fn ds_word_w(s, text) { return str_len(text) * (s + 1.0) }

# double-rule frame with corner brackets and a tabbed title
fn ds_frame(x, y, w, h, col, title) {
    px_outline(x, y, w, h, col, 0.55)
    px_outline(x + 3.0, y + 3.0, w - 6.0, h - 6.0, col, 0.35)
    ds_brackets(x - 1.0, y - 1.0, w + 2.0, h + 2.0, 10.0, col)
    bind tw = ds_word_w(14.0, title) + 10.0
    px_rect(x + 14.0, y - 8.0, tw, 16.0, 0.0)
    ds_word(x + 19.0, y - 7.0, 14.0, title, col, false)
}

# ── controls ─────────────────────────────────────────────────────────────────
# selector: a row of tile words; returns the new index or -1
fn ds_selector(id, x, y, s, names, cur, col, tip) {
    bind out = 0.0 - 1.0
    bind px = x
    bind i = 0.0
    while i < len(names) {
        bind nm = list_get(names, i)
        bind ww = ds_word_w(s, nm)
        bind on = i == cur
        ds_word(px, y, s, nm, sel(on, col, ds_col("ink")), on)
        if u_hot(px, y, ww, s) {
            u_tip(tip + ": " + nm)
            if u_pressed() { bind out = i }
        }
        bind px = px + ww + 7.0
        bind i = i + 1.0
    }
    return out
}
fn ds_selector_w(s, names) {
    bind w = 0.0
    bind i = 0.0
    while i < len(names) {
        bind w = w + ds_word_w(s, list_get(names, i)) + 7.0
        bind i = i + 1.0
    }
    return w
}

# switch: a tile, crosshatched when on, with a tile-word label. Returns the new state (0/1) or -1.
fn ds_switch(id, x, y, s, label, on, col, tip) {
    bind out = 0.0 - 1.0
    px_outline(x, y, s, s, sel(on, col, ds_col("ink")), 0.8)
    ds_brackets(x, y, s, s, 4.0, sel(on, col, ds_col("ink")))
    if on { ds_cross(x + 1.0, y + 1.0, s - 2.0, s - 2.0, col, 3.0, 0.7) }
    ds_word(x + s + 5.0, y + (s - 12.0) * 0.5, 12.0, label, sel(on, col, ds_col("ink")), false)
    bind w = s + 8.0 + ds_word_w(12.0, label)
    if u_hot(x, y, w, s) {
        u_tip(tip + sel(on, " (on)", " (off)"))
        if u_pressed() { bind out = sel(on, 0.0, 1.0) }
    }
    return out
}

# knob: 270 degree arc, swept part crosshatched, the rest single-hatched, hatched tapered hand, pin.
# Drag vertically (Shift = fine), wheel works too. Returns the new 0..1 value or -999.
fn ds_knob(id, cx, cy, r, label, v, col, readout, tip) {
    bind result = 0.0 - 999.0
    bind rin = r * 0.58
    bind hot = u_hot(cx - r - 3.0, cy - r - 3.0, (r + 3.0) * 2.0, (r + 3.0) * 2.0 + 14.0)
    bind dragging = u_active() == id
    if hot && u_pressed() {
        u_set_active(id)
        st_set("kn_y", u_my())
        st_set("kn_v", v)
        bind dragging = true
    }
    if dragging {
        if u_down() {
            bind sens = sel(u_shift(), 600.0, 150.0)
            bind nv = clampf(st_get("kn_v", v) + (st_get("kn_y", 0.0) - u_my()) / sens, 0.0, 1.0)
            if abs(nv - v) > 0.0005 { bind result = nv }
        } else { u_set_active("") bind dragging = false }
    } else if hot && u_scroll() != 0.0 && st_get("wheel_used", 0.0) < 0.5 {
        bind result = clampf(v + u_scroll() * sel(u_shift(), 0.004, 0.02), 0.0, 1.0)
        st_set("wheel_used", 1.0)
    }
    if hot { u_tip(tip) }
    bind shown = sel(result > -500.0, result, v)
    bind a0 = 135.0
    bind sw = 270.0 * shown
    bind k = 0.0
    while k < 270.0 {
        bind ang = (a0 + k) * 3.14159265 / 180.0
        bind ang2 = (a0 + k + 15.0) * 3.14159265 / 180.0
        bind swept = k < sw
        px_line(cx + cos(ang) * r, cy + sin(ang) * r, cx + cos(ang2) * r, cy + sin(ang2) * r, col, sel(swept, 0.95, 0.4))
        px_line(cx + cos(ang) * rin, cy + sin(ang) * rin, cx + cos(ang2) * rin, cy + sin(ang2) * rin, col, sel(swept, 0.8, 0.3))
        bind k = k + 15.0
    }
    bind k = 0.0
    while k <= 270.0 {
        bind ang = (a0 + k) * 3.14159265 / 180.0
        bind swept = k <= sw
        if swept {
            px_line(cx + cos(ang) * rin, cy + sin(ang) * rin, cx + cos(ang) * r, cy + sin(ang) * r, col, 0.7)
        } else if k % 20.0 < 0.5 {
            px_line(cx + cos(ang) * rin, cy + sin(ang) * rin, cx + cos(ang) * r, cy + sin(ang) * r, col, 0.3)
        }
        bind k = k + 10.0
    }
    bind ha = (a0 + sw) * 3.14159265 / 180.0
    bind hx = cos(ha)
    bind hy = sin(ha)
    bind tipx = cx + hx * (rin - 1.0)
    bind tipy = cy + hy * (rin - 1.0)
    px_line(cx - hy * 2.5, cy + hx * 2.5, tipx, tipy, col, 0.9)
    px_line(cx + hy * 2.5, cy - hx * 2.5, tipx, tipy, col, 0.9)
    px_line(cx, cy, tipx, tipy, col, 0.9)
    px_ring(cx, cy, 2.5, col, 1.0)
    bind txt = sel(dragging, readout, label)
    bind ts = 9.0
    bind lw = ds_word_w(ts, txt)
    ds_word(cx - lw * 0.5, cy + r + 4.0, ts, txt, sel(dragging, c_mix(col, 16777215.0, 0.35), col), dragging)
    return result
}

# vertical fader: hatched fill up to the value. Returns new 0..1 or -999.
fn ds_fader(id, x, y, w, h, label, v, col, spring, tip) {
    bind result = 0.0 - 999.0
    bind hot = u_hot(x, y, w, h)
    if hot { u_tip(tip) }
    if hot && u_pressed() { u_set_active(id) }
    if u_active() == id {
        if u_down() {
            bind result = clampf(1.0 - (u_my() - y) / h, 0.0, 1.0)
        } else {
            u_set_active("")
            if spring >= 0.0 { bind result = spring }
        }
    }
    bind shown = sel(result > -500.0, result, v)
    px_outline(x, y, w, h, col, 0.7)
    px_outline(x + 2.0, y + 2.0, w - 4.0, h - 4.0, col, 0.3)
    bind fh = (h - 4.0) * shown
    ds_cross(x + 3.0, y + h - 3.0 - fh, w - 6.0, fh, col, 3.0, 0.6)
    px_hline(x - 2.0, y + h - 3.0 - fh, w + 4.0, col, 1.0)
    bind lw = ds_word_w(9.0, label)
    ds_word(x + (w - lw) * 0.5, y + h + 4.0, 9.0, label, col, false)
    return result
}

← Back to index