灵符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/json.ling
# ━━━ json.ling — a small JSON reader/writer (for glTF). Nodes live in st lists; no maps needed ━━━
# Parse: js_parse(text) -> root node index (or -1). Node arrays (parallel):
#   jn_t   type: 0 null, 1 bool, 2 number, 3 string, 4 array, 5 object
#   jn_v   value (number / bool as 0,1 / string)
#   jn_k0  first child slot, jn_kn child count  (children in jc_node; object keys in jc_key)
# Tokenising is done with native str_split so large files stay fast (char-by-char slicing is O(n) each).

fn js_reset() {
    st_set("jn_t", []) st_set("jn_v", []) st_set("jn_k0", []) st_set("jn_kn", [])
    st_set("jc_node", []) st_set("jc_key", [])
}
fn js_type(n) { return list_get(st_get("jn_t", []), n) }
fn js_num(n) {
    if n < 0.0 { return 0.0 }
    bind t = js_type(n)
    if t == 2.0 || t == 1.0 { return list_get(st_get("jn_v", []), n) }
    return 0.0
}
fn js_str(n) {
    if n < 0.0 { return "" }
    if js_type(n) == 3.0 { return list_get(st_get("jn_v", []), n) }
    return ""
}
fn js_len(n) { if n < 0.0 { return 0.0 } return list_get(st_get("jn_kn", []), n) }
# i-th child of an array (or object)
fn js_idx(n, i) {
    if n < 0.0 { return 0.0 - 1.0 }
    bind kn = list_get(st_get("jn_kn", []), n)
    if i < 0.0 || i >= kn { return 0.0 - 1.0 }
    return list_get(st_get("jc_node", []), list_get(st_get("jn_k0", []), n) + i)
}
fn js_key_at(n, i) { return list_get(st_get("jc_key", []), list_get(st_get("jn_k0", []), n) + i) }
# object member by key, -1 when missing
fn js_get(n, key) {
    if n < 0.0 { return 0.0 - 1.0 }
    if js_type(n) != 5.0 { return 0.0 - 1.0 }
    bind k0 = list_get(st_get("jn_k0", []), n)
    bind kn = list_get(st_get("jn_kn", []), n)
    bind keys = st_get("jc_key", [])
    bind nodes = st_get("jc_node", [])
    bind i = 0.0
    while i < kn {
        if list_get(keys, k0 + i) == key { return list_get(nodes, k0 + i) }
        bind i = i + 1.0
    }
    return 0.0 - 1.0
}
fn js_get_num(n, key, d) { bind c = js_get(n, key) if c < 0.0 { return d } return js_num(c) }
fn js_get_str(n, key, d) { bind c = js_get(n, key) if c < 0.0 { return d } return js_str(c) }
# number list from an array node
fn js_nums(n) {
    bind out = list_new()
    bind i = 0.0
    while i < js_len(n) { bind out = list_push(out, js_num(js_idx(n, i))) bind i = i + 1.0 }
    return out
}

# ── tokeniser: split on quotes, then on structural characters ────────────────
fn js_unescape(s) {
    if str_find(s, "\\") < 0.0 { return s }
    bind t = str_replace(s, "\\n", "\n")
    bind t = str_replace(t, "\\t", "\t")
    bind t = str_replace(t, "\\r", "\r")
    bind t = str_replace(t, "\\/", "/")
    bind t = str_replace(t, "\\\"", "\"")
    bind t = str_replace(t, "\\\\", "\\")
    return t
}
fn js_tokens(text) {
    # pieces alternate: outside-string, inside-string, outside, ... (a backslash before the quote re-joins)
    bind parts = str_split(text, "\"")
    bind toks = list_new()
    bind kinds = list_new()
    bind inside = false
    bind pending = ""
    bind i = 0.0
    while i < len(parts) {
        bind p = list_get(parts, i)
        if inside {
            bind pending = pending + p
            if str_ends_with(p, "\\") && !str_ends_with(p, "\\\\") {
                # escaped quote inside the string
                bind pending = str_slice(pending, 0.0, str_len(pending) - 1.0) + "\""
            } else {
                bind toks = list_push(toks, js_unescape(pending))
                bind kinds = list_push(kinds, 1.0)
                bind pending = ""
                bind inside = false
            }
        } else {
            # structure + literals: pad the punctuation then split on spaces
            bind q = str_replace(p, "{", " { ")
            bind q = str_replace(q, "}", " } ")
            bind q = str_replace(q, "[", " [ ")
            bind q = str_replace(q, "]", " ] ")
            bind q = str_replace(q, ",", " , ")
            bind q = str_replace(q, ":", " : ")
            bind q = str_replace(q, "\n", " ")
            bind q = str_replace(q, "\r", " ")
            bind q = str_replace(q, "\t", " ")
            bind ws = str_split(q, " ")
            bind k = 0.0
            while k < len(ws) {
                bind w = list_get(ws, k)
                if w != "" { bind toks = list_push(toks, w) bind kinds = list_push(kinds, 0.0) }
                bind k = k + 1.0
            }
            bind inside = true
        }
        bind i = i + 1.0
    }
    st_set("js_toks", toks)
    st_set("js_kinds", kinds)
}

fn js_new_node(t, v) {
    bind idx = len(st_get("jn_t", []))
    st_lpush("jn_t", t) st_lpush("jn_v", v) st_lpush("jn_k0", 0.0) st_lpush("jn_kn", 0.0)
    return idx
}
# parse the value starting at token position p; returns [node, nextpos]
fn js_value(p) {
    bind toks = st_get("js_toks", [])
    bind kinds = st_get("js_kinds", [])
    if p >= len(toks) { return [0.0 - 1.0, p] }
    bind tk = list_get(toks, p)
    if list_get(kinds, p) > 0.5 { return [js_new_node(3.0, tk), p + 1.0] }
    if tk == "{" {
        bind n = js_new_node(5.0, 0.0)
        bind keys = list_new() bind kids = list_new()
        bind q = p + 1.0
        bind go = true
        while go {
            if q >= len(toks) { bind go = false }
            else if list_get(toks, q) == "}" { bind q = q + 1.0 bind go = false }
            else if list_get(toks, q) == "," { bind q = q + 1.0 }
            else {
                bind key = list_get(toks, q)
                bind q = q + 1.0
                if q < len(toks) { if list_get(toks, q) == ":" { bind q = q + 1.0 } }
                bind r = js_value(q)
                bind keys = list_push(keys, key)
                bind kids = list_push(kids, list_get(r, 0))
                bind q = list_get(r, 1)
            }
        }
        bind k0 = len(st_get("jc_node", []))
        bind i = 0.0
        while i < len(kids) { st_lpush("jc_node", list_get(kids, i)) st_lpush("jc_key", list_get(keys, i)) bind i = i + 1.0 }
        st_lset("jn_k0", n, k0)
        st_lset("jn_kn", n, len(kids))
        return [n, q]
    }
    if tk == "[" {
        bind n = js_new_node(4.0, 0.0)
        bind kids = list_new()
        bind q = p + 1.0
        bind go = true
        while go {
            if q >= len(toks) { bind go = false }
            else if list_get(toks, q) == "]" { bind q = q + 1.0 bind go = false }
            else if list_get(toks, q) == "," { bind q = q + 1.0 }
            else {
                bind r = js_value(q)
                bind kids = list_push(kids, list_get(r, 0))
                bind q = list_get(r, 1)
            }
        }
        bind k0 = len(st_get("jc_node", []))
        bind i = 0.0
        while i < len(kids) { st_lpush("jc_node", list_get(kids, i)) st_lpush("jc_key", "") bind i = i + 1.0 }
        st_lset("jn_k0", n, k0)
        st_lset("jn_kn", n, len(kids))
        return [n, q]
    }
    if tk == "true" { return [js_new_node(1.0, 1.0), p + 1.0] }
    if tk == "false" { return [js_new_node(1.0, 0.0), p + 1.0] }
    if tk == "null" { return [js_new_node(0.0, 0.0), p + 1.0] }
    return [js_new_node(2.0, to_number(tk)), p + 1.0]
}
fn js_parse(text) {
    js_reset()
    js_tokens(text)
    bind r = js_value(0.0)
    return list_get(r, 0)
}

# ── writer helpers ───────────────────────────────────────────────────────────
fn js_q(s) {
    bind t = str_replace(s, "\\", "\\\\")
    bind t = str_replace(t, "\"", "\\\"")
    bind t = str_replace(t, "\n", "\\n")
    return "\"" + t + "\""
}
fn js_numlist(l) {
    bind parts = list_new()
    bind i = 0.0
    while i < len(l) { bind parts = list_push(parts, mx_num(list_get(l, i))) bind i = i + 1.0 }
    return "[" + p_join(parts, ",") + "]"
}

← Back to index