auto-git:

[add] src/rendering/terrain-layer-material.ts
 [add] tests/domain/terrains.test.ts
 [change] src/app/App.tsx
 [change] src/core/terrain-brush.ts
 [change] src/document/migrate-scene-document.ts
 [change] src/document/scene-document-validation.ts
 [change] src/document/scene-document.ts
 [change] src/document/terrains.ts
 [change] src/geometry/terrain-brush.ts
 [change] src/geometry/terrain-mesh.ts
 [change] src/runtime-three/rapier-collision-world.ts
 [change] src/runtime-three/runtime-host.ts
 [change] src/runtime-three/runtime-scene-build.ts
 [change] src/viewport-three/ViewportCanvas.tsx
 [change] src/viewport-three/ViewportPanel.tsx
 [change] src/viewport-three/viewport-host.ts
 [change] tests/domain/build-runtime-scene.test.ts
 [change] tests/domain/rapier-collision-world.test.ts
 [change] tests/domain/terrain.command.test.ts
 [change] tests/domain/water-material.test.ts
 [change] tests/geometry/terrain-brush.test.ts
 [change] tests/geometry/terrain-mesh.test.ts
 [change] tests/serialization/scene-document-json.test.ts
 [change] tests/unit/terrain-foundation.integration.test.tsx
 [change] tests/unit/viewport-canvas.test.tsx
This commit is contained in:
2026-04-20 02:36:38 +02:00
parent 02f6d058a0
commit 94dec56eb4
25 changed files with 2199 additions and 70 deletions

View File

@@ -19,6 +19,7 @@ import { getFirstPersonPlayerShapeSignature } from "./player-collision";
import type {
RuntimeBrushTriMeshCollider,
RuntimeNpcCollider,
RuntimeTerrainHeightfieldCollider,
RuntimeSceneCollider
} from "./runtime-scene-build";
@@ -86,7 +87,11 @@ function scaleBoundsCenter(bounds: { min: Vec3; max: Vec3 }, scale: Vec3): Vec3
};
}
function createRapierHeightfieldHeights(collider: GeneratedModelHeightfieldCollider): Float32Array {
function createRapierHeightfieldHeights(collider: {
rows: number;
cols: number;
heights: ArrayLike<number>;
}): Float32Array {
const heights = new Float32Array(collider.heights.length);
// Rapier's heightfield samples are column-major, with the Z axis varying
@@ -182,6 +187,45 @@ function attachTerrainModelCollider(world: RAPIER.World, collider: GeneratedMode
);
}
function attachTerrainCollider(
world: RAPIER.World,
collider: RuntimeTerrainHeightfieldCollider
) {
if (collider.rows < 2 || collider.cols < 2) {
throw new Error(
`Terrain collider ${collider.terrainId} must have at least a 2x2 height sample grid.`
);
}
const body = world.createRigidBody(
RAPIER.RigidBodyDesc.fixed().setTranslation(
collider.position.x,
collider.position.y,
collider.position.z
)
);
const rowSubdivisions = collider.rows - 1;
const colSubdivisions = collider.cols - 1;
world.createCollider(
RAPIER.ColliderDesc.heightfield(
rowSubdivisions,
colSubdivisions,
createRapierHeightfieldHeights(collider),
{
x: collider.maxX - collider.minX,
y: 1,
z: collider.maxZ - collider.minZ
}
).setTranslation(
(collider.minX + collider.maxX) * 0.5,
0,
(collider.minZ + collider.maxZ) * 0.5
),
body
);
}
function attachDynamicModelCollider(world: RAPIER.World, collider: GeneratedModelCompoundCollider) {
const body = createFixedBodyForModelCollider(world, collider);
@@ -385,6 +429,11 @@ export class RapierCollisionWorld {
});
for (const collider of colliders) {
if (collider.source === "terrain") {
attachTerrainCollider(world, collider);
continue;
}
if (collider.source === "brush") {
attachBrushCollider(world, collider);
continue;