灵符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/mesh.ling
# ━━━ mesh.ling — scene objects, polygon meshes (tris / quads / n-gons), edges, selection, undo ━━━
# Every object lives in the native store under "m<id>_<key>":
#   vx vy vz   vertex coordinates (object space)
#   fi fs fc   faces: fi = flat corner vertex indices, fs = start offset per face, fc = corner count per face
#   uv         2 numbers per corner (parallel to fi)
#   vsel fsel  selection flags per vertex / face, esel per edge
#   ea eb ce   unique edges (vertex pairs) + corner -> edge index (built lazily, "edirty")
#   fcol       per-face baked colour (-1 = object colour)
#   wb         skin weights: 8 numbers per vertex (bone, weight) x 4, bone -1 = empty
#   bones / pose / actions: see rig.ling and pose.ling (same prefix)
# The object list is the st list "ob_ids"; "sel_o" is the active object id (-1 none).

fn mk(o, key) { return "m" + num_str(o) + "_" + key }

fn me_object_keys() {
    return ["name", "px", "py", "pz", "rx", "ry", "rz", "sx", "sy", "sz", "col", "vis", "tex", "smooth", "osel", "vx", "vy", "vz", "fi", "fs", "fc", "uv", "vsel", "fsel", "esel", "ea", "eb", "ce", "edirty", "fcol", "wb", "bn_name", "bn_par", "bn_hx", "bn_hy", "bn_hz", "bn_tx", "bn_ty", "bn_tz", "bn_sel", "bp_rx", "bp_ry", "bp_rz", "bp_tx", "bp_ty", "bp_tz", "bp_sx", "bp_sy", "bp_sz", "act_names", "act_cur", "act_data", "act_len", "bound"]
}

fn me_count() { return st_llen("ob_ids") }
fn me_first() { if st_llen("ob_ids") < 0.5 { return 0.0 - 1.0 } return st_lget("ob_ids", 0.0) }
fn me_ids() { return st_get("ob_ids", []) }
fn me_sel_o() {
    bind o = st_get("sel_o", 0.0 - 1.0)
    if o < 0.0 { return 0.0 - 1.0 }
    if st_has(mk(o, "name")) < 0.5 { st_set("sel_o", 0.0 - 1.0) return 0.0 - 1.0 }
    return o
}
fn me_exists(o) { return o >= 0.0 && st_has(mk(o, "name")) > 0.5 }

fn me_new_scene() {
    bind ids = me_ids()
    bind i = 0.0
    while i < len(ids) { me_free(list_get(ids, i)) bind i = i + 1.0 }
    st_lclear("ob_ids")
    st_set("sel_o", 0.0 - 1.0)
    st_set("ob_next", 0.0)
    st_set("dirty", 0.0)
    st_set("scene_name", "untitled")
    st_set("scene_path", "")
    st_set("undo_n", 0.0)
    st_set("redo_n", 0.0)
    st_set("cursor_x", 0.0)
    st_set("cursor_y", 0.0)
    st_set("cursor_z", 0.0)
}
fn me_free(o) {
    bind keys = me_object_keys()
    bind i = 0.0
    while i < len(keys) { st_set(mk(o, list_get(keys, i)), 0.0) bind i = i + 1.0 }
}

# create an object from flat lists. returns the new id (and makes it the active, only selected object)
fn me_add_object(name, vx, vy, vz, fi, fs, fc) {
    bind o = st_get("ob_next", 0.0)
    st_set("ob_next", o + 1.0)
    st_lpush("ob_ids", o)
    st_set(mk(o, "name"), me_unique_name(name))
    st_set(mk(o, "px"), 0.0) st_set(mk(o, "py"), 0.0) st_set(mk(o, "pz"), 0.0)
    st_set(mk(o, "rx"), 0.0) st_set(mk(o, "ry"), 0.0) st_set(mk(o, "rz"), 0.0)
    st_set(mk(o, "sx"), 1.0) st_set(mk(o, "sy"), 1.0) st_set(mk(o, "sz"), 1.0)
    st_set(mk(o, "col"), c_pal(o + 4.0))
    st_set(mk(o, "vis"), 1.0)
    st_set(mk(o, "tex"), 0.0 - 1.0)
    st_set(mk(o, "smooth"), 0.0)
    st_set(mk(o, "osel"), 1.0)
    st_set(mk(o, "vx"), vx) st_set(mk(o, "vy"), vy) st_set(mk(o, "vz"), vz)
    st_set(mk(o, "fi"), fi) st_set(mk(o, "fs"), fs) st_set(mk(o, "fc"), fc)
    bind uv = list_new()
    bind i = 0.0
    while i < len(fi) * 2.0 { bind uv = list_push(uv, 0.0) bind i = i + 1.0 }
    st_set(mk(o, "uv"), uv)
    st_set(mk(o, "vsel"), me_zeros(len(vx)))
    st_set(mk(o, "fsel"), me_zeros(len(fc)))
    st_set(mk(o, "esel"), [])
    st_set(mk(o, "ea"), []) st_set(mk(o, "eb"), []) st_set(mk(o, "ce"), [])
    st_set(mk(o, "edirty"), 1.0)
    st_set(mk(o, "fcol"), me_fill(len(fc), 0.0 - 1.0))
    st_set(mk(o, "wb"), [])
    st_set(mk(o, "bn_name"), []) st_set(mk(o, "bn_par"), [])
    st_set(mk(o, "bn_hx"), []) st_set(mk(o, "bn_hy"), []) st_set(mk(o, "bn_hz"), [])
    st_set(mk(o, "bn_tx"), []) st_set(mk(o, "bn_ty"), []) st_set(mk(o, "bn_tz"), [])
    st_set(mk(o, "bn_sel"), [])
    st_set(mk(o, "bp_rx"), []) st_set(mk(o, "bp_ry"), []) st_set(mk(o, "bp_rz"), [])
    st_set(mk(o, "bp_tx"), []) st_set(mk(o, "bp_ty"), []) st_set(mk(o, "bp_tz"), [])
    st_set(mk(o, "bp_sx"), []) st_set(mk(o, "bp_sy"), []) st_set(mk(o, "bp_sz"), [])
    st_set(mk(o, "act_names"), []) st_set(mk(o, "act_cur"), 0.0 - 1.0) st_set(mk(o, "act_data"), []) st_set(mk(o, "act_len"), [])
    st_set(mk(o, "bound"), 0.0)
    uv_reset(o)
    me_select_object(o, false)
    st_set("dirty", 1.0)
    return o
}
fn me_unique_name(base) {
    bind ids = me_ids()
    bind nm = base
    bind k = 1.0
    bind taken = true
    while taken {
        bind taken = false
        bind i = 0.0
        while i < len(ids) {
            if st_get(mk(list_get(ids, i), "name"), "") == nm { bind taken = true }
            bind i = i + 1.0
        }
        if taken { bind k = k + 1.0 bind nm = base + "." + sel(k < 10.0, "00", sel(k < 100.0, "0", "")) + num_str(k) }
    }
    return nm
}
fn me_zeros(n) { return me_fill(n, 0.0) }
fn me_fill(n, v) {
    bind l = list_new()
    bind i = 0.0
    while i < n { bind l = list_push(l, v) bind i = i + 1.0 }
    return l
}

fn me_remove(o) {
    if !me_exists(o) { return 0.0 }
    bind ids = me_ids()
    st_lclear("ob_ids")
    bind i = 0.0
    while i < len(ids) {
        if list_get(ids, i) != o { st_lpush("ob_ids", list_get(ids, i)) }
        bind i = i + 1.0
    }
    me_free(o)
    if st_get("sel_o", 0.0 - 1.0) == o { st_set("sel_o", me_first()) }
    st_set("dirty", 1.0)
}

# duplicate an object (all data). returns the new id
fn me_dup(o) {
    bind keys = me_object_keys()
    bind n = st_get("ob_next", 0.0)
    st_set("ob_next", n + 1.0)
    st_lpush("ob_ids", n)
    bind i = 0.0
    while i < len(keys) {
        bind k = list_get(keys, i)
        st_set(mk(n, k), st_get(mk(o, k), 0.0))
        bind i = i + 1.0
    }
    st_set(mk(n, "name"), me_unique_name(st_get(mk(o, "name"), "Object")))
    st_set(mk(n, "edirty"), 1.0)
    st_set("dirty", 1.0)
    return n
}

fn me_name(o) { return st_get(mk(o, "name"), "") }
fn me_nverts(o) { return len(st_get(mk(o, "vx"), [])) }
fn me_nfaces(o) { return len(st_get(mk(o, "fc"), [])) }
fn me_ntris(o) {
    bind fc = st_get(mk(o, "fc"), [])
    bind t = 0.0
    bind i = 0.0
    while i < len(fc) { bind t = t + max(0.0, list_get(fc, i) - 2.0) bind i = i + 1.0 }
    return t
}
fn me_has_bones(o) { return len(st_get(mk(o, "bn_name"), [])) > 0.5 }

# ── object selection ─────────────────────────────────────────────────────────
fn me_select_object(o, extend) {
    bind ids = me_ids()
    bind i = 0.0
    while i < len(ids) {
        bind id = list_get(ids, i)
        if !extend { st_set(mk(id, "osel"), 0.0) }
        bind i = i + 1.0
    }
    if o >= 0.0 { st_set(mk(o, "osel"), 1.0) st_set("sel_o", o) }
}
fn me_toggle_object(o) {
    bind on = st_get(mk(o, "osel"), 0.0) > 0.5
    st_set(mk(o, "osel"), sel(on, 0.0, 1.0))
    if !on { st_set("sel_o", o) }
}
fn me_selected_objects() {
    bind out = list_new()
    bind ids = me_ids()
    bind i = 0.0
    while i < len(ids) {
        bind id = list_get(ids, i)
        if st_get(mk(id, "osel"), 0.0) > 0.5 { bind out = list_push(out, id) }
        bind i = i + 1.0
    }
    return out
}
fn me_deselect_all_objects() {
    bind ids = me_ids()
    bind i = 0.0
    while i < len(ids) { st_set(mk(list_get(ids, i), "osel"), 0.0) bind i = i + 1.0 }
}

# ── object matrix ────────────────────────────────────────────────────────────
fn me_matrix(o) {
    return m_trs(st_get(mk(o, "px"), 0.0), st_get(mk(o, "py"), 0.0), st_get(mk(o, "pz"), 0.0), st_get(mk(o, "rx"), 0.0), st_get(mk(o, "ry"), 0.0), st_get(mk(o, "rz"), 0.0), st_get(mk(o, "sx"), 1.0), st_get(mk(o, "sy"), 1.0), st_get(mk(o, "sz"), 1.0))
}

# ── edges (lazy) ─────────────────────────────────────────────────────────────
fn me_edges_dirty(o) { st_set(mk(o, "edirty"), 1.0) }
fn me_rebuild_edges(o) {
    if st_get(mk(o, "edirty"), 1.0) < 0.5 { return 0.0 }
    bind fi = st_get(mk(o, "fi"), [])
    bind fs = st_get(mk(o, "fs"), [])
    bind fc = st_get(mk(o, "fc"), [])
    bind nv = me_nverts(o)
    bind nc = len(fi)
    # a key per corner: min * nv + max
    bind keys = list_new()
    bind f = 0.0
    while f < len(fc) {
        bind s = list_get(fs, f)
        bind c = list_get(fc, f)
        bind k = 0.0
        while k < c {
            bind a = list_get(fi, s + k)
            bind b = list_get(fi, s + (k + 1.0) % c)
            bind keys = list_push(keys, min(a, b) * nv + max(a, b))
            bind k = k + 1.0
        }
        bind f = f + 1.0
    }
    bind order = mx_argsort(keys)
    bind ea = list_new()
    bind eb = list_new()
    bind ce = me_zeros(nc)
    bind last = 0.0 - 1.0
    bind i = 0.0
    while i < nc {
        bind ci = list_get(order, i)
        bind k = list_get(keys, ci)
        if k != last {
            bind ea = list_push(ea, floor(k / nv))
            bind eb = list_push(eb, k % nv)
            bind last = k
        }
        bind ce = list_set(ce, ci, len(ea) - 1.0)
        bind i = i + 1.0
    }
    st_set(mk(o, "ea"), ea)
    st_set(mk(o, "eb"), eb)
    st_set(mk(o, "ce"), ce)
    bind old = st_get(mk(o, "esel"), [])
    if len(old) != len(ea) { st_set(mk(o, "esel"), me_zeros(len(ea))) }
    st_set(mk(o, "edirty"), 0.0)
    # keep selection flag lengths in sync with the topology
    if len(st_get(mk(o, "vsel"), [])) != nv { st_set(mk(o, "vsel"), me_zeros(nv)) }
    if len(st_get(mk(o, "fsel"), [])) != len(fc) { st_set(mk(o, "fsel"), me_zeros(len(fc))) }
    if len(st_get(mk(o, "fcol"), [])) != len(fc) { st_set(mk(o, "fcol"), me_fill(len(fc), 0.0 - 1.0)) }
    if len(st_get(mk(o, "uv"), [])) != nc * 2.0 { st_set(mk(o, "uv"), me_zeros(nc * 2.0)) }
    bind wb = st_get(mk(o, "wb"), [])
    if len(wb) > 0.5 && len(wb) != nv * 8.0 { st_set(mk(o, "wb"), me_wb_resize(wb, nv)) }
}
fn me_wb_resize(wb, nv) {
    bind out = list_new()
    bind i = 0.0
    while i < nv * 8.0 {
        if i < len(wb) { bind out = list_push(out, list_get(wb, i)) }
        else { bind out = list_push(out, sel(i % 2.0 < 0.5, 0.0 - 1.0, 0.0)) }
        bind i = i + 1.0
    }
    return out
}
# vertex -> faces adjacency (counting sort). returns [starts(nv+1), facelist]
fn me_vert_faces(o) {
    bind fi = st_get(mk(o, "fi"), [])
    bind fs = st_get(mk(o, "fs"), [])
    bind fc = st_get(mk(o, "fc"), [])
    bind nv = me_nverts(o)
    bind cnt = me_zeros(nv + 1.0)
    bind i = 0.0
    while i < len(fi) { bind v = list_get(fi, i) bind cnt = list_set(cnt, v + 1.0, list_get(cnt, v + 1.0) + 1.0) bind i = i + 1.0 }
    bind i = 1.0
    while i <= nv { bind cnt = list_set(cnt, i, list_get(cnt, i) + list_get(cnt, i - 1.0)) bind i = i + 1.0 }
    bind fill_at = list_new()
    bind i = 0.0
    while i <= nv { bind fill_at = list_push(fill_at, list_get(cnt, i)) bind i = i + 1.0 }
    bind out = me_zeros(len(fi))
    bind f = 0.0
    while f < len(fc) {
        bind s = list_get(fs, f)
        bind k = 0.0
        while k < list_get(fc, f) {
            bind v = list_get(fi, s + k)
            bind p = list_get(fill_at, v)
            bind out = list_set(out, p, f)
            bind fill_at = list_set(fill_at, v, p + 1.0)
            bind k = k + 1.0
        }
        bind f = f + 1.0
    }
    return [cnt, out]
}
# vertex -> neighbour vertices via edges. returns [starts(nv+1), vertlist]
fn me_vert_neighbours(o) {
    me_rebuild_edges(o)
    bind ea = st_get(mk(o, "ea"), [])
    bind eb = st_get(mk(o, "eb"), [])
    bind nv = me_nverts(o)
    bind cnt = me_zeros(nv + 1.0)
    bind i = 0.0
    while i < len(ea) {
        bind a = list_get(ea, i) bind b = list_get(eb, i)
        bind cnt = list_set(cnt, a + 1.0, list_get(cnt, a + 1.0) + 1.0)
        bind cnt = list_set(cnt, b + 1.0, list_get(cnt, b + 1.0) + 1.0)
        bind i = i + 1.0
    }
    bind i = 1.0
    while i <= nv { bind cnt = list_set(cnt, i, list_get(cnt, i) + list_get(cnt, i - 1.0)) bind i = i + 1.0 }
    bind fill_at = list_new()
    bind i = 0.0
    while i <= nv { bind fill_at = list_push(fill_at, list_get(cnt, i)) bind i = i + 1.0 }
    bind out = me_zeros(len(ea) * 2.0)
    bind i = 0.0
    while i < len(ea) {
        bind a = list_get(ea, i) bind b = list_get(eb, i)
        bind p = list_get(fill_at, a)
        bind out = list_set(out, p, b)
        bind fill_at = list_set(fill_at, a, p + 1.0)
        bind p = list_get(fill_at, b)
        bind out = list_set(out, p, a)
        bind fill_at = list_set(fill_at, b, p + 1.0)
        bind i = i + 1.0
    }
    return [cnt, out]
}

# ── normals ──────────────────────────────────────────────────────────────────
# face normal (object space, Newell) as [nx,ny,nz] (unnormalised length = 2*area)
fn me_face_normal(vx, vy, vz, fi, s, c) {
    bind nx = 0.0 bind ny = 0.0 bind nz = 0.0
    bind k = 0.0
    while k < c {
        bind a = list_get(fi, s + k)
        bind b = list_get(fi, s + (k + 1.0) % c)
        bind ax = list_get(vx, a) bind ay = list_get(vy, a) bind az = list_get(vz, a)
        bind bx = list_get(vx, b) bind by = list_get(vy, b) bind bz = list_get(vz, b)
        bind nx = nx + (ay - by) * (az + bz)
        bind ny = ny + (az - bz) * (ax + bx)
        bind nz = nz + (ax - bx) * (ay + by)
        bind k = k + 1.0
    }
    return [nx, ny, nz]
}
# per-vertex normals (area weighted). returns [nx list, ny list, nz list]
fn me_vertex_normals(o) {
    bind vx = st_get(mk(o, "vx"), []) bind vy = st_get(mk(o, "vy"), []) bind vz = st_get(mk(o, "vz"), [])
    bind fi = st_get(mk(o, "fi"), []) bind fs = st_get(mk(o, "fs"), []) bind fc = st_get(mk(o, "fc"), [])
    bind nv = len(vx)
    bind nx = me_zeros(nv) bind ny = me_zeros(nv) bind nz = me_zeros(nv)
    bind f = 0.0
    while f < len(fc) {
        bind s = list_get(fs, f) bind c = list_get(fc, f)
        bind n = me_face_normal(vx, vy, vz, fi, s, c)
        bind k = 0.0
        while k < c {
            bind v = list_get(fi, s + k)
            bind nx = list_set(nx, v, list_get(nx, v) + list_get(n, 0))
            bind ny = list_set(ny, v, list_get(ny, v) + list_get(n, 1))
            bind nz = list_set(nz, v, list_get(nz, v) + list_get(n, 2))
            bind k = k + 1.0
        }
        bind f = f + 1.0
    }
    bind i = 0.0
    while i < nv {
        bind l = sqrt(list_get(nx, i) * list_get(nx, i) + list_get(ny, i) * list_get(ny, i) + list_get(nz, i) * list_get(nz, i))
        if l > 0.000001 {
            bind nx = list_set(nx, i, list_get(nx, i) / l)
            bind ny = list_set(ny, i, list_get(ny, i) / l)
            bind nz = list_set(nz, i, list_get(nz, i) / l)
        } else { bind ny = list_set(ny, i, 1.0) }
        bind i = i + 1.0
    }
    return [nx, ny, nz]
}

# ── bounds ───────────────────────────────────────────────────────────────────
# world-space bounds of an object: [minx,miny,minz,maxx,maxy,maxz] or [] when empty
fn me_bounds(o) {
    bind vx = st_get(mk(o, "vx"), []) bind vy = st_get(mk(o, "vy"), []) bind vz = st_get(mk(o, "vz"), [])
    if len(vx) < 0.5 {
        bind p = [st_get(mk(o, "px"), 0.0), st_get(mk(o, "py"), 0.0), st_get(mk(o, "pz"), 0.0)]
        return [list_get(p, 0) - 0.5, list_get(p, 1) - 0.5, list_get(p, 2) - 0.5, list_get(p, 0) + 0.5, list_get(p, 1) + 0.5, list_get(p, 2) + 0.5]
    }
    bind m = me_matrix(o)
    bind mn = [1000000.0, 1000000.0, 1000000.0]
    bind mx = [0.0 - 1000000.0, 0.0 - 1000000.0, 0.0 - 1000000.0]
    bind i = 0.0
    while i < len(vx) {
        bind p = m_apply(m, [list_get(vx, i), list_get(vy, i), list_get(vz, i)])
        bind k = 0.0
        while k < 3.0 {
            if list_get(p, k) < list_get(mn, k) { bind mn = list_set(mn, k, list_get(p, k)) }
            if list_get(p, k) > list_get(mx, k) { bind mx = list_set(mx, k, list_get(p, k)) }
            bind k = k + 1.0
        }
        bind i = i + 1.0
    }
    return [list_get(mn, 0), list_get(mn, 1), list_get(mn, 2), list_get(mx, 0), list_get(mx, 1), list_get(mx, 2)]
}
# bounds of selected vertices (object space) or of the whole object
fn me_sel_bounds_local(o, selected_only) {
    bind vx = st_get(mk(o, "vx"), []) bind vy = st_get(mk(o, "vy"), []) bind vz = st_get(mk(o, "vz"), [])
    bind vsel = st_get(mk(o, "vsel"), [])
    bind mn = [1000000.0, 1000000.0, 1000000.0]
    bind mx = [0.0 - 1000000.0, 0.0 - 1000000.0, 0.0 - 1000000.0]
    bind any = false
    bind i = 0.0
    while i < len(vx) {
        if !selected_only || list_get(vsel, i) > 0.5 {
            bind any = true
            bind p = [list_get(vx, i), list_get(vy, i), list_get(vz, i)]
            bind k = 0.0
            while k < 3.0 {
                if list_get(p, k) < list_get(mn, k) { bind mn = list_set(mn, k, list_get(p, k)) }
                if list_get(p, k) > list_get(mx, k) { bind mx = list_set(mx, k, list_get(p, k)) }
                bind k = k + 1.0
            }
        }
        bind i = i + 1.0
    }
    if !any { return [] }
    return [list_get(mn, 0), list_get(mn, 1), list_get(mn, 2), list_get(mx, 0), list_get(mx, 1), list_get(mx, 2)]
}

# ── element selection (edit mode) ────────────────────────────────────────────
# sel_mode: 0 vertex, 1 edge, 2 face. Vertex flags are the source of truth; edge/face flags are derived
# (an edge/face is selected when all its vertices are) except that face mode keeps its own flags so
# that clicking one face of a cube does not select the neighbours.
fn me_sel_count(o) {
    bind vsel = st_get(mk(o, "vsel"), [])
    bind n = 0.0
    bind i = 0.0
    while i < len(vsel) { if list_get(vsel, i) > 0.5 { bind n = n + 1.0 } bind i = i + 1.0 }
    return n
}
fn me_sel_all(o, on) {
    bind nv = me_nverts(o)
    st_set(mk(o, "vsel"), me_fill(nv, sel(on, 1.0, 0.0)))
    st_set(mk(o, "fsel"), me_fill(me_nfaces(o), sel(on, 1.0, 0.0)))
    me_rebuild_edges(o)
    st_set(mk(o, "esel"), me_fill(len(st_get(mk(o, "ea"), [])), sel(on, 1.0, 0.0)))
}
fn me_sel_invert(o) {
    bind vsel = st_get(mk(o, "vsel"), [])
    bind i = 0.0
    while i < len(vsel) { bind vsel = list_set(vsel, i, sel(list_get(vsel, i) > 0.5, 0.0, 1.0)) bind i = i + 1.0 }
    st_set(mk(o, "vsel"), vsel)
    me_sel_sync_from_verts(o)
}
# derive face + edge flags from vertex flags
fn me_sel_sync_from_verts(o) {
    me_rebuild_edges(o)
    bind vsel = st_get(mk(o, "vsel"), [])
    bind fi = st_get(mk(o, "fi"), []) bind fs = st_get(mk(o, "fs"), []) bind fc = st_get(mk(o, "fc"), [])
    bind fsel = me_zeros(len(fc))
    bind f = 0.0
    while f < len(fc) {
        bind s = list_get(fs, f) bind c = list_get(fc, f)
        bind all = true
        bind k = 0.0
        while k < c { if list_get(vsel, list_get(fi, s + k)) < 0.5 { bind all = false } bind k = k + 1.0 }
        if all { bind fsel = list_set(fsel, f, 1.0) }
        bind f = f + 1.0
    }
    st_set(mk(o, "fsel"), fsel)
    bind ea = st_get(mk(o, "ea"), []) bind eb = st_get(mk(o, "eb"), [])
    bind esel = me_zeros(len(ea))
    bind i = 0.0
    while i < len(ea) {
        if list_get(vsel, list_get(ea, i)) > 0.5 && list_get(vsel, list_get(eb, i)) > 0.5 { bind esel = list_set(esel, i, 1.0) }
        bind i = i + 1.0
    }
    st_set(mk(o, "esel"), esel)
}
# select vertices of a face / edge
fn me_sel_face(o, f, on) {
    bind fi = st_get(mk(o, "fi"), []) bind fs = st_get(mk(o, "fs"), []) bind fc = st_get(mk(o, "fc"), [])
    bind s = list_get(fs, f) bind c = list_get(fc, f)
    bind vsel = st_get(mk(o, "vsel"), [])
    bind k = 0.0
    while k < c { bind vsel = list_set(vsel, list_get(fi, s + k), sel(on, 1.0, 0.0)) bind k = k + 1.0 }
    st_set(mk(o, "vsel"), vsel)
    me_sel_sync_from_verts(o)
    bind fsel = st_get(mk(o, "fsel"), [])
    bind fsel = list_set(fsel, f, sel(on, 1.0, 0.0))
    st_set(mk(o, "fsel"), fsel)
}
fn me_sel_edge(o, e, on) {
    me_rebuild_edges(o)
    bind ea = st_get(mk(o, "ea"), []) bind eb = st_get(mk(o, "eb"), [])
    bind vsel = st_get(mk(o, "vsel"), [])
    bind vsel = list_set(vsel, list_get(ea, e), sel(on, 1.0, 0.0))
    bind vsel = list_set(vsel, list_get(eb, e), sel(on, 1.0, 0.0))
    st_set(mk(o, "vsel"), vsel)
    me_sel_sync_from_verts(o)
}
fn me_sel_vert(o, v, on) {
    bind vsel = st_get(mk(o, "vsel"), [])
    bind vsel = list_set(vsel, v, sel(on, 1.0, 0.0))
    st_set(mk(o, "vsel"), vsel)
    me_sel_sync_from_verts(o)
}
fn me_vert_selected(o, v) { return list_get(st_get(mk(o, "vsel"), []), v) > 0.5 }
fn me_face_selected(o, f) {
    bind fsel = st_get(mk(o, "fsel"), [])
    if f >= len(fsel) { return false }
    return list_get(fsel, f) > 0.5
}
# selected face indices
fn me_selected_faces(o) {
    bind fsel = st_get(mk(o, "fsel"), [])
    bind out = list_new()
    bind i = 0.0
    while i < len(fsel) { if list_get(fsel, i) > 0.5 { bind out = list_push(out, i) } bind i = i + 1.0 }
    return out
}
fn me_selected_verts(o) {
    bind vsel = st_get(mk(o, "vsel"), [])
    bind out = list_new()
    bind i = 0.0
    while i < len(vsel) { if list_get(vsel, i) > 0.5 { bind out = list_push(out, i) } bind i = i + 1.0 }
    return out
}
fn me_selected_edges(o) {
    me_rebuild_edges(o)
    bind esel = st_get(mk(o, "esel"), [])
    bind out = list_new()
    bind i = 0.0
    while i < len(esel) { if list_get(esel, i) > 0.5 { bind out = list_push(out, i) } bind i = i + 1.0 }
    return out
}
fn me_sel_grow(o) {
    bind nb = me_vert_neighbours(o)
    bind starts = list_get(nb, 0) bind vl = list_get(nb, 1)
    bind vsel = st_get(mk(o, "vsel"), [])
    bind add = list_new()
    bind i = 0.0
    while i < len(vsel) {
        if list_get(vsel, i) > 0.5 {
            bind k = list_get(starts, i)
            while k < list_get(starts, i + 1.0) { bind add = list_push(add, list_get(vl, k)) bind k = k + 1.0 }
        }
        bind i = i + 1.0
    }
    bind i = 0.0
    while i < len(add) { bind vsel = list_set(vsel, list_get(add, i), 1.0) bind i = i + 1.0 }
    st_set(mk(o, "vsel"), vsel)
    me_sel_sync_from_verts(o)
}
fn me_sel_shrink(o) {
    bind nb = me_vert_neighbours(o)
    bind starts = list_get(nb, 0) bind vl = list_get(nb, 1)
    bind vsel = st_get(mk(o, "vsel"), [])
    bind keep = list_new()
    bind i = 0.0
    while i < len(vsel) {
        bind on = list_get(vsel, i) > 0.5
        if on {
            bind k = list_get(starts, i)
            while k < list_get(starts, i + 1.0) { if list_get(vsel, list_get(vl, k)) < 0.5 { bind on = false } bind k = k + 1.0 }
        }
        bind keep = list_push(keep, sel(on, 1.0, 0.0))
        bind i = i + 1.0
    }
    st_set(mk(o, "vsel"), keep)
    me_sel_sync_from_verts(o)
}
fn me_sel_linked(o) {
    bind nb = me_vert_neighbours(o)
    bind starts = list_get(nb, 0) bind vl = list_get(nb, 1)
    bind vsel = st_get(mk(o, "vsel"), [])
    bind stack = me_selected_verts(o)
    while len(stack) > 0.5 {
        bind v = list_get(stack, len(stack) - 1.0)
        bind nstack = list_new()
        bind i = 0.0
        while i < len(stack) - 1.0 { bind nstack = list_push(nstack, list_get(stack, i)) bind i = i + 1.0 }
        bind stack = nstack
        bind k = list_get(starts, v)
        while k < list_get(starts, v + 1.0) {
            bind w = list_get(vl, k)
            if list_get(vsel, w) < 0.5 { bind vsel = list_set(vsel, w, 1.0) bind stack = list_push(stack, w) }
            bind k = k + 1.0
        }
    }
    st_set(mk(o, "vsel"), vsel)
    me_sel_sync_from_verts(o)
}
# edge loop through edge e (walks across quads: opposite edge each step)
fn me_sel_loop(o, e) {
    me_rebuild_edges(o)
    bind ea = st_get(mk(o, "ea"), []) bind eb = st_get(mk(o, "eb"), []) bind ce = st_get(mk(o, "ce"), [])
    bind fi = st_get(mk(o, "fi"), []) bind fs = st_get(mk(o, "fs"), []) bind fc = st_get(mk(o, "fc"), [])
    # edge -> faces (max 2) via the corner map
    bind ef0 = me_fill(len(ea), 0.0 - 1.0)
    bind ef1 = me_fill(len(ea), 0.0 - 1.0)
    bind f = 0.0
    while f < len(fc) {
        bind s = list_get(fs, f) bind c = list_get(fc, f)
        bind k = 0.0
        while k < c {
            bind ed = list_get(ce, s + k)
            if list_get(ef0, ed) < 0.0 { bind ef0 = list_set(ef0, ed, f) } else if list_get(ef1, ed) < 0.0 { bind ef1 = list_set(ef1, ed, f) }
            bind k = k + 1.0
        }
        bind f = f + 1.0
    }
    bind vsel = st_get(mk(o, "vsel"), [])
    bind dir = 0.0
    while dir < 2.0 {
        bind cur = e
        bind prevf = sel(dir < 0.5, list_get(ef1, e), list_get(ef0, e))
        bind steps = 0.0
        bind go = true
        while go && steps < 2000.0 {
            bind vsel = list_set(vsel, list_get(ea, cur), 1.0)
            bind vsel = list_set(vsel, list_get(eb, cur), 1.0)
            bind fa = list_get(ef0, cur)
            bind fb = list_get(ef1, cur)
            bind nf = sel(fa == prevf, fb, fa)
            bind quad = false
            if nf >= 0.0 { if list_get(fc, nf) == 4.0 { bind quad = true } }
            if !quad { bind go = false }
            else {
                # opposite edge in the quad
                bind s = list_get(fs, nf)
                bind pos = 0.0 - 1.0
                bind k = 0.0
                while k < 4.0 { if list_get(ce, s + k) == cur { bind pos = k } bind k = k + 1.0 }
                bind nxt = list_get(ce, s + (pos + 2.0) % 4.0)
                bind prevf = nf
                bind cur = nxt
                if cur == e { bind go = false }
            }
            bind steps = steps + 1.0
        }
        bind dir = dir + 1.0
    }
    st_set(mk(o, "vsel"), vsel)
    me_sel_sync_from_verts(o)
}

# ── undo / redo (snapshots of the whole scene: cheap, lists are shared until edited) ──
fn me_scene_keys() { return ["ob_ids", "sel_o", "ob_next", "cursor_x", "cursor_y", "cursor_z"] }
fn me_snapshot(prefix) {
    bind sk = me_scene_keys()
    bind i = 0.0
    while i < len(sk) { st_set(prefix + list_get(sk, i), st_get(list_get(sk, i), 0.0)) bind i = i + 1.0 }
    bind ids = me_ids()
    bind keys = me_object_keys()
    st_set(prefix + "ids", ids)
    bind j = 0.0
    while j < len(ids) {
        bind o = list_get(ids, j)
        bind i = 0.0
        while i < len(keys) {
            bind k = list_get(keys, i)
            st_set(prefix + mk(o, k), st_get(mk(o, k), 0.0))
            bind i = i + 1.0
        }
        bind j = j + 1.0
    }
}
fn me_restore(prefix) {
    bind ids_now = me_ids()
    bind i = 0.0
    while i < len(ids_now) { me_free(list_get(ids_now, i)) bind i = i + 1.0 }
    bind sk = me_scene_keys()
    bind i = 0.0
    while i < len(sk) { st_set(list_get(sk, i), st_get(prefix + list_get(sk, i), 0.0)) bind i = i + 1.0 }
    bind ids = st_get(prefix + "ids", [])
    st_set("ob_ids", ids)
    bind keys = me_object_keys()
    bind j = 0.0
    while j < len(ids) {
        bind o = list_get(ids, j)
        bind i = 0.0
        while i < len(keys) {
            bind k = list_get(keys, i)
            st_set(mk(o, k), st_get(prefix + mk(o, k), 0.0))
            bind i = i + 1.0
        }
        st_set(mk(o, "edirty"), 1.0)
        bind j = j + 1.0
    }
    st_set("dirty", 1.0)
}
fn me_undo_push() {
    bind n = st_get("undo_n", 0.0)
    if n >= 40.0 {
        # drop the oldest
        bind i = 1.0
        while i < n { me_snapshot_move("u" + num_str(i) + "_", "u" + num_str(i - 1.0) + "_") bind i = i + 1.0 }
        bind n = n - 1.0
    }
    me_snapshot("u" + num_str(n) + "_")
    st_set("undo_n", n + 1.0)
    st_set("redo_n", 0.0)
    st_set("dirty", 1.0)
}
fn me_snapshot_move(from, to) {
    bind sk = me_scene_keys()
    bind i = 0.0
    while i < len(sk) { st_set(to + list_get(sk, i), st_get(from + list_get(sk, i), 0.0)) bind i = i + 1.0 }
    bind ids = st_get(from + "ids", [])
    st_set(to + "ids", ids)
    bind keys = me_object_keys()
    bind j = 0.0
    while j < len(ids) {
        bind o = list_get(ids, j)
        bind i = 0.0
        while i < len(keys) { bind k = list_get(keys, i) st_set(to + mk(o, k), st_get(from + mk(o, k), 0.0)) bind i = i + 1.0 }
        bind j = j + 1.0
    }
}
fn me_undo() {
    bind n = st_get("undo_n", 0.0)
    if n < 0.5 { st_set("status", "Nothing to undo") return 0.0 }
    bind r = st_get("redo_n", 0.0)
    me_snapshot("r" + num_str(r) + "_")
    st_set("redo_n", r + 1.0)
    me_restore("u" + num_str(n - 1.0) + "_")
    st_set("undo_n", n - 1.0)
    v3_cancel_modal()
    st_set("status", "Undo")
}
fn me_redo() {
    bind r = st_get("redo_n", 0.0)
    if r < 0.5 { st_set("status", "Nothing to redo") return 0.0 }
    bind n = st_get("undo_n", 0.0)
    me_snapshot("u" + num_str(n) + "_")
    st_set("undo_n", n + 1.0)
    me_restore("r" + num_str(r - 1.0) + "_")
    st_set("redo_n", r - 1.0)
    v3_cancel_modal()
    st_set("status", "Redo")
}

# ── vertex / face edits used by several modules ──────────────────────────────
# replace the whole topology of an object (keeps transform, colour, bones; resets uv/selection/weights sizes)
fn me_set_geometry(o, vx, vy, vz, fi, fs, fc) {
    st_set(mk(o, "vx"), vx) st_set(mk(o, "vy"), vy) st_set(mk(o, "vz"), vz)
    st_set(mk(o, "fi"), fi) st_set(mk(o, "fs"), fs) st_set(mk(o, "fc"), fc)
    st_set(mk(o, "vsel"), me_zeros(len(vx)))
    st_set(mk(o, "fsel"), me_zeros(len(fc)))
    st_set(mk(o, "fcol"), me_fill(len(fc), 0.0 - 1.0))
    st_set(mk(o, "uv"), me_zeros(len(fi) * 2.0))
    bind wb = st_get(mk(o, "wb"), [])
    if len(wb) > 0.5 { st_set(mk(o, "wb"), me_wb_resize(wb, len(vx))) }
    me_edges_dirty(o)
    me_rebuild_edges(o)
    st_set("dirty", 1.0)
}
# triangulated index list (flat) for export and rendering helpers
fn me_tri_indices(o) {
    bind fi = st_get(mk(o, "fi"), []) bind fs = st_get(mk(o, "fs"), []) bind fc = st_get(mk(o, "fc"), [])
    bind out = list_new()
    bind f = 0.0
    while f < len(fc) {
        bind s = list_get(fs, f) bind c = list_get(fc, f)
        bind k = 1.0
        while k + 1.0 < c {
            bind out = list_push(out, list_get(fi, s))
            bind out = list_push(out, list_get(fi, s + k))
            bind out = list_push(out, list_get(fi, s + k + 1.0))
            bind k = k + 1.0
        }
        bind f = f + 1.0
    }
    return out
}

← Back to index