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/gen.ling
# ━━━ gen.ling — procedural game-asset generators (rock, crystals, tree, barrel, crate, pillar, stairs, ramp, terrain, fence, gem, character) ━━━
# Every generator is a parameter dialog (gn_open) + a builder (gn_make) that assembles pr_* primitives.
fn gn_params(kind) {
if kind == "rock" { return [["Detail", "Roughness", "Seed", "Size", "Flatten bottom"], [2.0, 0.35, 7.0, 1.0, 0.3], [0.0, 0.0, 0.0, 0.1, 0.0], [3.0, 1.0, 100.0, 20.0, 1.0], [0.0, 2.0, 0.0, 2.0, 2.0]] }
if kind == "crystal" { return [["Count", "Height", "Radius", "Seed", "Sides"], [5.0, 1.5, 0.25, 3.0, 6.0], [1.0, 0.2, 0.05, 0.0, 3.0], [24.0, 10.0, 3.0, 100.0, 12.0], [0.0, 2.0, 2.0, 0.0, 0.0]] }
if kind == "tree" { return [["Trunk height", "Trunk radius", "Canopy size", "Canopy layers", "Seed", "Style (0 pine, 1 round)"], [1.5, 0.18, 1.2, 3.0, 4.0, 0.0], [0.2, 0.02, 0.2, 1.0, 0.0, 0.0], [20.0, 3.0, 10.0, 6.0, 100.0, 1.0], [2.0, 2.0, 2.0, 0.0, 0.0, 0.0]] }
if kind == "barrel" { return [["Radius", "Height", "Bulge", "Segments", "Rings"], [0.5, 1.2, 0.15, 12.0, 4.0], [0.1, 0.1, 0.0, 4.0, 2.0], [5.0, 20.0, 1.0, 48.0, 12.0], [2.0, 2.0, 2.0, 0.0, 0.0]] }
if kind == "crate" { return [["Size", "Panel inset", "Panel depth", "Planks per side"], [1.0, 0.1, 0.05, 1.0], [0.1, 0.0, 0.0, 1.0], [20.0, 0.45, 0.5, 6.0], [2.0, 2.0, 2.0, 0.0]] }
if kind == "pillar" { return [["Height", "Radius", "Capital size", "Sides", "Base height"], [3.0, 0.3, 0.5, 12.0, 0.2], [0.2, 0.05, 0.1, 3.0, 0.0], [40.0, 5.0, 3.0, 48.0, 2.0], [2.0, 2.0, 2.0, 0.0, 2.0]] }
if kind == "stairs" { return [["Steps", "Width", "Step height", "Step depth"], [6.0, 2.0, 0.25, 0.4], [1.0, 0.1, 0.02, 0.02], [64.0, 50.0, 5.0, 5.0], [0.0, 2.0, 2.0, 2.0]] }
if kind == "ramp" { return [["Width", "Height", "Length"], [2.0, 1.0, 3.0], [0.1, 0.05, 0.1], [50.0, 50.0, 50.0], [2.0, 2.0, 2.0]] }
if kind == "terrain" { return [["Size", "Subdivisions", "Height", "Noise scale", "Seed", "Octaves"], [10.0, 24.0, 1.5, 2.0, 9.0, 4.0], [1.0, 2.0, 0.0, 0.2, 0.0, 1.0], [500.0, 96.0, 100.0, 20.0, 100.0, 8.0], [1.0, 0.0, 2.0, 2.0, 0.0, 0.0]] }
if kind == "fence" { return [["Posts", "Spacing", "Height", "Rails", "Thickness"], [4.0, 1.5, 1.2, 2.0, 0.08], [2.0, 0.2, 0.2, 0.0, 0.02], [40.0, 10.0, 10.0, 6.0, 0.5], [0.0, 2.0, 2.0, 0.0, 2.0]] }
if kind == "gem" { return [["Kind (0 coin, 1 gem)", "Radius", "Thickness", "Sides"], [1.0, 0.5, 0.3, 8.0], [0.0, 0.05, 0.02, 3.0], [1.0, 10.0, 5.0, 32.0], [0.0, 2.0, 2.0, 0.0]] }
if kind == "character" { return [["Height", "Shoulder width", "Head size", "Limb thickness", "Auto-rig + weights (0/1)"], [1.8, 0.5, 0.26, 0.11, 1.0], [0.5, 0.2, 0.1, 0.03, 0.0], [5.0, 2.0, 1.0, 0.5, 1.0], [2.0, 2.0, 2.0, 2.0, 0.0]] }
return [[], [], [], [], []]
}
fn gn_open(kind) {
bind p = gn_params(kind)
if len(list_get(p, 0)) < 0.5 { st_set("status", "Unknown generator " + kind) return 0.0 }
st_set("pm_live", 0.0)
params_open("Generate " + kind, "gen." + kind, list_get(p, 0), list_get(p, 1), list_get(p, 2), list_get(p, 3), list_get(p, 4))
}
# ── geometry helpers on [vx,vy,vz,fi,fs,fc] ──────────────────────────────────
fn gn_xform(g, m) {
bind vx = list_get(g, 0) bind vy = list_get(g, 1) bind vz = list_get(g, 2)
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
}
return [vx, vy, vz, list_get(g, 3), list_get(g, 4), list_get(g, 5)]
}
fn gn_merge(a, b) {
bind vx = list_get(a, 0) bind vy = list_get(a, 1) bind vz = list_get(a, 2)
bind fi = list_get(a, 3) bind fs = list_get(a, 4) bind fc = list_get(a, 5)
bind base = len(vx)
bind bvx = list_get(b, 0) bind bvy = list_get(b, 1) bind bvz = list_get(b, 2)
bind i = 0.0
while i < len(bvx) { bind vx = list_push(vx, list_get(bvx, i)) bind vy = list_push(vy, list_get(bvy, i)) bind vz = list_push(vz, list_get(bvz, i)) bind i = i + 1.0 }
bind bfi = list_get(b, 3) bind bfs = list_get(b, 4) bind bfc = list_get(b, 5)
bind f = 0.0
while f < len(bfc) {
bind fs = list_push(fs, len(fi)) bind fc = list_push(fc, list_get(bfc, f))
bind k = 0.0
while k < list_get(bfc, f) { bind fi = list_push(fi, base + list_get(bfi, list_get(bfs, f) + k)) bind k = k + 1.0 }
bind f = f + 1.0
}
return [vx, vy, vz, fi, fs, fc]
}
fn gn_empty() { return [[], [], [], [], [], []] }
fn gn_box(cx, cy, cz, sx, sy, sz) { return gn_xform(pr_cube(1.0, 1.0), m_mul(m_trans(cx, cy, cz), m_scale(sx, sy, sz))) }
fn gn_finish(name, g, col) {
bind o = me_add_object(name, list_get(g, 0), list_get(g, 1), list_get(g, 2), list_get(g, 3), list_get(g, 4), list_get(g, 5))
st_set(mk(o, "px"), st_get("cursor_x", 0.0)) st_set(mk(o, "py"), st_get("cursor_y", 0.0)) st_set(mk(o, "pz"), st_get("cursor_z", 0.0))
if col > 0.0 { st_set(mk(o, "col"), col) }
uv_box(o)
mo_recalc(o)
me_select_object(o, false)
v3_frame(false)
st_set("status", "Generated " + me_name(o) + ": " + num_str(me_nverts(o)) + " verts, " + num_str(me_nfaces(o)) + " faces")
return o
}
fn gn_make(kind, v) {
me_undo_push()
bind p0 = list_get(v, 0) bind p1 = list_get(v, 1) bind p2 = list_get(v, 2) bind p3 = list_get(v, 3)
bind p4 = sel(len(v) > 4.0, list_get(v, 4), 0.0) bind p5 = sel(len(v) > 5.0, list_get(v, 5), 0.0)
if kind == "rock" {
bind g = pr_icosphere(p0, p3)
bind vx = list_get(g, 0) bind vy = list_get(g, 1) bind vz = list_get(g, 2)
bind i = 0.0
while i < len(vx) {
bind x = list_get(vx, i) bind y = list_get(vy, i) bind z = list_get(vz, i)
bind n = mx_fbm3(x * 1.3 / p3 + p2, y * 1.3 / p3, z * 1.3 / p3, 4.0, p2)
bind s = 1.0 + (n - 0.5) * 2.0 * p1
bind sq = 1.0 + (mx_hash3(floor(x * 3.0), floor(y * 3.0), floor(z * 3.0), p2) - 0.5) * 0.15 * p1
bind y2 = y * s * sq
if y2 < 0.0 - p3 * (1.0 - p4) { bind y2 = 0.0 - p3 * (1.0 - p4) }
bind vx = list_set(vx, i, x * s * sq) bind vy = list_set(vy, i, y2) bind vz = list_set(vz, i, z * s * sq)
bind i = i + 1.0
}
return gn_finish("Rock", [vx, vy, vz, list_get(g, 3), list_get(g, 4), list_get(g, 5)], c_rgb(120.0, 115.0, 110.0))
}
if kind == "crystal" {
mx_srand(p3)
bind g = gn_empty()
bind k = 0.0
while k < p0 {
bind h = p1 * (0.5 + mx_rnd())
bind r = p2 * (0.6 + mx_rnd() * 0.8)
bind c = pr_cone(p4, r, r * 0.35, h, 1.0)
# pointed tip: a small cone on top
bind tip = pr_cone(p4, r * 0.35, 0.0, h * 0.25, 0.0)
bind tip = gn_xform(tip, m_trans(0.0, h * 0.5 + h * 0.125, 0.0))
bind c = gn_merge(c, tip)
bind ang = mx_rnd() * 6.28318
bind tilt = (mx_rnd() - 0.5) * 1.0
bind dist = sel(k == 0.0, 0.0, p2 * 2.0 * (0.5 + mx_rnd() * 1.5))
bind m = m_mul(m_trans(cos(ang) * dist, h * 0.3, sin(ang) * dist), m_mul(m_roty(ang), m_rotz(tilt)))
bind g = gn_merge(g, gn_xform(c, m))
bind k = k + 1.0
}
return gn_finish("Crystals", g, c_rgb(120.0, 220.0, 255.0))
}
if kind == "tree" {
mx_srand(p4)
bind trunk = pr_cone(7.0, p1, p1 * 0.6, p0, 1.0)
bind trunk = gn_xform(trunk, m_trans(0.0, p0 * 0.5, 0.0))
bind g = trunk
bind layers = max(1.0, floor(p3))
bind k = 0.0
while k < layers {
bind t = k / layers
bind y = p0 + t * p2 * 0.9
bind size = p2 * (1.0 - t * 0.6)
bind part = gn_empty()
if p5 < 0.5 { bind part = pr_cone(8.0, size * 0.7, 0.0, size * 0.9, 1.0) bind part = gn_xform(part, m_trans(0.0, y + size * 0.45, 0.0)) }
else {
bind part = pr_icosphere(1.0, size * 0.55)
bind jx = (mx_rnd() - 0.5) * size * 0.5 bind jz = (mx_rnd() - 0.5) * size * 0.5
bind part = gn_xform(part, m_mul(m_trans(jx, y + size * 0.35, jz), m_scale(1.0, 0.75, 1.0)))
}
bind g = gn_merge(g, part)
bind k = k + 1.0
}
bind o = gn_finish("Tree", g, c_rgb(80.0, 160.0, 70.0))
# colour the trunk brown by face position
bind vy = st_get(mk(o, "vy"), []) bind fi = st_get(mk(o, "fi"), []) bind fs = st_get(mk(o, "fs"), []) bind fc = st_get(mk(o, "fc"), [])
bind fcol = list_new()
bind f = 0.0
while f < len(fc) {
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind ymax = 0.0 - 1000.0
bind q = 0.0
while q < c { bind yy = list_get(vy, list_get(fi, s + q)) if yy > ymax { bind ymax = yy } bind q = q + 1.0 }
bind fcol = list_push(fcol, sel(ymax <= p0 + 0.001, c_rgb(110.0, 70.0, 40.0), 0.0 - 1.0))
bind f = f + 1.0
}
st_set(mk(o, "fcol"), fcol)
return o
}
if kind == "barrel" {
bind segs = max(4.0, floor(p3)) bind rings = max(2.0, floor(p4))
bind vx = list_new() bind vy = list_new() bind vz = list_new()
bind r = 0.0
while r <= rings {
bind t = r / rings
bind rad = p0 * (1.0 + p2 * sin(t * 3.14159265))
bind i = 0.0
while i < segs {
bind a = i / segs * 6.28318530718
bind vx = list_push(vx, cos(a) * rad) bind vy = list_push(vy, (t - 0.5) * p1) bind vz = list_push(vz, sin(a) * rad)
bind i = i + 1.0
}
bind r = r + 1.0
}
bind fi = list_new() bind fs = list_new() bind fc = list_new()
bind r = 0.0
while r < rings {
bind i = 0.0
while i < segs {
bind j = (i + 1.0) % segs
bind q = pr_quad(fi, fs, fc, r * segs + i, (r + 1.0) * segs + i, (r + 1.0) * segs + j, r * segs + j)
bind fi = list_get(q, 0) bind fs = list_get(q, 1) bind fc = list_get(q, 2)
bind i = i + 1.0
}
bind r = r + 1.0
}
# caps
bind fs = list_push(fs, len(fi)) bind fc = list_push(fc, segs)
bind i = 0.0
while i < segs { bind fi = list_push(fi, i) bind i = i + 1.0 }
bind fs = list_push(fs, len(fi)) bind fc = list_push(fc, segs)
bind i = segs - 1.0
while i >= 0.0 { bind fi = list_push(fi, rings * segs + i) bind i = i - 1.0 }
bind o = gn_finish("Barrel", [vx, vy, vz, fi, fs, fc], c_rgb(140.0, 95.0, 50.0))
# metal bands: darker rings of faces
bind fcol = st_get(mk(o, "fcol"), [])
bind f = 0.0
while f < rings * segs {
bind r = floor(f / segs)
if r == 0.0 || r == rings - 1.0 { bind fcol = list_set(fcol, f, c_rgb(70.0, 70.0, 75.0)) }
bind f = f + 1.0
}
st_set(mk(o, "fcol"), fcol)
return o
}
if kind == "crate" {
bind o = gn_finish("Crate", pr_cube(p0, 1.0), c_rgb(160.0, 120.0, 70.0))
if p1 > 0.001 {
st_set("mode_bak", st_get("mode", 0.0)) st_set("mode", 1.0)
me_sel_all(o, true)
mo_inset(o, p1 * p0)
# push the inner panels in
bind faces = me_selected_faces(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 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 n = v3_norm(me_face_normal(vx, vy, vz, fi, s, c))
bind k = 0.0
while k < c {
bind vv = list_get(fi, s + k)
bind vx = list_set(vx, vv, list_get(vx, vv) - list_get(n, 0) * p2 * p0)
bind vy = list_set(vy, vv, list_get(vy, vv) - list_get(n, 1) * p2 * p0)
bind vz = list_set(vz, vv, list_get(vz, vv) - list_get(n, 2) * p2 * p0)
bind k = k + 1.0
}
bind i = i + 1.0
}
st_set(mk(o, "vx"), vx) st_set(mk(o, "vy"), vy) st_set(mk(o, "vz"), vz)
bind fcol = st_get(mk(o, "fcol"), [])
bind i = 0.0
while i < len(faces) { bind fcol = list_set(fcol, list_get(faces, i), c_rgb(120.0, 85.0, 45.0)) bind i = i + 1.0 }
st_set(mk(o, "fcol"), fcol)
me_sel_all(o, false)
st_set("mode", st_get("mode_bak", 0.0))
}
uv_box(o)
return o
}
if kind == "pillar" {
bind sides = max(3.0, floor(p3))
bind shaft = gn_xform(pr_cone(sides, p1, p1 * 0.85, p0 - p4 - p2 * 0.6, 1.0), m_trans(0.0, p4 + (p0 - p4 - p2 * 0.6) * 0.5, 0.0))
bind base = gn_xform(pr_cube(1.0, 1.0), m_mul(m_trans(0.0, p4 * 0.5, 0.0), m_scale(p2 * 1.1, p4, p2 * 1.1)))
bind cap = gn_xform(pr_cone(sides, p1 * 0.9, p2 * 0.8, p2 * 0.35, 1.0), m_trans(0.0, p0 - p2 * 0.6 + p2 * 0.175, 0.0))
bind top = gn_xform(pr_cube(1.0, 1.0), m_mul(m_trans(0.0, p0 - p2 * 0.125, 0.0), m_scale(p2 * 1.1, p2 * 0.25, p2 * 1.1)))
bind g = gn_merge(gn_merge(gn_merge(base, shaft), cap), top)
return gn_finish("Pillar", g, c_rgb(200.0, 195.0, 180.0))
}
if kind == "stairs" {
bind g = gn_empty()
bind n = max(1.0, floor(p0))
bind k = 0.0
while k < n {
bind h = (k + 1.0) * p2
bind g = gn_merge(g, gn_box(0.0, h * 0.5, k * p3 + p3 * 0.5, p1, h, p3))
bind k = k + 1.0
}
bind o = gn_finish("Stairs", g, c_rgb(170.0, 170.0, 175.0))
mo_merge_dist(o, 0.0001)
return o
}
if kind == "ramp" {
bind w = p0 * 0.5
bind vx = [0.0 - w, w, w, 0.0 - w, 0.0 - w, w]
bind vy = [0.0, 0.0, 0.0, 0.0, p1, p1]
bind vz = [0.0, 0.0, p2, p2, p2, p2]
bind fi = [0.0, 1.0, 5.0, 4.0, 1.0, 2.0, 5.0, 0.0, 4.0, 3.0, 3.0, 4.0, 5.0, 2.0, 3.0, 2.0, 1.0, 0.0]
bind fs = [0.0, 4.0, 7.0, 10.0, 14.0]
bind fc = [4.0, 3.0, 3.0, 4.0, 4.0]
return gn_finish("Ramp", [vx, vy, vz, fi, fs, fc], c_rgb(150.0, 160.0, 170.0))
}
if kind == "terrain" {
bind g = pr_grid(p1, p1, p0)
bind vx = list_get(g, 0) bind vy = list_get(g, 1) bind vz = list_get(g, 2)
bind i = 0.0
while i < len(vx) {
bind n = mx_fbm(list_get(vx, i) / p0 * p3 + p4, list_get(vz, i) / p0 * p3, max(1.0, floor(p5)), p4)
# gentle falloff so the tile edges stay low
bind ex = abs(list_get(vx, i)) / (p0 * 0.5) bind ez = abs(list_get(vz, i)) / (p0 * 0.5)
bind edge = 1.0 - pow(max(ex, ez), 4.0)
bind vy = list_set(vy, i, pow(n, 1.6) * p2 * max(0.15, edge))
bind i = i + 1.0
}
bind o = gn_finish("Terrain", [vx, vy, vz, list_get(g, 3), list_get(g, 4), list_get(g, 5)], c_rgb(90.0, 150.0, 80.0))
# colour by height: water / grass / rock / snow
bind fi = st_get(mk(o, "fi"), []) bind fs = st_get(mk(o, "fs"), []) bind fc = st_get(mk(o, "fc"), [])
bind fcol = list_new()
bind f = 0.0
while f < len(fc) {
bind s = list_get(fs, f) bind c = list_get(fc, f)
bind hy = 0.0
bind q = 0.0
while q < c { bind hy = hy + list_get(vy, list_get(fi, s + q)) bind q = q + 1.0 }
bind hy = hy / c / max(0.001, p2)
bind col = c_rgb(80.0, 150.0, 70.0)
if hy < 0.08 { bind col = c_rgb(190.0, 175.0, 120.0) }
if hy > 0.45 { bind col = c_rgb(120.0, 110.0, 100.0) }
if hy > 0.7 { bind col = c_rgb(240.0, 240.0, 245.0) }
bind fcol = list_push(fcol, col)
bind f = f + 1.0
}
st_set(mk(o, "fcol"), fcol)
return o
}
if kind == "fence" {
bind g = gn_empty()
bind n = max(2.0, floor(p0))
bind k = 0.0
while k < n {
bind g = gn_merge(g, gn_box(k * p1, p2 * 0.5, 0.0, p4, p2, p4))
bind k = k + 1.0
}
bind rails = max(0.0, floor(p3))
bind r = 0.0
while r < rails {
bind y = p2 * (r + 1.0) / (rails + 1.0)
bind g = gn_merge(g, gn_box((n - 1.0) * p1 * 0.5, y, 0.0, (n - 1.0) * p1 + p4, p4 * 0.8, p4 * 0.5))
bind r = r + 1.0
}
return gn_finish("Fence", gn_xform(g, m_trans(0.0 - (n - 1.0) * p1 * 0.5, 0.0, 0.0)), c_rgb(150.0, 110.0, 60.0))
}
if kind == "gem" {
bind sides = max(3.0, floor(p3))
if p0 < 0.5 {
bind g = pr_cone(sides, p1, p1, p2, 1.0)
bind o = gn_finish("Coin", g, c_rgb(240.0, 200.0, 60.0))
return o
}
# brilliant-ish: girdle polygon, crown to a table, pavilion to a point
bind vx = list_new() bind vy = list_new() bind vz = list_new()
bind i = 0.0
while i < sides {
bind a = i / sides * 6.28318530718
bind vx = list_push(vx, cos(a) * p1) bind vy = list_push(vy, 0.0) bind vz = list_push(vz, sin(a) * p1)
bind i = i + 1.0
}
bind i = 0.0
while i < sides {
bind a = (i + 0.5) / sides * 6.28318530718
bind vx = list_push(vx, cos(a) * p1 * 0.55) bind vy = list_push(vy, p2 * 0.5) bind vz = list_push(vz, sin(a) * p1 * 0.55)
bind i = i + 1.0
}
bind tip = len(vx)
bind vx = list_push(vx, 0.0) bind vy = list_push(vy, 0.0 - p2 * 1.2) bind vz = list_push(vz, 0.0)
bind fi = list_new() bind fs = list_new() bind fc = list_new()
bind i = 0.0
while i < sides {
bind j = (i + 1.0) % sides
bind q = pr_tri(fi, fs, fc, i, tip, j)
bind fi = list_get(q, 0) bind fs = list_get(q, 1) bind fc = list_get(q, 2)
bind q = pr_tri(fi, fs, fc, i, j, sides + i)
bind fi = list_get(q, 0) bind fs = list_get(q, 1) bind fc = list_get(q, 2)
bind q = pr_tri(fi, fs, fc, j, sides + j, sides + i)
bind fi = list_get(q, 0) bind fs = list_get(q, 1) bind fc = list_get(q, 2)
bind i = i + 1.0
}
bind fs = list_push(fs, len(fi)) bind fc = list_push(fc, sides)
bind i = sides - 1.0
while i >= 0.0 { bind fi = list_push(fi, sides + i) bind i = i - 1.0 }
return gn_finish("Gem", [vx, vy, vz, fi, fs, fc], c_rgb(90.0, 220.0, 200.0))
}
if kind == "character" { return gn_character(p0, p1, p2, p3, p4 > 0.5) }
st_set("status", "Unknown generator " + kind)
return 0.0 - 1.0
}
# a low-poly biped base mesh (blocky, quad-based) with an optional auto-rig
fn gn_character(height, shoulders, head, limb, rig) {
bind leg = height * 0.47
bind torso = height * 0.33
bind hipw = shoulders * 0.8
bind g = gn_empty()
# torso: slightly tapered box (hips narrower)
bind t = pr_cube(1.0, 2.0)
bind t = gn_xform(t, m_mul(m_trans(0.0, leg + torso * 0.5, 0.0), m_scale(shoulders, torso, shoulders * 0.5)))
bind tvx = list_get(t, 0) bind tvy = list_get(t, 1) bind tvz = list_get(t, 2)
bind i = 0.0
while i < len(tvx) {
bind f = (list_get(tvy, i) - leg) / torso
bind k = mx_lerp(hipw / shoulders, 1.0, f)
bind tvx = list_set(tvx, i, list_get(tvx, i) * k) bind tvz = list_set(tvz, i, list_get(tvz, i) * mx_lerp(0.9, 1.0, f))
bind i = i + 1.0
}
bind g = gn_merge(g, [tvx, tvy, tvz, list_get(t, 3), list_get(t, 4), list_get(t, 5)])
# neck + head
bind g = gn_merge(g, gn_box(0.0, leg + torso + head * 0.25, 0.0, head * 0.45, head * 0.5, head * 0.45))
bind hd = gn_xform(pr_cube(1.0, 2.0), m_mul(m_trans(0.0, leg + torso + head * 0.5 + head * 0.55, 0.0), m_scale(head, head * 1.1, head)))
bind g = gn_merge(g, hd)
# arms: upper + lower, hanging down, slightly out
bind side = 0.0 - 1.0
while side <= 1.0 {
bind ax = side * (shoulders * 0.5 + limb * 0.6)
bind sy = leg + torso * 0.92
bind ua = height * 0.19 bind la = height * 0.18
bind g = gn_merge(g, gn_box(ax, sy - ua * 0.5, 0.0, limb, ua, limb))
bind g = gn_merge(g, gn_box(ax, sy - ua - la * 0.5, 0.0, limb * 0.9, la, limb * 0.9))
bind g = gn_merge(g, gn_box(ax, sy - ua - la - limb * 0.6, 0.0, limb * 1.0, limb * 1.2, limb * 0.6))
# legs
bind lx = side * hipw * 0.28
bind ul = leg * 0.5 bind ll = leg * 0.45
bind g = gn_merge(g, gn_box(lx, leg - ul * 0.5, 0.0, limb * 1.4, ul, limb * 1.4))
bind g = gn_merge(g, gn_box(lx, leg - ul - ll * 0.5, 0.0, limb * 1.2, ll, limb * 1.2))
bind g = gn_merge(g, gn_box(lx, limb * 0.5, limb * 0.5, limb * 1.3, limb, limb * 2.4))
bind side = side + 2.0
}
bind o = gn_finish("Character", g, c_rgb(210.0, 170.0, 140.0))
if !rig { return o }
# armature (names end in .L / .R so mirror tools work)
bind hip = [0.0, leg, 0.0]
bind b_hips = rg_push_bone(o, "Hips", 0.0 - 1.0, hip, [0.0, leg + torso * 0.25, 0.0])
bind b_spine = rg_push_bone(o, "Spine", b_hips, [0.0, leg + torso * 0.25, 0.0], [0.0, leg + torso * 0.6, 0.0])
bind b_chest = rg_push_bone(o, "Chest", b_spine, [0.0, leg + torso * 0.6, 0.0], [0.0, leg + torso, 0.0])
bind b_neck = rg_push_bone(o, "Neck", b_chest, [0.0, leg + torso, 0.0], [0.0, leg + torso + head * 0.5, 0.0])
bind b_head = rg_push_bone(o, "Head", b_neck, [0.0, leg + torso + head * 0.5, 0.0], [0.0, leg + torso + head * 1.6, 0.0])
bind side = 0.0 - 1.0
while side <= 1.0 {
bind sfx = sel(side < 0.0, ".R", ".L")
bind ax = side * (shoulders * 0.5 + limb * 0.6)
bind sy = leg + torso * 0.92
bind ua = height * 0.19 bind la = height * 0.18
bind b_sh = rg_push_bone(o, "Shoulder" + sfx, b_chest, [0.0, leg + torso * 0.95, 0.0], [ax, sy, 0.0])
bind b_ua = rg_push_bone(o, "UpperArm" + sfx, b_sh, [ax, sy, 0.0], [ax, sy - ua, 0.0])
bind b_la = rg_push_bone(o, "Forearm" + sfx, b_ua, [ax, sy - ua, 0.0], [ax, sy - ua - la, 0.0])
bind b_hd = rg_push_bone(o, "Hand" + sfx, b_la, [ax, sy - ua - la, 0.0], [ax, sy - ua - la - limb * 1.2, 0.0])
bind lx = side * hipw * 0.28
bind ul = leg * 0.5 bind ll = leg * 0.45
bind b_th = rg_push_bone(o, "Thigh" + sfx, b_hips, [lx, leg, 0.0], [lx, leg - ul, 0.0])
bind b_sn = rg_push_bone(o, "Shin" + sfx, b_th, [lx, leg - ul, 0.0], [lx, leg - ul - ll, 0.0])
bind b_ft = rg_push_bone(o, "Foot" + sfx, b_sn, [lx, leg - ul - ll, 0.0], [lx, limb * 0.3, limb * 1.6])
bind side = side + 2.0
}
st_set(mk(o, "bound"), 1.0)
st_set(mk(o, "wb"), me_wb_resize([], me_nverts(o)))
st_set("sel_o", o)
st_set("aw_pow", 6.0) st_set("aw_env", 0.1) st_set("aw_max", 4.0) st_set("aw_smooth", 1.0) st_set("aw_cut", 0.03)
rg_auto_weights()
bind n = st_get("undo_n", 0.0)
if n > 0.5 { st_set("undo_n", n - 1.0) }
st_set("status", "Character generated with " + num_str(rg_count(o)) + " bones and auto weights - Pose tab to animate, Weights tab to refine")
return o
}