-- A large heightmap is drawn in chunks, each culled alone, and looks as it did drawn whole. -- testScripts/ridges.png is 257 samples a side, so meshHeightmap makes 256 by 256 cells in 16 -- chunks of 64; a twin built cell for cell through meshNew from the same formula is one mesh -- with no chunks. sceneGetStats has to count 16 draws for the chunked one, fewer of them inside -- the view (the camera stands in the middle, so what is behind it is not drawn), and one for the -- twin. terrainGetHeight has to read the samples exactly, which a greyscale PNG did not until -- the loader's grey ramp was fixed. The screenshots chunked and whole should match: the -- triangles are the same and only the order the normals are summed in differs. Each check prints -- "CHUNKS ok" or "CHUNKS FAIL". No disc: run with -C 720x480. dofile("Singe/Framework.singe") local font = fontLoad("Singe/FreeSansBold.ttf", 14) local ground = materialNew() local camera = nodeNew() local sun = lightNew(LIGHT_DIRECTIONAL) local chunked = nodeNew() local whole = nodeNew() local frames = 0 local failures = 0 local SAMPLES = 257 local SIZE = 256 -- Across, so every vertex lands on a whole number local HEIGHT = 16 local LAST = 10 local function check(name, ok, detail) if ok then debugPrint("CHUNKS ok " .. name) else failures = failures + 1 debugPrint("CHUNKS FAIL " .. name .. (detail and (" (" .. detail .. ")") or "")) end end -- The sample at column x, row y, as the PNG holds it: the same formula made ridges.png. local function sample(x, y) return math.floor((0.5 + 0.25 * math.sin(x * 0.05) + 0.25 * math.cos(y * 0.07)) * 255 + 0.5) / 255 end -- The grid meshHeightmap makes, a vertex per sample with row 0 at -Z, wound to face up. local function twinMesh() local positions = {} local indices = {} local cells = SAMPLES - 1 for y = 0, cells do for x = 0, cells do positions[#positions + 1] = -SIZE / 2 + x positions[#positions + 1] = sample(x, y) * HEIGHT positions[#positions + 1] = -SIZE / 2 + y end end for y = 0, cells - 1 do for x = 0, cells - 1 do local a = y * SAMPLES + x + 1 local b = a + 1 local c = a + SAMPLES local d = c + 1 for _, index in ipairs({ a, d, b, a, c, d }) do indices[#indices + 1] = index end end end return meshNew(positions, nil, nil, indices) end fontSelect(font) overlaySetResolution(720, 480) sceneEnable(true) cameraSet(camera) nodeSetPosition(camera, 0, 30, 0) nodeLookAt(camera, 60, 6, 80) nodeLookAt(sun, -1, -2, -0.5) materialSetColor(ground, 110, 160, 90) nodeSetMesh(chunked, meshHeightmap("testScripts/ridges.png", SIZE, HEIGHT, SIZE), ground) nodeSetMesh(whole, twinMesh(), ground) nodeSetVisible(whole, false) do local got = terrainGetHeight(chunked, -SIZE / 2 + 10, -SIZE / 2 + 20) check("terrainGetHeight reads the samples", (got ~= nil) and (math.abs(got - sample(10, 20) * HEIGHT) < 0.001), tostring(got) .. " against " .. (sample(10, 20) * HEIGHT)) end function onOverlayUpdate() local total, drawn = sceneGetStats() frames = frames + 1 overlayClear() if frames == 3 then check("16 chunk draws", total == 16, tostring(total)) check("chunks out of view are culled", (drawn > 0) and (drawn < total), drawn .. " of " .. total) singeScreenshot("chunked") nodeSetVisible(chunked, false) nodeSetVisible(whole, true) elseif frames == 6 then check("the twin is one draw", total == 1, tostring(total)) singeScreenshot("whole") elseif frames == LAST then debugPrint(string.format("CHUNKS RESULT failures=%d", failures)) singeQuit() end return OVERLAY_UPDATED end