灵符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/pose.ling
# ━━━ pose.ling — pose channels, linear blend skinning, actions + keyframes for bones ━━━
# Pose channels per bone (object prefix): bp_rx bp_ry bp_rz (radians, local XYZ), bp_tx/ty/tz, bp_sx/sy/sz.
# Actions: act_names (list), act_cur (index), act_data (list of key lists), act_len (end frame per action).
# A key is 4 numbers: bone, channel (0 rx .. 8 sz), frame, value.

fn ps_mark(o) { st_set("pv_" + num_str(o), st_get("pv_" + num_str(o), 0.0) + 1.0) }
fn ps_ver(o) { return st_get("pv_" + num_str(o), 0.0) }

fn ps_identity(o) {
    bind n = rg_count(o)
    bind keys = ["bp_rx", "bp_ry", "bp_rz", "bp_tx", "bp_ty", "bp_tz"]
    bind k = 0.0
    while k < 6.0 {
        bind l = st_get(mk(o, list_get(keys, k)), [])
        bind i = 0.0
        while i < len(l) { if abs(list_get(l, i)) > 0.000001 { return false } bind i = i + 1.0 }
        bind k = k + 1.0
    }
    bind keys2 = ["bp_sx", "bp_sy", "bp_sz"]
    bind k = 0.0
    while k < 3.0 {
        bind l = st_get(mk(o, list_get(keys2, k)), [])
        bind i = 0.0
        while i < len(l) { if abs(list_get(l, i) - 1.0) > 0.000001 { return false } bind i = i + 1.0 }
        bind k = k + 1.0
    }
    return true
}

# object-space pose matrix per bone (flat 12 each): P = P_parent * L * T * R * S
fn ps_pose_matrices(o) {
    bind n = rg_count(o)
    bind rest = rg_rest_matrices(o)
    bind par = st_get(mk(o, "bn_par"), [])
    bind rx = st_get(mk(o, "bp_rx"), []) bind ry = st_get(mk(o, "bp_ry"), []) bind rz = st_get(mk(o, "bp_rz"), [])
    bind tx = st_get(mk(o, "bp_tx"), []) bind ty = st_get(mk(o, "bp_ty"), []) bind tz = st_get(mk(o, "bp_tz"), [])
    bind sx = st_get(mk(o, "bp_sx"), []) bind sy = st_get(mk(o, "bp_sy"), []) bind sz = st_get(mk(o, "bp_sz"), [])
    bind out = me_zeros(n * 12.0)
    bind done = me_zeros(n)
    # bones may reference parents with larger indices: iterate until all are resolved
    bind remaining = n
    bind guard = 0.0
    while remaining > 0.5 && guard < n + 2.0 {
        bind b = 0.0
        while b < n {
            if list_get(done, b) < 0.5 {
                bind p = list_get(par, b)
                bind ready = p < 0.0
                if p >= 0.0 { if list_get(done, p) > 0.5 { bind ready = true } }
                if ready {
                    bind Rb = rg_mat_at(rest, b)
                    bind L = Rb
                    bind Pp = m_ident()
                    if p >= 0.0 { bind L = m_mul(m_inv(rg_mat_at(rest, p)), Rb) bind Pp = rg_mat_at(out, p) }
                    bind X = m_trs(sel(b < len(tx), list_get(tx, b), 0.0), sel(b < len(ty), list_get(ty, b), 0.0), sel(b < len(tz), list_get(tz, b), 0.0), sel(b < len(rx), list_get(rx, b), 0.0), sel(b < len(ry), list_get(ry, b), 0.0), sel(b < len(rz), list_get(rz, b), 0.0), sel(b < len(sx), list_get(sx, b), 1.0), sel(b < len(sy), list_get(sy, b), 1.0), sel(b < len(sz), list_get(sz, b), 1.0))
                    bind P = m_mul(Pp, m_mul(L, X))
                    bind k = 0.0
                    while k < 12.0 { bind out = list_set(out, b * 12.0 + k, list_get(P, k)) bind k = k + 1.0 }
                    bind done = list_set(done, b, 1.0)
                    bind remaining = remaining - 1.0
                }
            }
            bind b = b + 1.0
        }
        bind guard = guard + 1.0
    }
    return out
}

# skinned vertex positions [vx, vy, vz] or [] when the rest pose should be shown
fn ps_deformed(o) {
    if st_get(mk(o, "bound"), 0.0) < 0.5 { return [] }
    bind n = rg_count(o)
    if n < 0.5 { return [] }
    bind mode = st_get("mode", 0.0)
    if mode == 2.0 || mode == 1.0 { return [] }
    if !(mode == 4.0 || tl_playing()) { if ps_identity(o) { return [] } }
    bind ver = ps_ver(o)
    if st_get(pj(o, "dver"), 0.0 - 1.0) == ver {
        bind cdx = st_get(pj(o, "dx"), [])
        if len(cdx) == me_nverts(o) { return [cdx, st_get(pj(o, "dy"), []), st_get(pj(o, "dz"), [])] }
    }
    bind rest = rg_rest_matrices(o)
    bind pm = ps_pose_matrices(o)
    # skin matrices K = P * inv(R)
    bind K = me_zeros(n * 12.0)
    bind b = 0.0
    while b < n {
        bind m = m_mul(rg_mat_at(pm, b), m_inv(rg_mat_at(rest, b)))
        bind k = 0.0
        while k < 12.0 { bind K = list_set(K, b * 12.0 + k, list_get(m, k)) bind k = k + 1.0 }
        bind b = b + 1.0
    }
    bind vx = st_get(mk(o, "vx"), []) bind vy = st_get(mk(o, "vy"), []) bind vz = st_get(mk(o, "vz"), [])
    bind wb = st_get(mk(o, "wb"), [])
    bind nv = len(vx)
    bind dx = list_new() bind dy = list_new() bind dz = list_new()
    bind v = 0.0
    while v < nv {
        bind x = list_get(vx, v) bind y = list_get(vy, v) bind z = list_get(vz, v)
        bind ax = 0.0 bind ay = 0.0 bind az = 0.0 bind tot = 0.0
        if v * 8.0 + 7.0 < len(wb) {
            bind k = 0.0
            while k < 4.0 {
                bind bb = list_get(wb, v * 8.0 + k * 2.0)
                bind w = list_get(wb, v * 8.0 + k * 2.0 + 1.0)
                if bb >= 0.0 && bb < n && w > 0.0 {
                    bind q = bb * 12.0
                    bind ax = ax + w * (list_get(K, q) * x + list_get(K, q + 1.0) * y + list_get(K, q + 2.0) * z + list_get(K, q + 3.0))
                    bind ay = ay + w * (list_get(K, q + 4.0) * x + list_get(K, q + 5.0) * y + list_get(K, q + 6.0) * z + list_get(K, q + 7.0))
                    bind az = az + w * (list_get(K, q + 8.0) * x + list_get(K, q + 9.0) * y + list_get(K, q + 10.0) * z + list_get(K, q + 11.0))
                    bind tot = tot + w
                }
                bind k = k + 1.0
            }
        }
        if tot >= 0.999 { bind dx = list_push(dx, ax / tot) bind dy = list_push(dy, ay / tot) bind dz = list_push(dz, az / tot) }
        else if tot > 0.000001 { bind dx = list_push(dx, ax + x * (1.0 - tot)) bind dy = list_push(dy, ay + y * (1.0 - tot)) bind dz = list_push(dz, az + z * (1.0 - tot)) }
        else { bind dx = list_push(dx, x) bind dy = list_push(dy, y) bind dz = list_push(dz, z) }
        bind v = v + 1.0
    }
    st_set(pj(o, "dx"), dx) st_set(pj(o, "dy"), dy) st_set(pj(o, "dz"), dz)
    st_set(pj(o, "dver"), ver)
    return [dx, dy, dz]
}

# ── actions / keyframes ──────────────────────────────────────────────────────
fn ps_channel_key(ch) { return list_get(["bp_rx", "bp_ry", "bp_rz", "bp_tx", "bp_ty", "bp_tz", "bp_sx", "bp_sy", "bp_sz"], ch) }
fn ps_action_count(o) { return len(st_get(mk(o, "act_names"), [])) }
fn ps_cur_action(o) {
    bind c = st_get(mk(o, "act_cur"), 0.0 - 1.0)
    if c >= ps_action_count(o) { return 0.0 - 1.0 }
    return c
}
fn ps_new_action(o, name) {
    if o < 0.0 { return 0.0 - 1.0 }
    st_set(mk(o, "act_names"), list_push(st_get(mk(o, "act_names"), []), name))
    st_set(mk(o, "act_data"), list_push(st_get(mk(o, "act_data"), []), []))
    st_set(mk(o, "act_len"), list_push(st_get(mk(o, "act_len"), []), st_get("tl_end", 48.0)))
    st_set(mk(o, "act_cur"), ps_action_count(o) - 1.0)
    st_set("dirty", 1.0)
    st_set("status", "New action: " + name)
    return ps_action_count(o) - 1.0
}
fn ps_action_keys(o) {
    bind a = ps_cur_action(o)
    if a < 0.0 { return [] }
    return list_get(st_get(mk(o, "act_data"), []), a)
}
fn ps_set_action_keys(o, keys) {
    bind a = ps_cur_action(o)
    if a < 0.0 { return 0.0 }
    bind d = st_get(mk(o, "act_data"), [])
    bind d = list_set(d, a, keys)
    st_set(mk(o, "act_data"), d)
    st_set("dirty", 1.0)
    tl_mark_dirty()
}
# set/replace one key
fn ps_put_key(keys, b, ch, frame, v) {
    bind i = 0.0
    while i + 3.0 < len(keys) {
        if list_get(keys, i) == b && list_get(keys, i + 1.0) == ch && abs(list_get(keys, i + 2.0) - frame) < 0.001 { return list_set(keys, i + 3.0, v) }
        bind i = i + 4.0
    }
    bind keys = list_push(keys, b) bind keys = list_push(keys, ch) bind keys = list_push(keys, frame)
    return list_push(keys, v)
}
fn ps_key() {
    bind o = me_sel_o()
    if o < 0.0 || rg_count(o) < 0.5 { st_set("status", "No bones to key") return 0.0 }
    bind bones = rg_selected(o)
    if len(bones) < 0.5 {
        bind all = list_new()
        bind b = 0.0
        while b < rg_count(o) { bind all = list_push(all, b) bind b = b + 1.0 }
        bind bones = all
    }
    if ps_cur_action(o) < 0.0 { ps_new_action(o, "Action") }
    me_undo_push()
    bind keys = ps_action_keys(o)
    bind frame = tl_frame()
    bind i = 0.0
    while i < len(bones) {
        bind b = list_get(bones, i)
        bind ch = 0.0
        while ch < 9.0 {
            bind l = st_get(mk(o, ps_channel_key(ch)), [])
            bind v = sel(b < len(l), list_get(l, b), sel(ch >= 6.0, 1.0, 0.0))
            bind keys = ps_put_key(keys, b, ch, frame, v)
            bind ch = ch + 1.0
        }
        bind i = i + 1.0
    }
    ps_set_action_keys(o, keys)
    bind alen = st_get(mk(o, "act_len"), [])
    bind a = ps_cur_action(o)
    if frame > list_get(alen, a) { st_lset(mk(o, "act_len"), a, frame) }
    st_set("status", "Keyed " + num_str(len(bones)) + " bones at frame " + fmt0(frame))
}
fn ps_unkey() {
    bind o = me_sel_o()
    if o < 0.0 || ps_cur_action(o) < 0.0 { return 0.0 }
    me_undo_push()
    bind bones = rg_selected(o)
    bind keys = ps_action_keys(o)
    bind frame = tl_frame()
    bind out = list_new()
    bind i = 0.0
    while i + 3.0 < len(keys) {
        bind b = list_get(keys, i)
        bind on = false
        bind q = 0.0
        while q < len(bones) { if list_get(bones, q) == b { bind on = true } bind q = q + 1.0 }
        if len(bones) < 0.5 { bind on = true }
        if !(on && abs(list_get(keys, i + 2.0) - frame) < 0.001) {
            bind out = list_push(out, b) bind out = list_push(out, list_get(keys, i + 1.0)) bind out = list_push(out, list_get(keys, i + 2.0)) bind out = list_push(out, list_get(keys, i + 3.0))
        }
        bind i = i + 4.0
    }
    ps_set_action_keys(o, out)
    st_set("status", "Removed keys at frame " + fmt0(frame))
}
# evaluate the current action at a frame into the pose channels
fn ps_eval(o, frame) {
    bind keys = ps_action_keys(o)
    if len(keys) < 3.5 { return 0.0 }
    bind n = rg_count(o)
    # per bone/channel: prev key (frame <= f) and next key (frame > f)
    bind pf = me_fill(n * 9.0, 0.0 - 1000000.0) bind pv = me_zeros(n * 9.0)
    bind nf = me_fill(n * 9.0, 1000000.0) bind nv = me_zeros(n * 9.0)
    bind has = me_zeros(n * 9.0)
    bind i = 0.0
    while i + 3.0 < len(keys) {
        bind b = list_get(keys, i) bind ch = list_get(keys, i + 1.0) bind kf = list_get(keys, i + 2.0) bind kv = list_get(keys, i + 3.0)
        if b < n {
            bind idx = b * 9.0 + ch
            bind has = list_set(has, idx, 1.0)
            if kf <= frame && kf > list_get(pf, idx) { bind pf = list_set(pf, idx, kf) bind pv = list_set(pv, idx, kv) }
            if kf > frame && kf < list_get(nf, idx) { bind nf = list_set(nf, idx, kf) bind nv = list_set(nv, idx, kv) }
        }
        bind i = i + 4.0
    }
    bind b = 0.0
    while b < n {
        bind ch = 0.0
        while ch < 9.0 {
            bind idx = b * 9.0 + ch
            if list_get(has, idx) > 0.5 {
                bind v = 0.0
                bind p = list_get(pf, idx) bind q = list_get(nf, idx)
                if p < 0.0 - 999999.0 { bind v = list_get(nv, idx) }
                else if q > 999999.0 { bind v = list_get(pv, idx) }
                else {
                    bind t = (frame - p) / max(0.000001, q - p)
                    bind t = t * t * (3.0 - 2.0 * t)
                    bind a = list_get(pv, idx) bind c = list_get(nv, idx)
                    if ch < 3.0 { bind c = a + mx_wrap_angle(c - a) }
                    bind v = a + (c - a) * t
                }
                st_lset(mk(o, ps_channel_key(ch)), b, v)
            }
            bind ch = ch + 1.0
        }
        bind b = b + 1.0
    }
    ps_mark(o)
}
fn ps_key_frames(o) {
    bind keys = ps_action_keys(o)
    bind out = list_new()
    bind i = 0.0
    while i + 3.0 < len(keys) {
        bind f = list_get(keys, i + 2.0)
        bind dup = false
        bind q = 0.0
        while q < len(out) { if abs(list_get(out, q) - f) < 0.001 { bind dup = true } bind q = q + 1.0 }
        if !dup { bind out = list_push(out, f) }
        bind i = i + 4.0
    }
    return out
}
fn ps_clear() {
    bind o = me_sel_o()
    if o < 0.0 { return 0.0 }
    me_undo_push()
    bind bones = rg_selected(o)
    bind n = rg_count(o)
    bind b = 0.0
    while b < n {
        bind on = len(bones) < 0.5
        bind q = 0.0
        while q < len(bones) { if list_get(bones, q) == b { bind on = true } bind q = q + 1.0 }
        if on {
            bind ch = 0.0
            while ch < 9.0 { st_lset(mk(o, ps_channel_key(ch)), b, sel(ch >= 6.0, 1.0, 0.0)) bind ch = ch + 1.0 }
        }
        bind b = b + 1.0
    }
    ps_mark(o)
    st_set("status", "Pose cleared")
}
fn ps_apply_rest() {
    bind o = me_sel_o()
    if o < 0.0 { return 0.0 }
    me_undo_push()
    bind pm = ps_pose_matrices(o)
    bind n = rg_count(o)
    bind b = 0.0
    while b < n {
        bind m = rg_mat_at(pm, b)
        bind ln = rg_len(o, b)
        rg_set_head(o, b, m_apply(m, [0.0, 0.0, 0.0]))
        rg_set_tail(o, b, m_apply(m, [0.0, ln, 0.0]))
        bind b = b + 1.0
    }
    # bake the deformation into the mesh so it matches the new rest
    bind d = ps_deformed(o)
    if len(d) > 2.5 { st_set(mk(o, "vx"), list_get(d, 0)) st_set(mk(o, "vy"), list_get(d, 1)) st_set(mk(o, "vz"), list_get(d, 2)) }
    bind bones = rg_selected(o)
    rg_sel_all(o, false)
    ps_clear()
    st_set("status", "Pose applied as the new rest pose")
}
fn ps_bake() {
    bind o = me_sel_o()
    if o < 0.0 { return 0.0 }
    me_undo_push()
    bind d = ps_deformed(o)
    if len(d) < 2.5 { st_set("status", "Nothing to bake (no deformation)") return 0.0 }
    st_set(mk(o, "vx"), list_get(d, 0)) st_set(mk(o, "vy"), list_get(d, 1)) st_set(mk(o, "vz"), list_get(d, 2))
    rg_sel_all(o, false)
    ps_clear()
    st_set("status", "Deformed mesh baked")
}

# ── UI ───────────────────────────────────────────────────────────────────────
fn ps_toolbar(x, by) {
    bind o = me_sel_o()
    bind xx = x
    if o < 0.0 { return xx }
    bind names = st_get(mk(o, "act_names"), [])
    bind cn = list_new()
    bind i = 0.0
    while i < len(names) { bind cn = list_push(cn, list_get(names, i)) bind i = i + 1.0 }
    bind cn = list_push(cn, "+ New action")
    bind cur = ps_cur_action(o)
    bind nv = u_combo("ps_act", xx, by, 150.0, 24.0, cn, sel(cur < 0.0, len(cn) - 1.0, cur))
    if nv >= 0.0 { if nv >= len(names) { modal_text("New action", "action", "Action") } else { st_set(mk(o, "act_cur"), nv) tl_mark_dirty() } }
    bind xx = xx + 156.0
    if u_tool("ps_key", xx, by, 64.0, 24.0, "key", "Key (I)", false, C_GREEN, "Insert keyframes for the selected bones") { ps_key() }
    if u_tool("ps_unkey", xx + 66.0, by, 64.0, 24.0, "key_off", "Unkey", false, C_RED, "Remove keyframes at this frame (Alt+I)") { ps_unkey() }
    if u_chip("ps_autokey", xx + 136.0, by + 2.0, 70.0, 20.0, "Auto key", st_get("autokey", 0.0) > 0.5, C_RED) { st_set("autokey", 1.0 - st_get("autokey", 0.0)) }
    if u_tool("ps_clear", xx + 212.0, by, 74.0, 24.0, "refresh", "Clear pose", false, 0.0, "Reset the selected bones (Alt+R)") { ps_clear() }
    if u_tool("ps_g", xx + 290.0, by, 28.0, 24.0, "move", "", v3_modal_active() && st_get("tr_kind", "") == "g", C_BLUE, "Move bone (G)") { v3_begin_modal("g") }
    if u_tool("ps_r", xx + 320.0, by, 28.0, 24.0, "rotate", "", v3_modal_active() && st_get("tr_kind", "") == "r", C_BLUE, "Rotate bone (R)") { v3_begin_modal("r") }
    if u_tool("ps_s", xx + 350.0, by, 28.0, 24.0, "scale", "", v3_modal_active() && st_get("tr_kind", "") == "s", C_BLUE, "Scale bone (S)") { v3_begin_modal("s") }
    return xx + 386.0
}
fn ps_props(x, y, w) {
    bind o = me_sel_o()
    bind cy = y
    u_header(x, cy, w, "Pose", ds_col("bind"))
    bind cy = cy + 20.0
    if o < 0.0 || rg_count(o) < 0.5 { g_text_wrap(x, cy, w, 11.0, C_TEXT_DIM, "Select a rigged mesh. Bones come from the Rig tab.") return cy + 30.0 }
    if st_get(mk(o, "bound"), 0.0) < 0.5 { g_text_wrap(x, cy, w, 11.0, C_RED, "Mesh is not bound - Rig > Bind / Auto Weights so the mesh follows the bones.") bind cy = cy + 30.0 }
    bind b = rg_active(o)
    if b < 0.0 { g_text(x, cy, 11.0, C_TEXT_DIM, "Click a bone to pose it (R rotates).") bind cy = cy + 22.0 }
    else {
        g_text(x, cy, 12.0, C_TEXT_STRONG, rg_bone_name(o, b))
        bind cy = cy + 20.0
        g_text(x, cy + 4.0, 11.0, C_TEXT_DIM, "Rotate")
        bind rx = st_get(mk(o, "bp_rx"), []) bind ry = st_get(mk(o, "bp_ry"), []) bind rz = st_get(mk(o, "bp_rz"), [])
        bind r = u_vec3("psp_rot", x + 46.0, cy, w - 46.0, mx_deg(list_get(rx, b)), mx_deg(list_get(ry, b)), mx_deg(list_get(rz, b)), 0.0 - 100000.0, 100000.0, 0.5)
        if list_get(r, 0) >= 0.0 { if u_pressed() { me_undo_push() } st_lset(mk(o, ps_channel_key(list_get(r, 0))), b, mx_rad(list_get(r, 1))) ps_mark(o) if st_get("autokey", 0.0) > 0.5 && u_released() { ps_key() } }
        bind cy = cy + 26.0
        g_text(x, cy + 4.0, 11.0, C_TEXT_DIM, "Move")
        bind tx = st_get(mk(o, "bp_tx"), []) bind ty = st_get(mk(o, "bp_ty"), []) bind tz = st_get(mk(o, "bp_tz"), [])
        bind r = u_vec3("psp_loc", x + 46.0, cy, w - 46.0, list_get(tx, b), list_get(ty, b), list_get(tz, b), 0.0 - 10000.0, 10000.0, 0.01)
        if list_get(r, 0) >= 0.0 { if u_pressed() { me_undo_push() } st_lset(mk(o, ps_channel_key(3.0 + list_get(r, 0))), b, list_get(r, 1)) ps_mark(o) }
        bind cy = cy + 26.0
        g_text(x, cy + 4.0, 11.0, C_TEXT_DIM, "Scale")
        bind sx = st_get(mk(o, "bp_sx"), []) bind sy = st_get(mk(o, "bp_sy"), []) bind sz = st_get(mk(o, "bp_sz"), [])
        bind r = u_vec3("psp_scl", x + 46.0, cy, w - 46.0, list_get(sx, b), list_get(sy, b), list_get(sz, b), 0.001, 1000.0, 0.01)
        if list_get(r, 0) >= 0.0 { if u_pressed() { me_undo_push() } st_lset(mk(o, ps_channel_key(6.0 + list_get(r, 0))), b, list_get(r, 1)) ps_mark(o) }
        bind cy = cy + 30.0
    }
    bind bw = floor((w - 6.0) / 2.0)
    if u_button("psp_key", x, cy, bw, 22.0, "Insert key (I)", C_BLUE) { ps_key() }
    if u_button("psp_unkey", x + bw + 6.0, cy, bw, 22.0, "Delete key", 0.0) { ps_unkey() }
    bind cy = cy + 26.0
    if u_button("psp_clear", x, cy, bw, 22.0, "Clear pose", 0.0) { ps_clear() }
    if u_button("psp_rest", x + bw + 6.0, cy, bw, 22.0, "Apply as rest", 0.0) { ps_apply_rest() }
    bind cy = cy + 26.0
    if u_button("psp_bake", x, cy, bw, 22.0, "Bake to mesh", 0.0) { ps_bake() }
    if u_button("psp_newact", x + bw + 6.0, cy, bw, 22.0, "New action", 0.0) { modal_text("New action", "action", "Action") }
    bind cy = cy + 34.0
    u_header(x, cy, w, "Actions", ds_col("ctl"))
    bind cy = cy + 20.0
    bind names = st_get(mk(o, "act_names"), [])
    bind cur = ps_cur_action(o)
    bind i = 0.0
    while i < len(names) {
        bind r = u_row("psp_act" + num_str(i), x, cy, w - 28.0, 20.0, list_get(names, i) + "  (" + num_str(floor(len(list_get(st_get(mk(o, "act_data"), []), i)) / 4.0)) + " keys)", i == cur, C_GREEN, "anim")
        if r > 0.5 { st_set(mk(o, "act_cur"), i) tl_mark_dirty() }
        if u_icon_button("psp_actdel" + num_str(i), x + w - 24.0, cy, 22.0, 20.0, "trash", 10.0, C_TEXT_DIM, false) {
            me_undo_push()
            bind nn = list_new() bind nd = list_new() bind nl = list_new()
            bind q = 0.0
            while q < len(names) { if q != i { bind nn = list_push(nn, list_get(names, q)) bind nd = list_push(nd, list_get(st_get(mk(o, "act_data"), []), q)) bind nl = list_push(nl, list_get(st_get(mk(o, "act_len"), []), q)) } bind q = q + 1.0 }
            st_set(mk(o, "act_names"), nn) st_set(mk(o, "act_data"), nd) st_set(mk(o, "act_len"), nl)
            st_set(mk(o, "act_cur"), sel(len(nn) > 0.5, 0.0, 0.0 - 1.0))
        }
        bind cy = cy + 22.0
        bind i = i + 1.0
    }
    if len(names) < 0.5 { g_text(x, cy, 11.0, C_TEXT_DIM, "No actions - press I to key a pose.") bind cy = cy + 20.0 }
    return cy + 10.0
}
fn ps_keys(shift, ctrl, alt) {
    if key_pressed("i") && !ctrl { if alt { ps_unkey() } else { ps_key() } return true }
    if alt && (key_pressed("r") || key_pressed("g") || key_pressed("s")) { ps_clear() return true }
    if key_pressed("left") { tl_set_frame(tl_frame() - 1.0) return true }
    if key_pressed("right") { tl_set_frame(tl_frame() + 1.0) return true }
    return false
}

← Back to index