Physics Mesh Terrain
App: apps/physics_mesh_terrain/
Demonstrates the static triangle-mesh collider (Physics.addStaticMesh) on arbitrary concave terrain. A parabolic, U-shaped trough is generated in Wren as a vertex + index grid, and that same geometry is used both as the render mesh and as the physics collider — so what you see is exactly what the ball collides with. A dynamic sphere is dropped onto the slope and rolls down into the bottom of the trough. There is no input and no blend file: the geometry is built procedurally, and the app takes a screenshot at ~2.4s and exits.
Run from root
Section titled “Run from root”% ./plume3d physics_mesh_terrainWhat it does
Section titled “What it does”- Builds the terrain in a
buildGrid_helper: a 17 × 9 grid of points overx ∈ [-6, 6],z ∈ [-4, 4], with height fromy = 0.14 * x²— a trough that is low atx = 0and rises on both sides.zdoes not affect height, so the shape is a constant cross-section channel. - Flattens the grid points into a
[x, y, z, x, y, z, ...]list and hands it, with the triangle index list, toPhysics.addStaticMesh(_scene, terrain, flat, idx)on aterrainnode at the origin. - Winding matters. Jolt mesh shapes are one-sided, so
buildGrid_emits each cell’s two triangles as(a, d, b)and(a, c, d)— wound so their normals face up, the side the ball collides from.buildRenderMesh_computes its face normals with the same cross-product convention, so the lit surface and the collider agree. - Feeds the same points and indices to
buildRenderMesh_, which expands them into non-indexed, flat-shaded triangles (12-float vertices —[x, y, z, nx, ny, nz, r, g, b, a, u, v]) viaGraphics.newMesh(verts, "triangles"). - Drops the ball: a
ballnode at(4.2, 6, 0)withPhysics.addDynamicSphere(_scene, _ball, 0.35, 0.1), tuned withPhysics.setRestitution(0.35),Physics.setFriction(0.3)andPhysics.setDamping(0.05, 0.2). Gravity is set explicitly withPhysics.setGravity(_scene, 0, -9.81, 0). - Draws the ball with a procedural UV sphere (
createSphereMesh, 12 stacks × 18 slices) positioned each frame by a translation matrix built from_ball.getPosition()— the terrain mesh is drawn with an identity matrix, since its vertices are already in world space. - Sets up the camera by hand:
_scene.addCamera(), taggedmain,setFovYRadians(0.72), near0.1/ far200, attached to acamnode at(6.5, 6.0, 12)aimed withcamNode.lookAt(0, 1.6, 0). - Uses
shaders/default(Graphics.loadShader+useShader) and drives view/projection manually per frame —setViewProjectionEnabled(true),setViewMatrix,setProjectionMatrix(aspect)— with aspect fromWindow.getWidth()/Window.getHeight(). - After 2.4 seconds — by which point the ball has landed and rolled toward the trough bottom — calls
Engine.screenshot("physics_mesh_terrain.png")and thenEngine.exit(0).
Key pattern
Section titled “Key pattern”// Build a point grid + triangle indices, wound for upward normals.buildGrid_(nx, nz, minX, maxX, minZ, maxZ, heightFn) { var pts = [] for (iz in 0...nz) { for (ix in 0...nx) { var x = minX + (maxX - minX) * ix / (nx - 1) var z = minZ + (maxZ - minZ) * iz / (nz - 1) pts.add([x, heightFn.call(x, z), z]) } } var idx = [] for (iz in 0...(nz - 1)) { for (ix in 0...(nx - 1)) { var a = iz * nx + ix var b = iz * nx + ix + 1 var c = (iz + 1) * nx + ix var d = (iz + 1) * nx + ix + 1 idx.add(a); idx.add(d); idx.add(b) // Jolt mesh shapes are one-sided — idx.add(a); idx.add(c); idx.add(d) // these wind normals upward. } } return [pts, idx]}
// y = 0.14*x^2 — a trough, low at x = 0.var grid = buildGrid_(17, 9, -6, 6, -4, 4, Fn.new { |x, z| 0.14 * x * x })var pts = grid[0]var idx = grid[1]
// Flatten to [x,y,z, x,y,z, ...] and use it as the collider.var flat = []for (p in pts) { flat.add(p[0]) flat.add(p[1]) flat.add(p[2])}var terrain = _scene.addNode("terrain")terrain.setPosition(0, 0, 0)Physics.addStaticMesh(_scene, terrain, flat, idx)
// The render mesh is built from the same pts/idx, so visuals match collision._terrainMesh = buildRenderMesh_(pts, idx, [0.30, 0.52, 0.34])- Physics —
addStaticMesh,addDynamicSphere,setGravity,setRestitution,setFriction,setDamping. - Scene —
new,addNode,addCamera,draw. - Node —
setPosition,getPosition,lookAt. - Camera —
setTag,setNode,setFovYRadians,setNearPlane,setFarPlane,getViewMatrix,getProjectionMatrix. - Graphics —
loadShader,useShader,newMesh,drawMesh,setViewProjectionEnabled,setViewMatrix,setProjectionMatrix. - Window —
getWidth,getHeight. - Math —
sqrt,sin,cos. - Engine —
screenshot,exit. - Logger —
info.