灵符lingfu

lingdev

↓ 0 downloads

LingDev: 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).

Windows · x86-64Linux · x86-64LingOS · x86-64LingOS · Pi 5 ARM64Web · WASM
Keywords game-engine, editor, 3d, 2d, cutscene, timeline, ling, lingos, raspberry-pi
lingfu install lingdev
LicenseApache-2.0 OR MIT
Authorsling-lin
READMECode / FilesVersionsDependency GraphDependentsSecurityBuildsSettings

Code / Files (136)

ling/rt/rt_draw.ling
# rt_draw.ling - drawing a 3-D scene: with the software renderer (editor viewport, LingOS builds)
# or with the native renderer (desktop / web play mode). Both read the same node rows.

fn nd_colour(i) { return nd_get(i, "col") }

# model handles are cached per path in the store ("mdl:<path>")
fn model_handle(path) {
    if path == "" || !plat_has_models() { return 0.0 - 1.0 }
    bind key = "mdl:" + path
    if st_has(key) > 0.5 { return st_get(key, 0.0 - 1.0) }
    bind full = path
    if !p_is_abs(full) && st_get("proj_root", "") != "" { bind full = p_join(st_get("proj_root", ""), path) }
    bind h = plat_model_load(full)
    st_set(key, h)
    return h
}

# ── software path ────────────────────────────────────────────────────────────
# editor: gizmos + selection; solid: filled faces for primitives
fn draw_scene_sw(t, solid, editor) {
    bind n = nd_count()
    bind gizmos = st_get("view_gizmos", 1.0) > 0.5 || !editor
    # pass 1: solid faces of the primitives, flushed back to front
    if solid {
        sw_solid_begin()
        bind i = 0.0
        while i < n {
            bind kind = nd_get(i, "kind")
            if nd_get(i, "vis") > 0.5 && (kind == "box" || kind == "sphere" || kind == "cylinder" || kind == "cone" || kind == "capsule" || kind == "torus" || kind == "plane") {
                bind wp = nd_world_pos_t(i, t)
                sw_shape(kind, list_get(wp, 0), list_get(wp, 1), list_get(wp, 2), nd_val(i, "sx", t), nd_val(i, "sy", t), nd_val(i, "sz", t), nd_val(i, "rx", t), nd_world_yaw_t(i, t), nd_val(i, "rz", t), nd_colour(i), 1.0, true)
            }
            bind i = i + 1.0
        }
        sw_solid_flush()
    }
    # pass 2: wires, gizmos, labels
    bind i = 0.0
    while i < n {
        bind kind = nd_get(i, "kind")
        if nd_get(i, "vis") > 0.5 && !nd_is_2d(kind) {
            bind wp = nd_world_pos_t(i, t)
            bind x = list_get(wp, 0)
            bind y = list_get(wp, 1)
            bind z = list_get(wp, 2)
            bind rx = nd_val(i, "rx", t)
            bind ry = nd_world_yaw_t(i, t)
            bind rz = nd_val(i, "rz", t)
            bind sx = nd_val(i, "sx", t)
            bind sy = nd_val(i, "sy", t)
            bind sz = nd_val(i, "sz", t)
            bind c = nd_colour(i)
            bind selected = editor && rt_selected(nd_id(i))
            bind a = sel(selected, 1.0, 0.85)
            if kind == "box" || kind == "sphere" || kind == "cylinder" || kind == "cone" || kind == "capsule" || kind == "torus" || kind == "plane" {
                if !solid || selected || kind == "plane" { sw_shape(kind, x, y, z, sx, sy, sz, rx, ry, rz, sel(selected, C_GIZMO, c), sel(solid, 0.6, a), false) }
            } else if kind == "model" {
                sw_box(x, y - sy, z, sx, sy, sz, 0.0, ry, 0.0, sel(selected, C_GIZMO, c), 0.8)
                sw_line(x, y, z, x + sin(ry) * sx * 1.4, y, z + cos(ry) * sx * 1.4, c, 0.9)
                if editor { sw_label(x, y - sy * 2.0, z, 11.0, c, nd_get(i, "name")) }
            } else if kind == "actor" {
                if nd_ex(i, "shape", "") == "sphere" { sw_sphere(x, y + sy - sx, z, sx, sx, sx, 0.0, ry, 0.0, sel(selected, C_GIZMO, c), 0.95) }
                else { sw_actor_gizmo(x, y + sy, z, ry, sy * 2.0, sel(selected, C_GIZMO, c), 0.95) }
                if editor { sw_label(x, y - sy, z, 11.0, c, nd_get(i, "name")) }
            } else if kind == "text3d" {
                sw_label(x, y, z, 12.0 + nd_val(i, "a", t) * 8.0, c, nd_get(i, "txt"))
                if selected { sw_point(x, y, z, 3.0, C_GIZMO) }
            } else if gizmos {
                if kind == "light" { sw_light_gizmo(x, y, z, nd_val(i, "b", t), sel(selected, C_GIZMO, c), 0.9) }
                else if kind == "camera" { sw_camera_gizmo(x, y, z, ry, rx, sel(selected, C_GIZMO, c), 0.9) }
                else if kind == "spawn" { sw_spawn_gizmo(x, y, z, ry, sel(selected, C_GIZMO, c), 0.95) }
                else if kind == "trigger" {
                    sw_box(x, y, z, sx, sy, sz, 0.0, ry, 0.0, sel(selected, C_GIZMO, c), 0.8)
                    sw_line(x - sx, y + sy, z - sz, x + sx, y + sy, z + sz, c, 0.3)
                    sw_line(x + sx, y + sy, z - sz, x - sx, y + sy, z + sz, c, 0.3)
                } else if kind == "audio" || kind == "music" || kind == "midi" { sw_audio_gizmo(x, y, z, nd_val(i, "d", t) * 3.0, sel(selected, C_GIZMO, c), 0.9) }
                else if kind == "empty" {
                    sw_axes(x, y, z, 0.6)
                    if selected { sw_point(x, y, z, 3.0, C_GIZMO) }
                }
                if editor && (kind == "light" || kind == "camera" || kind == "spawn" || kind == "trigger" || kind == "audio" || kind == "music" || kind == "midi") { sw_label(x, y - sy * 0.5, z, 10.0, c, nd_get(i, "name")) }
            }
        }
        bind i = i + 1.0
    }
    draw_effects(sel(editor, st_get("now", t), t), editor, false)
}

# ── native path (desktop / web) ──────────────────────────────────────────────
fn draw_scene_native(t) {
    bind n = nd_count()
    plat_3d_begin(sc_get("amb", 0.25))
    plat_depth(to_number(sc_ex("depth", sel(sc_ex("mode", "") == "brick" || sc_ex("mode", "") == "soul" || sc_ex("mode", "") == "story", "1", "0"))))
    bind fog = nums_parse(sc_ex("fog", ""))
    if len(fog) >= 5.0 { plat_fog(list_get(fog, 0), list_get(fog, 1), list_get(fog, 2), list_get(fog, 3), list_get(fog, 4)) } else { plat_fog(0.0, 0.0, 0.0, 0.0, 0.0) }
    # shading: 0 flat, 1 cel (default), 2 holographic; the rim is the Fresnel edge glow (strong rims wash out big planes)
    plat_shade(to_number(sc_ex("shade", "1")), to_number(sc_ex("rim", "0.12")), c_parse(sc_ex("rimcol", "115,217,255")))
    bind lights = 0.0
    bind i = 0.0
    while i < n {
        if nd_get(i, "kind") == "light" && nd_get(i, "vis") > 0.5 {
            bind wp = nd_world_pos_t(i, t)
            bind c = nd_colour(i)
            plat_light(list_get(wp, 0), list_get(wp, 1), list_get(wp, 2), c_r(c), c_g(c), c_b(c), nd_val(i, "a", t), nd_val(i, "b", t))
            bind lights = lights + 1.0
        }
        bind i = i + 1.0
    }
    if lights < 0.5 { plat_light(4.0, 0.0 - 8.0, 0.0 - 4.0, 255.0, 245.0, 230.0, 2.5, 30.0) }
    fx_lights_apply()
    brick_lights()
    story_lights()
    cam_apply_native()
    soul_world_native()
    bind i = 0.0
    while i < n {
        bind kind = nd_get(i, "kind")
        bind fps_skip = fps_on() && (kind == "actor" || nd_get(i, "tag") == "pickup")
        if brick_on() && (nd_get(i, "tag") == "brick" || nd_get(i, "tag") == "paddle") { bind fps_skip = true }
        if soul_on() && (kind == "actor" || nd_get(i, "tag") == "level") { bind fps_skip = true }
        if story_on() && l_has(["fruit", "puff", "player", "eggblock", "spring", "flower", "tree", "cloudface"], nd_get(i, "tag")) { bind fps_skip = true }
        if story_on() && abs(nd_get(i, "px") - st_get("sy_cam_x", 0.0)) > 22.0 + nd_get(i, "sx") + abs(nd_get(i, "pz")) * 0.6 { bind fps_skip = true }
        if nd_get(i, "vis") > 0.5 && !nd_is_2d(kind) && !fps_skip {
            bind wp = nd_world_pos_t(i, t)
            bind x = list_get(wp, 0)
            bind y = list_get(wp, 1)
            bind z = list_get(wp, 2)
            bind ry = nd_world_yaw_t(i, t)
            bind sx = nd_val(i, "sx", t)
            bind sy = nd_val(i, "sy", t)
            bind sz = nd_val(i, "sz", t)
            if kind == "box" || kind == "sphere" || kind == "cylinder" || kind == "cone" || kind == "capsule" || kind == "torus" || kind == "plane" {
                plat_color(nd_colour(i))
                plat_shape(kind, x, y, z, sx, sy, sz, nd_val(i, "rx", t), ry, nd_val(i, "rz", t), 0.0)
            } else if kind == "model" || kind == "actor" {
                bind h = model_handle(nd_get(i, "src"))
                if h >= 0.0 {
                    bind hue = to_number(nd_ex(i, "hue", "0"))
                    if hue != 0.0 { plat_mesh_hue(t * hue + i * 1.257, to_number(nd_ex(i, "gain", "1.1"))) }
                    plat_model_draw(h, x, y + sel(kind == "actor", sy, 0.0), z, sx, ry)
                    if hue != 0.0 { plat_mesh_hue(0.0, 1.0) }
                } else {
                    plat_color(nd_colour(i))
                    if kind == "actor" && nd_ex(i, "shape", "") == "sphere" { plat_shape("sphere", x, y + sy - sx, z, sx, sx, sx, 0.0, ry, 0.0, 0.0) }
                    else { plat_shape(sel(kind == "actor", "capsule", "box"), x, y, z, sel(kind == "actor", 0.45, sx), sy, sel(kind == "actor", 0.45, sz), 0.0, ry, 0.0, 0.0) }
                }
            } else if kind == "knot" {
                draw_knot_native(i, x, y, z, t)
            } else if kind == "swarm" && nd_get(i, "c") == 2.0 {
                draw_swarm_native(i, x, y, z, t)
            }
        }
        bind i = i + 1.0
    }
    if fps_on() { fps_world_native() }
    brick_world_native()
    story_world_native()
    brick_deco_native()
    plat_3d_flush()
    # labels / text3d on top
    bind i = 0.0
    while i < n {
        if nd_get(i, "kind") == "text3d" && nd_get(i, "vis") > 0.5 {
            bind wp = nd_world_pos_t(i, t)
            sw_label(list_get(wp, 0), list_get(wp, 1), list_get(wp, 2), 12.0 + nd_val(i, "a", t) * 8.0, nd_colour(i), nd_get(i, "txt"))
        }
        bind i = i + 1.0
    }
}

# the scene the way the player sees it: native when the runtime has it, software otherwise
fn draw_scene_game(t) {
    if plat_has_3d() && st_get("force_sw", 0.0) < 0.5 { draw_scene_native(t)  draw_effects(st_get("game_t", t), false, true) }
    else { draw_scene_sw(t, true, false)  draw_effects(st_get("game_t", t), false, false) }
    draw_midi_notes(st_get("game_scene_t", t))
}

# ── effect nodes: swarms (stars / wire cubes / orbs) and knotwork chains ─────
fn fx_hash(k, salt) {
    bind v = sin(k * 12.9898 + salt * 78.233) * 43758.5453
    return v - floor(v)
}
fn fx_hue(h, br) {
    return c_rgb((sin(h) * 0.5 + 0.5) * 205.0 * br + 15.0, (sin(h + 2.094) * 0.5 + 0.5) * 205.0 * br + 15.0, (sin(h + 4.189) * 0.5 + 0.5) * 205.0 * br + 15.0)
}
# drawn after the scene (software primitives over either renderer). native_solid: solid parts were already drawn natively
fn draw_effects(t, editor, native_solid) {
    bind n = nd_count()
    bind i = 0.0
    while i < n {
        bind kind = nd_get(i, "kind")
        if nd_get(i, "vis") > 0.5 && (kind == "swarm" || kind == "knot") {
            bind wp = nd_world_pos_t(i, t)
            bind x = list_get(wp, 0)
            bind y = list_get(wp, 1)
            bind z = list_get(wp, 2)
            if kind == "swarm" { draw_swarm_sw(i, x, y, z, t, native_solid) }
            else if !native_solid { draw_knot_sw(i, x, y, z, t) }
            if editor && rt_selected(nd_id(i)) { sw_sphere(x, y, z, nd_get(i, "b"), nd_get(i, "b"), nd_get(i, "b"), 0.0, 0.0, 0.0, C_GIZMO, 0.35) }
        }
        bind i = i + 1.0
    }
}
fn draw_swarm_sw(i, x, y, z, t, native_solid) {
    bind cnt = min(400.0, nd_get(i, "a"))
    bind rad = nd_get(i, "b")
    bind shape = nd_get(i, "c")
    bind drift = nd_get(i, "d")
    bind size = nd_get(i, "sx")
    bind col = nd_colour(i)
    bind rainbow = nd_ex(i, "rainbow", "0") == "1"
    bind flat = nd_ex(i, "flat", "0") == "1"
    bind id = nd_id(i)
    bind k = 0.0
    while k < cnt {
        bind px = x + (fx_hash(k, id) - 0.5) * 2.0 * rad + sin(t * drift + k) * 0.4
        bind py = y + (fx_hash(k + 100.0, id) - 0.5) * 2.0 * rad * sel(flat, 0.05, 1.0) + cos(t * drift * 0.7 + k * 1.3) * 0.3
        bind pz = z + (fx_hash(k + 200.0, id) - 0.5) * 2.0 * rad + sin(t * drift * 0.5 + k * 0.7) * 0.4
        bind c = col
        if rainbow { bind c = fx_hue(fx_hash(k + 300.0, id) * 6.2832 + t * 0.3, 1.0) }
        if shape < 0.5 {
            sw_box(px, py, pz, size, size, size, t * 0.2 + k, t * 0.15 + k * 0.5, 0.0, c, 0.8)
        } else if shape < 1.5 {
            bind p = cam_project(px, py, pz)
            if cam_visible(list_get(p, 2)) {
                bind tw = 0.55 + 0.45 * sin(t * 2.0 + k * 2.1)
                bind r = max(1.0, size * cam_scale_at(list_get(p, 2)))
                px_disc(list_get(p, 0), list_get(p, 1), r, c, tw)
            }
        } else if !native_solid {
            sw_sphere(px, py, pz, size, size, size, 0.0, 0.0, 0.0, c, 0.8)
        }
        bind k = k + 1.0
    }
}
fn draw_swarm_native(i, x, y, z, t) {
    bind cnt = min(64.0, nd_get(i, "a"))
    bind rad = nd_get(i, "b")
    bind drift = nd_get(i, "d")
    bind size = nd_get(i, "sx")
    bind col = nd_colour(i)
    bind rainbow = nd_ex(i, "rainbow", "0") == "1"
    bind flat = nd_ex(i, "flat", "0") == "1"
    bind id = nd_id(i)
    bind k = 0.0
    while k < cnt {
        bind px = x + (fx_hash(k, id) - 0.5) * 2.0 * rad + sin(t * drift + k) * 0.4
        bind py = y + (fx_hash(k + 100.0, id) - 0.5) * 2.0 * rad * sel(flat, 0.05, 1.0) + cos(t * drift * 0.7 + k * 1.3) * 0.3
        bind pz = z + (fx_hash(k + 200.0, id) - 0.5) * 2.0 * rad + sin(t * drift * 0.5 + k * 0.7) * 0.4
        bind c = col
        if rainbow { bind c = fx_hue(fx_hash(k + 300.0, id) * 6.2832 + t * 0.3, 1.0) }
        plat_color(c)
        plat_shape("sphere", px, py, pz, size, size, size, 0.0, 0.0, 0.0, 0.0)
        bind k = k + 1.0
    }
}
# the rosette: x = b cos s + c cos 5s, y = b sin s - c sin 5s, z = (c/1.1) sin 10s, hue along the chain
fn knot_pos(i, k, t) {
    bind cnt = max(1.0, nd_get(i, "a"))
    bind b = nd_get(i, "b")
    bind c = nd_get(i, "c")
    bind f = t * nd_get(i, "d") + k / cnt
    bind f = f - floor(f)
    bind s = 6.2832 * f
    return [b * cos(s) + c * cos(5.0 * s), b * sin(s) - c * sin(5.0 * s), c / 1.1 * sin(10.0 * s), f]
}
fn knot_col(i, f, t) {
    if nd_ex(i, "rainbow", "1") != "1" { return nd_colour(i) }
    bind br = 0.55 + 0.45 * sin(f * 15.7 + t * 2.0)
    return fx_hue(f * 6.2832 + t, br)
}
fn draw_knot_sw(i, x, y, z, t) {
    bind cnt = min(120.0, nd_get(i, "a"))
    bind size = nd_get(i, "sx")
    bind k = 0.0
    while k < cnt {
        bind p = knot_pos(i, k, t)
        bind f = list_get(p, 3)
        bind sz = size * (0.8 + 0.5 * (sin(f * 12.566 + t * 3.0) * 0.5 + 0.5))
        sw_box(x + list_get(p, 0), y + list_get(p, 1), z + list_get(p, 2), sz, sz, sz, 0.0, t * 0.4 + k, 0.0, knot_col(i, f, t), 0.9)
        bind k = k + 1.0
    }
}
fn draw_knot_native(i, x, y, z, t) {
    bind cnt = min(120.0, nd_get(i, "a"))
    bind size = nd_get(i, "sx")
    bind k = 0.0
    while k < cnt {
        bind p = knot_pos(i, k, t)
        bind f = list_get(p, 3)
        bind sz = size * (0.8 + 0.5 * (sin(f * 12.566 + t * 3.0) * 0.5 + 0.5))
        plat_color(knot_col(i, f, t))
        plat_shape("box", x + list_get(p, 0), y + list_get(p, 1), z + list_get(p, 2), sz, sz, sz, 0.0, t * 0.4 + k, 0.0, 0.0)
        bind k = k + 1.0
    }
}

# MIDI nodes with "visualise" on: upcoming notes as a ribbon of bars flowing toward the node (play mode)
fn draw_midi_notes(t) {
    bind n = nd_count()
    bind i = 0.0
    while i < n {
        if nd_get(i, "kind") == "midi" && nd_get(i, "b") > 0.5 && nd_get(i, "vis") > 0.5 {
            bind notes = st_get("midin_" + num_str(nd_id(i)), [])
            bind cnt = floor(len(notes) / 3.0)
            if cnt > 0.0 {
                bind wp = nd_world_pos(i)
                bind ox = list_get(wp, 0)
                bind oy = list_get(wp, 1)
                bind oz = list_get(wp, 2)
                bind col = nd_colour(i)
                bind k = 0.0
                bind drawn = 0.0
                while k < cnt && drawn < 64.0 {
                    bind nt = list_get(notes, k * 3.0)
                    bind pitch = list_get(notes, k * 3.0 + 1.0)
                    bind dur = list_get(notes, k * 3.0 + 2.0)
                    bind dt = nt - t
                    if dt > 0.0 - dur && dt < 6.0 {
                        bind z = oz + dt * 3.0
                        bind x = ox + (pitch - 60.0) * 0.18
                        bind lit = dt <= 0.0
                        sw_line(x, oy, z, x, oy, z + max(0.2, dur * 3.0), sel(lit, C_WHITE, col), sel(lit, 1.0, 0.7))
                        sw_line(x - 0.08, oy, z, x + 0.08, oy, z, sel(lit, C_WHITE, col), 1.0)
                        bind drawn = drawn + 1.0
                    }
                    if dt > 6.0 { bind k = cnt }
                    bind k = k + 1.0
                }
                sw_line(ox - 6.0, oy, oz, ox + 6.0, oy, oz, col, 0.6)
            }
        }
        bind i = i + 1.0
    }
}

← Back to index