灵符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/anim.ling
# ━━━ anim.ling — 2D vector animation: transform/opacity keyframes, shape keys, onion skin ━━━
# Per item: keys = flat [channel, frame, value] (0 tx 1 ty 2 rot 3 sx 4 sy 5 op); skf = shape-key frames, skp = pts per shape key.
# Interpolation is smoothstep-eased between neighbouring keys; shape keys need equal node counts (else they snap).

fn an_init() { st_set("an_fps", 24.0) }
fn an_channels() { return ["tx", "ty", "rot", "sx", "sy", "op"] }

fn an_targets() {
    bind s = vc_selected()
    if len(s) > 0.5 { return s }
    return []
}
fn an_put(keys, ch, frame, v) {
    bind i = 0.0
    while i + 2.0 < len(keys) {
        if list_get(keys, i) == ch && abs(list_get(keys, i + 1.0) - frame) < 0.001 { return list_set(keys, i + 2.0, v) }
        bind i = i + 3.0
    }
    bind keys = list_push(keys, ch) bind keys = list_push(keys, frame)
    return list_push(keys, v)
}
fn an_key() {
    bind s = an_targets()
    if len(s) < 0.5 { st_set("status", "Select items to keyframe") return 0.0 }
    vc_push()
    bind f = tl_frame()
    bind chans = an_channels()
    bind i = 0.0
    while i < len(s) {
        bind k = list_get(s, i)
        bind keys = st_get(vk(k, "keys"), [])
        bind c = 0.0
        while c < 6.0 { bind keys = an_put(keys, c, f, st_get(vk(k, list_get(chans, c)), sel(c == 3.0 || c == 4.0 || c == 5.0, 1.0, 0.0))) bind c = c + 1.0 }
        st_set(vk(k, "keys"), keys)
        bind i = i + 1.0
    }
    if f > st_get("tl_end", 48.0) { st_set("tl_end", f) }
    st_set("status", "Keyed " + num_str(len(s)) + " items at frame " + fmt0(f))
}
fn an_shapekey() {
    bind s = an_targets()
    if len(s) < 0.5 { st_set("status", "Select items to shape-key") return 0.0 }
    vc_push()
    bind f = tl_frame()
    bind i = 0.0
    while i < len(s) {
        bind k = list_get(s, i)
        bind skf = st_get(vk(k, "skf"), []) bind skp = st_get(vk(k, "skp"), [])
        bind found = 0.0 - 1.0
        bind j = 0.0
        while j < len(skf) { if abs(list_get(skf, j) - f) < 0.001 { bind found = j } bind j = j + 1.0 }
        if found >= 0.0 { bind skp = list_set(skp, found, st_get(vk(k, "pts"), [])) }
        else { bind skf = list_push(skf, f) bind skp = list_push(skp, st_get(vk(k, "pts"), [])) }
        st_set(vk(k, "skf"), skf) st_set(vk(k, "skp"), skp)
        bind i = i + 1.0
    }
    if f > st_get("tl_end", 48.0) { st_set("tl_end", f) }
    st_set("status", "Shape key at frame " + fmt0(f) + " (node edits morph between shape keys)")
}
fn an_unkey() {
    bind s = an_targets()
    if len(s) < 0.5 { return 0.0 }
    vc_push()
    bind f = tl_frame()
    bind i = 0.0
    while i < len(s) {
        bind k = list_get(s, i)
        bind keys = st_get(vk(k, "keys"), [])
        bind out = list_new()
        bind j = 0.0
        while j + 2.0 < len(keys) {
            if abs(list_get(keys, j + 1.0) - f) > 0.001 { bind out = list_push(out, list_get(keys, j)) bind out = list_push(out, list_get(keys, j + 1.0)) bind out = list_push(out, list_get(keys, j + 2.0)) }
            bind j = j + 3.0
        }
        st_set(vk(k, "keys"), out)
        bind skf = st_get(vk(k, "skf"), []) bind skp = st_get(vk(k, "skp"), [])
        bind nf = list_new() bind np = list_new()
        bind j = 0.0
        while j < len(skf) { if abs(list_get(skf, j) - f) > 0.001 { bind nf = list_push(nf, list_get(skf, j)) bind np = list_push(np, list_get(skp, j)) } bind j = j + 1.0 }
        st_set(vk(k, "skf"), nf) st_set(vk(k, "skp"), np)
        bind i = i + 1.0
    }
    st_set("status", "Removed keys at frame " + fmt0(f))
}
fn an_ease(t) { return t * t * (3.0 - 2.0 * t) }
# evaluate every item at frame f
fn an_eval(f) {
    bind ids = vc_ids()
    bind chans = an_channels()
    bind i = 0.0
    while i < len(ids) {
        bind k = list_get(ids, i)
        bind keys = st_get(vk(k, "keys"), [])
        if len(keys) > 2.5 {
            bind c = 0.0
            while c < 6.0 {
                bind pf = 0.0 - 1000000.0 bind pv = 0.0 bind nf = 1000000.0 bind nv = 0.0 bind has = false
                bind j = 0.0
                while j + 2.0 < len(keys) {
                    if list_get(keys, j) == c {
                        bind has = true
                        bind kf = list_get(keys, j + 1.0)
                        if kf <= f && kf > pf { bind pf = kf bind pv = list_get(keys, j + 2.0) }
                        if kf > f && kf < nf { bind nf = kf bind nv = list_get(keys, j + 2.0) }
                    }
                    bind j = j + 3.0
                }
                if has {
                    bind v = 0.0
                    if pf < 0.0 - 999999.0 { bind v = nv } else if nf > 999999.0 { bind v = pv }
                    else { bind v = pv + (nv - pv) * an_ease((f - pf) / max(0.0001, nf - pf)) }
                    st_set(vk(k, list_get(chans, c)), v)
                }
                bind c = c + 1.0
            }
        }
        bind skf = st_get(vk(k, "skf"), [])
        if len(skf) > 0.5 {
            bind skp = st_get(vk(k, "skp"), [])
            bind pi = 0.0 - 1.0 bind ni = 0.0 - 1.0 bind pf = 0.0 - 1000000.0 bind nf = 1000000.0
            bind j = 0.0
            while j < len(skf) {
                bind kf = list_get(skf, j)
                if kf <= f && kf > pf { bind pf = kf bind pi = j }
                if kf > f && kf < nf { bind nf = kf bind ni = j }
                bind j = j + 1.0
            }
            if pi < 0.0 { st_set(vk(k, "pts"), list_get(skp, ni)) }
            else if ni < 0.0 { st_set(vk(k, "pts"), list_get(skp, pi)) }
            else {
                bind a = list_get(skp, pi) bind b = list_get(skp, ni)
                if len(a) == len(b) {
                    bind t = an_ease((f - pf) / max(0.0001, nf - pf))
                    bind out = list_new()
                    bind q = 0.0
                    while q < len(a) { bind out = list_push(out, list_get(a, q) + (list_get(b, q) - list_get(a, q)) * t) bind q = q + 1.0 }
                    st_set(vk(k, "pts"), out)
                } else { st_set(vk(k, "pts"), a) }
            }
        }
        bind i = i + 1.0
    }
}
fn an_key_frames() {
    bind s = vc_selected()
    if len(s) < 0.5 { bind s = vc_ids() }
    bind out = list_new()
    bind i = 0.0
    while i < len(s) {
        bind k = list_get(s, i)
        bind keys = st_get(vk(k, "keys"), [])
        bind j = 0.0
        while j + 2.0 < len(keys) { bind out = an_add_unique(out, list_get(keys, j + 1.0)) bind j = j + 3.0 }
        bind skf = st_get(vk(k, "skf"), [])
        bind j = 0.0
        while j < len(skf) { bind out = an_add_unique(out, list_get(skf, j)) bind j = j + 1.0 }
        bind i = i + 1.0
    }
    return out
}
fn an_add_unique(l, f) {
    bind i = 0.0
    while i < len(l) { if abs(list_get(l, i) - f) < 0.001 { return l } bind i = i + 1.0 }
    return list_push(l, f)
}
fn an_jump(dir) {
    bind keys = an_key_frames()
    bind f = tl_frame()
    bind best = f
    bind bd = 1000000.0
    bind i = 0.0
    while i < len(keys) {
        bind k = list_get(keys, i)
        bind d = (k - f) * dir
        if d > 0.001 && d < bd { bind bd = d bind best = k }
        bind i = i + 1.0
    }
    tl_set_frame(best)
}
# state save / restore around an off-frame evaluation
fn an_save() {
    bind ids = vc_ids()
    bind chans = an_channels()
    bind snap = list_new()
    bind i = 0.0
    while i < len(ids) {
        bind k = list_get(ids, i)
        bind c = 0.0
        while c < 6.0 { bind snap = list_push(snap, st_get(vk(k, list_get(chans, c)), 0.0)) bind c = c + 1.0 }
        st_set("ans_pts_" + num_str(k), st_get(vk(k, "pts"), []))
        bind i = i + 1.0
    }
    st_set("ans_snap", snap)
}
fn an_restore() {
    bind ids = vc_ids()
    bind chans = an_channels()
    bind snap = st_get("ans_snap", [])
    bind i = 0.0
    while i < len(ids) {
        bind k = list_get(ids, i)
        bind c = 0.0
        while c < 6.0 { st_set(vk(k, list_get(chans, c)), list_get(snap, i * 6.0 + c)) bind c = c + 1.0 }
        st_set(vk(k, "pts"), st_get("ans_pts_" + num_str(k), []))
        bind i = i + 1.0
    }
}
fn an_draw_onion() {
    bind keys = an_key_frames()
    bind f = tl_frame()
    bind prev = 0.0 - 1.0 bind nxt = 0.0 - 1.0
    bind i = 0.0
    while i < len(keys) {
        bind k = list_get(keys, i)
        if k < f - 0.001 && (prev < 0.0 || k > prev) { bind prev = k }
        if k > f + 0.001 && (nxt < 0.0 || k < nxt) { bind nxt = k }
        bind i = i + 1.0
    }
    if prev < 0.0 && nxt < 0.0 { return 0.0 }
    an_save()
    bind ids = vc_ids()
    if prev >= 0.0 {
        an_eval(prev)
        bind i = 0.0
        while i < len(ids) { vc_draw_item(list_get(ids, i), C_RED) bind i = i + 1.0 }
    }
    if nxt >= 0.0 {
        an_eval(nxt)
        bind i = 0.0
        while i < len(ids) { vc_draw_item(list_get(ids, i), C_GREEN) bind i = i + 1.0 }
    }
    an_restore()
}
fn an_props(x, y, w) {
    bind cy = y
    u_header(x, cy, w, "Animation", ds_col("ctl"))
    bind cy = cy + 20.0
    g_text(x, cy, 11.0, C_TEXT, "Frame " + fmt0(tl_frame()) + " of " + fmt0(st_get("tl_end", 48.0)) + " at " + fmt0(st_get("tl_fps", 24.0)) + " fps")
    bind cy = cy + 20.0
    bind bw = floor((w - 6.0) / 2.0)
    if u_button("anp_key", x, cy, bw, 22.0, "Key (I)", C_BLUE) { an_key() }
    if u_button("anp_shape", x + bw + 6.0, cy, bw, 22.0, "Shape key (K)", C_BLUE) { an_shapekey() }
    bind cy = cy + 26.0
    if u_button("anp_unkey", x, cy, bw, 22.0, "Delete keys", 0.0) { an_unkey() }
    if u_button("anp_play", x + bw + 6.0, cy, bw, 22.0, sel(tl_playing(), "Pause", "Play (Space)"), 0.0) { tl_toggle_play() }
    bind cy = cy + 26.0
    if u_button("anp_prev", x, cy, bw, 22.0, "< Prev key", 0.0) { an_jump(0.0 - 1.0) }
    if u_button("anp_next", x + bw + 6.0, cy, bw, 22.0, "Next key >", 0.0) { an_jump(1.0) }
    bind cy = cy + 30.0
    if u_check("anp_onion", x, cy, "Onion skin", st_get("vc_onion", 0.0) > 0.5) { st_set("vc_onion", 1.0 - st_get("vc_onion", 0.0)) }
    bind cy = cy + 22.0
    if u_check("anp_auto", x, cy, "Auto key on edit", st_get("autokey", 0.0) > 0.5) { st_set("autokey", 1.0 - st_get("autokey", 0.0)) }
    bind cy = cy + 30.0
    u_header(x, cy, w, "Export", ds_col("call"))
    bind cy = cy + 20.0
    if u_button("anp_svg", x, cy, bw, 22.0, "Animated SVG...", 0.0) { do_action("anim.export_svg") }
    if u_button("anp_png", x + bw + 6.0, cy, bw, 22.0, "PNG frames...", 0.0) { do_action("anim.export_png") }
    bind cy = cy + 26.0
    if u_button("anp_sheet", x, cy, bw, 22.0, "Sprite sheet...", 0.0) { do_action("anim.export_sheet") }
    if u_button("anp_ling", x + bw + 6.0, cy, bw, 22.0, "Save .ling...", 0.0) { do_action("vec.save_ling") }
    bind cy = cy + 34.0
    return vc_props(x, cy, w)
}

← Back to index