lingdev
↓ 0 downloadsLingDev: a Godot / Unity-style game editor written in Ling. 2-D and 3-D scenes, Flash-style timeline and cutscenes, HUDs over 3-D, Ling scripting, audio / music / MIDI, builds for Windows, Linux, macOS, web, Raspberry Pi and LingOS. Ships with Ling Agent (an N64-style spy FPS), Ling Breaker (a 3-D breakout) and Moko's Picture Book (a kawaii storybook platformer).
Keywords game-engine, editor, 3d, 2d, cutscene, timeline, ling, lingos, raspberry-pi
lingfu install lingdevCode / Files (136)
ling/rt/rt_scene.ling
# rt_scene.ling - the scene document: nodes, keyframes and cutscene clips, in the native store.
# Shared by the editor and exported games (the player loads the same text the editor saves).
#
# Nodes are rows of parallel store lists <ns>n_<col>; every node has a stable numeric id (n_id) that
# parents, keyframes, clips and the selection refer to, so rows can be removed without remapping.
# Several scenes can be loaded at once under different namespaces (nd_use): "" is the main scene,
# "h" a HUD / menu drawn over it, "b" a 3-D background behind a 2-D menu.
# A scene file is text: one record per line, "type|key=value|key=value" (see rt_data.ling).
fn ns() { return st_get("ns", "") }
fn nd_use(prefix) { st_set("ns", prefix) }
fn nk(col) { return ns() + "n_" + col }
fn nd_cols() {
return ["id", "kind", "name", "par", "px", "py", "pz", "rx", "ry", "rz", "sx", "sy", "sz", "col", "src", "vis", "lock", "tag", "scr", "a", "b", "c", "d", "txt", "ex", "w", "h", "anc"]
}
fn nd_is_text(col) {
return col == "kind" || col == "name" || col == "src" || col == "tag" || col == "scr" || col == "txt" || col == "ex" || col == "anc"
}
fn nd_props_anim() { return ["px", "py", "pz", "rx", "ry", "rz", "sx", "sy", "sz", "a", "b", "w", "h"] }
# ── scene ────────────────────────────────────────────────────────────────────
fn sc_get(k, d) { return st_get(ns() + "sc_" + k, d) }
fn sc_set(k, v) { st_set(ns() + "sc_" + k, v) }
fn sc_clear() {
bind cols = nd_cols()
bind i = 0.0
while i < len(cols) {
st_lclear(nk(list_get(cols, i)))
bind i = i + 1.0
}
st_lclear(ns() + "k_node")
st_lclear(ns() + "k_prop")
st_lclear(ns() + "k_t")
st_lclear(ns() + "k_v")
st_lclear(ns() + "k_e")
st_lclear(ns() + "c_track")
st_lclear(ns() + "c_t")
st_lclear(ns() + "c_d")
st_lclear(ns() + "c_kind")
st_lclear(ns() + "c_a")
st_lclear(ns() + "c_b")
st_lclear(ns() + "c_c")
st_lclear(ns() + "c_node")
st_lclear(ns() + "c_e")
sc_set("nextid", 1.0)
}
fn sc_new(name, kind) {
sc_clear()
sc_set("name", name)
sc_set("kind", kind)
sc_set("bg", sel(kind == "2d", 1776411.0, 526603.0))
sc_set("fps", 30.0)
sc_set("len", 10.0)
sc_set("cw", 1280.0)
sc_set("ch", 720.0)
sc_set("amb", 0.25)
sc_set("camx", 0.0)
sc_set("camy", 0.0 - 1.0)
sc_set("camz", 0.0)
sc_set("camyaw", 0.6)
sc_set("campitch", 0.42)
sc_set("camzd", 16.0)
sc_set("hud", "")
sc_set("next", "")
sc_set("music", "")
sc_set("grav", 9.0)
sc_set("floor", 0.0)
sc_set("ex", "")
sc_set("path", "")
}
# ── nodes ────────────────────────────────────────────────────────────────────
fn nd_count() { return st_llen(nk("id")) }
fn nd_get(i, col) { return st_lget(nk(col), i) }
fn nd_set(i, col, v) { st_lset(nk(col), i, v) }
fn nd_id(i) { return st_lget(nk("id"), i) }
fn nd_find(id) {
bind key = nk("id")
bind n = st_llen(key)
bind i = 0.0
while i < n {
if st_lget(key, i) == id { return i }
bind i = i + 1.0
}
return 0.0 - 1.0
}
fn nd_gid(id, col) {
bind i = nd_find(id)
if i < 0.0 { return sel(nd_is_text(col), "", 0.0) }
return nd_get(i, col)
}
fn nd_sid(id, col, v) {
bind i = nd_find(id)
if i >= 0.0 { nd_set(i, col, v) }
}
fn nd_find_name(name) {
bind key = nk("name")
bind n = st_llen(key)
bind i = 0.0
while i < n {
if st_lget(key, i) == name { return i }
bind i = i + 1.0
}
return 0.0 - 1.0
}
fn nd_find_tag(tag) {
bind key = nk("tag")
bind n = st_llen(key)
bind i = 0.0
while i < n {
if st_lget(key, i) == tag { return i }
bind i = i + 1.0
}
return 0.0 - 1.0
}
fn nd_is_2d(kind) {
return kind == "panel" || kind == "label" || kind == "button" || kind == "image" || kind == "bar"
}
fn nd_kinds_3d() { return ["empty", "box", "sphere", "cylinder", "cone", "capsule", "torus", "plane", "model", "actor", "light", "camera", "spawn", "trigger", "audio", "music", "midi", "text3d", "swarm", "knot"] }
fn nd_kinds_2d() { return ["panel", "label", "button", "image", "bar"] }
# a new row with the defaults of its kind; returns the row index
fn nd_add(kind, name, parent_id) {
bind id = sc_get("nextid", 1.0)
sc_set("nextid", id + 1.0)
bind i = nd_count()
st_lpush(nk("id"), id)
st_lpush(nk("kind"), kind)
st_lpush(nk("name"), name)
st_lpush(nk("par"), parent_id)
st_lpush(nk("px"), 0.0)
st_lpush(nk("py"), 0.0)
st_lpush(nk("pz"), 0.0)
st_lpush(nk("rx"), 0.0)
st_lpush(nk("ry"), 0.0)
st_lpush(nk("rz"), 0.0)
st_lpush(nk("sx"), 1.0)
st_lpush(nk("sy"), 1.0)
st_lpush(nk("sz"), 1.0)
st_lpush(nk("col"), 13488344.0)
st_lpush(nk("src"), "")
st_lpush(nk("vis"), 1.0)
st_lpush(nk("lock"), 0.0)
st_lpush(nk("tag"), "")
st_lpush(nk("scr"), "")
st_lpush(nk("a"), 0.0)
st_lpush(nk("b"), 0.0)
st_lpush(nk("c"), 0.0)
st_lpush(nk("d"), 0.0)
st_lpush(nk("txt"), "")
st_lpush(nk("ex"), "")
st_lpush(nk("w"), 160.0)
st_lpush(nk("h"), 40.0)
st_lpush(nk("anc"), "tl")
nd_apply_defaults(i, kind)
return i
}
fn nd_apply_defaults(i, kind) {
if kind == "light" { nd_set(i, "col", 16773836.0) nd_set(i, "a", 2.5) nd_set(i, "b", 16.0) nd_set(i, "py", 0.0 - 4.0) }
else if kind == "camera" { nd_set(i, "col", 3910379.0) nd_set(i, "a", 2.0) nd_set(i, "b", 0.0) nd_set(i, "d", 8.0) nd_set(i, "py", 0.0 - 3.0) nd_set(i, "pz", 0.0 - 8.0) nd_set(i, "rx", 0.25) }
else if kind == "spawn" { nd_set(i, "col", 4573296.0) nd_set(i, "tag", "player") }
else if kind == "trigger" { nd_set(i, "col", 10114006.0) nd_set(i, "tag", "zone") nd_set(i, "sx", 2.0) nd_set(i, "sy", 1.5) nd_set(i, "sz", 2.0) nd_set(i, "a", 1.0) }
else if kind == "audio" { nd_set(i, "col", 4875480.0) nd_set(i, "a", 1.0) nd_set(i, "b", 0.0) nd_set(i, "c", 1.0) nd_set(i, "d", 1.0) }
else if kind == "music" { nd_set(i, "col", 4875480.0) nd_set(i, "a", 0.7) nd_set(i, "b", 1.0) nd_set(i, "c", 1.0) }
else if kind == "midi" { nd_set(i, "col", 10114006.0) nd_set(i, "a", 0.0 - 1.0) nd_set(i, "b", 1.0) nd_set(i, "c", 1.0) }
else if kind == "model" { nd_set(i, "col", 16225310.0) nd_set(i, "b", 1.0) nd_set(i, "c", 1.0) }
else if kind == "actor" { nd_set(i, "col", 16225310.0) nd_set(i, "b", 1.0) nd_set(i, "c", 1.0) nd_set(i, "d", 6.0) nd_set(i, "tag", "player") nd_set(i, "sx", 0.45) nd_set(i, "sz", 0.45) }
else if kind == "plane" { nd_set(i, "col", 2763826.0) nd_set(i, "sx", 10.0) nd_set(i, "sy", 0.02) nd_set(i, "sz", 10.0) }
else if kind == "box" { nd_set(i, "col", 3910379.0) }
else if kind == "sphere" { nd_set(i, "col", 15551031.0) }
else if kind == "cylinder" || kind == "cone" || kind == "capsule" || kind == "torus" { nd_set(i, "col", 4573296.0) }
else if kind == "text3d" { nd_set(i, "col", 16242758.0) nd_set(i, "txt", "Text") nd_set(i, "a", 1.0) }
else if kind == "swarm" { nd_set(i, "col", 15658734.0) nd_set(i, "a", 60.0) nd_set(i, "b", 12.0) nd_set(i, "c", 1.0) nd_set(i, "d", 0.1) nd_set(i, "sx", 0.08) nd_set(i, "sy", 0.08) nd_set(i, "sz", 0.08) }
else if kind == "knot" { nd_set(i, "col", 16777215.0) nd_set(i, "a", 30.0) nd_set(i, "b", 6.0) nd_set(i, "c", 2.4) nd_set(i, "d", 0.06) nd_set(i, "sx", 0.3) nd_set(i, "sy", 0.3) nd_set(i, "sz", 0.3) nd_set(i, "ex", "rainbow=1") }
else if kind == "panel" { nd_set(i, "col", 1447706.0) nd_set(i, "w", 320.0) nd_set(i, "h", 200.0) nd_set(i, "a", 0.9) }
else if kind == "label" { nd_set(i, "col", 16119285.0) nd_set(i, "txt", "Label") nd_set(i, "a", 18.0) nd_set(i, "w", 200.0) nd_set(i, "h", 24.0) }
else if kind == "button" { nd_set(i, "col", 3910379.0) nd_set(i, "txt", "Button") nd_set(i, "a", 14.0) nd_set(i, "w", 160.0) nd_set(i, "h", 36.0) nd_set(i, "scr", "goto:") }
else if kind == "image" { nd_set(i, "col", 16777215.0) nd_set(i, "w", 128.0) nd_set(i, "h", 128.0) }
else if kind == "bar" { nd_set(i, "col", 4573296.0) nd_set(i, "a", 70.0) nd_set(i, "b", 100.0) nd_set(i, "w", 200.0) nd_set(i, "h", 16.0) }
}
fn nd_children_ids(parent_id) {
bind out = []
bind key = nk("par")
bind idk = nk("id")
bind n = st_llen(key)
bind i = 0.0
while i < n {
if st_lget(key, i) == parent_id { bind out = list_push(out, st_lget(idk, i)) }
bind i = i + 1.0
}
return out
}
fn nd_depth(i) {
bind d = 0.0
bind p = nd_get(i, "par")
bind guard = 0.0
while p > 0.0 && guard < 64.0 {
bind d = d + 1.0
bind pi = nd_find(p)
if pi < 0.0 { return d }
bind p = nd_get(pi, "par")
bind guard = guard + 1.0
}
return d
}
fn nd_is_descendant(id, ancestor_id) {
bind i = nd_find(id)
bind guard = 0.0
while i >= 0.0 && guard < 64.0 {
bind p = nd_get(i, "par")
if p == ancestor_id { return true }
if p <= 0.0 { return false }
bind i = nd_find(p)
bind guard = guard + 1.0
}
return false
}
# delete a node and everything under it (keys and clips that pointed at them go too)
fn nd_del_id(id) {
bind kids = nd_children_ids(id)
bind k = 0.0
while k < len(kids) {
nd_del_id(list_get(kids, k))
bind k = k + 1.0
}
bind i = nd_find(id)
if i < 0.0 { return 0.0 }
bind cols = nd_cols()
bind c = 0.0
while c < len(cols) {
st_ldel(nk(list_get(cols, c)), i)
bind c = c + 1.0
}
key_remove_node(id)
bind ci = clip_count() - 1.0
while ci >= 0.0 {
if clip_get(ci, "node") == id { clip_del(ci) }
bind ci = ci - 1.0
}
return 0.0
}
# duplicate a node (and its subtree); returns the new id
fn nd_dup_id(id, new_parent) {
bind i = nd_find(id)
if i < 0.0 { return 0.0 - 1.0 }
bind ni = nd_add(nd_get(i, "kind"), nd_get(i, "name") + " copy", new_parent)
bind nid = nd_id(ni)
bind cols = nd_cols()
bind c = 0.0
while c < len(cols) {
bind col = list_get(cols, c)
if col != "id" && col != "par" && col != "name" { nd_set(ni, col, nd_get(i, col)) }
bind c = c + 1.0
}
bind kn = key_count()
bind k = 0.0
while k < kn {
if key_get(k, "node") == id { key_add(nid, key_get(k, "prop"), key_get(k, "t"), key_get(k, "v"), key_get(k, "e")) }
bind k = k + 1.0
}
bind kids = nd_children_ids(id)
bind k = 0.0
while k < len(kids) {
nd_dup_id(list_get(kids, k), nid)
bind k = k + 1.0
}
return nid
}
# move row `from` to sit at row `to` (keeps parents as they are)
fn nd_reorder(from, to) {
if from == to || from < 0.0 || to < 0.0 { return 0.0 }
bind cols = nd_cols()
bind c = 0.0
while c < len(cols) {
bind key = nk(list_get(cols, c))
bind v = st_lget(key, from)
st_ldel(key, from)
bind dst = to
if to > from { bind dst = to - 1.0 }
sl_insert(key, dst, v)
bind c = c + 1.0
}
return 0.0
}
# world position at time t: parent chain translation + parent yaw (animated values included)
fn nd_world_pos_t(i, t) {
bind x = nd_val(i, "px", t)
bind y = nd_val(i, "py", t)
bind z = nd_val(i, "pz", t)
bind p = nd_get(i, "par")
bind guard = 0.0
while p > 0.0 && guard < 64.0 {
bind pi = nd_find(p)
if pi < 0.0 { return [x, y, z] }
bind yaw = nd_val(pi, "ry", t)
bind cy = cos(yaw)
bind sy = sin(yaw)
bind nx = cy * x + sy * z
bind nz = 0.0 - sy * x + cy * z
bind x = nx + nd_val(pi, "px", t)
bind y = y + nd_val(pi, "py", t)
bind z = nz + nd_val(pi, "pz", t)
bind p = nd_get(pi, "par")
bind guard = guard + 1.0
}
return [x, y, z]
}
fn nd_world_pos(i) { return nd_world_pos_t(i, 0.0 - 1.0) }
fn nd_world_yaw_t(i, t) {
bind yaw = nd_val(i, "ry", t)
bind p = nd_get(i, "par")
bind guard = 0.0
while p > 0.0 && guard < 64.0 {
bind pi = nd_find(p)
if pi < 0.0 { return yaw }
bind yaw = yaw + nd_val(pi, "ry", t)
bind p = nd_get(pi, "par")
bind guard = guard + 1.0
}
return yaw
}
fn nd_world_yaw(i) { return nd_world_yaw_t(i, 0.0 - 1.0) }
# world -> local (for setting a child's position from a world point)
fn nd_local_from_world(parent_id, x, y, z) {
if parent_id <= 0.0 { return [x, y, z] }
bind pi = nd_find(parent_id)
if pi < 0.0 { return [x, y, z] }
bind wp = nd_world_pos(pi)
bind yaw = nd_world_yaw(pi)
bind dx = x - list_get(wp, 0)
bind dy = y - list_get(wp, 1)
bind dz = z - list_get(wp, 2)
bind cy = cos(yaw)
bind sy = sin(yaw)
return [cy * dx - sy * dz, dy, sy * dx + cy * dz]
}
# ── keyframes ────────────────────────────────────────────────────────────────
fn key_count() { return st_llen(ns() + "k_node") }
fn key_get(k, col) { return st_lget(ns() + "k_" + col, k) }
fn key_set(k, col, v) { st_lset(ns() + "k_" + col, k, v) }
fn key_at(id, prop, t) {
bind n = key_count()
bind kn = ns() + "k_node"
bind kp = ns() + "k_prop"
bind kt = ns() + "k_t"
bind k = 0.0
while k < n {
if st_lget(kn, k) == id && st_lget(kp, k) == prop && abs(st_lget(kt, k) - t) < 0.0005 { return k }
bind k = k + 1.0
}
return 0.0 - 1.0
}
fn key_add(id, prop, t, v, e) {
bind k = key_at(id, prop, t)
if k >= 0.0 {
key_set(k, "v", v)
key_set(k, "e", e)
return k
}
st_lpush(ns() + "k_node", id)
st_lpush(ns() + "k_prop", prop)
st_lpush(ns() + "k_t", t)
st_lpush(ns() + "k_v", v)
st_lpush(ns() + "k_e", e)
return key_count() - 1.0
}
fn key_del(k) {
st_ldel(ns() + "k_node", k)
st_ldel(ns() + "k_prop", k)
st_ldel(ns() + "k_t", k)
st_ldel(ns() + "k_v", k)
st_ldel(ns() + "k_e", k)
}
fn key_remove_node(id) {
bind k = key_count() - 1.0
while k >= 0.0 {
if key_get(k, "node") == id { key_del(k) }
bind k = k - 1.0
}
}
fn key_remove_prop(id, prop) {
bind k = key_count() - 1.0
while k >= 0.0 {
if key_get(k, "node") == id && key_get(k, "prop") == prop { key_del(k) }
bind k = k - 1.0
}
}
fn key_has(id, prop) {
bind n = key_count()
bind kn = ns() + "k_node"
bind kp = ns() + "k_prop"
bind k = 0.0
while k < n {
if st_lget(kn, k) == id && st_lget(kp, k) == prop { return true }
bind k = k + 1.0
}
return false
}
fn key_node_has(id) {
bind n = key_count()
bind kn = ns() + "k_node"
bind k = 0.0
while k < n {
if st_lget(kn, k) == id { return true }
bind k = k + 1.0
}
return false
}
# indices of the keys of (id, prop), sorted by time
fn key_list(id, prop) {
bind out = []
bind n = key_count()
bind kn = ns() + "k_node"
bind kp = ns() + "k_prop"
bind kt = ns() + "k_t"
bind k = 0.0
while k < n {
if st_lget(kn, k) == id && st_lget(kp, k) == prop {
bind t = st_lget(kt, k)
bind pos = 0.0
while pos < len(out) && st_lget(kt, list_get(out, pos)) <= t { bind pos = pos + 1.0 }
bind out = l_insert_at(out, pos, k)
}
bind k = k + 1.0
}
return out
}
fn key_props_of(id) {
bind out = []
bind props = nd_props_anim()
bind p = 0.0
while p < len(props) {
if key_has(id, list_get(props, p)) { bind out = list_push(out, list_get(props, p)) }
bind p = p + 1.0
}
return out
}
# distinct key times of a node, sorted
fn key_times_of(id) {
bind out = []
bind n = key_count()
bind kn = ns() + "k_node"
bind kt = ns() + "k_t"
bind k = 0.0
while k < n {
if st_lget(kn, k) == id {
bind t = st_lget(kt, k)
bind found = false
bind pos = 0.0
bind go = pos < len(out)
while go {
bind v = list_get(out, pos)
if v <= t + 0.0005 {
if abs(v - t) < 0.0005 { bind found = true }
bind pos = pos + 1.0
bind go = pos < len(out)
} else { bind go = false }
}
if !found { bind out = l_insert_at(out, pos, t) }
}
bind k = k + 1.0
}
return out
}
fn key_eval(id, prop, t, d) {
bind ks = key_list(id, prop)
bind n = len(ks)
if n < 1.0 { return d }
bind kt = ns() + "k_t"
bind kv = ns() + "k_v"
bind first = list_get(ks, 0)
if t <= st_lget(kt, first) { return st_lget(kv, first) }
bind last = list_get(ks, n - 1.0)
if t >= st_lget(kt, last) { return st_lget(kv, last) }
bind i = 0.0
while i < n - 1.0 {
bind ka = list_get(ks, i)
bind kb = list_get(ks, i + 1.0)
bind ta = st_lget(kt, ka)
bind tb = st_lget(kt, kb)
if t >= ta && t <= tb {
bind u = sel(tb - ta > 0.00001, (t - ta) / (tb - ta), 1.0)
bind kind = key_get(kb, "e")
if prop == "rx" || prop == "ry" || prop == "rz" { return ease_angle(st_lget(kv, ka), st_lget(kv, kb), u, kind) }
return ease_lerp(st_lget(kv, ka), st_lget(kv, kb), u, kind)
}
bind i = i + 1.0
}
return st_lget(kv, last)
}
fn key_max_time() {
bind m = 0.0
bind n = key_count()
bind kt = ns() + "k_t"
bind k = 0.0
while k < n {
bind t = st_lget(kt, k)
if t > m { bind m = t }
bind k = k + 1.0
}
return m
}
# value of a node property at time t (animated when it has keys; t < 0 = the stored value)
fn nd_val(i, col, t) {
bind v = nd_get(i, col)
if t < 0.0 || key_count() < 1.0 { return v }
bind id = nd_get(i, "id")
if !key_has(id, col) { return v }
return key_eval(id, col, t, v)
}
# ── clips (cutscene / event tracks) ──────────────────────────────────────────
fn clip_count() { return st_llen(ns() + "c_track") }
fn clip_tracks() { return ["camera", "dialog", "anim", "audio", "fx", "event"] }
fn clip_add(track, t, d, kind, a, b, c, node, e) {
st_lpush(ns() + "c_track", track)
st_lpush(ns() + "c_t", t)
st_lpush(ns() + "c_d", d)
st_lpush(ns() + "c_kind", kind)
st_lpush(ns() + "c_a", a)
st_lpush(ns() + "c_b", b)
st_lpush(ns() + "c_c", c)
st_lpush(ns() + "c_node", node)
st_lpush(ns() + "c_e", e)
return clip_count() - 1.0
}
fn clip_del(i) {
st_ldel(ns() + "c_track", i)
st_ldel(ns() + "c_t", i)
st_ldel(ns() + "c_d", i)
st_ldel(ns() + "c_kind", i)
st_ldel(ns() + "c_a", i)
st_ldel(ns() + "c_b", i)
st_ldel(ns() + "c_c", i)
st_ldel(ns() + "c_node", i)
st_ldel(ns() + "c_e", i)
}
fn clip_get(i, col) { return st_lget(ns() + "c_" + col, i) }
fn clip_set(i, col, v) { st_lset(ns() + "c_" + col, i, v) }
fn clip_sorted(track) {
bind out = []
bind n = clip_count()
bind ct = ns() + "c_t"
bind ctr = ns() + "c_track"
bind i = 0.0
while i < n {
if track == "" || st_lget(ctr, i) == track {
bind t = st_lget(ct, i)
bind pos = 0.0
while pos < len(out) && st_lget(ct, list_get(out, pos)) <= t { bind pos = pos + 1.0 }
bind out = l_insert_at(out, pos, i)
}
bind i = i + 1.0
}
return out
}
fn clip_end_time() {
bind m = 0.0
bind n = clip_count()
bind i = 0.0
while i < n {
bind e = clip_get(i, "t") + clip_get(i, "d")
if e > m { bind m = e }
bind i = i + 1.0
}
return m
}
# the clip of a track active at time t (latest-starting one), or -1
fn clip_active(track, t) {
bind best = 0.0 - 1.0
bind bt = 0.0 - 1.0
bind n = clip_count()
bind ctr = ns() + "c_track"
bind ct = ns() + "c_t"
bind cd = ns() + "c_d"
bind i = 0.0
while i < n {
if st_lget(ctr, i) == track {
bind c0 = st_lget(ct, i)
if t >= c0 && t < c0 + max(0.001, st_lget(cd, i)) && c0 >= bt { bind best = i bind bt = c0 }
}
bind i = i + 1.0
}
return best
}
# ── serialization ────────────────────────────────────────────────────────────
fn sc_serialize() {
bind s = "lingdev-scene|v=1\n"
bind s = s + "scene" + fld("name", sc_get("name", "")) + fld("kind", sc_get("kind", "3d")) + fldn("bg", sc_get("bg", 0.0)) + fldn("fps", sc_get("fps", 30.0)) + fldn("len", sc_get("len", 10.0))
bind s = s + fldn("cw", sc_get("cw", 1280.0)) + fldn("ch", sc_get("ch", 720.0)) + fldn("amb", sc_get("amb", 0.25))
bind s = s + fldn("camx", sc_get("camx", 0.0)) + fldn("camy", sc_get("camy", 0.0)) + fldn("camz", sc_get("camz", 0.0)) + fldn("camyaw", sc_get("camyaw", 0.0)) + fldn("campitch", sc_get("campitch", 0.0)) + fldn("camzd", sc_get("camzd", 2.0))
bind s = s + fld("hud", sc_get("hud", "")) + fld("next", sc_get("next", "")) + fld("music", sc_get("music", "")) + fldn("grav", sc_get("grav", 9.0)) + fldn("floor", sc_get("floor", 0.0)) + fld("ex", sc_get("ex", "")) + fldn("nextid", sc_get("nextid", 1.0)) + "\n"
bind cols = nd_cols()
bind n = nd_count()
bind i = 0.0
while i < n {
bind line = "node"
bind c = 0.0
while c < len(cols) {
bind col = list_get(cols, c)
if nd_is_text(col) { bind line = line + fld(col, nd_get(i, col)) } else { bind line = line + fldn(col, nd_get(i, col)) }
bind c = c + 1.0
}
bind s = s + line + "\n"
bind i = i + 1.0
}
bind kn = key_count()
bind k = 0.0
while k < kn {
bind s = s + "key" + fldn("n", key_get(k, "node")) + fld("p", key_get(k, "prop")) + fldn("t", key_get(k, "t")) + fldn("v", key_get(k, "v")) + fld("e", key_get(k, "e")) + "\n"
bind k = k + 1.0
}
bind cn = clip_count()
bind c = 0.0
while c < cn {
bind s = s + "clip" + fld("tr", clip_get(c, "track")) + fldn("t", clip_get(c, "t")) + fldn("d", clip_get(c, "d")) + fld("k", clip_get(c, "kind")) + fld("a", clip_get(c, "a")) + fld("b", clip_get(c, "b")) + fld("c", clip_get(c, "c")) + fldn("n", clip_get(c, "node")) + fld("e", clip_get(c, "e")) + "\n"
bind c = c + 1.0
}
return s
}
fn sc_parse(text) {
sc_new("Untitled", "3d")
bind lines = str_split(text, "\n")
bind cols = nd_cols()
bind i = 0.0
while i < len(lines) {
bind ln = str_trim(list_get(lines, i))
if ln != "" {
bind f = str_split(ln, "|")
bind typ = list_get(f, 0)
if typ == "scene" {
sc_set("name", rec_get(f, "name", "Untitled"))
sc_set("kind", rec_get(f, "kind", "3d"))
sc_set("bg", rec_num(f, "bg", 526603.0))
sc_set("fps", rec_num(f, "fps", 30.0))
sc_set("len", rec_num(f, "len", 10.0))
sc_set("cw", rec_num(f, "cw", 1280.0))
sc_set("ch", rec_num(f, "ch", 720.0))
sc_set("amb", rec_num(f, "amb", 0.25))
sc_set("camx", rec_num(f, "camx", 0.0))
sc_set("camy", rec_num(f, "camy", 0.0 - 1.0))
sc_set("camz", rec_num(f, "camz", 0.0))
sc_set("camyaw", rec_num(f, "camyaw", 0.6))
sc_set("campitch", rec_num(f, "campitch", 0.42))
sc_set("camzd", rec_num(f, "camzd", 16.0))
sc_set("hud", rec_get(f, "hud", ""))
sc_set("next", rec_get(f, "next", ""))
sc_set("music", rec_get(f, "music", ""))
sc_set("grav", rec_num(f, "grav", 9.0))
sc_set("floor", rec_num(f, "floor", 0.0))
sc_set("ex", rec_get(f, "ex", ""))
sc_set("nextid", rec_num(f, "nextid", 1.0))
} else if typ == "node" {
bind c = 0.0
while c < len(cols) {
bind col = list_get(cols, c)
if nd_is_text(col) { st_lpush(nk(col), rec_get(f, col, "")) } else { st_lpush(nk(col), rec_num(f, col, sel(col == "vis" || col == "sx" || col == "sy" || col == "sz", 1.0, 0.0))) }
bind c = c + 1.0
}
} else if typ == "key" {
st_lpush(ns() + "k_node", rec_num(f, "n", 0.0))
st_lpush(ns() + "k_prop", rec_get(f, "p", "px"))
st_lpush(ns() + "k_t", rec_num(f, "t", 0.0))
st_lpush(ns() + "k_v", rec_num(f, "v", 0.0))
st_lpush(ns() + "k_e", rec_get(f, "e", "linear"))
} else if typ == "clip" {
st_lpush(ns() + "c_track", rec_get(f, "tr", "event"))
st_lpush(ns() + "c_t", rec_num(f, "t", 0.0))
st_lpush(ns() + "c_d", rec_num(f, "d", 1.0))
st_lpush(ns() + "c_kind", rec_get(f, "k", ""))
st_lpush(ns() + "c_a", rec_get(f, "a", ""))
st_lpush(ns() + "c_b", rec_get(f, "b", ""))
st_lpush(ns() + "c_c", rec_get(f, "c", ""))
st_lpush(ns() + "c_node", rec_num(f, "n", 0.0))
st_lpush(ns() + "c_e", rec_get(f, "e", "linear"))
}
}
bind i = i + 1.0
}
bind maxid = 0.0
bind n = nd_count()
bind j = 0.0
while j < n {
if nd_id(j) > maxid { bind maxid = nd_id(j) }
bind j = j + 1.0
}
if sc_get("nextid", 1.0) <= maxid { sc_set("nextid", maxid + 1.0) }
return nd_count()
}
fn sc_load(path) {
bind text = plat_read(path)
if text == "" { return false }
sc_parse(text)
sc_set("path", path)
return true
}
fn sc_save(path) {
bind ok = plat_write(path, sc_serialize())
if ok { sc_set("path", path) }
return ok
}
# a named value inside a node's `ex` field ("k=v;k=v")
fn nd_ex(i, key, d) {
bind parts = str_split(nd_get(i, "ex"), ";")
bind k = 0.0
while k < len(parts) {
bind p = list_get(parts, k)
if kv_key(p) == key { return kv_val(p) }
bind k = k + 1.0
}
return d
}
# set / replace one key in a node's `ex` field
fn nd_ex_set(i, key, v) {
bind parts = str_split(nd_get(i, "ex"), ";")
bind out = ""
bind done = false
bind k = 0.0
while k < len(parts) {
bind p = list_get(parts, k)
if str_trim(p) != "" {
if kv_key(p) == key {
if "" + v != "" { bind out = out + sel(out == "", "", ";") + key + "=" + v }
bind done = true
} else { bind out = out + sel(out == "", "", ";") + p }
}
bind k = k + 1.0
}
if !done && "" + v != "" { bind out = out + sel(out == "", "", ";") + key + "=" + v }
nd_set(i, "ex", out)
}
fn sc_ex_set(key, v) {
bind parts = str_split(sc_get("ex", ""), ";")
bind out = ""
bind done = false
bind k = 0.0
while k < len(parts) {
bind p = list_get(parts, k)
if str_trim(p) != "" {
if kv_key(p) == key {
if "" + v != "" { bind out = out + sel(out == "", "", ";") + key + "=" + v }
bind done = true
} else { bind out = out + sel(out == "", "", ";") + p }
}
bind k = k + 1.0
}
if !done && "" + v != "" { bind out = out + sel(out == "", "", ";") + key + "=" + v }
sc_set("ex", out)
}
# a named value inside the scene's `ex` field ("k=v;k=v")
fn sc_ex(key, d) {
bind parts = str_split(sc_get("ex", ""), ";")
bind i = 0.0
while i < len(parts) {
bind p = list_get(parts, i)
if kv_key(p) == key { return kv_val(p) }
bind i = i + 1.0
}
return d
}