-- The 3D calls no other scene exercised, checked against what the Reference says of them: a node -- found by the name nodeSetName gave it, a crate lifted by bodyApplyForce, the simulation held -- still by physicsSetEnabled and let go again, a player put somewhere by playerSetPosition, an -- emitter deleted, navNearest snapping a point onto the floor and finding -- nothing far off, an agent that navAgentStop halts short of its target and that arrives when sent -- on (navAgentIsArrived), navAgentDelete, and a ragdoll that ragdollDelete frees for a new one. -- Each check prints "SCENE3D ok" or "SCENE3D FAIL". No disc: run with -C 720x480. dofile("Singe/Framework.singe") local font = fontLoad("Singe/FreeSansBold.ttf", 14) local stone = materialNew() local wood = materialNew() local camera = nodeNew() local sun = lightNew(LIGHT_DIRECTIONAL) local floor = nodeNew() local crate = nodeNew() local player = nodeNew() local walker = nodeNew() local fox = modelInstance(modelLoad("testScripts/Models/Fox.glb")) local nav = nil local agent = nil local frames = 0 local failures = 0 local stage = "lift" local mark = 0 local heldY = 0 local arrived = 0 local LAST = 900 local function check(name, ok, detail) if ok then debugPrint("SCENE3D ok " .. name) else failures = failures + 1 debugPrint("SCENE3D FAIL " .. name .. (detail and (" (" .. detail .. ")") or "")) end end function onNavArrived(which) if which == agent then arrived = arrived + 1 end end fontSelect(font) overlaySetResolution(720, 480) sceneEnable(true) cameraSet(camera) nodeSetPosition(camera, 0, 9, 14) nodeLookAt(camera, 0, 0, 0) nodeLookAt(sun, -1, -2, -1) materialSetColor(stone, 120, 120, 130) materialSetColor(wood, 170, 120, 70) nodeSetMesh(floor, meshBox(20, 0.2, 20), stone) nodeSetPosition(floor, 0, -0.1, 0) bodyNew(floor, BODY_STATIC, SHAPE_BOX, 20, 0.2, 20) nodeSetMesh(crate, meshBox(0.6, 0.6, 0.6), wood) nodeSetPosition(crate, -3, 0.3, 3) bodyNew(crate, BODY_DYNAMIC, SHAPE_BOX, 0.6, 0.6, 0.6) bodySetMass(crate, 1) -- So 40 newtons outweighs gravity nodeSetName(crate, "theCrate") check("nodeFind finds what nodeSetName named", nodeFind("theCrate") == crate) nodeSetMesh(player, meshCylinder(0.3, 1.0, 12), wood) nodeSetPosition(player, 0, 0.5, 0) playerNew(player, 0.3, 1.0) nodeSetMesh(walker, meshBox(0.4, 1.0, 0.4), wood) nodeSetPosition(walker, -6, 0, -4) nodeSetPosition(fox, 6, 0, -4) nodeSetScale(fox, 0.02) nav = navNew(0.3, 1.0, 45, 0.3) navAddNode(nav, floor) navBuild(nav) do local x, y, z = navNearest(nav, 1, 1.5, 1) check("navNearest snaps onto the floor", (x ~= nil) and (math.abs(x - 1) < 0.3) and (math.abs(z - 1) < 0.3) and (math.abs(y) < 0.3), string.format("%s %s %s", tostring(x), tostring(y), tostring(z))) check("navNearest finds nothing far off", navNearest(nav, 100, 0, 100) == nil) end agent = navAgentNew(nav, walker, 0.3, 1.0, 2.0) navAgentMoveTo(agent, 6, 0, 4) -- A dead handle ends the script rather than raising an error a pcall could catch, so these are -- proved by what follows them: a second ragdollNew on a node that still had one would end the run. emitterDelete(emitterNew()) check("emitterDelete takes the emitter", true) ragdollNew(fox) ragdollDelete(fox) ragdollNew(fox) check("ragdollDelete frees the node for a new ragdoll", true) ragdollDelete(fox) function onOverlayUpdate() local cx, cy, cz = nodeGetWorldPosition(crate) local wx, wy, wz = nodeGetWorldPosition(walker) frames = frames + 1 overlayClear() fontPrint(8, 6, string.format("frame %d %s crate y %.2f walker %.1f %.1f failures %d", frames, stage, cy, wx, wz, failures)) -- The crate is pushed up for a third of a second, then the simulation is held and let go. if stage == "lift" then bodyApplyForce(crate, 0, 40, 0) if frames == 15 then stage = "rising" end elseif stage == "rising" and frames == 18 then check("bodyApplyForce lifts the crate", cy > 0.6, string.format("y %.2f", cy)) physicsSetEnabled(false) stage = "held" mark = frames elseif stage == "held" then if frames == mark + 2 then heldY = cy elseif frames == mark + 12 then check("physicsSetEnabled(false) holds it still", math.abs(cy - heldY) < 0.001, string.format("%.3f then %.3f", heldY, cy)) physicsSetEnabled(true) stage = "resumed" mark = frames end elseif stage == "resumed" and frames == mark + 12 then check("physicsSetEnabled(true) lets it go", math.abs(cy - heldY) > 0.01, string.format("%.3f then %.3f", heldY, cy)) playerSetPosition(player, 5, 0.6, 5) stage = "teleported" mark = frames elseif stage == "teleported" and frames == mark + 3 then local px, _, pz = nodeGetWorldPosition(player) check("playerSetPosition puts the player there", (math.abs(px - 5) < 0.1) and (math.abs(pz - 5) < 0.1), string.format("%.2f %.2f", px, pz)) navAgentStop(agent) stage = "stopped" mark = frames elseif stage == "stopped" and frames == mark + 60 then check("navAgentStop halts it short", not navAgentIsArrived(agent) and (arrived == 0) and (math.abs(wx - 6) > 1), string.format("at %.1f %.1f", wx, wz)) navAgentMoveTo(agent, 6, 0, 4) stage = "sent" elseif stage == "sent" and arrived > 0 then check("navAgentIsArrived once it is there", navAgentIsArrived(agent)) navAgentDelete(agent) check("navAgentDelete leaves the walker where it was", (math.abs(wx - 6) < 1) and (math.abs(wz - 4) < 1), string.format("at %.1f %.1f", wx, wz)) stage = "done" end if (stage == "done") or (frames == LAST) then check("everything ran", stage == "done", stage) debugPrint(string.format("SCENE3D RESULT failures=%d frames=%d", failures, frames)) singeScreenshot("scene3d") singeQuit() end return OVERLAY_UPDATED end