灵符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/raster.ling
# ━━━ raster.ling — a small software rasterizer (RGBA buffer -> PNG) for animation frames, sprite sheets and turntables ━━━
# Buffers are flat lists of 4 numbers per pixel (r, g, b, a in 0..1). Polygons are filled with 4 sub-scanlines per row
# and exact horizontal coverage, so edges are anti-aliased.

fn ra_new(w, h, bg) {
    bind buf = list_new()
    bind r = 0.0 bind g = 0.0 bind b = 0.0 bind a = 0.0
    if bg >= 0.0 { bind r = c_r(bg) / 255.0 bind g = c_g(bg) / 255.0 bind b = c_b(bg) / 255.0 bind a = 1.0 }
    bind i = 0.0
    while i < w * h { bind buf = list_push(buf, r) bind buf = list_push(buf, g) bind buf = list_push(buf, b) bind buf = list_push(buf, a) bind i = i + 1.0 }
    st_set("ra_buf", buf)
    st_set("ra_w", w) st_set("ra_h", h)
}
# fill a polygon (flat [x,y,...] in buffer pixels), colour, alpha
fn ra_poly(pts, col, alpha) {
    bind n = floor(len(pts) / 2.0)
    if n < 3.0 || alpha <= 0.0 { return 0.0 }
    bind w = st_get("ra_w", 1.0) bind h = st_get("ra_h", 1.0)
    bind ymin = 1000000.0 bind ymax = 0.0 - 1000000.0
    bind i = 0.0
    while i < n { bind y = list_get(pts, i * 2.0 + 1.0) bind ymin = min(ymin, y) bind ymax = max(ymax, y) bind i = i + 1.0 }
    bind y0 = max(0.0, floor(ymin)) bind y1 = min(h - 1.0, ceil(ymax))
    bind cr = c_r(col) / 255.0 bind cg = c_g(col) / 255.0 bind cb = c_b(col) / 255.0
    bind buf = st_get("ra_buf", [])
    bind y = y0
    while y <= y1 {
        bind cov = list_new()
        bind xlo = w bind xhi = 0.0 - 1.0
        bind s = 0.0
        while s < 4.0 {
            bind sy = y + (s + 0.5) * 0.25
            bind xs = list_new()
            bind i = 0.0
            while i < n {
                bind j = (i + 1.0) % n
                bind ay = list_get(pts, i * 2.0 + 1.0) bind by = list_get(pts, j * 2.0 + 1.0)
                if (ay <= sy && by > sy) || (by <= sy && ay > sy) {
                    bind ax = list_get(pts, i * 2.0) bind bx = list_get(pts, j * 2.0)
                    bind xc = ax + (sy - ay) * (bx - ax) / (by - ay)
                    bind xs = list_push(xs, xc)
                    bind k = len(xs) - 1.0
                    bind go = true
                    while go {
                        if k <= 0.0 { bind go = false }
                        else if list_get(xs, k - 1.0) > list_get(xs, k) {
                            bind tmp = list_get(xs, k - 1.0)
                            bind xs = list_set(xs, k - 1.0, list_get(xs, k))
                            bind xs = list_set(xs, k, tmp)
                            bind k = k - 1.0
                        } else { bind go = false }
                    }
                }
                bind i = i + 1.0
            }
            bind k = 0.0
            while k + 1.0 < len(xs) {
                bind xa = max(0.0, list_get(xs, k)) bind xb = min(w, list_get(xs, k + 1.0))
                if xb > xa {
                    bind px = floor(xa)
                    while px < xb {
                        bind ov = min(xb, px + 1.0) - max(xa, px)
                        # sparse coverage row: pairs (x, amount) appended; merged below
                        bind cov = list_push(cov, px) bind cov = list_push(cov, ov * 0.25)
                        bind xlo = min(xlo, px) bind xhi = max(xhi, px)
                        bind px = px + 1.0
                    }
                }
                bind k = k + 2.0
            }
            bind s = s + 1.0
        }
        if xhi >= xlo {
            bind row = me_zeros(xhi - xlo + 1.0)
            bind q = 0.0
            while q + 1.0 < len(cov) { bind ix = list_get(cov, q) - xlo bind row = list_set(row, ix, list_get(row, ix) + list_get(cov, q + 1.0)) bind q = q + 2.0 }
            bind x = xlo
            while x <= xhi {
                bind a = min(1.0, list_get(row, x - xlo)) * alpha
                if a > 0.001 {
                    bind p = (y * w + x) * 4.0
                    bind da = list_get(buf, p + 3.0)
                    bind oa = a + da * (1.0 - a)
                    if oa > 0.000001 {
                        bind buf = list_set(buf, p, (cr * a + list_get(buf, p) * da * (1.0 - a)) / oa)
                        bind buf = list_set(buf, p + 1.0, (cg * a + list_get(buf, p + 1.0) * da * (1.0 - a)) / oa)
                        bind buf = list_set(buf, p + 2.0, (cb * a + list_get(buf, p + 2.0) * da * (1.0 - a)) / oa)
                        bind buf = list_set(buf, p + 3.0, oa)
                    }
                }
                bind x = x + 1.0
            }
        }
        bind y = y + 1.0
    }
    st_set("ra_buf", buf)
}
fn ra_stroke(pts, closed, col, width, alpha) {
    bind n = floor(len(pts) / 2.0)
    bind segs = sel(closed, n, n - 1.0)
    bind hw = max(0.5, width * 0.5)
    bind i = 0.0
    while i < segs {
        bind j = (i + 1.0) % n
        bind x0 = list_get(pts, i * 2.0) bind y0 = list_get(pts, i * 2.0 + 1.0)
        bind x1 = list_get(pts, j * 2.0) bind y1 = list_get(pts, j * 2.0 + 1.0)
        bind dx = x1 - x0 bind dy = y1 - y0
        bind l = sqrt(dx * dx + dy * dy)
        if l > 0.0001 {
            bind nx = 0.0 - dy / l * hw bind ny = dx / l * hw
            # extend a little along the segment so joints close
            bind ex = dx / l * hw * 0.5 bind ey = dy / l * hw * 0.5
            ra_poly([x0 + nx - ex, y0 + ny - ey, x1 + nx + ex, y1 + ny + ey, x1 - nx + ex, y1 - ny + ey, x0 - nx - ex, y0 - ny - ey], col, alpha)
        }
        bind i = i + 1.0
    }
}
# copy the buffer into an image at (ox, oy)
fn ra_blit(img, ox, oy) {
    bind buf = st_get("ra_buf", [])
    bind w = st_get("ra_w", 1.0) bind h = st_get("ra_h", 1.0)
    bind y = 0.0
    while y < h {
        bind x = 0.0
        while x < w {
            bind p = (y * w + x) * 4.0
            p_img_set(img, ox + x, oy + y, list_get(buf, p) * 255.0, list_get(buf, p + 1.0) * 255.0, list_get(buf, p + 2.0) * 255.0, list_get(buf, p + 3.0) * 255.0)
            bind x = x + 1.0
        }
        bind y = y + 1.0
    }
}

# ── vector document -> buffer (current animation state) ──────────────────────
fn ra_vector(size) {
    bind dw = st_get("vc_w", 512.0) bind dh = st_get("vc_h", 512.0)
    bind s = size / max(dw, dh)
    bind w = max(1.0, round(dw * s)) bind h = max(1.0, round(dh * s))
    ra_new(w, h, st_get("vc_bg", 0.0 - 1.0))
    bind ids = vc_ids()
    bind i = 0.0
    while i < len(ids) {
        bind k = list_get(ids, i)
        if vc_layer_visible(st_get(vk(k, "layer"), 0.0)) {
            bind f = vc_flatten(k, 0.75 / s)
            bind p = list_new()
            bind q = 0.0
            while q < len(f) { bind p = list_push(p, list_get(f, q) * s) bind q = q + 1.0 }
            bind closed = st_get(vk(k, "closed"), 0.0) > 0.5
            bind op = st_get(vk(k, "op"), 1.0)
            if closed && st_get(vk(k, "fill"), 0.0 - 1.0) >= 0.0 { ra_poly(p, st_get(vk(k, "fill"), 0.0), op) }
            if st_get(vk(k, "stroke"), 0.0 - 1.0) >= 0.0 && st_get(vk(k, "sw"), 0.0) > 0.0 { ra_stroke(p, closed, st_get(vk(k, "stroke"), 0.0), st_get(vk(k, "sw"), 1.0) * s, op) }
        }
        bind i = i + 1.0
    }
    return [w, h]
}

# ── 3D scene -> buffer from a camera orbit (uses the viewport's projection code) ──
fn ra_scene(size, yaw, pitch) {
    bind keep = [st_get("cam_yaw", 0.0), st_get("cam_pitch", 0.0), st_get("cam_dist", 7.0), st_get("cam_tx", 0.0), st_get("cam_ty", 0.0), st_get("cam_tz", 0.0), st_get("cam_persp", 1.0)]
    v3_frame(true)
    st_set("cam_yaw", yaw) st_set("cam_pitch", pitch)
    v3_update_camera(0.0, 0.0, size, size)
    v3_project_all()
    ra_new(size, size, 0.0 - 1.0)
    # painter's order over all faces
    bind ids = me_ids()
    bind fo = list_new() bind ff = list_new() bind fz = list_new()
    bind i = 0.0
    while i < len(ids) {
        bind o = list_get(ids, i)
        if st_get(mk(o, "vis"), 1.0) > 0.5 {
            bind fi = st_get(mk(o, "fi"), []) bind fs = st_get(mk(o, "fs"), []) bind fc = st_get(mk(o, "fc"), [])
            bind pd = st_get(pj(o, "d"), [])
            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 z = 0.0
                    bind k = 0.0
                    while k < c { bind z = z + list_get(pd, list_get(fi, s + k)) bind k = k + 1.0 }
                    bind fo = list_push(fo, o) bind ff = list_push(ff, f) bind fz = list_push(fz, 0.0 - z / c)
                }
                bind f = f + 1.0
            }
        }
        bind i = i + 1.0
    }
    bind order = mx_argsort(fz)
    bind i = 0.0
    while i < len(order) {
        bind di = list_get(order, i)
        bind o = list_get(fo, di) bind f = list_get(ff, di)
        bind fi = st_get(mk(o, "fi"), []) bind fs = st_get(mk(o, "fs"), []) bind fc = st_get(mk(o, "fc"), [])
        bind px = st_get(pj(o, "x"), []) bind py = st_get(pj(o, "y"), [])
        bind pcx = st_get(pj(o, "cx"), []) bind pcy = st_get(pj(o, "cy"), []) bind pcz = st_get(pj(o, "cz"), [])
        bind s = list_get(fs, f) bind c = list_get(fc, f)
        bind nx = 0.0 bind ny = 0.0 bind nz = 0.0
        bind pts = 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 nx = nx + (list_get(pcy, a) - list_get(pcy, b)) * (list_get(pcz, a) + list_get(pcz, b))
            bind ny = ny + (list_get(pcz, a) - list_get(pcz, b)) * (list_get(pcx, a) + list_get(pcx, b))
            bind nz = nz + (list_get(pcx, a) - list_get(pcx, b)) * (list_get(pcy, a) + list_get(pcy, b))
            bind pts = list_push(pts, list_get(px, a)) bind pts = list_push(pts, list_get(py, a))
            bind k = k + 1.0
        }
        bind nl = sqrt(nx * nx + ny * ny + nz * nz)
        if nl > 0.0000001 {
            bind a0 = list_get(fi, s)
            if nx * list_get(pcx, a0) + ny * list_get(pcy, a0) + nz * list_get(pcz, a0) < 0.0 {
                bind col = st_get(mk(o, "col"), C_BLUE)
                bind fcol = st_get(mk(o, "fcol"), [])
                if f < len(fcol) { if list_get(fcol, f) >= 0.0 { bind col = list_get(fcol, f) } }
                ra_poly(pts, v3_shade(col, nx / nl, ny / nl, nz / nl), 1.0)
            }
        }
        bind i = i + 1.0
    }
    st_set("cam_yaw", list_get(keep, 0)) st_set("cam_pitch", list_get(keep, 1)) st_set("cam_dist", list_get(keep, 2))
    st_set("cam_tx", list_get(keep, 3)) st_set("cam_ty", list_get(keep, 4)) st_set("cam_tz", list_get(keep, 5)) st_set("cam_persp", list_get(keep, 6))
}

# ── exports ──────────────────────────────────────────────────────────────────
fn ra_turntable() {
    if !p_has_images() { st_set("status", "Image export needs the desktop build") return 0.0 }
    bind path = p_dlg_save("Save turntable sprite sheet", "png", st_get("scene_name", "scene") + "_turntable.png")
    if path == "" { return 0.0 }
    ra_turntable_to(path)
}
fn ra_turntable_to(path) {
    bind n = max(1.0, st_get("tt_n", 8.0)) bind size = st_get("tt_size", 128.0)
    bind img = p_img_new(size * n, size)
    bind i = 0.0
    while i < n {
        ra_scene(size, i / n * 6.28318530718, mx_rad(st_get("tt_pitch", 25.0)))
        ra_blit(img, i * size, 0.0)
        bind i = i + 1.0
    }
    p_img_save(img, path)
    st_set("status", "Turntable: " + num_str(n) + " frames of " + fmt0(size) + " px -> " + path_base(path))
}
fn sheet_export(kind) {
    if !p_has_images() { st_set("status", "Image export needs the desktop build") return 0.0 }
    bind frames = max(1.0, st_get("sh_frames", 8.0)) bind size = st_get("sh_size", 128.0) bind cols = st_get("sh_cols", 0.0)
    if kind == "texsheet" {
        bind p = p_dlg_save("Save texture sprite sheet", "png", tx_cur_name() + "_sheet.png")
        if p == "" { return 0.0 }
        tx_export_sheet(p, frames, size, cols, st_get("sh_cycle", 1.0))
        st_set("status", "Texture sheet: " + num_str(frames) + " frames -> " + path_base(p))
        return 0.0
    }
    if kind == "frames" {
        bind dir = p_dlg_folder("Folder for the PNG frames")
        if dir == "" { return 0.0 }
        ra_vector_frames(dir, frames)
        return 0.0
    }
    bind p = p_dlg_save("Save animation sprite sheet", "png", st_get("vc_name", "drawing") + "_sheet.png")
    if p == "" { return 0.0 }
    ra_vector_sheet(p, frames, size, cols)
}
# frames sampled evenly across the timeline (start .. end)
fn ra_frame_at(i, frames) {
    bind s = st_get("tl_start", 0.0) bind e = st_get("tl_end", 48.0)
    if frames < 1.5 { return tl_frame() }
    return s + (e - s) * i / frames
}
fn ra_vector_frames(dir, frames) {
    an_save()
    bind size = st_get("sh_size", 128.0)
    bind i = 0.0
    while i < frames {
        an_eval(ra_frame_at(i, frames))
        bind wh = ra_vector(size)
        bind img = p_img_new(list_get(wh, 0), list_get(wh, 1))
        ra_blit(img, 0.0, 0.0)
        bind num = num_str(i)
        while str_len(num) < 4.0 { bind num = "0" + num }
        p_img_save(img, fs_join(dir, st_get("vc_name", "frame") + "_" + num + ".png"))
        bind i = i + 1.0
    }
    an_restore()
    st_set("status", "Wrote " + num_str(frames) + " PNG frames to " + dir)
}
fn ra_vector_sheet(path, frames, size, cols) {
    an_save()
    bind cols2 = sel(cols < 0.5, frames, cols)
    bind rows = ceil(frames / cols2)
    bind dw = st_get("vc_w", 512.0) bind dh = st_get("vc_h", 512.0)
    bind s = size / max(dw, dh)
    bind fw = max(1.0, round(dw * s)) bind fh = max(1.0, round(dh * s))
    bind img = p_img_new(fw * cols2, fh * rows)
    bind i = 0.0
    while i < frames {
        an_eval(ra_frame_at(i, frames))
        ra_vector(size)
        ra_blit(img, (i % cols2) * fw, floor(i / cols2) * fh)
        bind i = i + 1.0
    }
    an_restore()
    p_img_save(img, path)
    st_set("status", "Sprite sheet: " + num_str(frames) + " frames -> " + path_base(path))
}

← Back to index