灵符lingfu

lingmodeler

↓ 0 downloads

LingModeler: box modeling with quads and tris, rigging + weight painting, 2D vector illustration and animation, procedural textures and game assets; .lmesh / .ling native formats, OBJ, glTF and SVG import / export

Windows · x86-64Linux · x86-64LingOS · x86-64LingOS · Pi 5 ARM64Web · WASM
Keywords 3d, modeling, vector, svg, gltf, obj, rigging, animation, textures, ling
lingfu install lingmodeler
LicenseApache-2.0 OR MIT
Authorsling-lin
READMECode / FilesVersionsDependency GraphDependentsSecurityBuildsSettings

Code / Files (67)

ling/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
}

← Back to index