Update collision detection to handle triangle meshes and transformed colliders
This commit is contained in:
@@ -592,19 +592,36 @@ export class RuntimeHost {
|
||||
}
|
||||
collectRuntimeStaticWaterContactPatches(brush) {
|
||||
const contactBounds = [];
|
||||
for (const otherBrush of this.runtimeScene?.brushes ?? []) {
|
||||
if (otherBrush.id === brush.id || otherBrush.volume.mode !== "none") {
|
||||
const runtimeBrushesById = new Map((this.runtimeScene?.brushes ?? []).map((runtimeBrush) => [runtimeBrush.id, runtimeBrush]));
|
||||
for (const collider of this.runtimeScene?.colliders ?? []) {
|
||||
if (collider.source === "brush") {
|
||||
const otherBrush = runtimeBrushesById.get(collider.brushId);
|
||||
if (otherBrush === undefined || otherBrush.id === brush.id || otherBrush.volume.mode !== "none") {
|
||||
continue;
|
||||
}
|
||||
contactBounds.push({
|
||||
kind: "triangleMesh",
|
||||
vertices: collider.vertices,
|
||||
indices: collider.indices,
|
||||
transform: {
|
||||
position: collider.center,
|
||||
rotationDegrees: collider.rotationDegrees,
|
||||
scale: {
|
||||
x: 1,
|
||||
y: 1,
|
||||
z: 1
|
||||
}
|
||||
}
|
||||
});
|
||||
continue;
|
||||
}
|
||||
contactBounds.push({
|
||||
kind: "orientedBox",
|
||||
center: otherBrush.center,
|
||||
rotationDegrees: otherBrush.rotationDegrees,
|
||||
size: otherBrush.size
|
||||
});
|
||||
}
|
||||
for (const collider of this.runtimeScene?.colliders ?? []) {
|
||||
if (collider.source !== "modelInstance") {
|
||||
if (collider.kind === "trimesh") {
|
||||
contactBounds.push({
|
||||
kind: "triangleMesh",
|
||||
vertices: collider.vertices,
|
||||
indices: collider.indices,
|
||||
transform: collider.transform
|
||||
});
|
||||
continue;
|
||||
}
|
||||
contactBounds.push({
|
||||
|
||||
@@ -815,21 +815,40 @@ export class RuntimeHost {
|
||||
private collectRuntimeStaticWaterContactPatches(brush: RuntimeBoxBrushInstance) {
|
||||
const contactBounds: Parameters<typeof collectWaterContactPatches>[1] = [];
|
||||
|
||||
for (const otherBrush of this.runtimeScene?.brushes ?? []) {
|
||||
if (otherBrush.id === brush.id || otherBrush.volume.mode !== "none") {
|
||||
const runtimeBrushesById = new Map((this.runtimeScene?.brushes ?? []).map((runtimeBrush) => [runtimeBrush.id, runtimeBrush]));
|
||||
|
||||
for (const collider of this.runtimeScene?.colliders ?? []) {
|
||||
if (collider.source === "brush") {
|
||||
const otherBrush = runtimeBrushesById.get(collider.brushId);
|
||||
|
||||
if (otherBrush === undefined || otherBrush.id === brush.id || otherBrush.volume.mode !== "none") {
|
||||
continue;
|
||||
}
|
||||
|
||||
contactBounds.push({
|
||||
kind: "triangleMesh",
|
||||
vertices: collider.vertices,
|
||||
indices: collider.indices,
|
||||
transform: {
|
||||
position: collider.center,
|
||||
rotationDegrees: collider.rotationDegrees,
|
||||
scale: {
|
||||
x: 1,
|
||||
y: 1,
|
||||
z: 1
|
||||
}
|
||||
}
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
contactBounds.push({
|
||||
kind: "orientedBox",
|
||||
center: otherBrush.center,
|
||||
rotationDegrees: otherBrush.rotationDegrees,
|
||||
size: otherBrush.size
|
||||
});
|
||||
}
|
||||
|
||||
for (const collider of this.runtimeScene?.colliders ?? []) {
|
||||
if (collider.source !== "modelInstance") {
|
||||
if (collider.kind === "trimesh") {
|
||||
contactBounds.push({
|
||||
kind: "triangleMesh",
|
||||
vertices: collider.vertices,
|
||||
indices: collider.indices,
|
||||
transform: collider.transform
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -2204,11 +2204,20 @@ export class ViewportHost {
|
||||
if (brush.id === excludedBrushId || brush.volume.mode !== "none") {
|
||||
continue;
|
||||
}
|
||||
const derivedMesh = buildBoxBrushDerivedMeshData(brush);
|
||||
contactBounds.push({
|
||||
kind: "orientedBox",
|
||||
center: brush.center,
|
||||
rotationDegrees: brush.rotationDegrees,
|
||||
size: brush.size
|
||||
kind: "triangleMesh",
|
||||
vertices: derivedMesh.colliderVertices,
|
||||
indices: derivedMesh.colliderIndices,
|
||||
transform: {
|
||||
position: brush.center,
|
||||
rotationDegrees: brush.rotationDegrees,
|
||||
scale: {
|
||||
x: 1,
|
||||
y: 1,
|
||||
z: 1
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
for (const modelInstance of getModelInstances(document.modelInstances)) {
|
||||
@@ -2222,7 +2231,17 @@ export class ViewportHost {
|
||||
try {
|
||||
const generatedCollider = buildGeneratedModelCollider(modelInstance, asset, this.loadedModelAssets[modelInstance.assetId]);
|
||||
if (generatedCollider !== null) {
|
||||
contactBounds.push(generatedCollider.worldBounds);
|
||||
if (generatedCollider.kind === "trimesh") {
|
||||
contactBounds.push({
|
||||
kind: "triangleMesh",
|
||||
vertices: generatedCollider.vertices,
|
||||
indices: generatedCollider.indices,
|
||||
transform: generatedCollider.transform
|
||||
});
|
||||
}
|
||||
else {
|
||||
contactBounds.push(generatedCollider.worldBounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
|
||||
@@ -3013,11 +3013,21 @@ export class ViewportHost {
|
||||
continue;
|
||||
}
|
||||
|
||||
const derivedMesh = buildBoxBrushDerivedMeshData(brush);
|
||||
|
||||
contactBounds.push({
|
||||
kind: "orientedBox",
|
||||
center: brush.center,
|
||||
rotationDegrees: brush.rotationDegrees,
|
||||
size: brush.size
|
||||
kind: "triangleMesh",
|
||||
vertices: derivedMesh.colliderVertices,
|
||||
indices: derivedMesh.colliderIndices,
|
||||
transform: {
|
||||
position: brush.center,
|
||||
rotationDegrees: brush.rotationDegrees,
|
||||
scale: {
|
||||
x: 1,
|
||||
y: 1,
|
||||
z: 1
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3036,7 +3046,16 @@ export class ViewportHost {
|
||||
const generatedCollider = buildGeneratedModelCollider(modelInstance, asset, this.loadedModelAssets[modelInstance.assetId]);
|
||||
|
||||
if (generatedCollider !== null) {
|
||||
contactBounds.push(generatedCollider.worldBounds);
|
||||
if (generatedCollider.kind === "trimesh") {
|
||||
contactBounds.push({
|
||||
kind: "triangleMesh",
|
||||
vertices: generatedCollider.vertices,
|
||||
indices: generatedCollider.indices,
|
||||
transform: generatedCollider.transform
|
||||
});
|
||||
} else {
|
||||
contactBounds.push(generatedCollider.worldBounds);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Validation already surfaces unsupported collider modes; the viewport keeps rendering.
|
||||
|
||||
@@ -220,6 +220,62 @@ describe("water material helpers", () => {
|
||||
expect(Math.abs(clippedPatch?.axisZ ?? 0)).toBeGreaterThan(0.65);
|
||||
});
|
||||
|
||||
it("builds contact patches for transformed triangle meshes that cross the water surface", () => {
|
||||
const patches = collectWaterContactPatches(
|
||||
{
|
||||
center: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
rotationDegrees: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
size: {
|
||||
x: 8,
|
||||
y: 2,
|
||||
z: 8
|
||||
}
|
||||
},
|
||||
[
|
||||
{
|
||||
kind: "triangleMesh",
|
||||
vertices: new Float32Array([
|
||||
-1, 0, -1,
|
||||
1, 0, -1,
|
||||
1, 0, 1,
|
||||
-1, 0, 1
|
||||
]),
|
||||
indices: new Uint32Array([0, 1, 2, 0, 2, 3]),
|
||||
transform: {
|
||||
position: {
|
||||
x: 0,
|
||||
y: 1,
|
||||
z: 0
|
||||
},
|
||||
rotationDegrees: {
|
||||
x: 35,
|
||||
y: 28,
|
||||
z: 18
|
||||
},
|
||||
scale: {
|
||||
x: 2,
|
||||
y: 1,
|
||||
z: 1.4
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
expect(patches.length).toBeGreaterThan(0);
|
||||
expect(patches[0]?.halfWidth ?? 0).toBeGreaterThan(0.2);
|
||||
expect(Math.abs(patches[0]?.axisX ?? 0)).toBeGreaterThan(0.2);
|
||||
expect(Math.abs(patches[0]?.axisZ ?? 0)).toBeGreaterThan(0.2);
|
||||
});
|
||||
|
||||
it("builds a shared quality shader material for visible tinted water", () => {
|
||||
const result = createWaterMaterial({
|
||||
colorHex: "#4da6d9",
|
||||
|
||||
Reference in New Issue
Block a user