lingmodeler
↓ 0 downloadsLingModeler: 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
Keywords 3d, modeling, vector, svg, gltf, obj, rigging, animation, textures, ling
lingfu install lingmodelerCode / 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
}