render3d: ground_fill's conformance test and the generator of its golden
tests/ground_fill_test.ludic compares gg_json (gf_hash over fixed inputs, Math.lerp cases whose order matters, the yaw as model.vert turns by it, a hand-written density, one cover chunk with a clearing and one solid chunk) line for line with tests/ground_fill_golden.json, and checks ground_fill draws exactly the candidates and a solid layer's far bands a subset of band 0. The golden is written by tests/gen/ground_fill_golden.ludic, run from the repository root; it is not committed here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e535879262
commit
54e267dfa6
6 changed files with 362 additions and 0 deletions
96
packages/ludic.render3d/tests/fakes/ground_golden.ludic
Normal file
96
packages/ludic.render3d/tests/fakes/ground_golden.ludic
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
# fakes/ground_golden.ludic - ground_fill's conformance fixture: one density layer written by hand (R and G, a
|
||||||
|
# block of raw tiles round the chunk, every other tile empty), a cover layer with a clearing and a solid layer
|
||||||
|
# grown over it, and chunk (3, -2) of a 32 m grid from world zero - across four tiles, at negative z.
|
||||||
|
const GG_CX: int = 3
|
||||||
|
const GG_CZ: int = -2
|
||||||
|
const GG_BAND: int = 1 # the cover layer's band: a step that does not divide the chunk, and band_grow
|
||||||
|
const GG_T0: int = 64 # the raw tiles: x 64..67, z 61..64 (the chunk reads x 65..66, z 62..63)
|
||||||
|
const GG_Z0: int = 61
|
||||||
|
const GG_TN: int = 4
|
||||||
|
|
||||||
|
# the density's texels as written, 0..255: R ramps through empty and full, G is a weave
|
||||||
|
function gg_r(tx: int, tz: int) -> int { return min(max((tx * 53 + tz * 97) % 320 - 32, 0), 255) }
|
||||||
|
function gg_g(tx: int, tz: int) -> int { return (tx * 29 + tz * 13) & 255 }
|
||||||
|
|
||||||
|
function gg_put(b: []byte, at: int, v: int) -> void {
|
||||||
|
for k in 0 .. 4 { b[at + k] = (v >> (k * 8)) & 255 }
|
||||||
|
}
|
||||||
|
|
||||||
|
# an LGD2 payload: one layer of two channels, the raw tiles after the index
|
||||||
|
function gg_payload() -> []byte {
|
||||||
|
let n = TERRAIN_RES / TT_TEX
|
||||||
|
let tb = GD_SIDE * GD_SIDE * 2
|
||||||
|
let at0 = GD_HEADW * 4 + n * n * 8
|
||||||
|
let size = at0 + GG_TN * GG_TN * tb
|
||||||
|
let b = buffer(size)
|
||||||
|
for k in 0 .. size { b[k] = 0 }
|
||||||
|
gg_put(b, 0, GD_MAGIC)
|
||||||
|
gg_put(b, 4, GD_VERSION)
|
||||||
|
gg_put(b, 8, n)
|
||||||
|
gg_put(b, 12, GD_SIDE)
|
||||||
|
gg_put(b, 16, 1)
|
||||||
|
gg_put(b, 32, 2)
|
||||||
|
gg_put(b, 36, GD_HEADW * 4)
|
||||||
|
for q in 0 .. GG_TN * GG_TN {
|
||||||
|
let tx = GG_T0 + q % GG_TN
|
||||||
|
let tz = GG_Z0 + q / GG_TN
|
||||||
|
let at = at0 + q * tb
|
||||||
|
gg_put(b, GD_HEADW * 4 + (tz * n + tx) * 8, at)
|
||||||
|
gg_put(b, GD_HEADW * 4 + (tz * n + tx) * 8 + 4, tb)
|
||||||
|
for p in 0 .. GD_SIDE * GD_SIDE {
|
||||||
|
let x = tx * GD_SIDE + p % GD_SIDE
|
||||||
|
let z = tz * GD_SIDE + p / GD_SIDE
|
||||||
|
b[at + p * 2] = gg_r(x, z)
|
||||||
|
b[at + p * 2 + 1] = gg_g(x, z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
# an 8 km map at world zero with no terrain (heights 0), the densities above, and the one clearing
|
||||||
|
function gg_open(render3d_st: mut Render3dState, tag: string) -> bool {
|
||||||
|
render3d_st.TERRAIN_HALF = 4096
|
||||||
|
render3d_st.ter_ox = 0.0
|
||||||
|
render3d_st.ter_oz = 0.0
|
||||||
|
render3d_st.gd_trample_cb = null
|
||||||
|
r3d_ground_clearings_reset(render3d_st)
|
||||||
|
r3d_ground_clearing(render3d_st, 112.0, -48.0, 3.0, 7.5, 0.25)
|
||||||
|
let path = Os.temp_dir() + "/r3d_ground_golden_" + tag + ".lgd2"
|
||||||
|
let b = gg_payload()
|
||||||
|
return Fs.write_bytes(path, b, len(b)) and gd_open_at(render3d_st, path, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
function gg_cover() -> GroundFill {
|
||||||
|
let g = new GroundFill
|
||||||
|
g.step0 = 2.0
|
||||||
|
g.step1 = 2.5
|
||||||
|
g.scale_lo = 0.6
|
||||||
|
g.scale_hi = 1.4
|
||||||
|
g.wind_lo = 0.5
|
||||||
|
g.scale_var = 0.2
|
||||||
|
g.band_grow = 0.15
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
function gg_solid() -> GroundFill {
|
||||||
|
let g = gg_cover()
|
||||||
|
g.step0 = 4.0
|
||||||
|
g.step1 = 5.0
|
||||||
|
g.step2 = 8.0
|
||||||
|
g.step3 = 12.0
|
||||||
|
g.jitter = 0.8
|
||||||
|
g.scale_lo = 0.8
|
||||||
|
g.scale_hi = 1.6
|
||||||
|
g.band_grow = 0.3
|
||||||
|
g.trample = false
|
||||||
|
g.solid = true
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
function gg_stream(render3d_st: mut Render3dState) -> Stream {
|
||||||
|
if render3d_st.stream_scratch == null { render3d_st.stream_scratch = floats(4096 * INST_FLOATS) }
|
||||||
|
let s = new Stream
|
||||||
|
s.size = 32.0
|
||||||
|
s.cur = new Chunk
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
# fakes/ground_golden_check.ludic - what ground_fill_test compares drawn instances by
|
||||||
|
|
||||||
|
# how far drawn instance k is from candidate c: 0 when every number is the same bits
|
||||||
|
function gft_off(render3d_st: Render3dState, k: int, c: GroundCand) -> int {
|
||||||
|
let o = k * INST_FLOATS
|
||||||
|
let st = render3d_st.stream_scratch
|
||||||
|
if st[o] != c.x or st[o + 2] != c.z or st[o + 3] != c.scale or st[o + 6] != c.seed or st[o + 7] != c.wind { return 1 }
|
||||||
|
if st[o + 4] != Math.sin(c.yaw) or st[o + 5] != Math.cos(c.yaw) { return 1 }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# drawn instances not at a listed thing's place
|
||||||
|
function gft_subset(render3d_st: Render3dState, n: int, l: GroundSolids) -> int {
|
||||||
|
var bad = 0
|
||||||
|
for k in 0 .. n {
|
||||||
|
var hit = false
|
||||||
|
for q in 0 .. l.n { hit = hit or (render3d_st.stream_scratch[k * INST_FLOATS] == l.xs[q] and render3d_st.stream_scratch[k * INST_FLOATS + 2] == l.zs[q]) }
|
||||||
|
if not hit { bad += 1 }
|
||||||
|
}
|
||||||
|
return bad
|
||||||
|
}
|
||||||
66
packages/ludic.render3d/tests/fakes/ground_golden_json.ludic
Normal file
66
packages/ludic.render3d/tests/fakes/ground_golden_json.ludic
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
# fakes/ground_golden_json.ludic - the golden's first half: every float is its IEEE single bits as an int, so
|
||||||
|
# the file round-trips exactly; Math.lerp's order, the yaw the model shader turns by, gf_hash and the density.
|
||||||
|
|
||||||
|
function gg_b(x: float) -> string { return `{float_bits(x)}` }
|
||||||
|
|
||||||
|
# Math.lerp is a + (b - a) * t; each case here is one a * (1 - t) + b * t would round differently
|
||||||
|
function gg_lerps() -> string {
|
||||||
|
let a = [1.0, 2.0, 6.04, -2.19]
|
||||||
|
let b = [1.7, 7.0, 1.72, 3.62]
|
||||||
|
let t = [0.9, 0.71, 0.639, 0.868]
|
||||||
|
var s = " \"lerp_order\": \"a + (b - a) * t\",\n \"lerp\": [\n"
|
||||||
|
for k in 0 .. 4 {
|
||||||
|
let other = a[k] * (1.0 - t[k]) + b[k] * t[k]
|
||||||
|
s = s + ` [{gg_b(a[k])}, {gg_b(b[k])}, {gg_b(t[k])}, {gg_b(Math.lerp(a[k], b[k], t[k]))}, {gg_b(other)}]`
|
||||||
|
if k < 3 { s = s + "," }
|
||||||
|
s = s + "\n"
|
||||||
|
}
|
||||||
|
return s + " ],\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
# model.vert: p' = (c x + s z, y, -s x + c z) with (s, c) = (sin yaw, cos yaw) as stream_emit stores them
|
||||||
|
function gg_yaw() -> string {
|
||||||
|
let y = 0.5
|
||||||
|
let sn = Math.sin(y)
|
||||||
|
let cs = Math.cos(y)
|
||||||
|
var s = ` "yaw": {{"yaw": {gg_b(y)}, "sin": {gg_b(sn)}, "cos": {gg_b(cs)}, "rule": "p' = (c*x + s*z, y, -s*x + c*z)",\n`
|
||||||
|
s = s + ` "x_axis": {gg_turn(sn, cs, 1.0, 0.0)}, "z_axis": {gg_turn(sn, cs, 0.0, 1.0)}}},\n`
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
# where local (x, 0, z) lands, as the shader turns it
|
||||||
|
function gg_turn(sn: float, cs: float, x: float, z: float) -> string { return `[{gg_b(cs * x + sn * z)}, {gg_b(0.0)}, {gg_b(-sn * x + cs * z)}]` }
|
||||||
|
|
||||||
|
function gg_hashes() -> string {
|
||||||
|
let v = [0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 0, 3, -2, 1, 7 * 65536 + 9, 2, 13, -128, 127, 3, 32767, 5,
|
||||||
|
63, -1, -1, 0, 21 * 65536 + 21, 7, -5, 100000, -100000, 2, 123456789, 1, 2147483647, -2147483647, 65536, 3, -1, 4]
|
||||||
|
var s = " \"hash\": [\n"
|
||||||
|
let n = len(v) / 6
|
||||||
|
for r in 0 .. n {
|
||||||
|
let o = r * 6
|
||||||
|
s = s + ` [{v[o]}, {v[o + 1]}, {v[o + 2]}, {v[o + 3]}, {v[o + 4]}, {v[o + 5]}, {gg_b(gf_hash(v[o], v[o + 1], v[o + 2], v[o + 3], v[o + 4], v[o + 5]))}]`
|
||||||
|
if r < n - 1 { s = s + "," }
|
||||||
|
s = s + "\n"
|
||||||
|
}
|
||||||
|
return s + " ],\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
# the texels the chunk's bilinear reads (and a border), R and G row by row from (tx0, tz0); none outside is read
|
||||||
|
function gg_density() -> string {
|
||||||
|
let tx0 = 2094
|
||||||
|
let tz0 = 2014
|
||||||
|
let w = 20
|
||||||
|
var r = ""
|
||||||
|
var g = ""
|
||||||
|
for k in 0 .. w * w {
|
||||||
|
if k > 0 {
|
||||||
|
r = r + ","
|
||||||
|
g = g + ","
|
||||||
|
}
|
||||||
|
r = r + `{gg_r(tx0 + k % w, tz0 + k / w)}`
|
||||||
|
g = g + `{gg_g(tx0 + k % w, tz0 + k / w)}`
|
||||||
|
}
|
||||||
|
var s = ` "density": {{"layer": 0, "half": 4096, "ter_ox": 0, "ter_oz": 0, "texel_m": 2, "rule": "fx = (x - ter_ox + half) / texel - 0.5, bilinear, / 255",\n`
|
||||||
|
s = s + ` "tx0": {tx0}, "tz0": {tz0}, "w": {w},\n "r": [{r}],\n "g": [{g}]}},\n`
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
# fakes/ground_golden_things.ludic - the golden's second half: the clearing, the layers' numbers and what each
|
||||||
|
# grows in the chunk (a cover layer at GG_BAND, a solid layer at band 0 with what band 2 still draws)
|
||||||
|
|
||||||
|
function gg_fill(g: GroundFill) -> string {
|
||||||
|
var s = `{{"layer": {g.layer}, "steps": [{gg_b(g.step0)}, {gg_b(g.step1)}, {gg_b(g.step2)}, {gg_b(g.step3)}], "jitter": {gg_b(g.jitter)}, `
|
||||||
|
s = s + `"scale_lo": {gg_b(g.scale_lo)}, "scale_hi": {gg_b(g.scale_hi)}, "wind_lo": {gg_b(g.wind_lo)}, "wind_hi": {gg_b(g.wind_hi)}, `
|
||||||
|
s = s + `"scale_var": {gg_b(g.scale_var)}, "band_grow": {gg_b(g.band_grow)}, "sink": {gg_b(g.sink)}, "trample": {g.trample}, "solid": {g.solid}}}`
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function gg_clearings(render3d_st: Render3dState) -> string {
|
||||||
|
var s = " \"clearings_rule\": \"floor + (1 - floor) * smoothstep((d - r_in) / (r_out - r_in)), 1 past r_out\",\n \"clearings\": ["
|
||||||
|
for c in 0 .. render3d_st.gd_clear_n {
|
||||||
|
let o = c * 5
|
||||||
|
let cl = render3d_st.gd_clear
|
||||||
|
if c > 0 { s = s + ", " }
|
||||||
|
s = s + `[{gg_b(cl[o])}, {gg_b(cl[o + 1])}, {gg_b(cl[o + 2])}, {gg_b(cl[o + 3])}, {gg_b(cl[o + 4])}]`
|
||||||
|
}
|
||||||
|
return s + "],\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
function gg_thing(c: GroundCand) -> string { return `{gg_b(c.x)}, {gg_b(c.z)}, {gg_b(c.scale)}, {gg_b(c.yaw)}, {gg_b(c.seed)}, {gg_b(c.wind)}` }
|
||||||
|
|
||||||
|
# the cover layer's things in cell order: [cell, x, z, scale, yaw, seed, wind]
|
||||||
|
function gg_cover_json(render3d_st: mut Render3dState) -> string {
|
||||||
|
let g = gg_cover()
|
||||||
|
let size = 32.0
|
||||||
|
let cells = int(Math.ceil(size / gf_step(g, GG_BAND)))
|
||||||
|
let c = new GroundCand
|
||||||
|
var s = ` "cover": {{"fill": {gg_fill(g)}, "ox": 0, "oz": 0, "size": {gg_b(size)}, "cx": {GG_CX}, "cz": {GG_CZ}, "band": {GG_BAND}, "things": [\n`
|
||||||
|
var first = true
|
||||||
|
for j in 0 .. cells {
|
||||||
|
for i in 0 .. cells {
|
||||||
|
if not ground_candidate(render3d_st, g, GG_CX, GG_CZ, GG_BAND, i, j, float(GG_CX) * size, float(GG_CZ) * size, size, c) { continue }
|
||||||
|
if not first { s = s + ",\n" }
|
||||||
|
first = false
|
||||||
|
s = s + ` [{c.cell}, {gg_thing(c)}]`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s + "\n ]}},\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
# the solid layer's things: [id, cell, x, z, scale, yaw, seed, wind, drawn at band 2]
|
||||||
|
function gg_solid_json(render3d_st: mut Render3dState) -> string {
|
||||||
|
let g = gg_solid()
|
||||||
|
let l = ground_solids_new(256)
|
||||||
|
ground_solid_list(render3d_st, g, 0.0, 0.0, 32.0, GG_CX, GG_CZ, l)
|
||||||
|
let c = new GroundCand
|
||||||
|
var s = ` "solid": {{"fill": {gg_fill(g)}, "ox": 0, "oz": 0, "size": {gg_b(32.0)}, "cx": {GG_CX}, "cz": {GG_CZ}, "far_band": 2, "things": [\n`
|
||||||
|
for k in 0 .. l.n {
|
||||||
|
ground_solid_at(render3d_st, g, 0.0, 0.0, 32.0, l.ids[k], c)
|
||||||
|
let far = gf_hash(g.layer, GG_CX, GG_CZ, 0, c.cell, 7) < gs_thin(g, 2)
|
||||||
|
if k > 0 { s = s + ",\n" }
|
||||||
|
s = s + ` [{c.id}, {c.cell}, {gg_thing(c)}, {far}]`
|
||||||
|
}
|
||||||
|
return s + "\n ]}}\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
# the whole golden, as ground_fill_golden.json holds it
|
||||||
|
function gg_json(render3d_st: mut Render3dState) -> string {
|
||||||
|
var s = "{\n \"about\": \"ludic.render3d ground_fill conformance (tests/ground_fill_test.ludic; regenerate with tests/gen/ground_fill_golden.ludic)\",\n"
|
||||||
|
s = s + " \"numbers\": \"every float is its IEEE-754 single-precision bits as a signed 32-bit int\",\n"
|
||||||
|
s = s + gg_lerps() + gg_yaw() + gg_hashes() + gg_density() + gg_clearings(render3d_st)
|
||||||
|
return s + gg_cover_json(render3d_st) + gg_solid_json(render3d_st) + "}\n"
|
||||||
|
}
|
||||||
21
packages/ludic.render3d/tests/gen/ground_fill_golden.ludic
Normal file
21
packages/ludic.render3d/tests/gen/ground_fill_golden.ludic
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# gen/ground_fill_golden.ludic - writes tests/ground_fill_golden.json from what ground_fill grows today. Run it
|
||||||
|
# from the repository root after a deliberate change to the fill, and commit the file with the change.
|
||||||
|
import "ludic.render3d/r3d.ludic"
|
||||||
|
program GroundFillGolden {
|
||||||
|
numbers float
|
||||||
|
import "../fakes/ground_golden.ludic"
|
||||||
|
import "../fakes/ground_golden_json.ludic"
|
||||||
|
import "../fakes/ground_golden_things.ludic"
|
||||||
|
# never called: naming Input links the engine's runtime, which render3d's textures stand on
|
||||||
|
function links_runtime() -> bool { return Input.key_pressed(0) }
|
||||||
|
|
||||||
|
entry (render3d_st: mut Render3dState) {
|
||||||
|
if not gg_open(render3d_st, "write") {
|
||||||
|
print("ground_fill_golden: the density could not be written")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let path = "packages/ludic.render3d/tests/ground_fill_golden.json"
|
||||||
|
let text = gg_json(render3d_st)
|
||||||
|
if Fs.write_text(path, text) { print(`ground_fill_golden: wrote {path}`) } else { print(`ground_fill_golden: could not write {path}`) }
|
||||||
|
}
|
||||||
|
}
|
||||||
93
packages/ludic.render3d/tests/ground_fill_test.ludic
Normal file
93
packages/ludic.render3d/tests/ground_fill_test.ludic
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
# ground_fill_test.ludic - ground_fill against its golden (ground_fill_golden.json, which the studio's own test
|
||||||
|
# reads too): the hash, Math.lerp's order, the yaw, one chunk of a cover layer and of a solid layer over a
|
||||||
|
# hand-written density - and ground_fill drawing exactly the candidates, a solid layer's far band a subset
|
||||||
|
import "ludic.render3d/r3d.ludic"
|
||||||
|
program GroundFillTest {
|
||||||
|
numbers float
|
||||||
|
import "fakes/ground_golden.ludic"
|
||||||
|
import "fakes/ground_golden_json.ludic"
|
||||||
|
import "fakes/ground_golden_things.ludic"
|
||||||
|
import "fakes/ground_golden_check.ludic"
|
||||||
|
# never called: naming Input links the engine's runtime, which render3d's textures stand on
|
||||||
|
function links_runtime() -> bool { return Input.key_pressed(0) }
|
||||||
|
|
||||||
|
test "the golden is what the generator writes today, number for number" (render3d_st: mut Render3dState) {
|
||||||
|
expect(gg_open(render3d_st, "golden"))
|
||||||
|
let want = Fs.read_text("packages/ludic.render3d/tests/ground_fill_golden.json")
|
||||||
|
expect(want != null)
|
||||||
|
if want == null { return }
|
||||||
|
let got = Text.split(gg_json(render3d_st), "\n")
|
||||||
|
let had = Text.split(want, "\n")
|
||||||
|
expect_eq(len(got), len(had))
|
||||||
|
for k in 0 .. min(len(got), len(had)) {
|
||||||
|
if got[k] != had[k] { print(`line {k + 1}: got {got[k]}`) }
|
||||||
|
expect(got[k] == had[k])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Math.lerp is a + (b - a) * t" {
|
||||||
|
let a = [1.0, 2.0, 6.04, -2.19]
|
||||||
|
let b = [1.7, 7.0, 1.72, 3.62]
|
||||||
|
let t = [0.9, 0.71, 0.639, 0.868]
|
||||||
|
for k in 0 .. 4 { expect_eq(float_bits(Math.lerp(a[k], b[k], t[k])), float_bits(a[k] + (b[k] - a[k]) * t[k])) }
|
||||||
|
}
|
||||||
|
|
||||||
|
test "ground_fill draws exactly the cover layer's candidates, in cell order" (render3d_st: mut Render3dState) {
|
||||||
|
expect(gg_open(render3d_st, "cover"))
|
||||||
|
let g = gg_cover()
|
||||||
|
let s = gg_stream(render3d_st)
|
||||||
|
ground_fill(render3d_st, s, GG_CX, GG_CZ, GG_BAND, g)
|
||||||
|
let cells = int(Math.ceil(32.0 / gf_step(g, GG_BAND)))
|
||||||
|
let c = new GroundCand
|
||||||
|
var n = 0
|
||||||
|
var off = 0
|
||||||
|
for j in 0 .. cells {
|
||||||
|
for i in 0 .. cells {
|
||||||
|
if not ground_candidate(render3d_st, g, GG_CX, GG_CZ, GG_BAND, i, j, 96.0, -64.0, 32.0, c) { continue }
|
||||||
|
off += gft_off(render3d_st, n, c)
|
||||||
|
n += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(n > 20)
|
||||||
|
expect_eq(s.cur.count, n)
|
||||||
|
expect_eq(off, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
test "a solid layer stands still: every band draws a subset of band 0, listed and answered by id" (render3d_st: mut Render3dState) {
|
||||||
|
expect(gg_open(render3d_st, "solid"))
|
||||||
|
let g = gg_solid()
|
||||||
|
let l = ground_solids_new(256)
|
||||||
|
let n = ground_solid_list(render3d_st, g, 0.0, 0.0, 32.0, GG_CX, GG_CZ, l)
|
||||||
|
expect(n > 8)
|
||||||
|
expect_eq(l.n, n)
|
||||||
|
let c = new GroundCand
|
||||||
|
var off = 0
|
||||||
|
var far = 0
|
||||||
|
for k in 0 .. l.n {
|
||||||
|
expect(ground_solid_at(render3d_st, g, 0.0, 0.0, 32.0, l.ids[k], c))
|
||||||
|
if c.x != l.xs[k] or c.z != l.zs[k] or c.scale != l.scales[k] or c.yaw != l.yaws[k] { off += 1 }
|
||||||
|
if gf_hash(g.layer, GG_CX, GG_CZ, 0, c.cell, 7) < gs_thin(g, 2) { far += 1 }
|
||||||
|
}
|
||||||
|
expect_eq(off, 0)
|
||||||
|
for band in 0 .. 4 {
|
||||||
|
let s = gg_stream(render3d_st)
|
||||||
|
ground_fill(render3d_st, s, GG_CX, GG_CZ, band, g)
|
||||||
|
expect_eq(gft_subset(render3d_st, s.cur.count, l), 0)
|
||||||
|
if band == 0 { expect_eq(s.cur.count, n) }
|
||||||
|
if band == 2 { expect_eq(s.cur.count, far) }
|
||||||
|
}
|
||||||
|
expect(far < n)
|
||||||
|
expect(not ground_solid_at(render3d_st, g, 0.0, 0.0, 32.0, ground_solid_id(g.layer + 1, GG_CX, GG_CZ, 0, 0, 8), c))
|
||||||
|
}
|
||||||
|
|
||||||
|
test "a solid id holds its layer and chunk, and refuses what it cannot hold" {
|
||||||
|
let id = ground_solid_id(63, -128, 127, 21, 21, 22)
|
||||||
|
expect(id > 0)
|
||||||
|
expect_eq(ground_solid_layer(id), 63)
|
||||||
|
expect_eq(ground_solid_cx(id), -128)
|
||||||
|
expect_eq(ground_solid_cz(id), 127)
|
||||||
|
expect_eq(ground_solid_id(0, 128, 0, 0, 0, 8), -1)
|
||||||
|
expect_eq(ground_solid_id(0, 0, 0, 0, 0, 23), -1)
|
||||||
|
expect_eq(ground_solid_id(64, 0, 0, 0, 0, 8), -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue