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/meshops.ling
# ━━━ meshops.ling — box modeling operations on the active object ━━━
# Conventions: faces with 2 corners are loose edges (no fill); every op keeps uv per corner where it can,
# calls me_edges_dirty and leaves a sensible selection. Callers push undo first.
# ── helpers ──────────────────────────────────────────────────────────────────
# edge -> adjacent faces (up to 2): returns [ef0, ef1]
fn mo_edge_faces(o) {
me_rebuild_edges(o)
bind ce = st_get(mk(o, "ce"), [])
bind fs = st_get(mk(o, "fs"), []) bind fc = st_get(mk(o, "fc"), [])
bind ne = len(st_get(mk(o, "ea"), []))
bind ef0 = me_fill(ne, 0.0 - 1.0)
bind ef1 = me_fill(ne, 0.0 - 1.0)
bind f = 0.0
while f < len(fc) {
bind s = list_get(fs, f) bind c = list_get(fc, f)
if c >= 3.0 {
bind k = 0.0
while k < c {
bind e = list_get(ce, s + k)
if list_get(ef0, e) < 0.0 { bind ef0 = list_set(ef0, e, f) }
else if list_get(ef1, e) < 0.0 { bind ef1 = list_set(ef1, e, f) }
bind k = k + 1.0
}
}
bind f = f + 1.0
}
return [ef0, ef1]
}
# rebuild an object from new face lists and a vertex remap (old index -> new index or -1 to drop).
# nfaces = flat list of corner lists terminated by -1 each: a b c -1 d e f g -1 ...
# corner uv/fcol are reset for faces that are new (uvsrc = -1); existing faces may pass their old corner start via uvsrc list.
fn mo_compact(o, nvx, nvy, nvz, cfi, cfs, cfc, cuv, cfcol, cwb) {
st_set(mk(o, "vx"), nvx) st_set(mk(o, "vy"), nvy) st_set(mk(o, "vz"), nvz)
st_set(mk(o, "fi"), cfi) st_set(mk(o, "fs"), cfs) st_set(mk(o, "fc"), cfc)
st_set(mk(o, "uv"), cuv)
st_set(mk(o, "fcol"), cfcol)
st_set(mk(o, "wb"), cwb)
st_set(mk(o, "vsel"), me_zeros(len(nvx)))
st_set(mk(o, "fsel"), me_zeros(len(cfc)))
me_edges_dirty(o)
me_rebuild_edges(o)
st_set("dirty", 1.0)
}
# remove vertices that no face references (after a face deletion). keeps everything else.
fn mo_drop_unused(o, keep_isolated) {
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 uv = st_get(mk(o, "uv"), []) bind fcol = st_get(mk(o, "fcol"), []) bind wb = st_get(mk(o, "wb"), [])
bind nv = len(vx)
bind used = me_zeros(nv)
bind i = 0.0
while i < len(fi) { bind used = list_set(used, list_get(fi, i), 1.0) bind i = i + 1.0 }
bind remap = me_fill(nv, 0.0 - 1.0)
bind nvx = list_new() bind nvy = list_new() bind nvz = list_new() bind nwb = list_new()
bind i = 0.0
while i < nv {
bind keep = list_get(used, i) > 0.5
if len(keep_isolated) > i { if list_get(keep_isolated, i) > 0.5 { bind keep = true } }
if keep {
bind remap = list_set(remap, i, len(nvx))
bind nvx = list_push(nvx, list_get(vx, i)) bind nvy = list_push(nvy, list_get(vy, i)) bind nvz = list_push(nvz, list_get(vz, i))
if len(wb) > 0.5 { bind k = 0.0 while k < 8.0 { bind nwb = list_push(nwb, list_get(wb, i * 8.0 + k)) bind k = k + 1.0 } }
}
bind i = i + 1.0
}
bind nfi = list_new()
bind i = 0.0
while i < len(fi) { bind nfi = list_push(nfi, list_get(remap, list_get(fi, i))) bind i = i + 1.0 }
mo_compact(o, nvx, nvy, nvz, nfi, fs, fc, uv, fcol, nwb)
}
fn mo_avg_normal_faces(o, faces) {
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 n = [0.0, 0.0, 0.0]
bind i = 0.0
while i < len(faces) {
bind f = list_get(faces, i)
bind n = v3_add(n, me_face_normal(vx, vy, vz, fi, list_get(fs, f), list_get(fc, f)))
bind i = i + 1.0
}
bind l = v3_len(n)
if l < 0.000001 { return [0.0, 1.0, 0.0] }
return v3_scale(n, 1.0 / l)
}
# normal at a vertex (average of adjacent face normals), zero when none
fn mo_vertex_normal(o, v) {
bind vf = me_vert_faces(o)
bind starts = list_get(vf, 0) bind fl = list_get(vf, 1)
bind faces = list_new()
bind k = list_get(starts, v)
while k < list_get(starts, v + 1.0) { bind faces = list_push(faces, list_get(fl, k)) bind k = k + 1.0 }
if len(faces) < 0.5 { return [0.0, 0.0, 0.0] }
return mo_avg_normal_faces(o, faces)
}
fn mo_push_face(fi, fs, fc, corners) {
bind fs2 = list_push(fs, len(fi))
bind fc2 = list_push(fc, len(corners))
bind fi2 = fi
bind k = 0.0
while k < len(corners) { bind fi2 = list_push(fi2, list_get(corners, k)) bind k = k + 1.0 }
return [fi2, fs2, fc2]
}
fn mo_uv_pad(uv, n) {
bind out = uv
bind k = 0.0
while k < n * 2.0 { bind out = list_push(out, 0.0) bind k = k + 1.0 }
return out
}
fn mo_wb_dup(wb, v) {
# append a copy of vertex v's weights (or an empty slot when the mesh has no weights)
if len(wb) < 0.5 { return wb }
bind out = wb
bind k = 0.0
while k < 8.0 { bind out = list_push(out, list_get(wb, v * 8.0 + k)) bind k = k + 1.0 }
return out
}
fn mo_wb_avg(wb, a, b) {
if len(wb) < 0.5 { return wb }
bind out = wb
bind k = 0.0
while k < 8.0 {
bind va = list_get(wb, a * 8.0 + k) bind vb = list_get(wb, b * 8.0 + k)
if k % 2.0 < 0.5 { bind out = list_push(out, va) } else { bind out = list_push(out, (va + vb) * 0.5) }
bind k = k + 1.0
}
return out
}
# ── delete ───────────────────────────────────────────────────────────────────
# what: 0 vertices, 1 edges, 2 faces (selected elements)
fn mo_delete(o, what) {
bind vsel = st_get(mk(o, "vsel"), [])
bind fsel = st_get(mk(o, "fsel"), [])
bind fi = st_get(mk(o, "fi"), []) bind fs = st_get(mk(o, "fs"), []) bind fc = st_get(mk(o, "fc"), [])
bind uv = st_get(mk(o, "uv"), []) bind fcol = st_get(mk(o, "fcol"), [])
bind esel = []
bind ce = []
if what == 1.0 { me_rebuild_edges(o) bind esel = st_get(mk(o, "esel"), []) bind ce = st_get(mk(o, "ce"), []) }
bind nfi = list_new() bind nfs = list_new() bind nfc = list_new() bind nuv = list_new() bind nfcol = list_new()
bind f = 0.0
while f < len(fc) {
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind drop = false
if what == 2.0 { if f < len(fsel) { if list_get(fsel, f) > 0.5 { bind drop = true } } }
bind k = 0.0
while k < c {
bind v = list_get(fi, s + k)
if what == 0.0 { if list_get(vsel, v) > 0.5 { bind drop = true } }
if what == 1.0 { if list_get(esel, list_get(ce, s + k)) > 0.5 { bind drop = true } }
bind k = k + 1.0
}
if !drop {
bind nfs = list_push(nfs, len(nfi)) bind nfc = list_push(nfc, c)
bind nfcol = list_push(nfcol, sel(f < len(fcol), list_get(fcol, f), 0.0 - 1.0))
bind k = 0.0
while k < c {
bind nfi = list_push(nfi, list_get(fi, s + k))
bind nuv = list_push(nuv, list_get(uv, (s + k) * 2.0)) bind nuv = list_push(nuv, list_get(uv, (s + k) * 2.0 + 1.0))
bind k = k + 1.0
}
}
bind f = f + 1.0
}
st_set(mk(o, "fi"), nfi) st_set(mk(o, "fs"), nfs) st_set(mk(o, "fc"), nfc)
st_set(mk(o, "uv"), nuv) st_set(mk(o, "fcol"), nfcol)
# vertices: in vertex mode the selected ones go; otherwise only vertices that lost all their faces
bind keep = me_zeros(len(vsel))
if what != 0.0 {
bind i = 0.0
while i < len(vsel) { bind keep = list_set(keep, i, sel(list_get(vsel, i) > 0.5, 0.0, 1.0)) bind i = i + 1.0 }
}
mo_drop_unused(o, keep)
st_set("status", "Deleted")
}
# ── extrude ──────────────────────────────────────────────────────────────────
fn mo_extrude(o, dist) {
me_rebuild_edges(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 uv = st_get(mk(o, "uv"), []) bind fcol = st_get(mk(o, "fcol"), []) bind wb = st_get(mk(o, "wb"), [])
bind vsel = st_get(mk(o, "vsel"), []) bind fsel = st_get(mk(o, "fsel"), [])
bind ea = st_get(mk(o, "ea"), []) bind eb = st_get(mk(o, "eb"), []) bind ce = st_get(mk(o, "ce"), []) bind esel = st_get(mk(o, "esel"), [])
bind faces = me_selected_faces(o)
bind nv = len(vx)
bind newv = me_fill(nv, 0.0 - 1.0)
bind sm = st_get("sel_mode", 0.0)
if len(faces) > 0.5 && sm != 0.0 {
# region extrude: copy the region's vertices, re-point selected faces, add side quads on boundary edges
bind nrm = mo_avg_normal_faces(o, faces)
bind fsel_cnt = me_zeros(len(ea))
bind i = 0.0
while i < len(faces) {
bind f = list_get(faces, i)
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind k = 0.0
while k < c {
bind e = list_get(ce, s + k)
bind fsel_cnt = list_set(fsel_cnt, e, list_get(fsel_cnt, e) + 1.0)
bind v = list_get(fi, s + k)
if list_get(newv, v) < 0.0 {
bind newv = list_set(newv, v, len(vx))
bind vx = list_push(vx, list_get(vx, v) + list_get(nrm, 0) * dist)
bind vy = list_push(vy, list_get(vy, v) + list_get(nrm, 1) * dist)
bind vz = list_push(vz, list_get(vz, v) + list_get(nrm, 2) * dist)
bind wb = mo_wb_dup(wb, v)
}
bind k = k + 1.0
}
bind i = i + 1.0
}
# does a boundary edge also border an unselected face? then the region is "open" there -> side quad
bind i = 0.0
while i < len(faces) {
bind f = list_get(faces, i)
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind k = 0.0
while k < c {
bind e = list_get(ce, s + k)
if list_get(fsel_cnt, e) == 1.0 {
bind a = list_get(fi, s + k) bind b = list_get(fi, s + (k + 1.0) % c)
bind r = mo_push_face(fi, fs, fc, [b, a, list_get(newv, a), list_get(newv, b)])
bind fi = list_get(r, 0) bind fs = list_get(r, 1) bind fc = list_get(r, 2)
bind uv = mo_uv_pad(uv, 4.0)
bind fcol = list_push(fcol, sel(f < len(fcol), list_get(fcol, f), 0.0 - 1.0))
}
bind k = k + 1.0
}
bind i = i + 1.0
}
# re-point the selected faces to the copies
bind i = 0.0
while i < len(faces) {
bind f = list_get(faces, i)
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind k = 0.0
while k < c { bind fi = list_set(fi, s + k, list_get(newv, list_get(fi, s + k))) bind k = k + 1.0 }
bind i = i + 1.0
}
bind nvsel = me_zeros(len(vx))
bind i = 0.0
while i < nv { if list_get(newv, i) >= 0.0 { bind nvsel = list_set(nvsel, list_get(newv, i), 1.0) } bind i = i + 1.0 }
mo_compact(o, vx, vy, vz, fi, fs, fc, uv, fcol, wb)
st_set(mk(o, "vsel"), nvsel)
me_sel_sync_from_verts(o)
bind nfsel = st_get(mk(o, "fsel"), [])
bind i = 0.0
while i < len(faces) { bind nfsel = list_set(nfsel, list_get(faces, i), 1.0) bind i = i + 1.0 }
st_set(mk(o, "fsel"), nfsel)
v3_begin_modal("g")
st_set("tr_axis", 0.0 - 1.0)
st_set("status", "Extruded " + num_str(len(faces)) + " faces along the normal - move the mouse, click to confirm")
return 0.0
}
bind sedges = me_selected_edges(o)
if len(sedges) > 0.5 && sm != 0.0 {
# edge extrude: quads from each selected edge, direction = average normal of adjacent faces
bind efa = mo_edge_faces(o)
bind ef0 = list_get(efa, 0) bind ef1 = list_get(efa, 1)
bind i = 0.0
while i < len(sedges) {
bind e = list_get(sedges, i)
bind a = list_get(ea, e) bind b = list_get(eb, e)
bind adj = list_new()
if list_get(ef0, e) >= 0.0 { bind adj = list_push(adj, list_get(ef0, e)) }
if list_get(ef1, e) >= 0.0 { bind adj = list_push(adj, list_get(ef1, e)) }
bind nrm = sel(len(adj) > 0.5, mo_avg_normal_faces(o, adj), v3_cam_up())
bind k = 0.0
while k < 2.0 {
bind v = sel(k < 0.5, a, b)
if list_get(newv, v) < 0.0 {
bind newv = list_set(newv, v, len(vx))
bind vx = list_push(vx, list_get(vx, v) + list_get(nrm, 0) * dist)
bind vy = list_push(vy, list_get(vy, v) + list_get(nrm, 1) * dist)
bind vz = list_push(vz, list_get(vz, v) + list_get(nrm, 2) * dist)
bind wb = mo_wb_dup(wb, v)
}
bind k = k + 1.0
}
# winding: opposite to the single adjacent face, if any
bind flipq = false
if len(adj) == 1.0 {
bind f = list_get(adj, 0)
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind k = 0.0
while k < c { if list_get(fi, s + k) == a && list_get(fi, s + (k + 1.0) % c) == b { bind flipq = true } bind k = k + 1.0 }
}
bind quad = [a, b, list_get(newv, b), list_get(newv, a)]
if flipq { bind quad = [b, a, list_get(newv, a), list_get(newv, b)] }
bind r = mo_push_face(fi, fs, fc, quad)
bind fi = list_get(r, 0) bind fs = list_get(r, 1) bind fc = list_get(r, 2)
bind uv = mo_uv_pad(uv, 4.0)
bind fcol = list_push(fcol, 0.0 - 1.0)
bind i = i + 1.0
}
bind nvsel = me_zeros(len(vx))
bind i = 0.0
while i < nv { if list_get(newv, i) >= 0.0 { bind nvsel = list_set(nvsel, list_get(newv, i), 1.0) } bind i = i + 1.0 }
mo_compact(o, vx, vy, vz, fi, fs, fc, uv, fcol, wb)
st_set(mk(o, "vsel"), nvsel)
me_sel_sync_from_verts(o)
v3_begin_modal("g")
st_set("status", "Extruded " + num_str(len(sedges)) + " edges - move the mouse, click to confirm")
return 0.0
}
# vertex extrude: each selected vertex grows a loose edge to a copy
bind verts = me_selected_verts(o)
if len(verts) < 0.5 { st_set("status", "Nothing selected to extrude") return 0.0 }
bind i = 0.0
while i < len(verts) {
bind v = list_get(verts, i)
bind nrm = mo_vertex_normal(o, v)
bind nid = len(vx)
bind newv = list_set(newv, v, nid)
bind vx = list_push(vx, list_get(vx, v) + list_get(nrm, 0) * dist)
bind vy = list_push(vy, list_get(vy, v) + list_get(nrm, 1) * dist)
bind vz = list_push(vz, list_get(vz, v) + list_get(nrm, 2) * dist)
bind wb = mo_wb_dup(wb, v)
bind r = mo_push_face(fi, fs, fc, [v, nid])
bind fi = list_get(r, 0) bind fs = list_get(r, 1) bind fc = list_get(r, 2)
bind uv = mo_uv_pad(uv, 2.0)
bind fcol = list_push(fcol, 0.0 - 1.0)
bind i = i + 1.0
}
bind nvsel = me_zeros(len(vx))
bind i = 0.0
while i < nv { if list_get(newv, i) >= 0.0 { bind nvsel = list_set(nvsel, list_get(newv, i), 1.0) } bind i = i + 1.0 }
mo_compact(o, vx, vy, vz, fi, fs, fc, uv, fcol, wb)
st_set(mk(o, "vsel"), nvsel)
me_sel_sync_from_verts(o)
v3_begin_modal("g")
st_set("status", "Extruded " + num_str(len(verts)) + " vertices - move the mouse, click to confirm")
}
# ── inset (individual faces) ─────────────────────────────────────────────────
fn mo_inset(o, t) {
bind faces = me_selected_faces(o)
if len(faces) < 0.5 { st_set("status", "Select faces to inset") return 0.0 }
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 uv = st_get(mk(o, "uv"), []) bind fcol = st_get(mk(o, "fcol"), []) bind wb = st_get(mk(o, "wb"), [])
bind nvsel = me_zeros(len(vx))
bind inner = list_new()
bind i = 0.0
while i < len(faces) {
bind f = list_get(faces, i)
bind s = list_get(fs, f) bind c = list_get(fc, f)
if c >= 3.0 {
bind cx = 0.0 bind cy = 0.0 bind cz = 0.0
bind k = 0.0
while k < c { bind v = list_get(fi, s + k) bind cx = cx + list_get(vx, v) bind cy = cy + list_get(vy, v) bind cz = cz + list_get(vz, v) bind k = k + 1.0 }
bind cx = cx / c bind cy = cy / c bind cz = cz / c
bind newc = list_new()
bind k = 0.0
while k < c {
bind v = list_get(fi, s + k)
bind dx = cx - list_get(vx, v) bind dy = cy - list_get(vy, v) bind dz = cz - list_get(vz, v)
bind l = max(0.000001, sqrt(dx * dx + dy * dy + dz * dz))
bind tt = min(t, l * 0.95)
bind nid = len(vx)
bind vx = list_push(vx, list_get(vx, v) + dx / l * tt)
bind vy = list_push(vy, list_get(vy, v) + dy / l * tt)
bind vz = list_push(vz, list_get(vz, v) + dz / l * tt)
bind wb = mo_wb_dup(wb, v)
bind nvsel = list_push(nvsel, 1.0)
bind newc = list_push(newc, nid)
bind k = k + 1.0
}
# ring quads
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 r = mo_push_face(fi, fs, fc, [a, b, list_get(newc, (k + 1.0) % c), list_get(newc, k)])
bind fi = list_get(r, 0) bind fs = list_get(r, 1) bind fc = list_get(r, 2)
bind uv = mo_uv_pad(uv, 4.0)
bind fcol = list_push(fcol, sel(f < len(fcol), list_get(fcol, f), 0.0 - 1.0))
bind k = k + 1.0
}
# the face itself becomes the inner polygon
bind k = 0.0
while k < c { bind fi = list_set(fi, s + k, list_get(newc, k)) bind k = k + 1.0 }
bind inner = list_push(inner, f)
}
bind i = i + 1.0
}
mo_compact(o, vx, vy, vz, fi, fs, fc, uv, fcol, wb)
st_set(mk(o, "vsel"), nvsel)
me_sel_sync_from_verts(o)
bind nfsel = st_get(mk(o, "fsel"), [])
bind i = 0.0
while i < len(inner) { bind nfsel = list_set(nfsel, list_get(inner, i), 1.0) bind i = i + 1.0 }
st_set(mk(o, "fsel"), nfsel)
st_set("status", "Inset " + num_str(len(inner)) + " faces by " + fmt2(t))
}
# ── bevel (chamfer of selected edges) ────────────────────────────────────────
fn mo_bevel(o, w, segs) {
me_rebuild_edges(o)
bind sedges = me_selected_edges(o)
if len(sedges) < 0.5 { st_set("status", "Select edges to bevel (edge mode, or faces: their edges)") return 0.0 }
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 uv = st_get(mk(o, "uv"), []) bind fcol = st_get(mk(o, "fcol"), []) bind wb = st_get(mk(o, "wb"), [])
bind ea = st_get(mk(o, "ea"), []) bind eb = st_get(mk(o, "eb"), []) bind ce = st_get(mk(o, "ce"), []) bind esel = st_get(mk(o, "esel"), [])
bind nv = len(vx)
bind nf = len(fc)
# new corner vertex per (vertex, other-edge) for single-selected corners: key = v * ne + e, stored in st map
st_set("bv_n", st_get("bv_n", 0.0) + 1.0)
bind keyp = "bv" + num_str(st_get("bv_n", 0.0)) + "_"
# per face corner: the replacement vertex id (or the original)
bind rep = me_zeros(len(fi))
bind f = 0.0
while f < nf {
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind k = 0.0
while k < c {
bind v = list_get(fi, s + k)
bind prev = list_get(fi, s + (k + c - 1.0) % c)
bind nxt = list_get(fi, s + (k + 1.0) % c)
bind e_in = list_get(ce, s + (k + c - 1.0) % c)
bind e_out = list_get(ce, s + k)
bind sin_ = list_get(esel, e_in) > 0.5
bind sout = list_get(esel, e_out) > 0.5
bind r = v
if c >= 3.0 && (sin_ || sout) {
bind din = v3_norm([list_get(vx, prev) - list_get(vx, v), list_get(vy, prev) - list_get(vy, v), list_get(vz, prev) - list_get(vz, v)])
bind dout = v3_norm([list_get(vx, nxt) - list_get(vx, v), list_get(vy, nxt) - list_get(vy, v), list_get(vz, nxt) - list_get(vz, v)])
bind lin = v3_dist([list_get(vx, prev), list_get(vy, prev), list_get(vz, prev)], [list_get(vx, v), list_get(vy, v), list_get(vz, v)])
bind lout = v3_dist([list_get(vx, nxt), list_get(vy, nxt), list_get(vz, nxt)], [list_get(vx, v), list_get(vy, v), list_get(vz, v)])
bind ww = min(w, min(lin, lout) * 0.45)
if sin_ && sout {
bind r = len(vx)
bind vx = list_push(vx, list_get(vx, v) + (list_get(din, 0) + list_get(dout, 0)) * ww)
bind vy = list_push(vy, list_get(vy, v) + (list_get(din, 1) + list_get(dout, 1)) * ww)
bind vz = list_push(vz, list_get(vz, v) + (list_get(din, 2) + list_get(dout, 2)) * ww)
bind wb = mo_wb_dup(wb, v)
} else {
# move along the unselected edge; share the vertex with the face across that edge
bind oe = sel(sin_, e_out, e_in)
bind d = sel(sin_, dout, din)
bind key = keyp + num_str(v) + "_" + num_str(oe)
if st_has(key) > 0.5 { bind r = st_get(key, 0.0) }
else {
bind r = len(vx)
st_set(key, r)
bind vx = list_push(vx, list_get(vx, v) + list_get(d, 0) * ww)
bind vy = list_push(vy, list_get(vy, v) + list_get(d, 1) * ww)
bind vz = list_push(vz, list_get(vz, v) + list_get(d, 2) * ww)
bind wb = mo_wb_dup(wb, v)
}
}
}
bind rep = list_set(rep, s + k, r)
bind k = k + 1.0
}
bind f = f + 1.0
}
# bevel quads between the two faces of each selected edge
bind efa = mo_edge_faces(o)
bind ef0 = list_get(efa, 0) bind ef1 = list_get(efa, 1)
bind i = 0.0
while i < len(sedges) {
bind e = list_get(sedges, i)
bind f1 = list_get(ef0, e) bind f2 = list_get(ef1, e)
if f1 >= 0.0 && f2 >= 0.0 {
bind a = list_get(ea, e) bind b = list_get(eb, e)
# corner slots of a and b in f1 (a->b direction) and f2
bind a1 = 0.0 - 1.0 bind b1 = 0.0 - 1.0 bind a2 = 0.0 - 1.0 bind b2 = 0.0 - 1.0
bind s = list_get(fs, f1) bind c = list_get(fc, f1)
bind k = 0.0
while k < c {
if list_get(ce, s + k) == e {
bind p = list_get(fi, s + k) bind q = list_get(fi, s + (k + 1.0) % c)
bind rp = list_get(rep, s + k) bind rq = list_get(rep, s + (k + 1.0) % c)
if p == a { bind a1 = rp bind b1 = rq } else { bind a1 = rq bind b1 = rp }
}
bind k = k + 1.0
}
bind s = list_get(fs, f2) bind c = list_get(fc, f2)
bind k = 0.0
while k < c {
if list_get(ce, s + k) == e {
bind p = list_get(fi, s + k) bind q = list_get(fi, s + (k + 1.0) % c)
bind rp = list_get(rep, s + k) bind rq = list_get(rep, s + (k + 1.0) % c)
if p == a { bind a2 = rp bind b2 = rq } else { bind a2 = rq bind b2 = rp }
}
bind k = k + 1.0
}
# f1 traverses a->b when its stored direction is a->b; the quad must traverse the shared edges backwards
bind dir1 = false
bind s = list_get(fs, f1) bind c = list_get(fc, f1)
bind k = 0.0
while k < c { if list_get(fi, s + k) == a && list_get(fi, s + (k + 1.0) % c) == b { bind dir1 = true } bind k = k + 1.0 }
bind quad = [b1, a1, a2, b2]
if !dir1 { bind quad = [a1, b1, b2, a2] }
bind r = mo_push_face(fi, fs, fc, quad)
bind fi = list_get(r, 0) bind fs = list_get(r, 1) bind fc = list_get(r, 2)
bind uv = mo_uv_pad(uv, 4.0)
bind fcol = list_push(fcol, sel(f1 < len(fcol), list_get(fcol, f1), 0.0 - 1.0))
}
bind i = i + 1.0
}
# corner caps: vertices whose surrounding corners were all replaced by 3+ distinct points
bind vf = me_vert_faces(o)
bind vstarts = list_get(vf, 0) bind vfl = list_get(vf, 1)
bind v = 0.0
while v < nv {
bind cnt = list_get(vstarts, v + 1.0) - list_get(vstarts, v)
if cnt >= 3.0 {
# walk the fan: from face f, leave through the edge (v -> next), enter the face across it
bind f0 = list_get(vfl, list_get(vstarts, v))
bind ring = list_new()
bind cur = f0
bind guard = 0.0
bind go = true
bind closed = false
while go && guard < 64.0 {
bind s = list_get(fs, cur) bind c = list_get(fc, cur)
bind pos = 0.0 - 1.0
bind k = 0.0
while k < c { if list_get(fi, s + k) == v { bind pos = k } bind k = k + 1.0 }
if pos < 0.0 || c < 3.0 { bind go = false }
else {
bind ring = list_push(ring, list_get(rep, s + pos))
bind e_out = list_get(ce, s + pos)
bind nxtf = sel(list_get(ef0, e_out) == cur, list_get(ef1, e_out), list_get(ef0, e_out))
if nxtf < 0.0 { bind go = false }
else if nxtf == f0 { bind go = false bind closed = true }
else { bind cur = nxtf }
}
bind guard = guard + 1.0
}
# distinct points
bind pts = list_new()
bind k = 0.0
while k < len(ring) {
bind p = list_get(ring, k)
bind dup = false
bind q = 0.0
while q < len(pts) { if list_get(pts, q) == p { bind dup = true } bind q = q + 1.0 }
if !dup { bind pts = list_push(pts, p) }
bind k = k + 1.0
}
bind changed = false
bind k = 0.0
while k < len(pts) { if list_get(pts, k) != v { bind changed = true } bind k = k + 1.0 }
if closed && changed && len(pts) >= 3.0 {
# reverse so the cap faces outward like its neighbours
bind cap = list_new()
bind k = len(pts) - 1.0
while k >= 0.0 { bind cap = list_push(cap, list_get(pts, k)) bind k = k - 1.0 }
bind r = mo_push_face(fi, fs, fc, cap)
bind fi = list_get(r, 0) bind fs = list_get(r, 1) bind fc = list_get(r, 2)
bind uv = mo_uv_pad(uv, len(cap))
bind fcol = list_push(fcol, 0.0 - 1.0)
}
}
bind v = v + 1.0
}
# apply the corner replacements to the original faces
bind f = 0.0
while f < nf {
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind k = 0.0
while k < c { bind fi = list_set(fi, s + k, list_get(rep, s + k)) bind k = k + 1.0 }
bind f = f + 1.0
}
mo_compact(o, vx, vy, vz, fi, fs, fc, uv, fcol, wb)
mo_drop_unused(o, [])
me_sel_all(o, false)
st_set("status", "Bevelled " + num_str(len(sedges)) + " edges (width " + fmt2(w) + ")")
}
# ── loop cut ─────────────────────────────────────────────────────────────────
fn mo_loopcut(o, cuts) {
me_rebuild_edges(o)
bind sedges = me_selected_edges(o)
if len(sedges) < 0.5 { st_set("status", "Select one edge (edge mode): the cut runs across its ring of quads") return 0.0 }
bind e0 = list_get(sedges, 0)
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 uv = st_get(mk(o, "uv"), []) bind fcol = st_get(mk(o, "fcol"), []) bind wb = st_get(mk(o, "wb"), [])
bind ea = st_get(mk(o, "ea"), []) bind eb = st_get(mk(o, "eb"), []) bind ce = st_get(mk(o, "ce"), [])
bind efa = mo_edge_faces(o)
bind ef0 = list_get(efa, 0) bind ef1 = list_get(efa, 1)
bind ne = len(ea)
bind cuts = max(1.0, floor(cuts))
# ring walk in both directions. per ring edge: oriented pair (sideA, sideB); per crossed quad: entry edge, exit edge
bind esideA = me_fill(ne, 0.0 - 1.0)
bind esideB = me_fill(ne, 0.0 - 1.0)
bind quads = list_new()
bind qin = list_new()
bind qout = list_new()
bind esideA = list_set(esideA, e0, list_get(ea, e0))
bind esideB = list_set(esideB, e0, list_get(eb, e0))
bind dir = 0.0
while dir < 2.0 {
bind cur = e0
bind prevf = sel(dir < 0.5, list_get(ef1, e0), list_get(ef0, e0))
bind go = true
bind guard = 0.0
while go && guard < 4000.0 {
bind fa = list_get(ef0, cur) bind fb = list_get(ef1, cur)
bind nf = sel(fa == prevf, fb, fa)
bind isquad = false
if nf >= 0.0 { if list_get(fc, nf) == 4.0 { bind isquad = true } }
bind seen = false
bind q = 0.0
while q < len(quads) { if list_get(quads, q) == nf { bind seen = true } bind q = q + 1.0 }
if !isquad || seen { bind go = false }
else {
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 opp = list_get(ce, s + (pos + 2.0) % 4.0)
# orientation: in this quad the entry edge runs p->q (corners pos, pos+1); the rail from p goes to corner pos+3
bind p = list_get(fi, s + pos)
bind A = list_get(esideA, cur)
bind railA = sel(p == A, list_get(fi, s + (pos + 3.0) % 4.0), list_get(fi, s + (pos + 2.0) % 4.0))
bind railB = sel(p == A, list_get(fi, s + (pos + 2.0) % 4.0), list_get(fi, s + (pos + 3.0) % 4.0))
if list_get(esideA, opp) < 0.0 { bind esideA = list_set(esideA, opp, railA) bind esideB = list_set(esideB, opp, railB) }
bind quads = list_push(quads, nf) bind qin = list_push(qin, cur) bind qout = list_push(qout, opp)
bind prevf = nf
bind cur = opp
if cur == e0 { bind go = false }
}
bind guard = guard + 1.0
}
bind dir = dir + 1.0
}
if len(quads) < 0.5 { st_set("status", "No quad ring through that edge") return 0.0 }
# new vertices on every ring edge (cuts per edge), keyed by edge: index of first new vertex
bind efirst = me_fill(ne, 0.0 - 1.0)
bind ring_edges = list_new()
bind i = 0.0
while i < len(quads) {
bind k = 0.0
while k < 2.0 {
bind e = sel(k < 0.5, list_get(qin, i), list_get(qout, i))
if list_get(efirst, e) < 0.0 {
bind efirst = list_set(efirst, e, len(vx))
bind ring_edges = list_push(ring_edges, e)
bind A = list_get(esideA, e) bind B = list_get(esideB, e)
bind j = 1.0
while j <= cuts {
bind t = j / (cuts + 1.0)
bind vx = list_push(vx, list_get(vx, A) + (list_get(vx, B) - list_get(vx, A)) * t)
bind vy = list_push(vy, list_get(vy, A) + (list_get(vy, B) - list_get(vy, A)) * t)
bind vz = list_push(vz, list_get(vz, A) + (list_get(vz, B) - list_get(vz, A)) * t)
bind wb = mo_wb_avg(wb, A, B)
bind j = j + 1.0
}
}
bind k = k + 1.0
}
bind i = i + 1.0
}
# rebuild faces: crossed quads become cuts+1 quads; other faces touching a ring edge get the new verts inserted
bind nfi = list_new() bind nfs = list_new() bind nfc = list_new() bind nuv = list_new() bind nfcol = list_new()
bind f = 0.0
while f < len(fc) {
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind qi = 0.0 - 1.0
bind q = 0.0
while q < len(quads) { if list_get(quads, q) == f { bind qi = q } bind q = q + 1.0 }
bind col = sel(f < len(fcol), list_get(fcol, f), 0.0 - 1.0)
if qi >= 0.0 {
bind ein = list_get(qin, qi) bind eout = list_get(qout, qi)
# corner order so the quad reads (A_in, B_in, B_out, A_out) consistent with the face winding
bind pos = 0.0 - 1.0
bind k = 0.0
while k < 4.0 { if list_get(ce, s + k) == ein { bind pos = k } bind k = k + 1.0 }
bind p0 = list_get(fi, s + pos) bind p1 = list_get(fi, s + (pos + 1.0) % 4.0)
bind p2 = list_get(fi, s + (pos + 2.0) % 4.0) bind p3 = list_get(fi, s + (pos + 3.0) % 4.0)
# sequence of points along the entry edge from p0 to p1 and along the exit edge from p3 to p2
bind in_pts = mo_edge_chain(ein, p0, p1, esideA, efirst, cuts)
bind out_pts = mo_edge_chain(eout, p3, p2, esideA, efirst, cuts)
bind j = 0.0
while j <= cuts {
bind corners = [list_get(in_pts, j), list_get(in_pts, j + 1.0), list_get(out_pts, j + 1.0), list_get(out_pts, j)]
bind nfs = list_push(nfs, len(nfi)) bind nfc = list_push(nfc, 4.0) bind nfcol = list_push(nfcol, col)
bind k = 0.0
while k < 4.0 { bind nfi = list_push(nfi, list_get(corners, k)) bind nuv = list_push(nuv, 0.0) bind nuv = list_push(nuv, 0.0) bind k = k + 1.0 }
bind j = j + 1.0
}
} else {
bind corners = list_new()
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 e = list_get(ce, s + k)
bind corners = list_push(corners, a)
if c >= 3.0 && list_get(efirst, e) >= 0.0 {
bind chain = mo_edge_chain(e, a, b, esideA, efirst, cuts)
bind j = 1.0
while j <= cuts { bind corners = list_push(corners, list_get(chain, j)) bind j = j + 1.0 }
}
bind k = k + 1.0
}
bind nfs = list_push(nfs, len(nfi)) bind nfc = list_push(nfc, len(corners)) bind nfcol = list_push(nfcol, col)
bind k = 0.0
while k < len(corners) { bind nfi = list_push(nfi, list_get(corners, k)) bind nuv = list_push(nuv, 0.0) bind nuv = list_push(nuv, 0.0) bind k = k + 1.0 }
}
bind f = f + 1.0
}
bind nvsel = me_zeros(len(vx))
bind i = 0.0
while i < len(ring_edges) {
bind first = list_get(efirst, list_get(ring_edges, i))
bind j = 0.0
while j < cuts { bind nvsel = list_set(nvsel, first + j, 1.0) bind j = j + 1.0 }
bind i = i + 1.0
}
mo_compact(o, vx, vy, vz, nfi, nfs, nfc, nuv, nfcol, wb)
st_set(mk(o, "vsel"), nvsel)
me_sel_sync_from_verts(o)
st_set("sel_mode", 0.0)
st_set("status", "Loop cut: " + num_str(len(quads)) + " quads, " + num_str(cuts) + " cut(s) - G slides the new loop")
}
# points along edge e from vertex a to vertex b including both ends: [a, n1.., b]
fn mo_edge_chain(e, a, b, esideA, efirst, cuts) {
bind out = [a]
bind first = list_get(efirst, e)
bind forward = list_get(esideA, e) == a
bind j = 0.0
while j < cuts {
bind idx = sel(forward, first + j, first + cuts - 1.0 - j)
bind out = list_push(out, idx)
bind j = j + 1.0
}
return list_push(out, b)
}
# ── subdivide (selected faces, or all) ───────────────────────────────────────
fn mo_subdivide(o) {
me_rebuild_edges(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 fcol = st_get(mk(o, "fcol"), []) bind wb = st_get(mk(o, "wb"), [])
bind ea = st_get(mk(o, "ea"), []) bind eb = st_get(mk(o, "eb"), []) bind ce = st_get(mk(o, "ce"), [])
bind fsel = st_get(mk(o, "fsel"), [])
bind sel_faces = me_selected_faces(o)
bind all = len(sel_faces) < 0.5
bind ne = len(ea)
bind emid = me_fill(ne, 0.0 - 1.0)
bind use = me_zeros(len(fc))
bind f = 0.0
while f < len(fc) {
bind on = all
if !on { if f < len(fsel) { if list_get(fsel, f) > 0.5 { bind on = true } } }
if list_get(fc, f) < 3.0 { bind on = false }
if on {
bind use = list_set(use, f, 1.0)
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind k = 0.0
while k < c {
bind e = list_get(ce, s + k)
if list_get(emid, e) < 0.0 {
bind a = list_get(ea, e) bind b = list_get(eb, e)
bind emid = list_set(emid, e, len(vx))
bind vx = list_push(vx, (list_get(vx, a) + list_get(vx, b)) * 0.5)
bind vy = list_push(vy, (list_get(vy, a) + list_get(vy, b)) * 0.5)
bind vz = list_push(vz, (list_get(vz, a) + list_get(vz, b)) * 0.5)
bind wb = mo_wb_avg(wb, a, b)
}
bind k = k + 1.0
}
}
bind f = f + 1.0
}
bind nfi = list_new() bind nfs = list_new() bind nfc = list_new() bind nfcol = list_new()
bind nvsel = me_zeros(len(vx))
bind f = 0.0
while f < len(fc) {
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind col = sel(f < len(fcol), list_get(fcol, f), 0.0 - 1.0)
if list_get(use, f) > 0.5 {
bind cx = 0.0 bind cy = 0.0 bind cz = 0.0
bind k = 0.0
while k < c { bind v = list_get(fi, s + k) bind cx = cx + list_get(vx, v) bind cy = cy + list_get(vy, v) bind cz = cz + list_get(vz, v) bind k = k + 1.0 }
bind centre = len(vx)
bind vx = list_push(vx, cx / c) bind vy = list_push(vy, cy / c) bind vz = list_push(vz, cz / c)
bind wb = mo_wb_dup(wb, list_get(fi, s))
bind nvsel = list_push(nvsel, 1.0)
bind k = 0.0
while k < c {
bind v = list_get(fi, s + k)
bind m_prev = list_get(emid, list_get(ce, s + (k + c - 1.0) % c))
bind m_next = list_get(emid, list_get(ce, s + k))
bind r = mo_push_face(nfi, nfs, nfc, [v, m_next, centre, m_prev])
bind nfi = list_get(r, 0) bind nfs = list_get(r, 1) bind nfc = list_get(r, 2)
bind nfcol = list_push(nfcol, col)
bind nvsel = list_set(nvsel, v, 1.0)
bind nvsel = list_set(nvsel, m_next, 1.0)
bind k = k + 1.0
}
} else {
bind corners = list_new()
bind k = 0.0
while k < c {
bind corners = list_push(corners, list_get(fi, s + k))
bind m = list_get(emid, list_get(ce, s + k))
if c >= 3.0 && m >= 0.0 { bind corners = list_push(corners, m) }
bind k = k + 1.0
}
bind r = mo_push_face(nfi, nfs, nfc, corners)
bind nfi = list_get(r, 0) bind nfs = list_get(r, 1) bind nfc = list_get(r, 2)
bind nfcol = list_push(nfcol, col)
}
bind f = f + 1.0
}
mo_compact(o, vx, vy, vz, nfi, nfs, nfc, me_zeros(len(nfi) * 2.0), nfcol, wb)
st_set(mk(o, "vsel"), nvsel)
me_sel_sync_from_verts(o)
st_set("status", "Subdivided")
}
# ── Catmull-Clark (whole mesh) ───────────────────────────────────────────────
fn mo_subsurf(o) {
me_rebuild_edges(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 fcol = st_get(mk(o, "fcol"), []) bind wb = st_get(mk(o, "wb"), [])
bind ea = st_get(mk(o, "ea"), []) bind eb = st_get(mk(o, "eb"), []) bind ce = st_get(mk(o, "ce"), [])
bind nv = len(vx) bind ne = len(ea) bind nf = len(fc)
if nf < 0.5 { return 0.0 }
# face points
bind fpx = me_zeros(nf) bind fpy = me_zeros(nf) bind fpz = me_zeros(nf)
bind f = 0.0
while f < nf {
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind cx = 0.0 bind cy = 0.0 bind cz = 0.0
bind k = 0.0
while k < c { bind v = list_get(fi, s + k) bind cx = cx + list_get(vx, v) bind cy = cy + list_get(vy, v) bind cz = cz + list_get(vz, v) bind k = k + 1.0 }
bind fpx = list_set(fpx, f, cx / c) bind fpy = list_set(fpy, f, cy / c) bind fpz = list_set(fpz, f, cz / c)
bind f = f + 1.0
}
# edge -> faces
bind efa = mo_edge_faces(o)
bind ef0 = list_get(efa, 0) bind ef1 = list_get(efa, 1)
# edge points
bind epx = me_zeros(ne) bind epy = me_zeros(ne) bind epz = me_zeros(ne)
bind e = 0.0
while e < ne {
bind a = list_get(ea, e) bind b = list_get(eb, e)
bind mx2 = (list_get(vx, a) + list_get(vx, b)) * 0.5
bind my2 = (list_get(vy, a) + list_get(vy, b)) * 0.5
bind mz2 = (list_get(vz, a) + list_get(vz, b)) * 0.5
bind f1 = list_get(ef0, e) bind f2 = list_get(ef1, e)
if f1 >= 0.0 && f2 >= 0.0 {
bind epx = list_set(epx, e, (mx2 * 2.0 + list_get(fpx, f1) + list_get(fpx, f2)) * 0.25)
bind epy = list_set(epy, e, (my2 * 2.0 + list_get(fpy, f1) + list_get(fpy, f2)) * 0.25)
bind epz = list_set(epz, e, (mz2 * 2.0 + list_get(fpz, f1) + list_get(fpz, f2)) * 0.25)
} else {
bind epx = list_set(epx, e, mx2) bind epy = list_set(epy, e, my2) bind epz = list_set(epz, e, mz2)
}
bind e = e + 1.0
}
# vertex points: (F + 2R + (n-3)P) / n, boundary: (P*6 + M1 + M2)/8 with boundary edge midpoints
bind sfx = me_zeros(nv) bind sfy = me_zeros(nv) bind sfz = me_zeros(nv) bind vnf = me_zeros(nv)
bind f = 0.0
while f < nf {
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind k = 0.0
while k < c {
bind v = list_get(fi, s + k)
bind sfx = list_set(sfx, v, list_get(sfx, v) + list_get(fpx, f)) bind sfy = list_set(sfy, v, list_get(sfy, v) + list_get(fpy, f)) bind sfz = list_set(sfz, v, list_get(sfz, v) + list_get(fpz, f))
bind vnf = list_set(vnf, v, list_get(vnf, v) + 1.0)
bind k = k + 1.0
}
bind f = f + 1.0
}
bind srx = me_zeros(nv) bind sry = me_zeros(nv) bind srz = me_zeros(nv) bind vne = me_zeros(nv)
bind bx = me_zeros(nv) bind by = me_zeros(nv) bind bz = me_zeros(nv) bind vnb = me_zeros(nv)
bind e = 0.0
while e < ne {
bind a = list_get(ea, e) bind b = list_get(eb, e)
bind mx2 = (list_get(vx, a) + list_get(vx, b)) * 0.5 bind my2 = (list_get(vy, a) + list_get(vy, b)) * 0.5 bind mz2 = (list_get(vz, a) + list_get(vz, b)) * 0.5
bind k = 0.0
while k < 2.0 {
bind v = sel(k < 0.5, a, b)
bind srx = list_set(srx, v, list_get(srx, v) + mx2) bind sry = list_set(sry, v, list_get(sry, v) + my2) bind srz = list_set(srz, v, list_get(srz, v) + mz2)
bind vne = list_set(vne, v, list_get(vne, v) + 1.0)
if list_get(ef1, e) < 0.0 {
bind bx = list_set(bx, v, list_get(bx, v) + mx2) bind by = list_set(by, v, list_get(by, v) + my2) bind bz = list_set(bz, v, list_get(bz, v) + mz2)
bind vnb = list_set(vnb, v, list_get(vnb, v) + 1.0)
}
bind k = k + 1.0
}
bind e = e + 1.0
}
bind nvx = me_zeros(nv) bind nvy = me_zeros(nv) bind nvz = me_zeros(nv)
bind v = 0.0
while v < nv {
bind n = list_get(vne, v)
if list_get(vnb, v) > 0.5 {
bind nvx = list_set(nvx, v, (list_get(vx, v) * 6.0 + list_get(bx, v)) / (6.0 + list_get(vnb, v)))
bind nvy = list_set(nvy, v, (list_get(vy, v) * 6.0 + list_get(by, v)) / (6.0 + list_get(vnb, v)))
bind nvz = list_set(nvz, v, (list_get(vz, v) * 6.0 + list_get(bz, v)) / (6.0 + list_get(vnb, v)))
} else if n >= 3.0 && list_get(vnf, v) > 0.5 {
bind F = [list_get(sfx, v) / list_get(vnf, v), list_get(sfy, v) / list_get(vnf, v), list_get(sfz, v) / list_get(vnf, v)]
bind R = [list_get(srx, v) / n, list_get(sry, v) / n, list_get(srz, v) / n]
bind nvx = list_set(nvx, v, (list_get(F, 0) + 2.0 * list_get(R, 0) + (n - 3.0) * list_get(vx, v)) / n)
bind nvy = list_set(nvy, v, (list_get(F, 1) + 2.0 * list_get(R, 1) + (n - 3.0) * list_get(vy, v)) / n)
bind nvz = list_set(nvz, v, (list_get(F, 2) + 2.0 * list_get(R, 2) + (n - 3.0) * list_get(vz, v)) / n)
} else {
bind nvx = list_set(nvx, v, list_get(vx, v)) bind nvy = list_set(nvy, v, list_get(vy, v)) bind nvz = list_set(nvz, v, list_get(vz, v))
}
bind v = v + 1.0
}
# assemble: original verts, then edge points, then face points
bind e = 0.0
while e < ne { bind nvx = list_push(nvx, list_get(epx, e)) bind nvy = list_push(nvy, list_get(epy, e)) bind nvz = list_push(nvz, list_get(epz, e)) bind wb = mo_wb_avg(wb, list_get(ea, e), list_get(eb, e)) bind e = e + 1.0 }
bind f = 0.0
while f < nf { bind nvx = list_push(nvx, list_get(fpx, f)) bind nvy = list_push(nvy, list_get(fpy, f)) bind nvz = list_push(nvz, list_get(fpz, f)) bind wb = mo_wb_dup(wb, list_get(fi, list_get(fs, f))) bind f = f + 1.0 }
bind nfi = list_new() bind nfs = list_new() bind nfc = list_new() bind nfcol = list_new()
bind f = 0.0
while f < nf {
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind col = sel(f < len(fcol), list_get(fcol, f), 0.0 - 1.0)
if c >= 3.0 {
bind k = 0.0
while k < c {
bind v = list_get(fi, s + k)
bind e_prev = list_get(ce, s + (k + c - 1.0) % c)
bind e_next = list_get(ce, s + k)
bind r = mo_push_face(nfi, nfs, nfc, [v, nv + e_next, nv + ne + f, nv + e_prev])
bind nfi = list_get(r, 0) bind nfs = list_get(r, 1) bind nfc = list_get(r, 2)
bind nfcol = list_push(nfcol, col)
bind k = k + 1.0
}
} else {
bind r = mo_push_face(nfi, nfs, nfc, [list_get(fi, s), list_get(fi, s + 1.0)])
bind nfi = list_get(r, 0) bind nfs = list_get(r, 1) bind nfc = list_get(r, 2)
bind nfcol = list_push(nfcol, col)
}
bind f = f + 1.0
}
mo_compact(o, nvx, nvy, nvz, nfi, nfs, nfc, me_zeros(len(nfi) * 2.0), nfcol, wb)
mo_drop_unused(o, [])
uv_box(o)
st_set("status", "Catmull-Clark: " + num_str(me_nfaces(o)) + " faces")
}
# ── merge / weld ─────────────────────────────────────────────────────────────
fn mo_apply_remap(o, remap, nvx, nvy, nvz, nwb) {
bind fi = st_get(mk(o, "fi"), []) bind fs = st_get(mk(o, "fs"), []) bind fc = st_get(mk(o, "fc"), [])
bind uv = st_get(mk(o, "uv"), []) bind fcol = st_get(mk(o, "fcol"), [])
bind nfi = list_new() bind nfs = list_new() bind nfc = list_new() bind nuv = list_new() bind nfcol = list_new()
bind f = 0.0
while f < len(fc) {
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind corners = list_new() bind cuv = list_new()
bind k = 0.0
while k < c {
bind v = list_get(remap, list_get(fi, s + k))
bind addc = true
if len(corners) > 0.5 { if list_get(corners, len(corners) - 1.0) == v { bind addc = false } }
if addc {
bind corners = list_push(corners, v)
bind cuv = list_push(cuv, list_get(uv, (s + k) * 2.0)) bind cuv = list_push(cuv, list_get(uv, (s + k) * 2.0 + 1.0))
}
bind k = k + 1.0
}
if len(corners) > 1.5 { if list_get(corners, 0) == list_get(corners, len(corners) - 1.0) {
bind tmp = list_new() bind tuv = list_new()
bind k = 0.0
while k < len(corners) - 1.0 { bind tmp = list_push(tmp, list_get(corners, k)) bind tuv = list_push(tuv, list_get(cuv, k * 2.0)) bind tuv = list_push(tuv, list_get(cuv, k * 2.0 + 1.0)) bind k = k + 1.0 }
bind corners = tmp bind cuv = tuv
} }
bind keepf = len(corners) >= 3.0 || (len(corners) == 2.0 && c == 2.0)
if keepf {
bind nfs = list_push(nfs, len(nfi)) bind nfc = list_push(nfc, len(corners))
bind nfcol = list_push(nfcol, sel(f < len(fcol), list_get(fcol, f), 0.0 - 1.0))
bind k = 0.0
while k < len(corners) { bind nfi = list_push(nfi, list_get(corners, k)) bind nuv = list_push(nuv, list_get(cuv, k * 2.0)) bind nuv = list_push(nuv, list_get(cuv, k * 2.0 + 1.0)) bind k = k + 1.0 }
}
bind f = f + 1.0
}
mo_compact(o, nvx, nvy, nvz, nfi, nfs, nfc, nuv, nfcol, nwb)
}
fn mo_merge(o) {
bind verts = me_selected_verts(o)
if len(verts) < 2.0 { st_set("status", "Select at least two vertices to merge") return 0.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 cx = 0.0 bind cy = 0.0 bind cz = 0.0
bind i = 0.0
while i < len(verts) { bind v = list_get(verts, i) bind cx = cx + list_get(vx, v) bind cy = cy + list_get(vy, v) bind cz = cz + list_get(vz, v) bind i = i + 1.0 }
bind n = len(verts)
bind target = list_get(verts, 0)
bind vx = list_set(vx, target, cx / n) bind vy = list_set(vy, target, cy / n) bind vz = list_set(vz, target, cz / n)
bind vsel = st_get(mk(o, "vsel"), [])
bind remap = me_zeros(len(vx))
bind nvx = list_new() bind nvy = list_new() bind nvz = list_new() bind nwb = list_new()
bind i = 0.0
while i < len(vx) {
bind keep = list_get(vsel, i) < 0.5 || i == target
if keep {
bind remap = list_set(remap, i, len(nvx))
bind nvx = list_push(nvx, list_get(vx, i)) bind nvy = list_push(nvy, list_get(vy, i)) bind nvz = list_push(nvz, list_get(vz, i))
bind nwb = mo_wb_dup(wb, i)
}
bind i = i + 1.0
}
bind i = 0.0
while i < len(vx) { if list_get(vsel, i) > 0.5 { bind remap = list_set(remap, i, list_get(remap, target)) } bind i = i + 1.0 }
mo_apply_remap(o, remap, nvx, nvy, nvz, nwb)
bind nvsel = st_get(mk(o, "vsel"), [])
bind nvsel = list_set(nvsel, list_get(remap, target), 1.0)
st_set(mk(o, "vsel"), nvsel)
me_sel_sync_from_verts(o)
st_set("status", "Merged " + num_str(n) + " vertices")
}
fn mo_merge_dist(o, d) {
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 n = len(vx)
bind order = mx_argsort(vx)
bind remap = me_fill(n, 0.0 - 1.0)
bind nvx = list_new() bind nvy = list_new() bind nvz = list_new() bind nwb = list_new()
bind i = 0.0
while i < n {
bind vi = list_get(order, i)
if list_get(remap, vi) < 0.0 {
bind id = len(nvx)
bind nvx = list_push(nvx, list_get(vx, vi)) bind nvy = list_push(nvy, list_get(vy, vi)) bind nvz = list_push(nvz, list_get(vz, vi))
bind nwb = mo_wb_dup(wb, vi)
bind remap = list_set(remap, vi, id)
bind j = i + 1.0
bind go = true
while go {
if j >= n { bind go = false }
else {
bind vj = list_get(order, j)
if list_get(vx, vj) - list_get(vx, vi) > d { bind go = false }
else {
if list_get(remap, vj) < 0.0 && abs(list_get(vy, vj) - list_get(vy, vi)) <= d && abs(list_get(vz, vj) - list_get(vz, vi)) <= d { bind remap = list_set(remap, vj, id) }
bind j = j + 1.0
}
}
}
}
bind i = i + 1.0
}
bind before = n
mo_apply_remap(o, remap, nvx, nvy, nvz, nwb)
st_set("status", "Merged " + num_str(before - len(nvx)) + " vertices by distance")
}
# ── dissolve (selected edges merge their faces; 2-edge vertices disappear) ───
fn mo_dissolve(o) {
me_rebuild_edges(o)
bind sedges = me_selected_edges(o)
bind fi = st_get(mk(o, "fi"), []) bind fs = st_get(mk(o, "fs"), []) bind fc = st_get(mk(o, "fc"), [])
bind ce = st_get(mk(o, "ce"), []) bind ea = st_get(mk(o, "ea"), []) bind eb = st_get(mk(o, "eb"), [])
bind fcol = st_get(mk(o, "fcol"), [])
bind efa = mo_edge_faces(o)
bind ef0 = list_get(efa, 0) bind ef1 = list_get(efa, 1)
bind vsel = st_get(mk(o, "vsel"), [])
# union-find over faces joined by dissolved edges
bind parent = list_new()
bind f = 0.0
while f < len(fc) { bind parent = list_push(parent, f) bind f = f + 1.0 }
bind dis = me_zeros(len(ea))
bind i = 0.0
while i < len(sedges) {
bind e = list_get(sedges, i)
bind a = list_get(ef0, e) bind b = list_get(ef1, e)
if a >= 0.0 && b >= 0.0 && a != b {
bind dis = list_set(dis, e, 1.0)
bind ra = mo_find(parent, a) bind rb = mo_find(parent, b)
if ra != rb { bind parent = list_set(parent, ra, rb) }
}
bind i = i + 1.0
}
# for each group: walk the boundary (edges not dissolved) to build one polygon
bind done = me_zeros(len(fc))
bind nfi = list_new() bind nfs = list_new() bind nfc = list_new() bind nfcol = list_new()
bind f = 0.0
while f < len(fc) {
bind root = mo_find(parent, f)
if list_get(done, root) < 0.5 {
bind done = list_set(done, root, 1.0)
bind single = true
bind g = 0.0
while g < len(fc) { if g != f && mo_find(parent, g) == root { bind single = false } bind g = g + 1.0 }
bind col = sel(f < len(fcol), list_get(fcol, f), 0.0 - 1.0)
if single {
bind corners = list_new()
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind k = 0.0
while k < c { bind corners = list_push(corners, list_get(fi, s + k)) bind k = k + 1.0 }
bind r = mo_push_face(nfi, nfs, nfc, corners)
bind nfi = list_get(r, 0) bind nfs = list_get(r, 1) bind nfc = list_get(r, 2)
bind nfcol = list_push(nfcol, col)
} else {
# directed boundary half-edges of the group: (from, to) for corners whose edge is not dissolved
bind hfrom = list_new() bind hto = list_new()
bind g = 0.0
while g < len(fc) {
if mo_find(parent, g) == root {
bind s = list_get(fs, g) bind c = list_get(fc, g)
bind k = 0.0
while k < c {
if list_get(dis, list_get(ce, s + k)) < 0.5 {
bind hfrom = list_push(hfrom, list_get(fi, s + k))
bind hto = list_push(hto, list_get(fi, s + (k + 1.0) % c))
}
bind k = k + 1.0
}
}
bind g = g + 1.0
}
if len(hfrom) >= 3.0 {
bind used = me_zeros(len(hfrom))
bind corners = [list_get(hfrom, 0)]
bind cur = list_get(hto, 0)
bind used = list_set(used, 0, 1.0)
bind guard = 0.0
bind go = true
while go && guard < len(hfrom) {
bind corners = list_push(corners, cur)
bind nxt = 0.0 - 1.0
bind k = 0.0
while k < len(hfrom) { if nxt < 0.0 && list_get(used, k) < 0.5 && list_get(hfrom, k) == cur { bind nxt = k } bind k = k + 1.0 }
if nxt < 0.0 { bind go = false }
else { bind used = list_set(used, nxt, 1.0) bind cur = list_get(hto, nxt) if cur == list_get(corners, 0) { bind go = false } }
bind guard = guard + 1.0
}
bind r = mo_push_face(nfi, nfs, nfc, corners)
bind nfi = list_get(r, 0) bind nfs = list_get(r, 1) bind nfc = list_get(r, 2)
bind nfcol = list_push(nfcol, col)
}
}
}
bind f = f + 1.0
}
bind vx = st_get(mk(o, "vx"), []) bind vy = st_get(mk(o, "vy"), []) bind vz = st_get(mk(o, "vz"), [])
mo_compact(o, vx, vy, vz, nfi, nfs, nfc, me_zeros(len(nfi) * 2.0), nfcol, st_get(mk(o, "wb"), []))
# dissolve selected vertices that now sit on a straight run (exactly two edges)
mo_dissolve_verts(o, vsel)
mo_drop_unused(o, [])
st_set("status", "Dissolved")
}
fn mo_find(parent, i) {
bind r = i
bind guard = 0.0
while list_get(parent, r) != r && guard < 100000.0 { bind r = list_get(parent, r) bind guard = guard + 1.0 }
return r
}
fn mo_dissolve_verts(o, vsel) {
bind nb = me_vert_neighbours(o)
bind starts = list_get(nb, 0)
bind fi = st_get(mk(o, "fi"), []) bind fs = st_get(mk(o, "fs"), []) bind fc = st_get(mk(o, "fc"), [])
bind fcol = st_get(mk(o, "fcol"), [])
bind drop = me_zeros(me_nverts(o))
bind any = false
bind v = 0.0
while v < len(drop) {
if v < len(vsel) { if list_get(vsel, v) > 0.5 && list_get(starts, v + 1.0) - list_get(starts, v) == 2.0 { bind drop = list_set(drop, v, 1.0) bind any = true } }
bind v = v + 1.0
}
if !any { return 0.0 }
bind nfi = list_new() bind nfs = list_new() bind nfc = list_new() bind nfcol = list_new()
bind f = 0.0
while f < len(fc) {
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind corners = list_new()
bind k = 0.0
while k < c { bind v = list_get(fi, s + k) if list_get(drop, v) < 0.5 { bind corners = list_push(corners, v) } bind k = k + 1.0 }
if len(corners) >= 3.0 || (len(corners) == 2.0 && c == 2.0) {
bind r = mo_push_face(nfi, nfs, nfc, corners)
bind nfi = list_get(r, 0) bind nfs = list_get(r, 1) bind nfc = list_get(r, 2)
bind nfcol = list_push(nfcol, sel(f < len(fcol), list_get(fcol, f), 0.0 - 1.0))
}
bind f = f + 1.0
}
bind vx = st_get(mk(o, "vx"), []) bind vy = st_get(mk(o, "vy"), []) bind vz = st_get(mk(o, "vz"), [])
mo_compact(o, vx, vy, vz, nfi, nfs, nfc, me_zeros(len(nfi) * 2.0), nfcol, st_get(mk(o, "wb"), []))
}
# ── fill: a face from the selected vertices (ordered around their centre) ────
fn mo_fill(o) {
bind verts = me_selected_verts(o)
if len(verts) < 2.0 { st_set("status", "Select 2 vertices for an edge or 3+ for a face") return 0.0 }
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 corners = verts
if len(verts) >= 3.0 {
# best-fit plane: u = longest chord, n = u x (farthest point from that line)
bind c = [0.0, 0.0, 0.0]
bind i = 0.0
while i < len(verts) { bind v = list_get(verts, i) bind c = v3_add(c, [list_get(vx, v), list_get(vy, v), list_get(vz, v)]) bind i = i + 1.0 }
bind c = v3_scale(c, 1.0 / len(verts))
bind far = 0.0 bind fd = 0.0 - 1.0
bind i = 0.0
while i < len(verts) {
bind v = list_get(verts, i)
bind d = v3_dist(c, [list_get(vx, v), list_get(vy, v), list_get(vz, v)])
if d > fd { bind fd = d bind far = v }
bind i = i + 1.0
}
bind u = v3_norm(v3_sub([list_get(vx, far), list_get(vy, far), list_get(vz, far)], c))
bind n = [0.0, 0.0, 0.0] bind nd = 0.0 - 1.0
bind i = 0.0
while i < len(verts) {
bind v = list_get(verts, i)
bind p = v3_sub([list_get(vx, v), list_get(vy, v), list_get(vz, v)], c)
bind cr = v3_cross(u, p)
if v3_len(cr) > nd { bind nd = v3_len(cr) bind n = cr }
bind i = i + 1.0
}
bind n = v3_norm(n)
if v3_len(n) < 0.5 { bind n = v3_perp(u) }
bind w = v3_cross(n, u)
bind angs = list_new()
bind i = 0.0
while i < len(verts) {
bind v = list_get(verts, i)
bind p = v3_sub([list_get(vx, v), list_get(vy, v), list_get(vz, v)], c)
bind angs = list_push(angs, mx_atan2(v3_dot(p, w), v3_dot(p, u)))
bind i = i + 1.0
}
bind order = mx_argsort(angs)
bind corners = list_new()
bind i = 0.0
while i < len(order) { bind corners = list_push(corners, list_get(verts, list_get(order, i))) bind i = i + 1.0 }
# face the camera
bind nrm = me_face_normal(vx, vy, vz, corners, 0.0, len(corners))
bind cam = m_apply(m_inv(me_matrix(o)), v3_cam_pos())
if v3_dot(nrm, v3_sub(cam, c)) < 0.0 {
bind rev = list_new()
bind i = len(corners) - 1.0
while i >= 0.0 { bind rev = list_push(rev, list_get(corners, i)) bind i = i - 1.0 }
bind corners = rev
}
}
bind r = mo_push_face(fi, fs, fc, corners)
st_set(mk(o, "fi"), list_get(r, 0)) st_set(mk(o, "fs"), list_get(r, 1)) st_set(mk(o, "fc"), list_get(r, 2))
st_set(mk(o, "uv"), mo_uv_pad(st_get(mk(o, "uv"), []), len(corners)))
st_set(mk(o, "fcol"), list_push(st_get(mk(o, "fcol"), []), 0.0 - 1.0))
me_edges_dirty(o)
me_rebuild_edges(o)
me_sel_sync_from_verts(o)
st_set("dirty", 1.0)
st_set("status", sel(len(corners) == 2.0, "Added an edge", "Filled a face with " + num_str(len(corners)) + " corners"))
}
# ── connect two selected vertices across their shared face ───────────────────
fn mo_connect(o) {
bind verts = me_selected_verts(o)
if len(verts) != 2.0 { st_set("status", "Select exactly two vertices of one face to connect") return 0.0 }
bind a = list_get(verts, 0) bind b = list_get(verts, 1)
bind fi = st_get(mk(o, "fi"), []) bind fs = st_get(mk(o, "fs"), []) bind fc = st_get(mk(o, "fc"), [])
bind uv = st_get(mk(o, "uv"), []) bind fcol = st_get(mk(o, "fcol"), [])
bind f = 0.0
bind hit = 0.0 - 1.0 bind pa = 0.0 - 1.0 bind pb = 0.0 - 1.0
while f < len(fc) {
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind ia = 0.0 - 1.0 bind ib = 0.0 - 1.0
bind k = 0.0
while k < c { bind v = list_get(fi, s + k) if v == a { bind ia = k } if v == b { bind ib = k } bind k = k + 1.0 }
if hit < 0.0 && ia >= 0.0 && ib >= 0.0 && c >= 4.0 {
# must not already be adjacent
if (ia + 1.0) % c != ib && (ib + 1.0) % c != ia { bind hit = f bind pa = ia bind pb = ib }
}
bind f = f + 1.0
}
if hit < 0.0 { st_set("status", "Those vertices do not share a face (or are already connected)") return 0.0 }
bind s = list_get(fs, hit) bind c = list_get(fc, hit)
bind col = sel(hit < len(fcol), list_get(fcol, hit), 0.0 - 1.0)
bind c1 = list_new() bind c2 = list_new()
bind k = pa
bind go = true
while go { bind c1 = list_push(c1, list_get(fi, s + k)) if k == pb { bind go = false } else { bind k = (k + 1.0) % c } }
bind k = pb
bind go = true
while go { bind c2 = list_push(c2, list_get(fi, s + k)) if k == pa { bind go = false } else { bind k = (k + 1.0) % c } }
# rebuild faces replacing hit by c1 and appending c2
bind nfi = list_new() bind nfs = list_new() bind nfc = list_new() bind nfcol = list_new()
bind f = 0.0
while f < len(fc) {
bind s2 = list_get(fs, f) bind cc = list_get(fc, f)
if f == hit {
bind r = mo_push_face(nfi, nfs, nfc, c1) bind nfi = list_get(r, 0) bind nfs = list_get(r, 1) bind nfc = list_get(r, 2) bind nfcol = list_push(nfcol, col)
} else {
bind corners = list_new()
bind k = 0.0
while k < cc { bind corners = list_push(corners, list_get(fi, s2 + k)) bind k = k + 1.0 }
bind r = mo_push_face(nfi, nfs, nfc, corners) bind nfi = list_get(r, 0) bind nfs = list_get(r, 1) bind nfc = list_get(r, 2) bind nfcol = list_push(nfcol, sel(f < len(fcol), list_get(fcol, f), 0.0 - 1.0))
}
bind f = f + 1.0
}
bind r = mo_push_face(nfi, nfs, nfc, c2) bind nfi = list_get(r, 0) bind nfs = list_get(r, 1) bind nfc = list_get(r, 2) bind nfcol = list_push(nfcol, col)
bind vx = st_get(mk(o, "vx"), []) bind vy = st_get(mk(o, "vy"), []) bind vz = st_get(mk(o, "vz"), [])
mo_compact(o, vx, vy, vz, nfi, nfs, nfc, me_zeros(len(nfi) * 2.0), nfcol, st_get(mk(o, "wb"), []))
bind vsel = st_get(mk(o, "vsel"), [])
bind vsel = list_set(vsel, a, 1.0) bind vsel = list_set(vsel, b, 1.0)
st_set(mk(o, "vsel"), vsel)
me_sel_sync_from_verts(o)
st_set("status", "Connected")
}
# ── split (rip selected faces off their neighbours) ──────────────────────────
fn mo_split(o) {
bind faces = me_selected_faces(o)
if len(faces) < 0.5 { st_set("status", "Select faces to split off") return 0.0 }
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 wb = st_get(mk(o, "wb"), [])
bind fsel = st_get(mk(o, "fsel"), [])
bind nv = len(vx)
bind in_sel = me_zeros(nv) bind in_other = me_zeros(nv)
bind f = 0.0
while f < len(fc) {
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind on = false
if f < len(fsel) { if list_get(fsel, f) > 0.5 { bind on = true } }
bind k = 0.0
while k < c { bind v = list_get(fi, s + k) if on { bind in_sel = list_set(in_sel, v, 1.0) } else { bind in_other = list_set(in_other, v, 1.0) } bind k = k + 1.0 }
bind f = f + 1.0
}
bind newv = me_fill(nv, 0.0 - 1.0)
bind v = 0.0
while v < nv {
if list_get(in_sel, v) > 0.5 && list_get(in_other, v) > 0.5 {
bind newv = list_set(newv, v, len(vx))
bind vx = list_push(vx, list_get(vx, v)) bind vy = list_push(vy, list_get(vy, v)) bind vz = list_push(vz, list_get(vz, v))
bind wb = mo_wb_dup(wb, v)
}
bind v = v + 1.0
}
bind i = 0.0
while i < len(faces) {
bind f = list_get(faces, i)
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind k = 0.0
while k < c { bind v = list_get(fi, s + k) if list_get(newv, v) >= 0.0 { bind fi = list_set(fi, s + k, list_get(newv, v)) } bind k = k + 1.0 }
bind i = i + 1.0
}
mo_compact(o, vx, vy, vz, fi, fs, fc, st_get(mk(o, "uv"), []), st_get(mk(o, "fcol"), []), wb)
bind nfsel = st_get(mk(o, "fsel"), [])
bind i = 0.0
while i < len(faces) { bind nfsel = list_set(nfsel, list_get(faces, i), 1.0) bind i = i + 1.0 }
st_set(mk(o, "fsel"), nfsel)
bind nvsel = st_get(mk(o, "vsel"), [])
bind i = 0.0
while i < len(faces) {
bind f = list_get(faces, i)
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind k = 0.0
while k < c { bind nvsel = list_set(nvsel, list_get(fi, s + k), 1.0) bind k = k + 1.0 }
bind i = i + 1.0
}
st_set(mk(o, "vsel"), nvsel)
me_sel_sync_from_verts(o)
st_set("status", "Split off " + num_str(len(faces)) + " faces")
}
# ── separate selected faces into a new object ────────────────────────────────
fn mo_separate(o) {
bind faces = me_selected_faces(o)
if len(faces) < 0.5 { st_set("status", "Select faces to separate") return 0.0 }
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 uv = st_get(mk(o, "uv"), []) bind fcol = st_get(mk(o, "fcol"), [])
bind remap = me_fill(len(vx), 0.0 - 1.0)
bind nvx = list_new() bind nvy = list_new() bind nvz = list_new()
bind nfi = list_new() bind nfs = list_new() bind nfc = list_new() bind nuv = list_new() bind nfcol = list_new()
bind i = 0.0
while i < len(faces) {
bind f = list_get(faces, i)
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind nfs = list_push(nfs, len(nfi)) bind nfc = list_push(nfc, c) bind nfcol = list_push(nfcol, sel(f < len(fcol), list_get(fcol, f), 0.0 - 1.0))
bind k = 0.0
while k < c {
bind v = list_get(fi, s + k)
if list_get(remap, v) < 0.0 {
bind remap = list_set(remap, v, len(nvx))
bind nvx = list_push(nvx, list_get(vx, v)) bind nvy = list_push(nvy, list_get(vy, v)) bind nvz = list_push(nvz, list_get(vz, v))
}
bind nfi = list_push(nfi, list_get(remap, v))
bind nuv = list_push(nuv, list_get(uv, (s + k) * 2.0)) bind nuv = list_push(nuv, list_get(uv, (s + k) * 2.0 + 1.0))
bind k = k + 1.0
}
bind i = i + 1.0
}
mo_delete(o, 2.0)
bind n = me_add_object(me_name(o) + "_part", nvx, nvy, nvz, nfi, nfs, nfc)
st_set(mk(n, "uv"), nuv) st_set(mk(n, "fcol"), nfcol)
bind keys = ["px", "py", "pz", "rx", "ry", "rz", "sx", "sy", "sz", "col", "tex", "smooth"]
bind k = 0.0
while k < len(keys) { st_set(mk(n, list_get(keys, k)), st_get(mk(o, list_get(keys, k)), 0.0)) bind k = k + 1.0 }
st_set("sel_o", o)
st_set("status", "Separated into " + me_name(n))
return n
}
# ── join objects into the active one ─────────────────────────────────────────
fn mo_join(ids) {
bind o = me_sel_o()
if o < 0.0 || len(ids) < 2.0 { st_set("status", "Select two or more objects (the active one receives the others)") return 0.0 }
me_undo_push()
bind inv = m_inv(me_matrix(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 uv = st_get(mk(o, "uv"), []) bind fcol = st_get(mk(o, "fcol"), [])
bind i = 0.0
while i < len(ids) {
bind q = list_get(ids, i)
if q != o {
bind m = m_mul(inv, me_matrix(q))
bind base = len(vx)
bind qvx = st_get(mk(q, "vx"), []) bind qvy = st_get(mk(q, "vy"), []) bind qvz = st_get(mk(q, "vz"), [])
bind k = 0.0
while k < len(qvx) {
bind p = m_apply(m, [list_get(qvx, k), list_get(qvy, k), list_get(qvz, k)])
bind vx = list_push(vx, list_get(p, 0)) bind vy = list_push(vy, list_get(p, 1)) bind vz = list_push(vz, list_get(p, 2))
bind k = k + 1.0
}
bind qfi = st_get(mk(q, "fi"), []) bind qfs = st_get(mk(q, "fs"), []) bind qfc = st_get(mk(q, "fc"), [])
bind quv = st_get(mk(q, "uv"), []) bind qfcol = st_get(mk(q, "fcol"), [])
bind qcol = st_get(mk(q, "col"), C_BLUE)
bind f = 0.0
while f < len(qfc) {
bind s = list_get(qfs, f) bind c = list_get(qfc, f)
bind fs = list_push(fs, len(fi)) bind fc = list_push(fc, c)
bind fcv = sel(f < len(qfcol), list_get(qfcol, f), 0.0 - 1.0)
bind fcol = list_push(fcol, sel(fcv >= 0.0, fcv, qcol))
bind k = 0.0
while k < c {
bind fi = list_push(fi, base + list_get(qfi, s + k))
bind uv = list_push(uv, list_get(quv, (s + k) * 2.0)) bind uv = list_push(uv, list_get(quv, (s + k) * 2.0 + 1.0))
bind k = k + 1.0
}
bind f = f + 1.0
}
me_remove(q)
}
bind i = i + 1.0
}
st_set(mk(o, "wb"), [])
mo_compact(o, vx, vy, vz, fi, fs, fc, uv, fcol, [])
me_select_object(o, false)
st_set("status", "Joined into " + me_name(o))
}
# ── normals ──────────────────────────────────────────────────────────────────
fn mo_flip(o) {
bind faces = me_selected_faces(o)
bind fi = st_get(mk(o, "fi"), []) bind fs = st_get(mk(o, "fs"), []) bind fc = st_get(mk(o, "fc"), [])
bind uv = st_get(mk(o, "uv"), [])
bind all = len(faces) < 0.5 || st_get("mode", 0.0) != 1.0
bind f = 0.0
while f < len(fc) {
bind on = all
if !on { bind i = 0.0 while i < len(faces) { if list_get(faces, i) == f { bind on = true } bind i = i + 1.0 } }
if on {
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind k = 0.0
while k < floor(c / 2.0) {
bind a = s + k bind b = s + c - 1.0 - k
bind t = list_get(fi, a) bind fi = list_set(fi, a, list_get(fi, b)) bind fi = list_set(fi, b, t)
bind tu = list_get(uv, a * 2.0) bind tv = list_get(uv, a * 2.0 + 1.0)
bind uv = list_set(uv, a * 2.0, list_get(uv, b * 2.0)) bind uv = list_set(uv, a * 2.0 + 1.0, list_get(uv, b * 2.0 + 1.0))
bind uv = list_set(uv, b * 2.0, tu) bind uv = list_set(uv, b * 2.0 + 1.0, tv)
bind k = k + 1.0
}
}
bind f = f + 1.0
}
st_set(mk(o, "fi"), fi) st_set(mk(o, "uv"), uv)
me_edges_dirty(o)
st_set("dirty", 1.0)
st_set("status", "Flipped normals")
}
# make windings consistent across edges, then outward (positive signed volume)
fn mo_recalc(o) {
me_rebuild_edges(o)
bind fi = st_get(mk(o, "fi"), []) bind fs = st_get(mk(o, "fs"), []) bind fc = st_get(mk(o, "fc"), [])
bind ce = st_get(mk(o, "ce"), [])
bind efa = mo_edge_faces(o)
bind ef0 = list_get(efa, 0) bind ef1 = list_get(efa, 1)
bind nf = len(fc)
bind visited = me_zeros(nf)
bind flipf = me_zeros(nf)
bind f0 = 0.0
while f0 < nf {
if list_get(visited, f0) < 0.5 && list_get(fc, f0) >= 3.0 {
bind stack = [f0]
bind visited = list_set(visited, f0, 1.0)
while len(stack) > 0.5 {
bind f = list_get(stack, len(stack) - 1.0)
bind ns = list_new()
bind i = 0.0
while i < len(stack) - 1.0 { bind ns = list_push(ns, list_get(stack, i)) bind i = i + 1.0 }
bind stack = ns
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind k = 0.0
while k < c {
bind e = list_get(ce, s + k)
bind g = sel(list_get(ef0, e) == f, list_get(ef1, e), list_get(ef0, e))
if g >= 0.0 { if list_get(visited, g) < 0.5 {
# directions of the shared edge in f and g (after their current flips)
bind a = list_get(fi, s + k) bind b = list_get(fi, s + (k + 1.0) % c)
bind fwd_f = list_get(flipf, f) < 0.5
bind gs = list_get(fs, g) bind gc = list_get(fc, g)
bind same = false
bind q = 0.0
while q < gc { if list_get(fi, gs + q) == a && list_get(fi, gs + (q + 1.0) % gc) == b { bind same = true } bind q = q + 1.0 }
bind fwd_g = list_get(flipf, g) < 0.5
# consistent when g traverses the edge opposite to f
bind opp = sel(same, false, true)
if fwd_f != fwd_g { bind opp = !opp }
if !opp { bind flipf = list_set(flipf, g, sel(list_get(flipf, g) > 0.5, 0.0, 1.0)) }
bind visited = list_set(visited, g, 1.0)
bind stack = list_push(stack, g)
} }
bind k = k + 1.0
}
}
}
bind f0 = f0 + 1.0
}
bind fsel_bak = st_get(mk(o, "fsel"), [])
st_set(mk(o, "fsel"), flipf)
st_set("mode_bak", st_get("mode", 0.0))
st_set("mode", 1.0)
bind any = false
bind i = 0.0
while i < nf { if list_get(flipf, i) > 0.5 { bind any = true } bind i = i + 1.0 }
if any { mo_flip(o) }
st_set("mode", st_get("mode_bak", 0.0))
st_set(mk(o, "fsel"), fsel_bak)
# outward: signed volume
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 vol = 0.0
bind f = 0.0
while f < nf {
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind k = 1.0
while k + 1.0 < c {
bind a = list_get(fi, s) bind b = list_get(fi, s + k) bind cc = list_get(fi, s + k + 1.0)
bind vol = vol + v3_dot([list_get(vx, a), list_get(vy, a), list_get(vz, a)], v3_cross([list_get(vx, b), list_get(vy, b), list_get(vz, b)], [list_get(vx, cc), list_get(vy, cc), list_get(vz, cc)]))
bind k = k + 1.0
}
bind f = f + 1.0
}
if vol < 0.0 {
st_set("mode_bak", st_get("mode", 0.0)) st_set("mode", 0.0)
mo_flip(o)
st_set("mode", st_get("mode_bak", 0.0))
}
st_set("status", "Normals recalculated")
}
# ── triangulate / quads ──────────────────────────────────────────────────────
fn mo_triangulate(o) {
bind faces = me_selected_faces(o)
bind all = len(faces) < 0.5 || st_get("mode", 0.0) != 1.0
bind fi = st_get(mk(o, "fi"), []) bind fs = st_get(mk(o, "fs"), []) bind fc = st_get(mk(o, "fc"), [])
bind uv = st_get(mk(o, "uv"), []) bind fcol = st_get(mk(o, "fcol"), [])
bind fsel = st_get(mk(o, "fsel"), [])
bind nfi = list_new() bind nfs = list_new() bind nfc = list_new() bind nuv = list_new() bind nfcol = list_new()
bind f = 0.0
while f < len(fc) {
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind on = all
if !on { if f < len(fsel) { if list_get(fsel, f) > 0.5 { bind on = true } } }
bind col = sel(f < len(fcol), list_get(fcol, f), 0.0 - 1.0)
if on && c > 3.0 {
bind k = 1.0
while k + 1.0 < c {
bind idx = [s, s + k, s + k + 1.0]
bind nfs = list_push(nfs, len(nfi)) bind nfc = list_push(nfc, 3.0) bind nfcol = list_push(nfcol, col)
bind q = 0.0
while q < 3.0 { bind ci = list_get(idx, q) bind nfi = list_push(nfi, list_get(fi, ci)) bind nuv = list_push(nuv, list_get(uv, ci * 2.0)) bind nuv = list_push(nuv, list_get(uv, ci * 2.0 + 1.0)) bind q = q + 1.0 }
bind k = k + 1.0
}
} else {
bind nfs = list_push(nfs, len(nfi)) bind nfc = list_push(nfc, c) bind nfcol = list_push(nfcol, col)
bind k = 0.0
while k < c { bind nfi = list_push(nfi, list_get(fi, s + k)) bind nuv = list_push(nuv, list_get(uv, (s + k) * 2.0)) bind nuv = list_push(nuv, list_get(uv, (s + k) * 2.0 + 1.0)) bind k = k + 1.0 }
}
bind f = f + 1.0
}
bind vx = st_get(mk(o, "vx"), []) bind vy = st_get(mk(o, "vy"), []) bind vz = st_get(mk(o, "vz"), [])
mo_compact(o, vx, vy, vz, nfi, nfs, nfc, nuv, nfcol, st_get(mk(o, "wb"), []))
st_set("status", "Triangulated: " + num_str(len(nfc)) + " faces")
}
fn mo_quads(o) {
me_rebuild_edges(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 ce = st_get(mk(o, "ce"), []) bind fcol = st_get(mk(o, "fcol"), [])
bind efa = mo_edge_faces(o)
bind ef0 = list_get(efa, 0) bind ef1 = list_get(efa, 1)
bind nf = len(fc)
bind used = me_zeros(nf)
bind nfi = list_new() bind nfs = list_new() bind nfc = list_new() bind nfcol = list_new()
bind f = 0.0
while f < nf {
if list_get(used, f) < 0.5 {
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind merged = false
if c == 3.0 {
bind n1 = v3_norm(me_face_normal(vx, vy, vz, fi, s, 3.0))
bind best = 0.0 - 1.0 bind bestk = 0.0 bind bd = 0.0 - 2.0
bind k = 0.0
while k < 3.0 {
bind e = list_get(ce, s + k)
bind g = sel(list_get(ef0, e) == f, list_get(ef1, e), list_get(ef0, e))
if g >= 0.0 { if list_get(used, g) < 0.5 && list_get(fc, g) == 3.0 {
bind n2 = v3_norm(me_face_normal(vx, vy, vz, fi, list_get(fs, g), 3.0))
bind d = v3_dot(n1, n2)
if d > bd && d > 0.85 { bind bd = d bind best = g bind bestk = k }
} }
bind k = k + 1.0
}
if best >= 0.0 {
bind a = list_get(fi, s + bestk) bind b = list_get(fi, s + (bestk + 1.0) % 3.0) bind cc = list_get(fi, s + (bestk + 2.0) % 3.0)
bind gs = list_get(fs, best)
bind d = 0.0 - 1.0
bind q = 0.0
while q < 3.0 { bind v = list_get(fi, gs + q) if v != a && v != b { bind d = v } bind q = q + 1.0 }
bind r = mo_push_face(nfi, nfs, nfc, [a, d, b, cc])
bind nfi = list_get(r, 0) bind nfs = list_get(r, 1) bind nfc = list_get(r, 2)
bind nfcol = list_push(nfcol, sel(f < len(fcol), list_get(fcol, f), 0.0 - 1.0))
bind used = list_set(used, best, 1.0)
bind merged = true
}
}
if !merged {
bind corners = list_new()
bind k = 0.0
while k < c { bind corners = list_push(corners, list_get(fi, s + k)) bind k = k + 1.0 }
bind r = mo_push_face(nfi, nfs, nfc, corners)
bind nfi = list_get(r, 0) bind nfs = list_get(r, 1) bind nfc = list_get(r, 2)
bind nfcol = list_push(nfcol, sel(f < len(fcol), list_get(fcol, f), 0.0 - 1.0))
}
bind used = list_set(used, f, 1.0)
}
bind f = f + 1.0
}
mo_compact(o, vx, vy, vz, nfi, nfs, nfc, me_zeros(len(nfi) * 2.0), nfcol, st_get(mk(o, "wb"), []))
st_set("status", "Tris to quads: " + num_str(len(nfc)) + " faces")
}
# ── smooth ───────────────────────────────────────────────────────────────────
fn mo_smooth(o, strength) {
bind nb = me_vert_neighbours(o)
bind starts = list_get(nb, 0) bind vl = list_get(nb, 1)
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 all = me_sel_count(o) < 0.5 || st_get("mode", 0.0) != 1.0
bind iters = max(1.0, round(strength))
bind it = 0.0
while it < iters {
bind nx = vx bind ny = vy bind nz = vz
bind v = 0.0
while v < len(vx) {
bind on = all
if !on { if list_get(vsel, v) > 0.5 { bind on = true } }
bind n = list_get(starts, v + 1.0) - list_get(starts, v)
if on && n > 0.5 {
bind sx = 0.0 bind sy = 0.0 bind sz = 0.0
bind k = list_get(starts, v)
while k < list_get(starts, v + 1.0) { bind w = list_get(vl, k) bind sx = sx + list_get(vx, w) bind sy = sy + list_get(vy, w) bind sz = sz + list_get(vz, w) bind k = k + 1.0 }
bind nx = list_set(nx, v, list_get(vx, v) * 0.5 + sx / n * 0.5)
bind ny = list_set(ny, v, list_get(vy, v) * 0.5 + sy / n * 0.5)
bind nz = list_set(nz, v, list_get(vz, v) * 0.5 + sz / n * 0.5)
}
bind v = v + 1.0
}
bind vx = nx bind vy = ny bind vz = nz
bind it = it + 1.0
}
st_set(mk(o, "vx"), vx) st_set(mk(o, "vy"), vy) st_set(mk(o, "vz"), vz)
st_set("dirty", 1.0)
st_set("status", "Smoothed")
}
# ── mirror / symmetrize (X axis, object space) ───────────────────────────────
fn mo_mirror_x(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 uv = st_get(mk(o, "uv"), []) bind fcol = st_get(mk(o, "fcol"), []) bind wb = st_get(mk(o, "wb"), [])
bind nv = len(vx)
bind i = 0.0
while i < nv { bind vx = list_push(vx, 0.0 - list_get(vx, i)) bind vy = list_push(vy, list_get(vy, i)) bind vz = list_push(vz, list_get(vz, i)) bind wb = mo_wb_dup(wb, i) bind i = i + 1.0 }
bind nf = len(fc)
bind f = 0.0
while f < nf {
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind fs = list_push(fs, len(fi)) bind fc = list_push(fc, c) bind fcol = list_push(fcol, sel(f < len(fcol), list_get(fcol, f), 0.0 - 1.0))
bind k = c - 1.0
while k >= 0.0 { bind fi = list_push(fi, nv + list_get(fi, s + k)) bind uv = list_push(uv, list_get(uv, (s + k) * 2.0)) bind uv = list_push(uv, list_get(uv, (s + k) * 2.0 + 1.0)) bind k = k - 1.0 }
bind f = f + 1.0
}
mo_compact(o, vx, vy, vz, fi, fs, fc, uv, fcol, wb)
mo_merge_dist(o, 0.0001)
st_set("status", "Mirrored across X")
}
fn mo_symmetrize(o) {
# drop everything with x < 0 (faces whose centre is negative), then mirror
bind vx = st_get(mk(o, "vx"), [])
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 cx = 0.0
bind k = 0.0
while k < c { bind cx = cx + list_get(vx, list_get(fi, s + k)) bind k = k + 1.0 }
if cx / c < 0.0 - 0.00001 { bind fsel = list_set(fsel, f, 1.0) }
bind f = f + 1.0
}
st_set(mk(o, "fsel"), fsel)
mo_delete(o, 2.0)
# snap near-zero x to zero so the seam welds
bind vx = st_get(mk(o, "vx"), [])
bind i = 0.0
while i < len(vx) { if abs(list_get(vx, i)) < 0.001 { bind vx = list_set(vx, i, 0.0) } bind i = i + 1.0 }
st_set(mk(o, "vx"), vx)
mo_mirror_x(o)
st_set("status", "Symmetrized (+X half kept)")
}
# ── spin / lathe the selection around the Y axis through the 3D cursor ───────
fn mo_spin(o, steps, angle_deg) {
me_rebuild_edges(o)
bind verts = me_selected_verts(o)
if len(verts) < 0.5 { st_set("status", "Select a profile (vertices / edges) to spin") return 0.0 }
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 uv = st_get(mk(o, "uv"), []) bind fcol = st_get(mk(o, "fcol"), []) bind wb = st_get(mk(o, "wb"), [])
bind ea = st_get(mk(o, "ea"), []) bind eb = st_get(mk(o, "eb"), []) bind esel = st_get(mk(o, "esel"), [])
bind cur = m_apply(m_inv(me_matrix(o)), [st_get("cursor_x", 0.0), st_get("cursor_y", 0.0), st_get("cursor_z", 0.0)])
bind cx = list_get(cur, 0) bind cz = list_get(cur, 2)
bind steps = max(1.0, floor(steps))
bind full = abs(angle_deg - 360.0) < 0.001
bind nv = len(vx)
bind vsel = st_get(mk(o, "vsel"), [])
# ring index of each selected vertex
bind ridx = me_fill(nv, 0.0 - 1.0)
bind i = 0.0
while i < len(verts) { bind ridx = list_set(ridx, list_get(verts, i), i) bind i = i + 1.0 }
bind nsel = len(verts)
bind copies = sel(full, steps, steps + 1.0)
# vertex of ring r, profile i: r == 0 -> original, else nv + (r-1)*nsel + i
bind r = 1.0
while r < copies {
bind a = mx_rad(angle_deg) * r / steps
bind ca = cos(a) bind sa = sin(a)
bind i = 0.0
while i < nsel {
bind v = list_get(verts, i)
bind dx = list_get(vx, v) - cx bind dz = list_get(vz, v) - cz
bind vx = list_push(vx, cx + dx * ca + dz * sa)
bind vy = list_push(vy, list_get(vy, v))
bind vz = list_push(vz, cz - dx * sa + dz * ca)
bind wb = mo_wb_dup(wb, v)
bind i = i + 1.0
}
bind r = r + 1.0
}
# quads for every selected edge
bind e = 0.0
while e < len(ea) {
if list_get(esel, e) > 0.5 {
bind ia = list_get(ridx, list_get(ea, e)) bind ib = list_get(ridx, list_get(eb, e))
bind r = 0.0
while r < steps {
bind r2 = sel(full && r == steps - 1.0, 0.0, r + 1.0)
bind a0 = sel(r == 0.0, list_get(ea, e), nv + (r - 1.0) * nsel + ia)
bind b0 = sel(r == 0.0, list_get(eb, e), nv + (r - 1.0) * nsel + ib)
bind a1 = sel(r2 == 0.0, list_get(ea, e), nv + (r2 - 1.0) * nsel + ia)
bind b1 = sel(r2 == 0.0, list_get(eb, e), nv + (r2 - 1.0) * nsel + ib)
bind q = mo_push_face(fi, fs, fc, [a0, a1, b1, b0])
bind fi = list_get(q, 0) bind fs = list_get(q, 1) bind fc = list_get(q, 2)
bind uv = mo_uv_pad(uv, 4.0)
bind fcol = list_push(fcol, 0.0 - 1.0)
bind r = r + 1.0
}
}
bind e = e + 1.0
}
mo_compact(o, vx, vy, vz, fi, fs, fc, uv, fcol, wb)
mo_merge_dist(o, 0.0001)
mo_recalc(o)
st_set("status", "Spun " + num_str(steps) + " steps over " + fmt0(angle_deg) + " degrees")
}
# ── transforms ───────────────────────────────────────────────────────────────
fn mo_apply_xf(o) {
bind m = me_matrix(o)
bind vx = st_get(mk(o, "vx"), []) bind vy = st_get(mk(o, "vy"), []) bind vz = st_get(mk(o, "vz"), [])
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 vx = list_set(vx, i, list_get(p, 0)) bind vy = list_set(vy, i, list_get(p, 1)) bind vz = list_set(vz, i, list_get(p, 2))
bind i = i + 1.0
}
st_set(mk(o, "vx"), vx) st_set(mk(o, "vy"), vy) st_set(mk(o, "vz"), vz)
rg_transform_bones(o, m)
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("dirty", 1.0)
st_set("status", "Transform applied")
}
fn mo_center(o) {
bind b = me_sel_bounds_local(o, false)
if len(b) < 0.5 { return 0.0 }
bind cx = (list_get(b, 0) + list_get(b, 3)) * 0.5 bind cy = (list_get(b, 1) + list_get(b, 4)) * 0.5 bind cz = (list_get(b, 2) + list_get(b, 5)) * 0.5
bind vx = st_get(mk(o, "vx"), []) bind vy = st_get(mk(o, "vy"), []) bind vz = st_get(mk(o, "vz"), [])
bind i = 0.0
while i < len(vx) { bind vx = list_set(vx, i, list_get(vx, i) - cx) bind vy = list_set(vy, i, list_get(vy, i) - cy) bind vz = list_set(vz, i, list_get(vz, i) - cz) bind i = i + 1.0 }
st_set(mk(o, "vx"), vx) st_set(mk(o, "vy"), vy) st_set(mk(o, "vz"), vz)
rg_transform_bones(o, m_trans(0.0 - cx, 0.0 - cy, 0.0 - cz))
bind m = me_matrix(o)
bind p = m_apply(m, [cx, cy, cz])
st_set(mk(o, "px"), list_get(p, 0)) st_set(mk(o, "py"), list_get(p, 1)) st_set(mk(o, "pz"), list_get(p, 2))
st_set("dirty", 1.0)
}
# ── duplicate selected faces inside the object ───────────────────────────────
fn mo_dup_sel(o) {
bind faces = me_selected_faces(o)
bind verts = me_selected_verts(o)
if len(verts) < 0.5 { st_set("status", "Nothing selected") return 0.0 }
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 uv = st_get(mk(o, "uv"), []) bind fcol = st_get(mk(o, "fcol"), []) bind wb = st_get(mk(o, "wb"), [])
bind nv = len(vx)
bind newv = me_fill(nv, 0.0 - 1.0)
bind i = 0.0
while i < len(verts) {
bind v = list_get(verts, i)
bind newv = list_set(newv, v, len(vx))
bind vx = list_push(vx, list_get(vx, v)) bind vy = list_push(vy, list_get(vy, v)) bind vz = list_push(vz, list_get(vz, v))
bind wb = mo_wb_dup(wb, v)
bind i = i + 1.0
}
bind i = 0.0
while i < len(faces) {
bind f = list_get(faces, i)
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind fs = list_push(fs, len(fi)) bind fc = list_push(fc, c) bind fcol = list_push(fcol, sel(f < len(fcol), list_get(fcol, f), 0.0 - 1.0))
bind k = 0.0
while k < c { bind fi = list_push(fi, list_get(newv, list_get(fi, s + k))) bind uv = list_push(uv, list_get(uv, (s + k) * 2.0)) bind uv = list_push(uv, list_get(uv, (s + k) * 2.0 + 1.0)) bind k = k + 1.0 }
bind i = i + 1.0
}
bind nvsel = me_zeros(len(vx))
bind i = 0.0
while i < nv { if list_get(newv, i) >= 0.0 { bind nvsel = list_set(nvsel, list_get(newv, i), 1.0) } bind i = i + 1.0 }
mo_compact(o, vx, vy, vz, fi, fs, fc, uv, fcol, wb)
st_set(mk(o, "vsel"), nvsel)
me_sel_sync_from_verts(o)
}
fn mo_select_by_color(o) {
bind faces = me_selected_faces(o)
if len(faces) < 0.5 { return 0.0 }
bind fcol = st_get(mk(o, "fcol"), [])
bind target = list_get(fcol, list_get(faces, 0))
bind f = 0.0
while f < len(fcol) { if list_get(fcol, f) == target { me_sel_face(o, f, true) } bind f = f + 1.0 }
}