Merge branch 'lang/foundations' into lang/uifree
This commit is contained in:
commit
5d9cbddb72
43 changed files with 495 additions and 118 deletions
10
changes/model-release.md
Normal file
10
changes/model-release.md
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
bump: minor
|
||||||
|
type: feature
|
||||||
|
**A loaded model can be let go of: `model_release(model)`.** Its meshes are freed at once, and
|
||||||
|
each texture once no other model uses it - the glTF loader shares textures between models (one
|
||||||
|
path loaded once, a LOD chain borrowing its LOD0's material), so every cached texture now counts
|
||||||
|
the primitives using it, and the last one frees it and forgets it, with any remembered material
|
||||||
|
that named it. A game can unload a species' kit or a map's models when nothing of them is left,
|
||||||
|
and loading one again afterwards is a fresh load. A scatter layer takes room for what it holds
|
||||||
|
instead of its whole capacity up front (it doubles as it fills, to its cap), which in Maroon Lake
|
||||||
|
was 0.4 GB of never-filled instance arrays. `examples/rendering/release.ludic` is the check.
|
||||||
69
examples/rendering/release.ludic
Normal file
69
examples/rendering/release.ludic
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
# release.ludic - a loaded model let go of (model_release): its meshes go at once, a texture it
|
||||||
|
# shares with another model stays until that one goes too, and a model loaded again afterwards is
|
||||||
|
# whole. Prints RELEASE OK.
|
||||||
|
#
|
||||||
|
# bin/ludic build examples/rendering/release.ludic --headless && ./build/release_headless
|
||||||
|
program Release {
|
||||||
|
numbers float
|
||||||
|
import "ludic.render3d/r3d.ludic"
|
||||||
|
|
||||||
|
property Marker { on: int = 1 }
|
||||||
|
model Anchor { Marker }
|
||||||
|
|
||||||
|
function scene_draw(render3d_st: mut Render3dState) -> void { }
|
||||||
|
function scene_draw_casters(render3d_st: mut Render3dState, light_vp: floats) -> void { }
|
||||||
|
function stream_fill(s: Stream, cx: int, cz: int, band: int) -> void { }
|
||||||
|
|
||||||
|
function check(what: string, cond: bool) -> bool {
|
||||||
|
if not cond { print(`release: FAILED - {what}`) }
|
||||||
|
return cond
|
||||||
|
}
|
||||||
|
# whether handle `tex` is still a texture the renderer knows (its recorded kind is set)
|
||||||
|
function tex_live(render3d_st: mut Render3dState, tex: int) -> bool {
|
||||||
|
let o = gpu_tx_at(render3d_st, tex)
|
||||||
|
return tex != 0 and o >= 0 and render3d_st.gpu_tx[o] != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
handler Boot(render3d_st: mut Render3dState) phase Start {
|
||||||
|
spawn Anchor {}
|
||||||
|
r3d_on_draw(render3d_st, fn scene_draw)
|
||||||
|
r3d_on_casters(render3d_st, fn scene_draw_casters)
|
||||||
|
r3d_on_stream_fill(render3d_st, fn stream_fill)
|
||||||
|
r3d_plate_mode(render3d_st, true)
|
||||||
|
let dir = "packages/ludic.lab/plate"
|
||||||
|
render3d_st.r3d_sky_path = `{dir}/sky.hdr`
|
||||||
|
if not r3d_init(render3d_st, 320, 180, "Release") {
|
||||||
|
quit()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var ok = true
|
||||||
|
# two models of the same file share its two textures
|
||||||
|
let a = gltf_load(render3d_st, dir, "plate.gltf", "plate")
|
||||||
|
let b = gltf_load(render3d_st, dir, "plate.gltf", "plate")
|
||||||
|
ok = check("both plates load", a != null and b != null and len(a.prims) > 0) and ok
|
||||||
|
let diff = a.prims[0].diff
|
||||||
|
ok = check("the plates share their textures", diff != 0 and b.prims[0].diff == diff and render3d_st.gltf_tex_n == 2) and ok
|
||||||
|
model_release(render3d_st, a)
|
||||||
|
ok = check("the first plate is emptied", len(a.prims) == 0) and ok
|
||||||
|
ok = check("what the second still uses stays", tex_live(render3d_st, diff) and render3d_st.gltf_tex_n == 2) and ok
|
||||||
|
model_release(render3d_st, b)
|
||||||
|
ok = check("the last user frees the textures", not tex_live(render3d_st, diff) and render3d_st.gltf_tex_n == 0) and ok
|
||||||
|
ok = check("and its material is forgotten", render3d_st.gltf_mat_n == 0) and ok
|
||||||
|
# one model whose two materials share a texture
|
||||||
|
let p = gltf_load(render3d_st, dir, "probe.gltf", "probe")
|
||||||
|
ok = check("the probe loads its three textures", p != null and render3d_st.gltf_tex_n == 3) and ok
|
||||||
|
model_release(render3d_st, p)
|
||||||
|
ok = check("the probe gives all three back", render3d_st.gltf_tex_n == 0 and render3d_st.gltf_mat_n == 0) and ok
|
||||||
|
# and loading again after a release is a fresh load, whole
|
||||||
|
let c = gltf_load(render3d_st, dir, "plate.gltf", "plate")
|
||||||
|
ok = check("a plate loaded again is whole", c != null and len(c.prims) > 0 and tex_live(render3d_st, c.prims[0].diff) and render3d_st.gltf_tex_n == 2) and ok
|
||||||
|
model_release(render3d_st, c)
|
||||||
|
ok = check("and goes again", render3d_st.gltf_tex_n == 0) and ok
|
||||||
|
if ok { print("RELEASE OK") } else { print("RELEASE FAILED") }
|
||||||
|
quit()
|
||||||
|
}
|
||||||
|
|
||||||
|
handler Present(render3d_st: mut Render3dState) phase Render {
|
||||||
|
r3d_present(render3d_st)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,11 @@ import "ludic.base"
|
||||||
import "ludic.physics"
|
import "ludic.physics"
|
||||||
program CharacterTest {
|
program CharacterTest {
|
||||||
numbers float
|
numbers float
|
||||||
|
function wpose(physics_st: mut PhysicsState, id: int) -> PhysWalk {
|
||||||
|
let p = new PhysWalk
|
||||||
|
if not phys_walker_pose(physics_st, id, p) { return null }
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
# the world: x in 4..8 is a deck 45 cm up; z < -10 a cliff; z > 10 a gentle rise; x > 20 the lake
|
# the world: x in 4..8 is a deck 45 cm up; z < -10 a cliff; z > 10 a gentle rise; x > 20 the lake
|
||||||
function fk_height(x: float, z: float) -> float {
|
function fk_height(x: float, z: float) -> float {
|
||||||
|
|
@ -87,7 +92,7 @@ program CharacterTest {
|
||||||
function fk_walk(physics_st: mut PhysicsState, character_test_st: mut CharacterTestState, x: float, y: float, z: float, vx: float, vz: float, jump: float, step: float, slope: float, dt: float) -> int {
|
function fk_walk(physics_st: mut PhysicsState, character_test_st: mut CharacterTestState, x: float, y: float, z: float, vx: float, vz: float, jump: float, step: float, slope: float, dt: float) -> int {
|
||||||
if character_test_st.walker < 0 { character_test_st.walker = fk_jolt(physics_st) }
|
if character_test_st.walker < 0 { character_test_st.walker = fk_jolt(physics_st) }
|
||||||
let g = phys_walker_move(physics_st, character_test_st.walker, x, y, z, vx, vz, jump, step, slope, dt)
|
let g = phys_walker_move(physics_st, character_test_st.walker, x, y, z, vx, vz, jump, step, slope, dt)
|
||||||
let p = phys_walker_pose(physics_st, character_test_st.walker)
|
let p = wpose(physics_st, character_test_st.walker)
|
||||||
character_test_st.wx = p.x
|
character_test_st.wx = p.x
|
||||||
character_test_st.wy = p.y
|
character_test_st.wy = p.y
|
||||||
character_test_st.wz = p.z
|
character_test_st.wz = p.z
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ import "ludic.nav"
|
||||||
| `nav_next_corner(kind, filter, sx, sy, sz, ex, ey, ez) -> bool` | the next corner of that way, read back as the nearest point is (a walker's one question a frame) |
|
| `nav_next_corner(kind, filter, sx, sy, sz, ex, ey, ez) -> bool` | the next corner of that way, read back as the nearest point is (a walker's one question a frame) |
|
||||||
| `nav_straight(kind, filter, sx, sy, sz, ex, ez) -> bool` | does the straight line stay walkable |
|
| `nav_straight(kind, filter, sx, sy, sz, ex, ez) -> bool` | does the straight line stay walkable |
|
||||||
| `nav_random_near(kind, filter, x, y, z, r, seed) -> bool`, `nav_near_x/y/z()` | a reachable point about r away, from the caller's seed |
|
| `nav_random_near(kind, filter, x, y, z, r, seed) -> bool`, `nav_near_x/y/z()` | a reachable point about r away, from the caller's seed |
|
||||||
| `nav_asked()`, `nav_found()`, `nav_short()` | paths asked for, found and cut short since the meshes loaded (a game's measure of how often its walkers had a way) |
|
| `nav_asked()`, `nav_found()`, `nav_short()`, `nav_us()` | paths asked for, found and cut short since the meshes loaded, and the microseconds they took (timed in the shim by the OS clock: a game's measure of how often its walkers had a way, and what it cost) |
|
||||||
| `nav_area_cost(kind, filter, area, cost)` | how dear a kind of ground is to cross by that filter |
|
| `nav_area_cost(kind, filter, area, cost)` | how dear a kind of ground is to cross by that filter |
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
|
||||||
BIN
packages/ludic.nav/lib/macos-arm64/libludicnav.dylib
(Stored with Git LFS)
BIN
packages/ludic.nav/lib/macos-arm64/libludicnav.dylib
(Stored with Git LFS)
Binary file not shown.
BIN
packages/ludic.nav/lib/windows-x64/ludicnav.dll
(Stored with Git LFS)
BIN
packages/ludic.nav/lib/windows-x64/ludicnav.dll
(Stored with Git LFS)
Binary file not shown.
BIN
packages/ludic.nav/lib/windows-x64/ludicnav.lib
(Stored with Git LFS)
BIN
packages/ludic.nav/lib/windows-x64/ludicnav.lib
(Stored with Git LFS)
Binary file not shown.
|
|
@ -11,6 +11,7 @@ extern function nvc_nearest(h: pointer, x: float, y: float, z: float, out: point
|
||||||
extern function nvc_area_cost(h: pointer, f: int, area: int, cost: float) -> void = "nav_area_cost"
|
extern function nvc_area_cost(h: pointer, f: int, area: int, cost: float) -> void = "nav_area_cost"
|
||||||
extern function nvc_path(h: pointer, f: int, sx: float, sy: float, sz: float, ex: float, ey: float, ez: float, out: pointer, max: int) -> int = "nav_path"
|
extern function nvc_path(h: pointer, f: int, sx: float, sy: float, sz: float, ex: float, ey: float, ez: float, out: pointer, max: int) -> int = "nav_path"
|
||||||
extern function nvc_partial(h: pointer) -> int = "nav_partial"
|
extern function nvc_partial(h: pointer) -> int = "nav_partial"
|
||||||
|
extern function nvc_us(h: pointer) -> int = "nav_us"
|
||||||
extern function nvc_raycast(h: pointer, f: int, sx: float, sy: float, sz: float, ex: float, ez: float) -> int = "nav_raycast"
|
extern function nvc_raycast(h: pointer, f: int, sx: float, sy: float, sz: float, ex: float, ez: float) -> int = "nav_raycast"
|
||||||
extern function nvc_random_near(h: pointer, f: int, x: float, y: float, z: float, r: float, seed: int, out: pointer) -> int = "nav_random_near"
|
extern function nvc_random_near(h: pointer, f: int, x: float, y: float, z: float, r: float, seed: int, out: pointer) -> int = "nav_random_near"
|
||||||
extern function nvc_stage(size: int) -> pointer = "nav_stage"
|
extern function nvc_stage(size: int) -> pointer = "nav_stage"
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,7 @@ NAV_SHIM void nav_area_cost(void *h, int f, int area, float cost) {
|
||||||
// the corners of a path from one point to another, at most max of them, into out: the count, or
|
// the corners of a path from one point to another, at most max of them, into out: the count, or
|
||||||
// -1 when either end is off the mesh. One that cannot reach the end stops as near it as the mesh
|
// -1 when either end is off the mesh. One that cannot reach the end stops as near it as the mesh
|
||||||
// allows, and nav_partial says so
|
// allows, and nav_partial says so
|
||||||
NAV_SHIM int nav_path(void *h, int f, float sx, float sy, float sz, float ex, float ey, float ez, float *out, int max) {
|
static int nav_path_in(Nav *n, int f, float sx, float sy, float sz, float ex, float ey, float ez, float *out, int max) {
|
||||||
Nav *n = static_cast<Nav *>(h);
|
|
||||||
float s[3] = {sx, sy, sz}, e[3] = {ex, ey, ez}, so[3], eo[3];
|
float s[3] = {sx, sy, sz}, e[3] = {ex, ey, ez}, so[3], eo[3];
|
||||||
dtPolyRef a = nav_poly_at(n, s, so), b = nav_poly_at(n, e, eo);
|
dtPolyRef a = nav_poly_at(n, s, so), b = nav_poly_at(n, e, eo);
|
||||||
n->partial = 0;
|
n->partial = 0;
|
||||||
|
|
@ -39,6 +38,15 @@ NAV_SHIM int nav_path(void *h, int f, float sx, float sy, float sz, float ex, fl
|
||||||
if (dtStatusFailed(n->query->findStraightPath(so, end, n->polys, np, out, nullptr, nullptr, &count, max, 0))) return -1;
|
if (dtStatusFailed(n->query->findStraightPath(so, end, n->polys, np, out, nullptr, nullptr, &count, max, 0))) return -1;
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
NAV_SHIM int nav_path(void *h, int f, float sx, float sy, float sz, float ex, float ey, float ez, float *out, int max) {
|
||||||
|
Nav *n = static_cast<Nav *>(h);
|
||||||
|
long long t0 = nav_now_ns();
|
||||||
|
int r = nav_path_in(n, f, sx, sy, sz, ex, ey, ez, out, max);
|
||||||
|
n->ns += nav_now_ns() - t0;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
// microseconds this mesh has spent finding paths
|
||||||
|
NAV_SHIM int nav_us(void *h) { return static_cast<int>(static_cast<Nav *>(h)->ns / 1000); }
|
||||||
NAV_SHIM int nav_partial(void *h) { return static_cast<Nav *>(h)->partial; }
|
NAV_SHIM int nav_partial(void *h) { return static_cast<Nav *>(h)->partial; }
|
||||||
|
|
||||||
// does the straight line from one point to another stay on the mesh? 1 yes, 0 no, -1 off it
|
// does the straight line from one point to another stay on the mesh? 1 yes, 0 no, -1 off it
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,27 @@
|
||||||
#define NAV_SHIM extern "C" __attribute__((visibility("default")))
|
#define NAV_SHIM extern "C" __attribute__((visibility("default")))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
extern "C" __declspec(dllimport) int __stdcall QueryPerformanceCounter(long long *);
|
||||||
|
extern "C" __declspec(dllimport) int __stdcall QueryPerformanceFrequency(long long *);
|
||||||
|
#else
|
||||||
|
#include <time.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
// a monotonic clock in nanoseconds, from the OS alone (KERNEL32 on Windows: no C++ runtime to ship)
|
||||||
|
long long nav_now_ns() {
|
||||||
|
#ifdef _WIN32
|
||||||
|
long long c = 0, f = 1;
|
||||||
|
QueryPerformanceCounter(&c);
|
||||||
|
QueryPerformanceFrequency(&f);
|
||||||
|
return (long long)((double)c * 1e9 / (double)f);
|
||||||
|
#else
|
||||||
|
struct timespec t;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &t);
|
||||||
|
return (long long)t.tv_sec * 1000000000LL + t.tv_nsec;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
const int NAV_MAX_POLYS = 512; // polygons a path may cross; a longer one is cut there
|
const int NAV_MAX_POLYS = 512; // polygons a path may cross; a longer one is cut there
|
||||||
const int NAV_MAX_NODES = 4096; // the search's open list
|
const int NAV_MAX_NODES = 4096; // the search's open list
|
||||||
const int NAV_FILTERS = 8; // costs per kind of ground: one set per kind of walker's taste
|
const int NAV_FILTERS = 8; // costs per kind of ground: one set per kind of walker's taste
|
||||||
|
|
@ -30,6 +50,7 @@ struct Nav {
|
||||||
float ext[3] = {2.0f, 4.0f, 2.0f}; // how far to look for the mesh around a point
|
float ext[3] = {2.0f, 4.0f, 2.0f}; // how far to look for the mesh around a point
|
||||||
dtPolyRef polys[NAV_MAX_POLYS];
|
dtPolyRef polys[NAV_MAX_POLYS];
|
||||||
int partial = 0; // the last path stopped short of its end
|
int partial = 0; // the last path stopped short of its end
|
||||||
|
long long ns = 0; // time spent finding paths, for the measure (17.10)
|
||||||
};
|
};
|
||||||
|
|
||||||
// a navmesh with its query, from params (a set of tiles) or from one tile's data
|
// a navmesh with its query, from params (a set of tiles) or from one tile's data
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,15 @@ export function nav_path(nav_st: mut NavState, kind: int, filter: int, sx: float
|
||||||
export function nav_asked(nav_st: NavState) -> int { return nav_st.nv_asked }
|
export function nav_asked(nav_st: NavState) -> int { return nav_st.nv_asked }
|
||||||
export function nav_found(nav_st: NavState) -> int { return nav_st.nv_found }
|
export function nav_found(nav_st: NavState) -> int { return nav_st.nv_found }
|
||||||
export function nav_short(nav_st: NavState) -> int { return nav_st.nv_short }
|
export function nav_short(nav_st: NavState) -> int { return nav_st.nv_short }
|
||||||
|
# microseconds spent finding those paths, over every loaded mesh
|
||||||
|
export function nav_us(nav_st: NavState) -> int {
|
||||||
|
var us = 0
|
||||||
|
for k in 0 .. NAV_KINDS {
|
||||||
|
let h = nv_mesh(nav_st, k)
|
||||||
|
if h != null { us += nvc_us(h) }
|
||||||
|
}
|
||||||
|
return us
|
||||||
|
}
|
||||||
export function nav_partial(nav_st: NavState, kind: int) -> bool {
|
export function nav_partial(nav_st: NavState, kind: int) -> bool {
|
||||||
let h = nv_mesh(nav_st, kind)
|
let h = nv_mesh(nav_st, kind)
|
||||||
return h != null and nvc_partial(h) == 1
|
return h != null and nvc_partial(h) == 1
|
||||||
|
|
|
||||||
|
|
@ -124,4 +124,14 @@ program NavTest {
|
||||||
nav_reset(nav_st)
|
nav_reset(nav_st)
|
||||||
expect_eq(nav_polygons(nav_st, NAV_SMALL), 0)
|
expect_eq(nav_polygons(nav_st, NAV_SMALL), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "the time paths take is counted, and only while a mesh is loaded" (nav_st: mut NavState) {
|
||||||
|
expect_eq(nav_us(nav_st), 0)
|
||||||
|
expect(meadow(nav_st, NAV_PERSON, 0, true))
|
||||||
|
for i in 0 .. 2000 { nav_path(nav_st, NAV_PERSON, 0, 5.0, 0.0, 20.0, 35.0, 0.0, 20.0) }
|
||||||
|
expect(nav_us(nav_st) > 0)
|
||||||
|
expect(nav_us(nav_st) < 2000000)
|
||||||
|
nav_reset(nav_st)
|
||||||
|
expect_eq(nav_us(nav_st), 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ by a person's taste.
|
||||||
| `npc_pick_name(body)`, `npc_name_used(n)`, `npc_name_clashes()` | names |
|
| `npc_pick_name(body)`, `npc_name_used(n)`, `npc_name_clashes()` | names |
|
||||||
| `npc_talk(p, player)`, `npc_line_for(p)`, `npc_words(p, line)`, `npc_said()`, `npc_choose(p, line, c, player)`, `npc_talked_today(p)`, `npc_greeting(p)` | talk |
|
| `npc_talk(p, player)`, `npc_line_for(p)`, `npc_words(p, line)`, `npc_said()`, `npc_choose(p, line, c, player)`, `npc_talked_today(p)`, `npc_greeting(p)` | talk |
|
||||||
| `npc_mirror(on)`, `npc_mirroring()`, `npc_put(slot, id, kind, look, seed, name, hx, hz)`, `npc_heard(slot, x, y, z, yaw, speed, act, stalk)`, `npc_heard_lately(p)`, `npc_mirror_tick(dt)` | a guest's copy of the host's people |
|
| `npc_mirror(on)`, `npc_mirroring()`, `npc_put(slot, id, kind, look, seed, name, hx, hz)`, `npc_heard(slot, x, y, z, yaw, speed, act, stalk)`, `npc_heard_lately(p)`, `npc_mirror_tick(dt)` | a guest's copy of the host's people |
|
||||||
| `npc_facts() -> Queue<NpcFact>` | `NPC_F_GREETED` (`player`), `NPC_F_TALKED` (`player`, `line`, `choice`), `NPC_F_ARRIVED` (`act`) |
|
| `npc_facts() -> Queue<NpcFact>` | `NPC_F_GREETED` (`player`), `NPC_F_TALKED` (`player`, `line`, `choice`), `NPC_F_ARRIVED` (`act`), `NPC_F_STUCK` (`how` - `NPC_STUCK_DETOUR`, `_RETRY`, `_GAVE_UP` - at `x`, `z`: the way failed it and it fell back) |
|
||||||
| `npc_system() -> System` | `"npc"`, `PH_SIMULATE`, a reset and no tick; nothing saved |
|
| `npc_system() -> System` | `"npc"`, `PH_SIMULATE`, a reset and no tick; nothing saved |
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,7 @@ export open registry NpcNames of NpcName as NPCN
|
||||||
export const NPC_F_GREETED: int = 0 # a player came within notice
|
export const NPC_F_GREETED: int = 0 # a player came within notice
|
||||||
export const NPC_F_TALKED: int = 1 # a player talked (line, choice -1 or the choice taken)
|
export const NPC_F_TALKED: int = 1 # a player talked (line, choice -1 or the choice taken)
|
||||||
export const NPC_F_ARRIVED: int = 2 # got where it was walking, and began the act
|
export const NPC_F_ARRIVED: int = 2 # got where it was walking, and began the act
|
||||||
|
export const NPC_F_STUCK: int = 3 # the way failed it and it fell back (`how`, at `x`, `z`)
|
||||||
|
|
||||||
export property NpcFact {
|
export property NpcFact {
|
||||||
what: int = 0
|
what: int = 0
|
||||||
|
|
@ -141,4 +142,7 @@ export property NpcFact {
|
||||||
line: int = -1
|
line: int = -1
|
||||||
choice: int = -1
|
choice: int = -1
|
||||||
act: int = -1
|
act: int = -1
|
||||||
|
how: int = 0 # NPC_STUCK_*
|
||||||
|
x: float = 0.0
|
||||||
|
z: float = 0.0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ program NpcTest {
|
||||||
expect_eq(p.act, NPCA_SIT)
|
expect_eq(p.act, NPCA_SIT)
|
||||||
}
|
}
|
||||||
|
|
||||||
test "nobody walks into the lake" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
|
test "nobody walks into the lake, and turning back from it is told as a fallback" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
|
||||||
let p = one(npc_st, npc_tests_fake_st, NPCK_HIKER)
|
let p = one(npc_st, npc_tests_fake_st, NPCK_HIKER)
|
||||||
npc_go(npc_st, p, -80.0, 0.0, NPCA_SIT, 30.0)
|
npc_go(npc_st, p, -80.0, 0.0, NPCA_SIT, 30.0)
|
||||||
var west = 0.0
|
var west = 0.0
|
||||||
|
|
@ -123,6 +123,12 @@ program NpcTest {
|
||||||
west = Math.min(west, p.x)
|
west = Math.min(west, p.x)
|
||||||
}
|
}
|
||||||
expect(west > -50.5)
|
expect(west > -50.5)
|
||||||
|
var fell = 0
|
||||||
|
let fs = q_drain(npc_facts(npc_st))
|
||||||
|
for i in 0 .. len(fs) {
|
||||||
|
if fs[i].what == NPC_F_STUCK and fs[i].x > -52.0 { fell += 1 }
|
||||||
|
}
|
||||||
|
expect(fell >= 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
test "a step's spot is where its hours say: the shore by day, home by night" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
|
test "a step's spot is where its hours say: the shore by day, home by night" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
|
||||||
|
|
|
||||||
|
|
@ -78,5 +78,7 @@ program NpcWayTest {
|
||||||
expect_eq(p.act, NPCA_SIT)
|
expect_eq(p.act, NPCA_SIT)
|
||||||
expect(npc_d(p.x, p.z, 38.0, -25.0) < 1.3)
|
expect(npc_d(p.x, p.z, 38.0, -25.0) < 1.3)
|
||||||
expect(way_test_st.asked > 5)
|
expect(way_test_st.asked > 5)
|
||||||
|
let fs = q_drain(npc_facts(npc_st))
|
||||||
|
for i in 0 .. len(fs) { expect(fs[i].what != NPC_F_STUCK) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ export function npc_walk(npc_st: mut NpcState, p: NpcPerson, dt: float) -> void
|
||||||
p.stall = p.stall + dt
|
p.stall = p.stall + dt
|
||||||
if p.stall > p.stall_max { p.stall_max = p.stall }
|
if p.stall > p.stall_max { p.stall_max = p.stall }
|
||||||
if p.detours < 10 and np_detour(p, want) {
|
if p.detours < 10 and np_detour(p, want) {
|
||||||
|
np_stuck_fact(npc_st, p, NPC_STUCK_DETOUR)
|
||||||
p.detours += 1
|
p.detours += 1
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,15 @@
|
||||||
# way.ludic - where a person walks when the straight line will not do: the world's way (NpcWorld.way,
|
# way.ludic - where a person walks when the straight line will not do: the world's way (NpcWorld.way,
|
||||||
# a navmesh by a person's taste in Maroon Lake), and without one a step round what is in the way,
|
# a navmesh by a person's taste in Maroon Lake), and without one a step round what is in the way,
|
||||||
# and giving the target up after three tries
|
# and giving the target up after three tries - each of those a NPC_F_STUCK fact, so the fallback is seen
|
||||||
|
export const NPC_STUCK_DETOUR: int = 0 # a step round what was in front of it
|
||||||
|
export const NPC_STUCK_RETRY: int = 1 # no closer for twenty seconds, or no step round: planned again
|
||||||
|
export const NPC_STUCK_GAVE_UP: int = 2 # the third of those: the target given up
|
||||||
|
function np_stuck_fact(npc_st: NpcState, p: NpcPerson, how: int) -> void {
|
||||||
|
let f = np_fact(npc_st, NPC_F_STUCK, p, -1)
|
||||||
|
f.how = how
|
||||||
|
f.x = p.x
|
||||||
|
f.z = p.z
|
||||||
|
}
|
||||||
|
|
||||||
# the next corner toward the target, asked again every half second, on reaching it, or for a new target
|
# the next corner toward the target, asked again every half second, on reaching it, or for a new target
|
||||||
const NP_WAY_EVERY: float = 0.5
|
const NP_WAY_EVERY: float = 0.5
|
||||||
|
|
@ -39,7 +48,11 @@ function np_stuck(npc_st: mut NpcState, p: NpcPerson) -> void {
|
||||||
p.fails += 1
|
p.fails += 1
|
||||||
p.via_on = false
|
p.via_on = false
|
||||||
if p.fails >= 3 {
|
if p.fails >= 3 {
|
||||||
|
np_stuck_fact(npc_st, p, NPC_STUCK_GAVE_UP)
|
||||||
p.fails = 0
|
p.fails = 0
|
||||||
npc_wander(npc_st, p)
|
npc_wander(npc_st, p)
|
||||||
} else { npc_plan(npc_st, p) }
|
} else {
|
||||||
|
np_stuck_fact(npc_st, p, NPC_STUCK_RETRY)
|
||||||
|
npc_plan(npc_st, p)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,9 @@ import "ludic.physics"
|
||||||
|
|
||||||
## The API
|
## The API
|
||||||
|
|
||||||
|
A query fills a record the caller keeps (`p`, `h`) and allocates nothing: it is asked every frame, and
|
||||||
|
Ludic gives no allocation back.
|
||||||
|
|
||||||
| | |
|
| | |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `phys_open(st, max_bodies, threads) -> bool`, `phys_close(st)`, `phys_is_open(st)` | a world, and everything in it let go |
|
| `phys_open(st, max_bodies, threads) -> bool`, `phys_close(st)`, `phys_is_open(st)` | a world, and everything in it let go |
|
||||||
|
|
@ -34,15 +37,15 @@ import "ludic.physics"
|
||||||
| `phys_ground_add`, `phys_static_add`, `phys_kinematic_add`, `phys_body_add(..., mass)` | bodies: the ground, still things, what the game moves (the player), what falls and floats |
|
| `phys_ground_add`, `phys_static_add`, `phys_kinematic_add`, `phys_body_add(..., mass)` | bodies: the ground, still things, what the game moves (the player), what falls and floats |
|
||||||
| `phys_settle(st)` | after adding many still things, before the first query |
|
| `phys_settle(st)` | after adding many still things, before the first query |
|
||||||
| `phys_remove(st, id)`, `phys_count(st)` | gone for good |
|
| `phys_remove(st, id)`, `phys_count(st)` | gone for good |
|
||||||
| `phys_pose(st, id) -> PhysPose`, `phys_yaw_of(p)` | position, rotation, velocity |
|
| `phys_pose(st, id, p) -> bool`, `phys_yaw_of(p)` | position, rotation, velocity |
|
||||||
| `phys_place`, `phys_impulse`, `phys_set_velocity`, `phys_set_kinematic_target`, `phys_awake` | moving a body |
|
| `phys_place`, `phys_impulse`, `phys_set_velocity`, `phys_set_kinematic_target`, `phys_awake` | moving a body |
|
||||||
| `phys_ray(st, o, d, mask) -> PhysHit` | the first thing along a ray |
|
| `phys_ray(st, o, d, mask, h) -> bool` | the first thing along a ray |
|
||||||
| `phys_top_at(st, x, z, r, from_y, depth, mask) -> float` | what a foot would land on (`CharacterGround.top_at`), `PHYS_NONE` if nothing |
|
| `phys_top_at(st, x, z, r, from_y, depth, mask) -> float` | what a foot would land on (`CharacterGround.top_at`), `PHYS_NONE` if nothing |
|
||||||
| `phys_push(st, x, z, r, y0, y1, mask) -> PhysPush` | the move that stands an upright body clear (`CharacterGround.push`) |
|
| `phys_push(st, x, z, r, y0, y1, mask, p) -> int` | the move that stands an upright body clear (`CharacterGround.push`) |
|
||||||
| `phys_resolve(st, x, z, r, y0, y1, mask) -> bool`, `phys_resolved_x` / `_z` | pushed clear in two passes, the place kept (`CharacterGround.push`, `pushed_x` / `_z`); a thing wholly under the feet or over the head is not in the way |
|
| `phys_resolve(st, x, z, r, y0, y1, mask) -> bool`, `phys_resolved_x` / `_z` | pushed clear in two passes, the place kept (`CharacterGround.push`, `pushed_x` / `_z`); a thing wholly under the feet or over the head is not in the way |
|
||||||
| `phys_step_top(st, x, z, r, feet, reach, mask) -> float` | the highest top a foot could step up to, anything taller than `feet + reach` left out as a wall |
|
| `phys_step_top(st, x, z, r, feet, reach, mask) -> float` | the highest top a foot could step up to, anything taller than `feet + reach` left out as a wall |
|
||||||
| `phys_overlap(st, x, y, z, r, mask) -> []int` | the bodies a ball touches |
|
| `phys_overlap(st, x, y, z, r, mask) -> int`, `phys_overlap_id(st, i)` | how many bodies a ball touches, and each one |
|
||||||
| `phys_walker_add(st, r, h, x, y, z, max_slope, mass)`, `phys_walker_step(st, id, dt, vx, vz, jump) -> ground`, `phys_walker_pose -> PhysWalk`, `phys_walker_place`, `phys_walker_limits(step_up, stick)`, `phys_walker_remove` | a walking body (Jolt's CharacterVirtual): gravity, landing, steps, slopes, following the ground down; it shoves dynamic things and rays find its `body` |
|
| `phys_walker_add(st, r, h, x, y, z, max_slope, mass)`, `phys_walker_step(st, id, dt, vx, vz, jump) -> ground`, `phys_walker_pose(st, id, p) -> bool`, `phys_walker_place`, `phys_walker_limits(step_up, stick)`, `phys_walker_remove` | a walking body (Jolt's CharacterVirtual): gravity, landing, steps, slopes, following the ground down; it shoves dynamic things and rays find its `body` |
|
||||||
| `phys_force`, `phys_torque`, `phys_angular_impulse`, `phys_spin_y`, `phys_set_pose(st, id, pose)` | an oar's stroke and a turn; a guest's copy put where the host said |
|
| `phys_force`, `phys_torque`, `phys_angular_impulse`, `phys_spin_y`, `phys_set_pose(st, id, pose)` | an oar's stroke and a turn; a guest's copy put where the host said |
|
||||||
| `phys_float(st, id, buoyancy, linear_drag, angular_drag)`, `phys_sink`, `phys_floating` | a body buoyed each step against `PhysWater` and carried by its current |
|
| `phys_float(st, id, buoyancy, linear_drag, angular_drag)`, `phys_sink`, `phys_floating` | a body buoyed each step against `PhysWater` and carried by its current |
|
||||||
| `phys_facts(st)` | `PhysFact`: `PHYS_HIT` (two bodies met at `ph_hard` m/s or more), `PHYS_SPLASH` (a floating body reached water) |
|
| `phys_facts(st)` | `PhysFact`: `PHYS_HIT` (two bodies met at `ph_hard` m/s or more), `PHYS_SPLASH` (a floating body reached water) |
|
||||||
|
|
|
||||||
|
|
@ -43,17 +43,16 @@ export function phys_remove(physics_st: mut PhysicsState, id: int) -> void {
|
||||||
jph_body_remove(physics_st.ph_world, id)
|
jph_body_remove(physics_st.ph_world, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
# where a body is, how it is turned and how fast it goes; null when there is no such body
|
# where a body is, how it is turned and how fast it goes, into the caller's own record (a query asked
|
||||||
export function phys_pose(physics_st: mut PhysicsState, id: int) -> PhysPose {
|
# every frame allocates nothing: there is no GC); false, and p untouched, when there is no such body
|
||||||
if physics_st.ph_world == null { return null }
|
export function phys_pose(physics_st: mut PhysicsState, id: int, p: PhysPose) -> bool {
|
||||||
ph_buffers()
|
if physics_st.ph_world == null { return false }
|
||||||
if jph_body_read(physics_st.ph_world, id, physics_st.ph_vals) != 0 { return null }
|
if jph_body_read(physics_st.ph_world, id, physics_st.ph_vals) != 0 { return false }
|
||||||
let v = physics_st.ph_vals
|
let v = physics_st.ph_vals
|
||||||
let p = new PhysPose
|
|
||||||
p.x = v[0]; p.y = v[1]; p.z = v[2]
|
p.x = v[0]; p.y = v[1]; p.z = v[2]
|
||||||
p.qx = v[3]; p.qy = v[4]; p.qz = v[5]; p.qw = v[6]
|
p.qx = v[3]; p.qy = v[4]; p.qz = v[5]; p.qw = v[6]
|
||||||
p.vx = v[7]; p.vy = v[8]; p.vz = v[9]
|
p.vx = v[7]; p.vy = v[8]; p.vz = v[9]
|
||||||
return p
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
# the heading a pose's rotation gives, in radians about y
|
# the heading a pose's rotation gives, in radians about y
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ export function phys_hull_add(physics_st: mut PhysicsState, x: float, y: float,
|
||||||
# a stroke of `forward` newtons along the bow and a turn of `turn` newton-metres about y, over the
|
# a stroke of `forward` newtons along the bow and a turn of `turn` newton-metres about y, over the
|
||||||
# next step
|
# next step
|
||||||
export function phys_row(physics_st: mut PhysicsState, id: int, forward: float, turn: float) -> void {
|
export function phys_row(physics_st: mut PhysicsState, id: int, forward: float, turn: float) -> void {
|
||||||
let p = phys_pose(physics_st, id)
|
if physics_st.ph_world == null or jph_body_read(physics_st.ph_world, id, physics_st.ph_vals) != 0 { return }
|
||||||
if p == null { return }
|
let v = physics_st.ph_vals
|
||||||
let yaw = phys_yaw_of(p)
|
let yaw = Math.atan2(2.0 * (v[6] * v[4] + v[3] * v[5]), 1.0 - 2.0 * (v[4] * v[4] + v[3] * v[3]))
|
||||||
phys_force(physics_st, id, -Math.sin(yaw) * forward, 0.0, -Math.cos(yaw) * forward)
|
phys_force(physics_st, id, -Math.sin(yaw) * forward, 0.0, -Math.cos(yaw) * forward)
|
||||||
phys_torque(physics_st, id, 0.0, turn, 0.0)
|
phys_torque(physics_st, id, 0.0, turn, 0.0)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,19 @@ export property PhysHit {
|
||||||
nz: float = 0.0
|
nz: float = 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
# a ray from (ox, oy, oz) along (dx, dy, dz), whose length is the reach
|
# a ray from (ox, oy, oz) along (dx, dy, dz), whose length is the reach, into the caller's record;
|
||||||
export function phys_ray(physics_st: mut PhysicsState, ox: float, oy: float, oz: float, dx: float, dy: float, dz: float, mask: int) -> PhysHit {
|
# true when it hit something (h.body -1 and h.fraction 1 when not)
|
||||||
let h = new PhysHit
|
export function phys_ray(physics_st: mut PhysicsState, ox: float, oy: float, oz: float, dx: float, dy: float, dz: float, mask: int, h: PhysHit) -> bool {
|
||||||
if physics_st.ph_world == null { return h }
|
h.body = -1
|
||||||
ph_buffers()
|
h.fraction = 1.0
|
||||||
|
if physics_st.ph_world == null { return false }
|
||||||
h.body = jph_ray(physics_st.ph_world, ox, oy, oz, dx, dy, dz, mask, physics_st.ph_vals)
|
h.body = jph_ray(physics_st.ph_world, ox, oy, oz, dx, dy, dz, mask, physics_st.ph_vals)
|
||||||
if h.body == -1 { return h }
|
if h.body == -1 { return false }
|
||||||
let v = physics_st.ph_vals
|
let v = physics_st.ph_vals
|
||||||
h.fraction = v[0]
|
h.fraction = v[0]
|
||||||
h.x = v[1]; h.y = v[2]; h.z = v[3]
|
h.x = v[1]; h.y = v[2]; h.z = v[3]
|
||||||
h.nx = v[4]; h.ny = v[5]; h.nz = v[6]
|
h.nx = v[4]; h.ny = v[5]; h.nz = v[6]
|
||||||
return h
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
# a ball r across let down from from_y at most depth metres: the height of what it lands on, or
|
# a ball r across let down from from_y at most depth metres: the height of what it lands on, or
|
||||||
|
|
@ -45,15 +46,13 @@ export function phys_step_top(physics_st: PhysicsState, x: float, z: float, r: f
|
||||||
return y
|
return y
|
||||||
}
|
}
|
||||||
|
|
||||||
# the bodies a ball at (x, y, z) touches, in the order Jolt sorts them
|
# how many bodies a ball at (x, y, z) touches, in the order Jolt sorts them; phys_overlap_id(i)
|
||||||
export function phys_overlap(physics_st: mut PhysicsState, x: float, y: float, z: float, r: float, mask: int) -> []int {
|
# reads them back until the next query
|
||||||
let out = new []int
|
export function phys_overlap(physics_st: mut PhysicsState, x: float, y: float, z: float, r: float, mask: int) -> int {
|
||||||
if physics_st.ph_world == null { return out }
|
if physics_st.ph_world == null { return 0 }
|
||||||
ph_buffers()
|
return jph_overlap(physics_st.ph_world, x, y, z, r, mask, physics_st.ph_ids, PH_CAP)
|
||||||
let n = jph_overlap(physics_st.ph_world, x, y, z, r, mask, physics_st.ph_ids, PH_CAP)
|
|
||||||
for i in 0 .. n { push(out, physics_st.ph_ids[i]) }
|
|
||||||
return out
|
|
||||||
}
|
}
|
||||||
|
export function phys_overlap_id(physics_st: PhysicsState, i: int) -> int { return physics_st.ph_ids[i] }
|
||||||
|
|
||||||
export property PhysPush {
|
export property PhysPush {
|
||||||
x: float = 0.0 # how far to move in x and z to stand clear
|
x: float = 0.0 # how far to move in x and z to stand clear
|
||||||
|
|
@ -63,14 +62,18 @@ export property PhysPush {
|
||||||
|
|
||||||
# an upright body r wide from y0 to y1 at (x, z): the move that stands it clear - what
|
# an upright body r wide from y0 to y1 at (x, z): the move that stands it clear - what
|
||||||
# CharacterGround.push asks
|
# CharacterGround.push asks
|
||||||
export function phys_push(physics_st: mut PhysicsState, x: float, z: float, r: float, y0: float, y1: float, mask: int) -> PhysPush {
|
export function phys_push(physics_st: mut PhysicsState, x: float, z: float, r: float, y0: float, y1: float, mask: int, p: PhysPush) -> int {
|
||||||
let p = new PhysPush
|
p.n = ph_push(physics_st, x, z, r, y0, y1, mask)
|
||||||
if physics_st.ph_world == null { return p }
|
|
||||||
ph_buffers()
|
|
||||||
p.n = jph_push(physics_st.ph_world, x, z, r, y0, y1, mask, physics_st.ph_vals)
|
|
||||||
p.x = physics_st.ph_vals[0]
|
p.x = physics_st.ph_vals[0]
|
||||||
p.z = physics_st.ph_vals[1]
|
p.z = physics_st.ph_vals[1]
|
||||||
return p
|
return p.n
|
||||||
|
}
|
||||||
|
# how many things are in the way, the move clear in ph_vals[0], [1]
|
||||||
|
function ph_push(physics_st: mut PhysicsState, x: float, z: float, r: float, y0: float, y1: float, mask: int) -> int {
|
||||||
|
physics_st.ph_vals[0] = 0.0
|
||||||
|
physics_st.ph_vals[1] = 0.0
|
||||||
|
if physics_st.ph_world == null { return 0 }
|
||||||
|
return jph_push(physics_st.ph_world, x, z, r, y0, y1, mask, physics_st.ph_vals)
|
||||||
}
|
}
|
||||||
|
|
||||||
# a body r wide from y0 to y1 at (x, z) moved clear of what it stands in, two passes over (a push out
|
# a body r wide from y0 to y1 at (x, z) moved clear of what it stands in, two passes over (a push out
|
||||||
|
|
@ -82,10 +85,9 @@ export function phys_resolve(physics_st: mut PhysicsState, x: float, z: float, r
|
||||||
var pz = z
|
var pz = z
|
||||||
var moved = false
|
var moved = false
|
||||||
for pass in 0 .. 2 {
|
for pass in 0 .. 2 {
|
||||||
let p = phys_push(physics_st, px, pz, r, y0, y1, mask)
|
if ph_push(physics_st, px, pz, r, y0, y1, mask) > 0 {
|
||||||
if p.n > 0 {
|
px += physics_st.ph_vals[0]
|
||||||
px += p.x
|
pz += physics_st.ph_vals[1]
|
||||||
pz += p.z
|
|
||||||
moved = true
|
moved = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,11 @@ import "ludic.physics"
|
||||||
import "ludic.base"
|
import "ludic.base"
|
||||||
program HullShapeTest {
|
program HullShapeTest {
|
||||||
numbers float
|
numbers float
|
||||||
|
function ray(physics_st: mut PhysicsState, ox: float, oy: float, oz: float, dx: float, dy: float, dz: float, mask: int) -> PhysHit {
|
||||||
|
let h = new PhysHit
|
||||||
|
phys_ray(physics_st, ox, oy, oz, dx, dy, dz, mask, h)
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
# a cube two metres across, its centre two metres along x from its origin
|
# a cube two metres across, its centre two metres along x from its origin
|
||||||
function cube(physics_st: mut PhysicsState) -> int {
|
function cube(physics_st: mut PhysicsState) -> int {
|
||||||
|
|
@ -15,7 +20,7 @@ program HullShapeTest {
|
||||||
}
|
}
|
||||||
return phys_convex(physics_st, v, 8)
|
return phys_convex(physics_st, v, 8)
|
||||||
}
|
}
|
||||||
function down(physics_st: mut PhysicsState, x: float, z: float) -> PhysHit { return phys_ray(physics_st, x, 10.0, z, 0.0, -20.0, 0.0, PHYS_M_ALL) }
|
function down(physics_st: mut PhysicsState, x: float, z: float) -> PhysHit { return ray(physics_st, x, 10.0, z, 0.0, -20.0, 0.0, PHYS_M_ALL) }
|
||||||
|
|
||||||
test "a hull stands where its points are, and too few points are no shape" (physics_st: mut PhysicsState) {
|
test "a hull stands where its points are, and too few points are no shape" (physics_st: mut PhysicsState) {
|
||||||
expect(phys_open(physics_st, 16, 1))
|
expect(phys_open(physics_st, 16, 1))
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,11 @@ import "ludic.physics"
|
||||||
import "ludic.base"
|
import "ludic.base"
|
||||||
program MotionTest {
|
program MotionTest {
|
||||||
numbers float
|
numbers float
|
||||||
|
function pose(physics_st: mut PhysicsState, id: int) -> PhysPose {
|
||||||
|
let p = new PhysPose
|
||||||
|
if not phys_pose(physics_st, id, p) { return null }
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
function lake_h(x: float, z: float) -> float {
|
function lake_h(x: float, z: float) -> float {
|
||||||
if x > 20.0 { return 0.0 }
|
if x > 20.0 { return 0.0 }
|
||||||
|
|
@ -45,7 +50,7 @@ program MotionTest {
|
||||||
let crate = phys_body_add(physics_st, phys_box(physics_st, 0.5, 0.25, 0.5), 30.0, 2.0, 0.0, 0.0, 60.0)
|
let crate = phys_body_add(physics_st, phys_box(physics_st, 0.5, 0.25, 0.5), 30.0, 2.0, 0.0, 0.0, 60.0)
|
||||||
phys_float(physics_st, crate, 1.2, 2.0, 2.0)
|
phys_float(physics_st, crate, 1.2, 2.0, 2.0)
|
||||||
sim(physics_st, 8.0)
|
sim(physics_st, 8.0)
|
||||||
let p = phys_pose(physics_st, crate)
|
let p = pose(physics_st, crate)
|
||||||
expect(p.y > -0.4)
|
expect(p.y > -0.4)
|
||||||
expect(p.y < 0.4)
|
expect(p.y < 0.4)
|
||||||
expect(Math.abs(p.vy) < 0.2)
|
expect(Math.abs(p.vy) < 0.2)
|
||||||
|
|
@ -55,7 +60,7 @@ program MotionTest {
|
||||||
phys_sink(physics_st, crate)
|
phys_sink(physics_st, crate)
|
||||||
expect(not phys_floating(physics_st, crate))
|
expect(not phys_floating(physics_st, crate))
|
||||||
sim(physics_st, 4.0)
|
sim(physics_st, 4.0)
|
||||||
expect(phys_pose(physics_st, crate).y < -2.5)
|
expect(pose(physics_st, crate).y < -2.5)
|
||||||
phys_close(physics_st)
|
phys_close(physics_st)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,7 +92,7 @@ program MotionTest {
|
||||||
phys_set_kinematic_target(physics_st, me, 2.0 + float(i + 1) * 0.03, 0.95, 0.0, 0.0)
|
phys_set_kinematic_target(physics_st, me, 2.0 + float(i + 1) * 0.03, 0.95, 0.0, 0.0)
|
||||||
phys_step(physics_st)
|
phys_step(physics_st)
|
||||||
}
|
}
|
||||||
expect(phys_pose(physics_st, crate).x > 5.0)
|
expect(pose(physics_st, crate).x > 5.0)
|
||||||
phys_close(physics_st)
|
phys_close(physics_st)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,7 +119,7 @@ program MotionTest {
|
||||||
for i in 0 .. 12 { last = phys_body_add(physics_st, b, 3.0 + float(i % 3) * 0.3, 2.0 + float(i) * 0.7, float(i % 2) * 0.2, float(i) * 0.4, 8.0) }
|
for i in 0 .. 12 { last = phys_body_add(physics_st, b, 3.0 + float(i % 3) * 0.3, 2.0 + float(i) * 0.7, float(i % 2) * 0.2, float(i) * 0.4, 8.0) }
|
||||||
phys_impulse(physics_st, last, 20.0, 0.0, 5.0)
|
phys_impulse(physics_st, last, 20.0, 0.0, 5.0)
|
||||||
sim(physics_st, 4.0)
|
sim(physics_st, 4.0)
|
||||||
let p = phys_pose(physics_st, last)
|
let p = pose(physics_st, last)
|
||||||
phys_close(physics_st)
|
phys_close(physics_st)
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
@ -132,21 +137,21 @@ program MotionTest {
|
||||||
world(physics_st)
|
world(physics_st)
|
||||||
let b = phys_hull_add(physics_st, 40.0, 0.5, 0.0, 1.5707963, 0.6, 0.25, 1.9, 180.0, 4.0, 1.0)
|
let b = phys_hull_add(physics_st, 40.0, 0.5, 0.0, 1.5707963, 0.6, 0.25, 1.9, 180.0, 4.0, 1.0)
|
||||||
sim(physics_st, 4.0)
|
sim(physics_st, 4.0)
|
||||||
let p0 = phys_pose(physics_st, b)
|
let p0 = pose(physics_st, b)
|
||||||
expect(p0.y > -0.2)
|
expect(p0.y > -0.2)
|
||||||
expect(p0.y < 0.2)
|
expect(p0.y < 0.2)
|
||||||
for i in 0 .. 240 {
|
for i in 0 .. 240 {
|
||||||
phys_row(physics_st, b, 420.0, 0.0)
|
phys_row(physics_st, b, 420.0, 0.0)
|
||||||
phys_step(physics_st)
|
phys_step(physics_st)
|
||||||
}
|
}
|
||||||
let p1 = phys_pose(physics_st, b)
|
let p1 = pose(physics_st, b)
|
||||||
expect(p1.x < 34.0)
|
expect(p1.x < 34.0)
|
||||||
expect(phys_bow_speed(p1) > 1.0)
|
expect(phys_bow_speed(p1) > 1.0)
|
||||||
for i in 0 .. 120 {
|
for i in 0 .. 120 {
|
||||||
phys_row(physics_st, b, 0.0, 520.0)
|
phys_row(physics_st, b, 0.0, 520.0)
|
||||||
phys_step(physics_st)
|
phys_step(physics_st)
|
||||||
}
|
}
|
||||||
expect(Math.abs(phys_yaw_of(phys_pose(physics_st, b)) - phys_yaw_of(p1)) > 0.5)
|
expect(Math.abs(phys_yaw_of(pose(physics_st, b)) - phys_yaw_of(p1)) > 0.5)
|
||||||
phys_close(physics_st)
|
phys_close(physics_st)
|
||||||
world(physics_st)
|
world(physics_st)
|
||||||
let c = phys_hull_add(physics_st, 24.0, 0.5, 0.0, 1.5707963, 0.6, 0.25, 1.9, 180.0, 4.0, 1.0)
|
let c = phys_hull_add(physics_st, 24.0, 0.5, 0.0, 1.5707963, 0.6, 0.25, 1.9, 180.0, 4.0, 1.0)
|
||||||
|
|
@ -154,7 +159,7 @@ program MotionTest {
|
||||||
phys_row(physics_st, c, 420.0, 0.0)
|
phys_row(physics_st, c, 420.0, 0.0)
|
||||||
phys_step(physics_st)
|
phys_step(physics_st)
|
||||||
}
|
}
|
||||||
expect(phys_pose(physics_st, c).x > 19.5)
|
expect(pose(physics_st, c).x > 19.5)
|
||||||
phys_close(physics_st)
|
phys_close(physics_st)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,21 @@ import "ludic.physics"
|
||||||
import "ludic.base"
|
import "ludic.base"
|
||||||
program PhysicsTest {
|
program PhysicsTest {
|
||||||
numbers float
|
numbers float
|
||||||
|
function pose(physics_st: mut PhysicsState, id: int) -> PhysPose {
|
||||||
|
let p = new PhysPose
|
||||||
|
if not phys_pose(physics_st, id, p) { return null }
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
function ray(physics_st: mut PhysicsState, ox: float, oy: float, oz: float, dx: float, dy: float, dz: float, mask: int) -> PhysHit {
|
||||||
|
let h = new PhysHit
|
||||||
|
phys_ray(physics_st, ox, oy, oz, dx, dy, dz, mask, h)
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
function push_of(physics_st: mut PhysicsState, x: float, z: float, r: float, y0: float, y1: float, mask: int) -> PhysPush {
|
||||||
|
let p = new PhysPush
|
||||||
|
phys_push(physics_st, x, z, r, y0, y1, mask, p)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
# the lake: everything east of x = 20, its surface at y = 0, drifting north at 0.5 m/s
|
# the lake: everything east of x = 20, its surface at y = 0, drifting north at 0.5 m/s
|
||||||
function lake_h(x: float, z: float) -> float {
|
function lake_h(x: float, z: float) -> float {
|
||||||
|
|
@ -38,15 +53,15 @@ program PhysicsTest {
|
||||||
let id = phys_body_add(physics_st, b, 0.0, 4.0, 0.0, 0.0, 20.0)
|
let id = phys_body_add(physics_st, b, 0.0, 4.0, 0.0, 0.0, 20.0)
|
||||||
expect(id != -1)
|
expect(id != -1)
|
||||||
sim(physics_st, 3.0)
|
sim(physics_st, 3.0)
|
||||||
let p = phys_pose(physics_st, id)
|
let p = pose(physics_st, id)
|
||||||
expect(p != null)
|
expect(p != null)
|
||||||
expect(Math.abs(p.y - 0.5) < 0.05)
|
expect(Math.abs(p.y - 0.5) < 0.05)
|
||||||
expect(Math.abs(p.vy) < 0.05)
|
expect(Math.abs(p.vy) < 0.05)
|
||||||
let h = phys_ray(physics_st, 0.0, 10.0, 0.0, 0.0, -20.0, 0.0, PHYS_M_ALL)
|
let h = ray(physics_st, 0.0, 10.0, 0.0, 0.0, -20.0, 0.0, PHYS_M_ALL)
|
||||||
expect_eq(h.body, id)
|
expect_eq(h.body, id)
|
||||||
expect(Math.abs(h.y - 1.0) < 0.05)
|
expect(Math.abs(h.y - 1.0) < 0.05)
|
||||||
expect(h.ny > 0.9)
|
expect(h.ny > 0.9)
|
||||||
let g = phys_ray(physics_st, 5.0, 10.0, 5.0, 0.0, -20.0, 0.0, PHYS_M_ALL)
|
let g = ray(physics_st, 5.0, 10.0, 5.0, 0.0, -20.0, 0.0, PHYS_M_ALL)
|
||||||
expect(g.body != -1)
|
expect(g.body != -1)
|
||||||
expect(g.body != id)
|
expect(g.body != id)
|
||||||
expect(Math.abs(g.y) < 0.01)
|
expect(Math.abs(g.y) < 0.01)
|
||||||
|
|
@ -59,14 +74,13 @@ program PhysicsTest {
|
||||||
let id = phys_static_add(physics_st, post, 2.0, 0.0, 2.0, 0.0)
|
let id = phys_static_add(physics_st, post, 2.0, 0.0, 2.0, 0.0)
|
||||||
phys_settle(physics_st)
|
phys_settle(physics_st)
|
||||||
let n = phys_count(physics_st)
|
let n = phys_count(physics_st)
|
||||||
let near = phys_overlap(physics_st, 2.0, 1.0, 2.0, 0.5, PHYS_M_STATIC)
|
expect_eq(phys_overlap(physics_st, 2.0, 1.0, 2.0, 0.5, PHYS_M_STATIC), 1)
|
||||||
expect_eq(len(near), 1)
|
expect_eq(phys_overlap_id(physics_st, 0), id)
|
||||||
expect_eq(near[0], id)
|
expect_eq(phys_overlap(physics_st, 2.0, 1.0, 2.0, 0.5, PHYS_M_GROUND), 0)
|
||||||
expect_eq(len(phys_overlap(physics_st, 2.0, 1.0, 2.0, 0.5, PHYS_M_GROUND)), 0)
|
|
||||||
phys_remove(physics_st, id)
|
phys_remove(physics_st, id)
|
||||||
expect_eq(phys_count(physics_st), n - 1)
|
expect_eq(phys_count(physics_st), n - 1)
|
||||||
expect_eq(len(phys_overlap(physics_st, 2.0, 1.0, 2.0, 0.5, PHYS_M_STATIC)), 0)
|
expect_eq(phys_overlap(physics_st, 2.0, 1.0, 2.0, 0.5, PHYS_M_STATIC), 0)
|
||||||
expect(phys_pose(physics_st, id) == null)
|
expect(pose(physics_st, id) == null)
|
||||||
phys_close(physics_st)
|
phys_close(physics_st)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,11 +100,11 @@ program PhysicsTest {
|
||||||
let post = phys_offset(physics_st, phys_cylinder(physics_st, 1.0, 0.3), 0.0, 1.0, 0.0, 0.0)
|
let post = phys_offset(physics_st, phys_cylinder(physics_st, 1.0, 0.3), 0.0, 1.0, 0.0, 0.0)
|
||||||
phys_static_add(physics_st, post, 0.0, 0.0, 0.0, 0.0)
|
phys_static_add(physics_st, post, 0.0, 0.0, 0.0, 0.0)
|
||||||
phys_settle(physics_st)
|
phys_settle(physics_st)
|
||||||
let p = phys_push(physics_st, 0.5, 0.0, 0.4, 0.1, 1.8, PHYS_M_STATIC)
|
let p = push_of(physics_st, 0.5, 0.0, 0.4, 0.1, 1.8, PHYS_M_STATIC)
|
||||||
expect_eq(p.n, 1)
|
expect_eq(p.n, 1)
|
||||||
expect(p.x > 0.1)
|
expect(p.x > 0.1)
|
||||||
expect(Math.abs(p.z) < 0.05)
|
expect(Math.abs(p.z) < 0.05)
|
||||||
let clear = phys_push(physics_st, 3.0, 0.0, 0.4, 0.1, 1.8, PHYS_M_STATIC)
|
let clear = push_of(physics_st, 3.0, 0.0, 0.4, 0.1, 1.8, PHYS_M_STATIC)
|
||||||
expect_eq(clear.n, 0)
|
expect_eq(clear.n, 0)
|
||||||
phys_close(physics_st)
|
phys_close(physics_st)
|
||||||
}
|
}
|
||||||
|
|
@ -141,8 +155,8 @@ program PhysicsTest {
|
||||||
expect(g >= 0)
|
expect(g >= 0)
|
||||||
phys_ground_add(physics_st, g)
|
phys_ground_add(physics_st, g)
|
||||||
phys_settle(physics_st)
|
phys_settle(physics_st)
|
||||||
let odd = phys_ray(physics_st, 5.5, 50.0, 10.5, 0.0, -100.0, 0.0, PHYS_M_GROUND)
|
let odd = ray(physics_st, 5.5, 50.0, 10.5, 0.0, -100.0, 0.0, PHYS_M_GROUND)
|
||||||
let even = phys_ray(physics_st, 6.5, 50.0, 10.5, 0.0, -100.0, 0.0, PHYS_M_GROUND)
|
let even = ray(physics_st, 6.5, 50.0, 10.5, 0.0, -100.0, 0.0, PHYS_M_GROUND)
|
||||||
expect(Math.abs(odd.y - 2.0) < 0.01)
|
expect(Math.abs(odd.y - 2.0) < 0.01)
|
||||||
expect(Math.abs(even.y - 1.0) < 0.01)
|
expect(Math.abs(even.y - 1.0) < 0.01)
|
||||||
phys_close(physics_st)
|
phys_close(physics_st)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,16 @@ import "ludic.physics"
|
||||||
import "ludic.base"
|
import "ludic.base"
|
||||||
program WalkerTest {
|
program WalkerTest {
|
||||||
numbers float
|
numbers float
|
||||||
|
function pose(physics_st: mut PhysicsState, id: int) -> PhysPose {
|
||||||
|
let p = new PhysPose
|
||||||
|
if not phys_pose(physics_st, id, p) { return null }
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
function wpose(physics_st: mut PhysicsState, id: int) -> PhysWalk {
|
||||||
|
let p = new PhysWalk
|
||||||
|
if not phys_walker_pose(physics_st, id, p) { return null }
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
function flat(physics_st: mut PhysicsState) -> void {
|
function flat(physics_st: mut PhysicsState) -> void {
|
||||||
expect(phys_open(physics_st, 1024, 1))
|
expect(phys_open(physics_st, 1024, 1))
|
||||||
|
|
@ -20,11 +30,11 @@ program WalkerTest {
|
||||||
flat(physics_st)
|
flat(physics_st)
|
||||||
let me = phys_walker_add(physics_st, 0.35, 1.8, 0.0, 1.0, 0.0, 50.0, 70.0)
|
let me = phys_walker_add(physics_st, 0.35, 1.8, 0.0, 1.0, 0.0, 50.0, 70.0)
|
||||||
walk(physics_st, me, 1.0, 0.0, 0.0)
|
walk(physics_st, me, 1.0, 0.0, 0.0)
|
||||||
let p = phys_walker_pose(physics_st, me)
|
let p = wpose(physics_st, me)
|
||||||
expect_eq(p.ground, PHYS_ON_GROUND)
|
expect_eq(p.ground, PHYS_ON_GROUND)
|
||||||
expect(Math.abs(p.y) < 0.05)
|
expect(Math.abs(p.y) < 0.05)
|
||||||
walk(physics_st, me, 2.0, 3.0, 0.0)
|
walk(physics_st, me, 2.0, 3.0, 0.0)
|
||||||
let q = phys_walker_pose(physics_st, me)
|
let q = wpose(physics_st, me)
|
||||||
expect(Math.abs(q.x - 6.0) < 0.2)
|
expect(Math.abs(q.x - 6.0) < 0.2)
|
||||||
expect(q.body >= 0)
|
expect(q.body >= 0)
|
||||||
phys_close(physics_st)
|
phys_close(physics_st)
|
||||||
|
|
@ -40,12 +50,12 @@ program WalkerTest {
|
||||||
let a = phys_walker_add(physics_st, 0.35, 1.8, 0.0, 0.0, 0.0, 50.0, 70.0)
|
let a = phys_walker_add(physics_st, 0.35, 1.8, 0.0, 0.0, 0.0, 50.0, 70.0)
|
||||||
phys_walker_limits(physics_st, a, 0.55, 0.5, 50.0)
|
phys_walker_limits(physics_st, a, 0.55, 0.5, 50.0)
|
||||||
walk(physics_st, a, 1.5, 2.0, 0.0)
|
walk(physics_st, a, 1.5, 2.0, 0.0)
|
||||||
let pa = phys_walker_pose(physics_st, a)
|
let pa = wpose(physics_st, a)
|
||||||
expect(pa.x > 2.5)
|
expect(pa.x > 2.5)
|
||||||
expect(Math.abs(pa.y - 0.3) < 0.05)
|
expect(Math.abs(pa.y - 0.3) < 0.05)
|
||||||
let b = phys_walker_add(physics_st, 0.35, 1.8, 0.0, 0.0, 10.0, 50.0, 70.0)
|
let b = phys_walker_add(physics_st, 0.35, 1.8, 0.0, 0.0, 10.0, 50.0, 70.0)
|
||||||
walk(physics_st, b, 2.0, 2.0, 0.0)
|
walk(physics_st, b, 2.0, 2.0, 0.0)
|
||||||
let pb = phys_walker_pose(physics_st, b)
|
let pb = wpose(physics_st, b)
|
||||||
expect(pb.x < 1.7)
|
expect(pb.x < 1.7)
|
||||||
expect(Math.abs(pb.y) < 0.05)
|
expect(Math.abs(pb.y) < 0.05)
|
||||||
phys_close(physics_st)
|
phys_close(physics_st)
|
||||||
|
|
@ -57,11 +67,11 @@ program WalkerTest {
|
||||||
walk(physics_st, me, 0.5, 0.0, 0.0)
|
walk(physics_st, me, 0.5, 0.0, 0.0)
|
||||||
phys_walker_step(physics_st, me, 1.0 / 60.0, 0.0, 0.0, 5.0)
|
phys_walker_step(physics_st, me, 1.0 / 60.0, 0.0, 0.0, 5.0)
|
||||||
walk(physics_st, me, 0.25, 0.0, 0.0)
|
walk(physics_st, me, 0.25, 0.0, 0.0)
|
||||||
let up = phys_walker_pose(physics_st, me)
|
let up = wpose(physics_st, me)
|
||||||
expect_eq(up.ground, PHYS_IN_AIR)
|
expect_eq(up.ground, PHYS_IN_AIR)
|
||||||
expect(up.y > 0.5)
|
expect(up.y > 0.5)
|
||||||
walk(physics_st, me, 1.5, 0.0, 0.0)
|
walk(physics_st, me, 1.5, 0.0, 0.0)
|
||||||
let down = phys_walker_pose(physics_st, me)
|
let down = wpose(physics_st, me)
|
||||||
expect_eq(down.ground, PHYS_ON_GROUND)
|
expect_eq(down.ground, PHYS_ON_GROUND)
|
||||||
expect(Math.abs(down.y) < 0.05)
|
expect(Math.abs(down.y) < 0.05)
|
||||||
phys_close(physics_st)
|
phys_close(physics_st)
|
||||||
|
|
@ -75,12 +85,12 @@ program WalkerTest {
|
||||||
phys_walker_step(physics_st, me, 1.0 / 60.0, 2.0, 0.0, 0.0)
|
phys_walker_step(physics_st, me, 1.0 / 60.0, 2.0, 0.0, 0.0)
|
||||||
phys_step(physics_st)
|
phys_step(physics_st)
|
||||||
}
|
}
|
||||||
expect(phys_pose(physics_st, crate).x > 2.5)
|
expect(pose(physics_st, crate).x > 2.5)
|
||||||
phys_walker_place(physics_st, me, -10.0, 0.0, -10.0)
|
phys_walker_place(physics_st, me, -10.0, 0.0, -10.0)
|
||||||
let p = phys_walker_pose(physics_st, me)
|
let p = wpose(physics_st, me)
|
||||||
expect(Math.abs(p.x + 10.0) < 0.01)
|
expect(Math.abs(p.x + 10.0) < 0.01)
|
||||||
phys_walker_remove(physics_st, me)
|
phys_walker_remove(physics_st, me)
|
||||||
expect(phys_walker_pose(physics_st, me) == null)
|
expect(wpose(physics_st, me) == null)
|
||||||
phys_close(physics_st)
|
phys_close(physics_st)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,11 +104,11 @@ program WalkerTest {
|
||||||
phys_step(physics_st)
|
phys_step(physics_st)
|
||||||
}
|
}
|
||||||
expect(phys_spin_y(physics_st, hull) > 0.1)
|
expect(phys_spin_y(physics_st, hull) > 0.1)
|
||||||
expect(phys_pose(physics_st, hull).z > 0.5)
|
expect(pose(physics_st, hull).z > 0.5)
|
||||||
let p = phys_pose(physics_st, hull)
|
let p = pose(physics_st, hull)
|
||||||
p.x = 5.0
|
p.x = 5.0
|
||||||
phys_set_pose(physics_st, hull, p)
|
phys_set_pose(physics_st, hull, p)
|
||||||
expect(Math.abs(phys_pose(physics_st, hull).x - 5.0) < 0.01)
|
expect(Math.abs(pose(physics_st, hull).x - 5.0) < 0.01)
|
||||||
phys_close(physics_st)
|
phys_close(physics_st)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,19 +53,18 @@ export function phys_walker_step(physics_st: mut PhysicsState, id: int, dt: floa
|
||||||
return jph_char_step(physics_st.ph_world, k, dt, vx, vz, jump)
|
return jph_char_step(physics_st.ph_world, k, dt, vx, vz, jump)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function phys_walker_pose(physics_st: mut PhysicsState, id: int) -> PhysWalk {
|
# where a walker is, into the caller's record; false when there is no such walker
|
||||||
|
export function phys_walker_pose(physics_st: mut PhysicsState, id: int, p: PhysWalk) -> bool {
|
||||||
let k = ph_walker(physics_st, id)
|
let k = ph_walker(physics_st, id)
|
||||||
if k == null { return null }
|
if k == null { return false }
|
||||||
ph_buffers()
|
|
||||||
jph_char_read(k, physics_st.ph_vals)
|
jph_char_read(k, physics_st.ph_vals)
|
||||||
let v = physics_st.ph_vals
|
let v = physics_st.ph_vals
|
||||||
let p = new PhysWalk
|
|
||||||
p.x = v[0]; p.y = v[1]; p.z = v[2]
|
p.x = v[0]; p.y = v[1]; p.z = v[2]
|
||||||
p.vx = v[3]; p.vy = v[4]; p.vz = v[5]
|
p.vx = v[3]; p.vy = v[4]; p.vz = v[5]
|
||||||
p.ground = int(v[6])
|
p.ground = int(v[6])
|
||||||
p.ny = v[7]
|
p.ny = v[7]
|
||||||
p.body = int(v[8])
|
p.body = int(v[8])
|
||||||
return p
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
# put it somewhere, standing still: a teleport, a swap, getting off a boat
|
# put it somewhere, standing still: a teleport, a swap, getting off a boat
|
||||||
|
|
@ -77,11 +76,12 @@ export function phys_walker_place(physics_st: mut PhysicsState, id: int, x: floa
|
||||||
# a body the game keeps elsewhere, moved by its walker: put where the body is if something else
|
# a body the game keeps elsewhere, moved by its walker: put where the body is if something else
|
||||||
# moved it (a teleport, a ride, a swim), then one step under the given limits; the ground after it
|
# moved it (a teleport, a ride, a swim), then one step under the given limits; the ground after it
|
||||||
export function phys_walker_move(physics_st: mut PhysicsState, id: int, x: float, y: float, z: float, vx: float, vz: float, jump: float, step: float, max_slope: float, dt: float) -> int {
|
export function phys_walker_move(physics_st: mut PhysicsState, id: int, x: float, y: float, z: float, vx: float, vz: float, jump: float, step: float, max_slope: float, dt: float) -> int {
|
||||||
let p = phys_walker_pose(physics_st, id)
|
let k = ph_walker(physics_st, id)
|
||||||
if p == null { return PHYS_IN_AIR }
|
if k == null { return PHYS_IN_AIR }
|
||||||
let dx = p.x - x
|
jph_char_read(k, physics_st.ph_vals)
|
||||||
let dy = p.y - y
|
let dx = physics_st.ph_vals[0] - x
|
||||||
let dz = p.z - z
|
let dy = physics_st.ph_vals[1] - y
|
||||||
|
let dz = physics_st.ph_vals[2] - z
|
||||||
if dx * dx + dy * dy + dz * dz > 0.0025 { phys_walker_place(physics_st, id, x, y, z) }
|
if dx * dx + dy * dy + dz * dz > 0.0025 { phys_walker_place(physics_st, id, x, y, z) }
|
||||||
phys_walker_limits(physics_st, id, step, 0.5, max_slope)
|
phys_walker_limits(physics_st, id, step, 0.5, max_slope)
|
||||||
return phys_walker_step(physics_st, id, dt, vx, vz, jump)
|
return phys_walker_step(physics_st, id, dt, vx, vz, jump)
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,7 @@ export state Render3dState {
|
||||||
gltf_comps: int = 0
|
gltf_comps: int = 0
|
||||||
gltf_tex_paths: []pointer = null
|
gltf_tex_paths: []pointer = null
|
||||||
gltf_tex_ids: words = null
|
gltf_tex_ids: words = null
|
||||||
|
gltf_tex_refs: words = null # how many primitives use each cached texture (model_release)
|
||||||
gltf_tex_n: int = 0
|
gltf_tex_n: int = 0
|
||||||
gltf_white: int = 0
|
gltf_white: int = 0
|
||||||
gltf_flat: int = 0
|
gltf_flat: int = 0
|
||||||
|
|
|
||||||
|
|
@ -50,17 +50,17 @@ function gltf_mat_remember(render3d_st: mut Render3dState, name: string, diff: i
|
||||||
}
|
}
|
||||||
|
|
||||||
function gltf_texture(render3d_st: mut Render3dState, uri: string, srgb: bool) -> int {
|
function gltf_texture(render3d_st: mut Render3dState, uri: string, srgb: bool) -> int {
|
||||||
if render3d_st.gltf_tex_paths == null { render3d_st.gltf_tex_paths = new []pointer; render3d_st.gltf_tex_ids = words(256) }
|
if render3d_st.gltf_tex_paths == null { render3d_st.gltf_tex_paths = new []pointer; render3d_st.gltf_tex_ids = words(256); render3d_st.gltf_tex_refs = words(256) }
|
||||||
var png: string = uri
|
var png: string = uri
|
||||||
let n = len(uri)
|
let n = len(uri)
|
||||||
if n > 4 and uri[n - 4] == '.' and uri[n - 3] == 'j' { png = uri[0 .. n - 4] + ".png" }
|
if n > 4 and uri[n - 4] == '.' and uri[n - 3] == 'j' { png = uri[0 .. n - 4] + ".png" }
|
||||||
let path = render3d_st.gltf_dir + "/" + png
|
let path = render3d_st.gltf_dir + "/" + png
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < render3d_st.gltf_tex_n { if render3d_st.gltf_tex_paths[i] == path { return render3d_st.gltf_tex_ids[i] }; i += 1 }
|
while i < render3d_st.gltf_tex_n { if render3d_st.gltf_tex_paths[i] == path { render3d_st.gltf_tex_refs[i] += 1; return render3d_st.gltf_tex_ids[i] }; i += 1 }
|
||||||
var dil = 0
|
var dil = 0
|
||||||
if render3d_st.gltf_cutout { dil = 24 }
|
if render3d_st.gltf_cutout { dil = 24 }
|
||||||
let id = tex_load_ex(render3d_st, path, srgb, dil)
|
let id = tex_load_ex(render3d_st, path, srgb, dil)
|
||||||
if render3d_st.gltf_tex_n < 256 { push(render3d_st.gltf_tex_paths, path); render3d_st.gltf_tex_ids[render3d_st.gltf_tex_n] = id; render3d_st.gltf_tex_n += 1 }
|
if render3d_st.gltf_tex_n < 256 { push(render3d_st.gltf_tex_paths, path); render3d_st.gltf_tex_ids[render3d_st.gltf_tex_n] = id; render3d_st.gltf_tex_refs[render3d_st.gltf_tex_n] = 1; render3d_st.gltf_tex_n += 1 }
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,7 +150,10 @@ function gltf_prim(render3d_st: mut Render3dState, p: Val) -> Prim {
|
||||||
if ud != null { gltf_mat_remember(render3d_st, mname, pr.diff, pr.nrm, pr.arm) }
|
if ud != null { gltf_mat_remember(render3d_st, mname, pr.diff, pr.nrm, pr.arm) }
|
||||||
else {
|
else {
|
||||||
let k = gltf_mat_find(render3d_st, mname)
|
let k = gltf_mat_find(render3d_st, mname)
|
||||||
if k >= 0 { pr.diff = render3d_st.gltf_mat_diff[k]; pr.nrm = render3d_st.gltf_mat_nrm[k]; pr.arm = render3d_st.gltf_mat_arm[k] }
|
if k >= 0 {
|
||||||
|
pr.diff = render3d_st.gltf_mat_diff[k]; pr.nrm = render3d_st.gltf_mat_nrm[k]; pr.arm = render3d_st.gltf_mat_arm[k]
|
||||||
|
gltf_tex_ref(render3d_st, pr.diff); gltf_tex_ref(render3d_st, pr.nrm); gltf_tex_ref(render3d_st, pr.arm)
|
||||||
|
}
|
||||||
else { print(`gltf: material {mname} has no textures and none were loaded before it`) }
|
else { print(`gltf: material {mname} has no textures and none were loaded before it`) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
70
packages/ludic.render3d/gltf_release.ludic
Normal file
70
packages/ludic.render3d/gltf_release.ludic
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
# gltf_release.ludic - a loaded model let go of: its meshes, and each texture once nothing else uses
|
||||||
|
# it. The glTF loader shares textures between models (a path loaded once, a LOD chain borrowing its
|
||||||
|
# LOD0's material), so each cached texture carries a count of the primitives using it.
|
||||||
|
|
||||||
|
# one more primitive uses the cached texture `id` (0, or a texture the cache did not load: nothing)
|
||||||
|
function gltf_tex_ref(render3d_st: mut Render3dState, id: int) -> void {
|
||||||
|
if id == 0 or render3d_st.gltf_tex_refs == null { return }
|
||||||
|
for i in 0 .. render3d_st.gltf_tex_n { if render3d_st.gltf_tex_ids[i] == id { render3d_st.gltf_tex_refs[i] += 1; return } }
|
||||||
|
}
|
||||||
|
|
||||||
|
# one primitive fewer: the last one frees the texture and forgets it, with every material naming it
|
||||||
|
function gltf_tex_unref(render3d_st: mut Render3dState, id: int) -> void {
|
||||||
|
if id == 0 or render3d_st.gltf_tex_refs == null { return }
|
||||||
|
for i in 0 .. render3d_st.gltf_tex_n {
|
||||||
|
if render3d_st.gltf_tex_ids[i] == id {
|
||||||
|
render3d_st.gltf_tex_refs[i] -= 1
|
||||||
|
if render3d_st.gltf_tex_refs[i] > 0 { return }
|
||||||
|
gpu_tex_free(render3d_st, id)
|
||||||
|
gltf_tex_forget(render3d_st, i)
|
||||||
|
gltf_mats_forget(render3d_st, id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# the cache entry i goes; the last entry takes its place
|
||||||
|
function gltf_tex_forget(render3d_st: mut Render3dState, i: int) -> void {
|
||||||
|
let last = render3d_st.gltf_tex_n - 1
|
||||||
|
let paths = render3d_st.gltf_tex_paths
|
||||||
|
paths[i] = paths[last]
|
||||||
|
render3d_st.gltf_tex_ids[i] = render3d_st.gltf_tex_ids[last]
|
||||||
|
render3d_st.gltf_tex_refs[i] = render3d_st.gltf_tex_refs[last]
|
||||||
|
List.pop(paths)
|
||||||
|
render3d_st.gltf_tex_n = last
|
||||||
|
}
|
||||||
|
|
||||||
|
# a remembered material whose texture was freed is forgotten, so no later load borrows it
|
||||||
|
function gltf_mats_forget(render3d_st: mut Render3dState, id: int) -> void {
|
||||||
|
if render3d_st.gltf_mat_names == null { return }
|
||||||
|
var k = 0
|
||||||
|
while k < render3d_st.gltf_mat_n {
|
||||||
|
if render3d_st.gltf_mat_diff[k] == id or render3d_st.gltf_mat_nrm[k] == id or render3d_st.gltf_mat_arm[k] == id {
|
||||||
|
let last = render3d_st.gltf_mat_n - 1
|
||||||
|
let names = render3d_st.gltf_mat_names
|
||||||
|
names[k] = names[last]
|
||||||
|
render3d_st.gltf_mat_diff[k] = render3d_st.gltf_mat_diff[last]
|
||||||
|
render3d_st.gltf_mat_nrm[k] = render3d_st.gltf_mat_nrm[last]
|
||||||
|
render3d_st.gltf_mat_arm[k] = render3d_st.gltf_mat_arm[last]
|
||||||
|
List.pop(names)
|
||||||
|
render3d_st.gltf_mat_n = last
|
||||||
|
} else {
|
||||||
|
k += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Let a model go: every primitive's mesh, and its textures once no other model uses them. The
|
||||||
|
# caller says when - no actor may draw the model afterwards (a released model's prims are emptied,
|
||||||
|
# so a stale draw of it draws nothing).
|
||||||
|
function model_release(render3d_st: mut Render3dState, m: Model) -> void {
|
||||||
|
if m == null or m.prims == null { return }
|
||||||
|
for i in 0 .. len(m.prims) {
|
||||||
|
let pr = m.prims[i]
|
||||||
|
if pr.mesh != null { gpu_mesh_free(render3d_st, pr.mesh); pr.mesh = null }
|
||||||
|
gltf_tex_unref(render3d_st, pr.diff); gltf_tex_unref(render3d_st, pr.nrm); gltf_tex_unref(render3d_st, pr.arm)
|
||||||
|
pr.diff = 0; pr.nrm = 0; pr.arm = 0
|
||||||
|
}
|
||||||
|
let prims = m.prims
|
||||||
|
while len(prims) > 0 { List.pop(prims) }
|
||||||
|
}
|
||||||
|
|
@ -1903,8 +1903,9 @@ function gvk_pipeline_fast(render3d_st: mut Render3dState, p: int, m: Mesh, st:
|
||||||
return render3d_st.gvk_pc_pipe[k]
|
return render3d_st.gvk_pc_pipe[k]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
# a pipeline the driver refused is remembered as 0 too: retried on every draw, each try built its
|
||||||
|
# key and its create structs again and gave none of them back
|
||||||
let pipe = gvk_pipeline(render3d_st, p, m, st, n_color, color_fmt, depth_fmt, samples)
|
let pipe = gvk_pipeline(render3d_st, p, m, st, n_color, color_fmt, depth_fmt, samples)
|
||||||
if pipe == 0 { return pipe }
|
|
||||||
push(render3d_st.gvk_pc_prog, p); push(render3d_st.gvk_pc_layout, layout); push(render3d_st.gvk_pc_state, state)
|
push(render3d_st.gvk_pc_prog, p); push(render3d_st.gvk_pc_layout, layout); push(render3d_st.gvk_pc_state, state)
|
||||||
push(render3d_st.gvk_pc_bias, bias); push(render3d_st.gvk_pc_pass, pass); push(render3d_st.gvk_pc_pipe, pipe)
|
push(render3d_st.gvk_pc_bias, bias); push(render3d_st.gvk_pc_pass, pass); push(render3d_st.gvk_pc_pipe, pipe)
|
||||||
if p < 4096 { render3d_st.gvk_pc_last[p] = len(render3d_st.gvk_pc_prog) - 1 }
|
if p < 4096 { render3d_st.gvk_pc_last[p] = len(render3d_st.gvk_pc_prog) - 1 }
|
||||||
|
|
|
||||||
|
|
@ -409,7 +409,7 @@ function gvk_tex_read(render3d_st: mut Render3dState, tex: int, ifmt: int, w: in
|
||||||
let depth = gvk_is_depth(ifmt)
|
let depth = gvk_is_depth(ifmt)
|
||||||
let cb = gvk_once_begin(render3d_st)
|
let cb = gvk_once_begin(render3d_st)
|
||||||
gvk_barrier(render3d_st, cb, image, depth, 0, 1, 1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
|
gvk_barrier(render3d_st, cb, image, depth, 0, 1, 1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
|
||||||
let bic = bytes(VkBufferImageCopy_sizeof)
|
let bic = gvk_tmp(render3d_st, VkBufferImageCopy_sizeof)
|
||||||
Vk.zero(bic, VkBufferImageCopy_sizeof)
|
Vk.zero(bic, VkBufferImageCopy_sizeof)
|
||||||
if depth { Vk.put_i32(bic, VkBufferImageCopy_imageSubresource + VkImageSubresourceLayers_aspectMask, VK_IMAGE_ASPECT_DEPTH_BIT) }
|
if depth { Vk.put_i32(bic, VkBufferImageCopy_imageSubresource + VkImageSubresourceLayers_aspectMask, VK_IMAGE_ASPECT_DEPTH_BIT) }
|
||||||
else { Vk.put_i32(bic, VkBufferImageCopy_imageSubresource + VkImageSubresourceLayers_aspectMask, VK_IMAGE_ASPECT_COLOR_BIT) }
|
else { Vk.put_i32(bic, VkBufferImageCopy_imageSubresource + VkImageSubresourceLayers_aspectMask, VK_IMAGE_ASPECT_COLOR_BIT) }
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import "shadow.ludic"
|
||||||
import "post.ludic"
|
import "post.ludic"
|
||||||
import "quat.ludic"
|
import "quat.ludic"
|
||||||
import "gltf.ludic"
|
import "gltf.ludic"
|
||||||
|
import "gltf_release.ludic"
|
||||||
import "skin.ludic"
|
import "skin.ludic"
|
||||||
import "scatter.ludic"
|
import "scatter.ludic"
|
||||||
import "actor.ludic"
|
import "actor.ludic"
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ property Layer {
|
||||||
inst: floats, # INST_FLOATS per instance
|
inst: floats, # INST_FLOATS per instance
|
||||||
count: int = 0,
|
count: int = 0,
|
||||||
cap: int = 0,
|
cap: int = 0,
|
||||||
|
have: int = 0, # instances inst and scratch have room for now: grown toward cap as filled
|
||||||
near: float = 0.0, # float bits; instances beyond it draw as impostors (or not at all)
|
near: float = 0.0, # float bits; instances beyond it draw as impostors (or not at all)
|
||||||
cull: float = 0.0, # float bits; instances beyond it are skipped (0 = never)
|
cull: float = 0.0, # float bits; instances beyond it are skipped (0 = never)
|
||||||
buf: int = 0,
|
buf: int = 0,
|
||||||
|
|
@ -386,8 +387,11 @@ function layer_new(render3d_st: mut Render3dState, model: Model, cap: int, folia
|
||||||
l.near = near
|
l.near = near
|
||||||
l.cull = cull
|
l.cull = cull
|
||||||
l.tint = v3_new(1.0, 1.0, 1.0)
|
l.tint = v3_new(1.0, 1.0, 1.0)
|
||||||
l.inst = floats(cap * INST_FLOATS)
|
# room for what the layer holds, not for what it might: 45 layers at full capacity up front were
|
||||||
l.scratch = floats(cap * INST_FLOATS)
|
# 0.4 GB, most of it never filled (plan 23 of maroon-lake)
|
||||||
|
l.have = min(cap, 256)
|
||||||
|
l.inst = floats(l.have * INST_FLOATS)
|
||||||
|
l.scratch = floats(l.have * INST_FLOATS)
|
||||||
l.last_cam = v3_new(100000.0, 0.0, 0.0)
|
l.last_cam = v3_new(100000.0, 0.0, 0.0)
|
||||||
l.buf = gpu_buffer_new(render3d_st)
|
l.buf = gpu_buffer_new(render3d_st)
|
||||||
l.imp_buf = gpu_buffer_new(render3d_st)
|
l.imp_buf = gpu_buffer_new(render3d_st)
|
||||||
|
|
@ -398,8 +402,23 @@ function layer_new(render3d_st: mut Render3dState, model: Model, cap: int, folia
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# room for n instances (at most the layer's cap), doubling what there is so a fill costs a few copies
|
||||||
|
function layer_room(l: Layer, n: int) -> void {
|
||||||
|
if n <= l.have { return }
|
||||||
|
var want = max(l.have * 2, n)
|
||||||
|
if want > l.cap { want = l.cap }
|
||||||
|
let inst = floats(want * INST_FLOATS)
|
||||||
|
if l.count > 0 { mem_copy(data_of(inst), data_of(l.inst), l.count * INST_FLOATS * 4) }
|
||||||
|
free(l.inst)
|
||||||
|
free(l.scratch)
|
||||||
|
l.inst = inst
|
||||||
|
l.scratch = floats(want * INST_FLOATS)
|
||||||
|
l.have = want
|
||||||
|
}
|
||||||
|
|
||||||
function layer_add(l: Layer, x: float, y: float, z: float, scale: float, yaw: float, seed: float, wind: float) -> void {
|
function layer_add(l: Layer, x: float, y: float, z: float, scale: float, yaw: float, seed: float, wind: float) -> void {
|
||||||
if l.count >= l.cap { return }
|
if l.count >= l.cap { return }
|
||||||
|
layer_room(l, l.count + 1)
|
||||||
let o = l.count * INST_FLOATS
|
let o = l.count * INST_FLOATS
|
||||||
l.inst[o] = x; l.inst[o + 1] = y; l.inst[o + 2] = z; l.inst[o + 3] = scale
|
l.inst[o] = x; l.inst[o + 1] = y; l.inst[o + 2] = z; l.inst[o + 3] = scale
|
||||||
l.inst[o + 4] = Math.sin(yaw); l.inst[o + 5] = Math.cos(yaw); l.inst[o + 6] = seed; l.inst[o + 7] = wind
|
l.inst[o + 4] = Math.sin(yaw); l.inst[o + 5] = Math.cos(yaw); l.inst[o + 6] = seed; l.inst[o + 7] = wind
|
||||||
|
|
|
||||||
|
|
@ -162,16 +162,24 @@ function stream_evict(render3d_st: mut Render3dState, s: Stream) -> void {
|
||||||
}
|
}
|
||||||
t = lo
|
t = lo
|
||||||
# everything wanted by the walk in progress stays whatever the threshold says
|
# everything wanted by the walk in progress stays whatever the threshold says
|
||||||
let kept = new []Chunk
|
# compacted in place, and an evicted chunk goes whole: a new list per eviction and the chunks'
|
||||||
|
# own records were never given back
|
||||||
|
var w = 0
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < s.n {
|
while i < s.n {
|
||||||
let c = s.chunks[i]
|
let c = s.chunks[i]
|
||||||
if c.used >= t or c.used == render3d_st.stream_walk_no { push(kept, c) }
|
if c.used >= t or c.used == render3d_st.stream_walk_no {
|
||||||
else { if c.data != null { free(c.data) } }
|
s.chunks[w] = c
|
||||||
|
w += 1
|
||||||
|
} else {
|
||||||
|
if c.data != null { free(c.data) }
|
||||||
|
free(c)
|
||||||
|
}
|
||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
s.chunks = kept
|
let ch = s.chunks
|
||||||
s.n = len(kept)
|
while len(ch) > w { List.pop(ch) }
|
||||||
|
s.n = w
|
||||||
for h in 0 .. STREAM_HASH { s.htab[h] = 0 }
|
for h in 0 .. STREAM_HASH { s.htab[h] = 0 }
|
||||||
i = 0
|
i = 0
|
||||||
while i < s.n { s.keys[i] = s.chunks[i].key; stream_remember(s, s.chunks[i].key, i); i += 1 }
|
while i < s.n { s.keys[i] = s.chunks[i].key; stream_remember(s, s.chunks[i].key, i); i += 1 }
|
||||||
|
|
@ -246,6 +254,7 @@ function stream_update(render3d_st: mut Render3dState, s: Stream, cam_x: float,
|
||||||
}
|
}
|
||||||
if c != null and c.count > 0 and l.count + c.count <= l.cap and stream_chunk_visible(render3d_st, s, cx, cz, c) {
|
if c != null and c.count > 0 and l.count + c.count <= l.cap and stream_chunk_visible(render3d_st, s, cx, cz, c) {
|
||||||
let tg = gl_now_us()
|
let tg = gl_now_us()
|
||||||
|
layer_room(l, l.count + c.count)
|
||||||
mem_copy(mem_off(l.inst, l.count * INST_FLOATS * 4), data_of(c.data), c.count * INST_FLOATS * 4)
|
mem_copy(mem_off(l.inst, l.count * INST_FLOATS * 4), data_of(c.data), c.count * INST_FLOATS * 4)
|
||||||
l.count += c.count
|
l.count += c.count
|
||||||
render3d_st.stream_us_gather = render3d_st.stream_us_gather + (gl_now_us() - tg)
|
render3d_st.stream_us_gather = render3d_st.stream_us_gather + (gl_now_us() - tg)
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,9 @@ function terrain_ortho_forest(render3d_st: mut Render3dState, x: float, z: float
|
||||||
function terrain_use_ortho(render3d_st: mut Render3dState, path: string) -> void {
|
function terrain_use_ortho(render3d_st: mut Render3dState, path: string) -> void {
|
||||||
let px = png_decode(render3d_st, path)
|
let px = png_decode(render3d_st, path)
|
||||||
if px == null { return }
|
if px == null { return }
|
||||||
|
# a map set up over another lets go of the last one's photograph first
|
||||||
|
if render3d_st.ter_ortho_px != null { free(render3d_st.ter_ortho_px) }
|
||||||
|
if render3d_st.ter_ortho_tex != 0 { gpu_tex_free(render3d_st, render3d_st.ter_ortho_tex) }
|
||||||
render3d_st.ter_ortho_px = px
|
render3d_st.ter_ortho_px = px
|
||||||
render3d_st.ter_ortho_w = render3d_st.tex_w
|
render3d_st.ter_ortho_w = render3d_st.tex_w
|
||||||
render3d_st.ter_ortho_c = render3d_st.tex_channels
|
render3d_st.ter_ortho_c = render3d_st.tex_channels
|
||||||
|
|
@ -154,6 +157,7 @@ function terrain_use_ortho(render3d_st: mut Render3dState, path: string) -> void
|
||||||
gpu_tex_param(render3d_st, GPU_TEX2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
|
gpu_tex_param(render3d_st, GPU_TEX2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
|
||||||
}
|
}
|
||||||
function terrain_use_dem(render3d_st: mut Render3dState, path: string, emin: float, emax: float, base: float, ox: float, oz: float) -> void {
|
function terrain_use_dem(render3d_st: mut Render3dState, path: string, emin: float, emax: float, base: float, ox: float, oz: float) -> void {
|
||||||
|
if render3d_st.ter_dem_tex != 0 { gpu_tex_free(render3d_st, render3d_st.ter_dem_tex) }
|
||||||
render3d_st.ter_dem_tex = tex_load(render3d_st, path, false)
|
render3d_st.ter_dem_tex = tex_load(render3d_st, path, false)
|
||||||
render3d_st.ter_dem_min = emin; render3d_st.ter_dem_max = emax; render3d_st.ter_dem_base = base
|
render3d_st.ter_dem_min = emin; render3d_st.ter_dem_max = emax; render3d_st.ter_dem_base = base
|
||||||
render3d_st.ter_ox = ox; render3d_st.ter_oz = oz
|
render3d_st.ter_ox = ox; render3d_st.ter_oz = oz
|
||||||
|
|
@ -167,6 +171,8 @@ function terrain_generate(render3d_st: mut Render3dState) -> void {
|
||||||
if render3d_st.ter_dem_tex != 0 { defs = "#define DEM\n" }
|
if render3d_st.ter_dem_tex != 0 { defs = "#define DEM\n" }
|
||||||
if render3d_st.ter_smooth { defs = "#define SMOOTH\n" }
|
if render3d_st.ter_smooth { defs = "#define SMOOTH\n" }
|
||||||
let p = r3d_program(render3d_st, "fullscreen.vert", "heightgen.frag", defs)
|
let p = r3d_program(render3d_st, "fullscreen.vert", "heightgen.frag", defs)
|
||||||
|
# a map generated over another: its 256 MB height texture goes first
|
||||||
|
if render3d_st.ter_height_tex != 0 { gpu_tex_free(render3d_st, render3d_st.ter_height_tex); render3d_st.ter_height_tex = 0 }
|
||||||
render3d_st.ter_height_tex = tex_target(render3d_st, TERRAIN_RES, TERRAIN_RES, GL_R32F, GL_RED, GL_FLOAT, GL_LINEAR)
|
render3d_st.ter_height_tex = tex_target(render3d_st, TERRAIN_RES, TERRAIN_RES, GL_R32F, GL_RED, GL_FLOAT, GL_LINEAR)
|
||||||
let fbo = gpu_fb_new(render3d_st)
|
let fbo = gpu_fb_new(render3d_st)
|
||||||
gpu_fb_bind(render3d_st, fbo)
|
gpu_fb_bind(render3d_st, fbo)
|
||||||
|
|
@ -203,8 +209,9 @@ function terrain_generate(render3d_st: mut Render3dState) -> void {
|
||||||
mesh_draw(render3d_st, render3d_st.sky_fullscreen)
|
mesh_draw(render3d_st, render3d_st.sky_fullscreen)
|
||||||
gpu_program_free(render3d_st, pn)
|
gpu_program_free(render3d_st, pn)
|
||||||
gpu_tex_free(render3d_st, raw)
|
gpu_tex_free(render3d_st, raw)
|
||||||
# read the heights back for placement
|
# read the heights back for placement, into the array a previous map had: always the same size,
|
||||||
render3d_st.ter_heights = floats(TERRAIN_RES * TERRAIN_RES)
|
# and made anew on every build it was 64 MB lost per world swap
|
||||||
|
if render3d_st.ter_heights == null { render3d_st.ter_heights = floats(TERRAIN_RES * TERRAIN_RES) }
|
||||||
gpu_tex_bind(render3d_st, GPU_TEX2D, render3d_st.ter_height_tex)
|
gpu_tex_bind(render3d_st, GPU_TEX2D, render3d_st.ter_height_tex)
|
||||||
gpu_pixel_store(render3d_st, GL_PACK_ALIGNMENT, 4)
|
gpu_pixel_store(render3d_st, GL_PACK_ALIGNMENT, 4)
|
||||||
gpu_tex_read(render3d_st, GPU_TEX2D, GL_RED, GL_FLOAT, data_of(render3d_st.ter_heights))
|
gpu_tex_read(render3d_st, GPU_TEX2D, GL_RED, GL_FLOAT, data_of(render3d_st.ter_heights))
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,16 @@ import "ludic.base"
|
||||||
import "ludic.physics"
|
import "ludic.physics"
|
||||||
program VehiclesTest {
|
program VehiclesTest {
|
||||||
numbers float
|
numbers float
|
||||||
|
function pose(physics_st: mut PhysicsState, id: int) -> PhysPose {
|
||||||
|
let p = new PhysPose
|
||||||
|
if not phys_pose(physics_st, id, p) { return null }
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
function wpose(physics_st: mut PhysicsState, id: int) -> PhysWalk {
|
||||||
|
let p = new PhysWalk
|
||||||
|
if not phys_walker_pose(physics_st, id, p) { return null }
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
function fk_ground(x: float, z: float) -> float {
|
function fk_ground(x: float, z: float) -> float {
|
||||||
if x > 10.0 { return -(x - 10.0) }
|
if x > 10.0 { return -(x - 10.0) }
|
||||||
|
|
@ -95,7 +105,7 @@ program VehiclesTest {
|
||||||
fk_lake(physics_st)
|
fk_lake(physics_st)
|
||||||
if vehicles_test_st.legs < 0 { vehicles_test_st.legs = phys_walker_add(physics_st, 0.8, 1.7, x, y, z, 40.0, 450.0) }
|
if vehicles_test_st.legs < 0 { vehicles_test_st.legs = phys_walker_add(physics_st, 0.8, 1.7, x, y, z, 40.0, 450.0) }
|
||||||
phys_walker_move(physics_st, vehicles_test_st.legs, x, y, z, vx, vz, 0.0, 0.45, 40.0, dt)
|
phys_walker_move(physics_st, vehicles_test_st.legs, x, y, z, vx, vz, 0.0, 0.45, 40.0, dt)
|
||||||
let p = phys_walker_pose(physics_st, vehicles_test_st.legs)
|
let p = wpose(physics_st, vehicles_test_st.legs)
|
||||||
vehicles_test_st.lx = p.x
|
vehicles_test_st.lx = p.x
|
||||||
vehicles_test_st.ly = p.y
|
vehicles_test_st.ly = p.y
|
||||||
vehicles_test_st.lz = p.z
|
vehicles_test_st.lz = p.z
|
||||||
|
|
@ -120,7 +130,7 @@ program VehiclesTest {
|
||||||
function fk_drift(physics_st: mut PhysicsState, vehicles_test_st: VehiclesTestState, nid: int, fx: float, fz: float) -> void { phys_force(physics_st, vehicles_test_st.hull, fx, 0.0, fz) }
|
function fk_drift(physics_st: mut PhysicsState, vehicles_test_st: VehiclesTestState, nid: int, fx: float, fz: float) -> void { phys_force(physics_st, vehicles_test_st.hull, fx, 0.0, fz) }
|
||||||
function fk_read(physics_st: mut PhysicsState, vehicles_test_st: mut VehiclesTestState, nid: int) -> bool {
|
function fk_read(physics_st: mut PhysicsState, vehicles_test_st: mut VehiclesTestState, nid: int) -> bool {
|
||||||
if vehicles_test_st.hull < 0 or vehicles_test_st.hull_nid != nid { return false }
|
if vehicles_test_st.hull < 0 or vehicles_test_st.hull_nid != nid { return false }
|
||||||
let p = phys_pose(physics_st, vehicles_test_st.hull)
|
let p = pose(physics_st, vehicles_test_st.hull)
|
||||||
if p == null { return false }
|
if p == null { return false }
|
||||||
vehicles_test_st.hx = p.x
|
vehicles_test_st.hx = p.x
|
||||||
vehicles_test_st.hy = p.y
|
vehicles_test_st.hy = p.y
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,16 @@ function wl_react(wildlife_st: mut WildlifeState, a: WildAnimal, np: int, dt: fl
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if a.state == WILD_WARY {
|
if a.state == WILD_WARY {
|
||||||
wl_face(a, a.x + (a.x - a.sus_x), a.z + (a.z - a.sus_z), 2.6, dt)
|
wl_away(a, a.sus_x, a.sus_z, 20.0)
|
||||||
|
wl_head(a, a.tx, a.tz, 2.6, dt)
|
||||||
a.speed = s.walk * 0.8
|
a.speed = s.walk * 0.8
|
||||||
wl_step(wildlife_st, a, dt)
|
wl_step(wildlife_st, a, dt)
|
||||||
if a.timer < 0.0 { wl_wander(wildlife_st, a, 3.0 + wl_rnd(wildlife_st) * 4.0) }
|
if a.timer < 0.0 { wl_wander(wildlife_st, a, 3.0 + wl_rnd(wildlife_st) * 4.0) }
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if a.state == WILD_FLEE {
|
if a.state == WILD_FLEE {
|
||||||
wl_face(a, a.x + (a.x - WildlifeWorld.player_x(np)), a.z + (a.z - WildlifeWorld.player_z(np)), 5.0, dt)
|
wl_away(a, WildlifeWorld.player_x(np), WildlifeWorld.player_z(np), 40.0)
|
||||||
|
wl_head(a, a.tx, a.tz, 5.0, dt)
|
||||||
a.speed = s.run
|
a.speed = s.run
|
||||||
wl_step(wildlife_st, a, dt)
|
wl_step(wildlife_st, a, dt)
|
||||||
if a.timer < 0.0 {
|
if a.timer < 0.0 {
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,9 @@ export state WildlifeState {
|
||||||
wl_dice: Rng = rng_new(97)
|
wl_dice: Rng = rng_new(97)
|
||||||
wl_hold: bool = false # staged: nobody thinks, everybody keeps their speed
|
wl_hold: bool = false # staged: nobody thinks, everybody keeps their speed
|
||||||
wl_spot_ask: WildSpot = new WildSpot
|
wl_spot_ask: WildSpot = new WildSpot
|
||||||
|
wl_turned: int = 0 # steps refused and turned 75 degrees instead: the fallback, counted
|
||||||
|
wl_turned_wet: int = 0 # of those, for water ahead
|
||||||
|
wl_turned_steep: int = 0 # for a climb too steep
|
||||||
}
|
}
|
||||||
|
|
||||||
function wl_sp_all(wildlife_st: WildlifeState) -> []WildSpecies {
|
function wl_sp_all(wildlife_st: WildlifeState) -> []WildSpecies {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,12 @@ function wl_face(a: WildAnimal, tx: float, tz: float, rate: float, dt: float) ->
|
||||||
}
|
}
|
||||||
|
|
||||||
# true when it moved; a walker is slid round a still thing (a trunk, a tent), a bird is not
|
# true when it moved; a walker is slid round a still thing (a trunk, a tent), a bird is not
|
||||||
|
# how many steps were refused and turned from since the start: how often the way was not enough
|
||||||
|
export function wildlife_turned(wildlife_st: WildlifeState) -> int { return wildlife_st.wl_turned }
|
||||||
|
# of those, how many for water ahead and how many for a climb (the rest: pushed back by a still thing)
|
||||||
|
export function wildlife_turned_wet(wildlife_st: WildlifeState) -> int { return wildlife_st.wl_turned_wet }
|
||||||
|
export function wildlife_turned_steep(wildlife_st: WildlifeState) -> int { return wildlife_st.wl_turned_steep }
|
||||||
|
|
||||||
function wl_step(wildlife_st: mut WildlifeState, a: WildAnimal, dt: float) -> bool {
|
function wl_step(wildlife_st: mut WildlifeState, a: WildAnimal, dt: float) -> bool {
|
||||||
let d = a.speed * dt
|
let d = a.speed * dt
|
||||||
if d < 0.0001 { return false }
|
if d < 0.0001 { return false }
|
||||||
|
|
@ -26,9 +32,13 @@ function wl_step(wildlife_st: mut WildlifeState, a: WildAnimal, dt: float) -> bo
|
||||||
}
|
}
|
||||||
let nh = WildlifeWorld.ground(nx, nz)
|
let nh = WildlifeWorld.ground(nx, nz)
|
||||||
let moved = Math.abs(nx - a.x) + Math.abs(nz - a.z)
|
let moved = Math.abs(nx - a.x) + Math.abs(nz - a.z)
|
||||||
if nh < WildlifeWorld.water(nx, nz) + 0.6 or nh - a.y > d * 1.2 or moved < d * 0.2 {
|
let wet = nh < WildlifeWorld.water(nx, nz) + 0.6
|
||||||
|
let steep = nh - a.y > d * 1.2
|
||||||
|
if wet or steep or moved < d * 0.2 {
|
||||||
|
if wet { wildlife_st.wl_turned_wet += 1 } else if steep { wildlife_st.wl_turned_steep += 1 }
|
||||||
a.yaw = a.yaw + Math.deg_to_rad(75.0)
|
a.yaw = a.yaw + Math.deg_to_rad(75.0)
|
||||||
a.timer = 0.0
|
a.timer = 0.0
|
||||||
|
wildlife_st.wl_turned += 1
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
a.x = nx
|
a.x = nx
|
||||||
|
|
|
||||||
|
|
@ -103,4 +103,23 @@ program WayTest {
|
||||||
expect_near(a.tx, a.home_x + 7.0, 0.001)
|
expect_near(a.tx, a.home_x + 7.0, 0.001)
|
||||||
expect_near(a.tz, a.home_z + 3.0, 0.001)
|
expect_near(a.tz, a.home_z + 3.0, 0.001)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "it runs from the player by the way too: round the wall's end, not into it" (wildlife_st: mut WildlifeState, wildlife_tests_fake_st: mut WildlifeTestsFakeState, way_test_st: WayTestState) {
|
||||||
|
fk_setup(wildlife_st, wildlife_tests_fake_st)
|
||||||
|
let a = wildlife_make(wildlife_st, 0, 0.0, 0.0, false, 0.0, 0, 1.0, 1)
|
||||||
|
wildlife_tests_fake_st.fk_px[0] = 0.0
|
||||||
|
wildlife_tests_fake_st.fk_pz[0] = 20.0
|
||||||
|
wildlife_tests_fake_st.fk_hidden = true
|
||||||
|
a.state = WILD_FLEE
|
||||||
|
a.timer = 300.0
|
||||||
|
var east = -100.0
|
||||||
|
for k in 0 .. 1200 {
|
||||||
|
wildlife_tick(wildlife_st, 0.05, 0.0)
|
||||||
|
if a.z > -42.0 and a.z < -38.0 { east = Math.max(east, a.x) }
|
||||||
|
}
|
||||||
|
expect_eq(a.state, WILD_FLEE)
|
||||||
|
expect(east > 29.0)
|
||||||
|
expect(a.z < -45.0)
|
||||||
|
expect(way_test_st.asked > 10)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,3 +34,20 @@ function wl_pick_target(wildlife_st: WildlifeState, a: WildAnimal, radius: float
|
||||||
a.tz = WildlifeWorld.way_z()
|
a.tz = WildlifeWorld.way_z()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# a target d metres straight away from (fx, fz): where it runs or backs off to, by the world's way -
|
||||||
|
# round water and a cliff where there is one, and straight away as before where there is not
|
||||||
|
function wl_away(a: WildAnimal, fx: float, fz: float, d: float) -> void {
|
||||||
|
var dx = a.x - fx
|
||||||
|
var dz = a.z - fz
|
||||||
|
let l = Math.sqrt(dx * dx + dz * dz)
|
||||||
|
if l < 0.01 {
|
||||||
|
dx = -Math.sin(a.yaw)
|
||||||
|
dz = -Math.cos(a.yaw)
|
||||||
|
} else {
|
||||||
|
dx = dx / l
|
||||||
|
dz = dz / l
|
||||||
|
}
|
||||||
|
a.tx = a.x + dx * d
|
||||||
|
a.tz = a.z + dz * d
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -608,6 +608,13 @@ function headless_case(path: pointer, exp: pointer, label: pointer) -> void {
|
||||||
let got = capture_line(`{out} < /dev/null`)
|
let got = capture_line(`{out} < /dev/null`)
|
||||||
if got == exp { ok(label) } else { bad2(label, `got [{got}] want [{exp}]`) }
|
if got == exp { ok(label) } else { bad2(label, `got [{got}] want [{exp}]`) }
|
||||||
}
|
}
|
||||||
|
# a program built headless whose verdict is one of its lines (the renderer logs before it)
|
||||||
|
function headless_line_case(path: pointer, exp: pointer, label: pointer) -> void {
|
||||||
|
let out = `{tmp_dir()}/h_{flat(path)}`
|
||||||
|
if not shq(`bin/ludicc --headless examples/{path}.ludic -o {out} > {out}.log 2>&1`) { bad2(label, capture_line(`grep -i error {out}.log | head -1`)); return }
|
||||||
|
let got = capture_line(`{out} < /dev/null 2>&1 | grep -c '^{exp}$'`)
|
||||||
|
if got == "1" { ok(label) } else { bad2(label, capture_line(`{out} < /dev/null 2>&1 | grep -i fail | head -1`)) }
|
||||||
|
}
|
||||||
# ludic migrate state and components: the header names what every member needs, a field read in a
|
# ludic migrate state and components: the header names what every member needs, a field read in a
|
||||||
# member is not edited, a module named like a package keeps a state of its own, and the program
|
# member is not edited, a module named like a package keeps a state of its own, and the program
|
||||||
# runs as it did
|
# runs as it did
|
||||||
|
|
@ -1045,6 +1052,7 @@ function cmd_dev_test() -> int {
|
||||||
reject_case("rejected/state_component_ro", "look is read-only here (look: Look)", "a component's state is read-only unless its header says mut")
|
reject_case("rejected/state_component_ro", "look is read-only here (look: Look)", "a component's state is read-only unless its header says mut")
|
||||||
controller_case("state/component", "", " #10 big 10 1", "component.ludic (0.S: a component's header names its states; its getters, functions and events take them and the template never sees them)")
|
controller_case("state/component", "", " #10 big 10 1", "component.ludic (0.S: a component's header names its states; its getters, functions and events take them and the template never sees them)")
|
||||||
headless_case("rendering/ui_render3d", "ok", "ui_render3d.ludic (ludic.ui's render3d backend builds against the renderer)")
|
headless_case("rendering/ui_render3d", "ok", "ui_render3d.ludic (ludic.ui's render3d backend builds against the renderer)")
|
||||||
|
headless_line_case("rendering/release", "RELEASE OK", "release.ludic (model_release: meshes go, a shared texture stays until its last user goes)")
|
||||||
migrate_component_case()
|
migrate_component_case()
|
||||||
migrate_foreign_case()
|
migrate_foreign_case()
|
||||||
reject_case("rejected/runtime_type_clash", "PadButton is the runtime's enum", "a program's type named like one of the runtime's is refused")
|
reject_case("rejected/runtime_type_clash", "PadButton is the runtime's enum", "a program's type named like one of the runtime's is refused")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue