[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
872 lines
19 KiB
TypeScript
872 lines
19 KiB
TypeScript
import { ShaderMaterial } from "three";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { MAX_BOX_BRUSH_WATER_FOAM_CONTACT_LIMIT } from "../../src/document/brushes";
|
|
import { createBoxBrush } from "../../src/document/brushes";
|
|
import { createTerrain } from "../../src/document/terrains";
|
|
import { buildBoxBrushDerivedMeshData } from "../../src/geometry/box-brush-mesh";
|
|
import { buildTerrainDerivedMeshData } from "../../src/geometry/terrain-mesh";
|
|
import { collectWaterContactPatches, createWaterMaterial } from "../../src/rendering/water-material";
|
|
|
|
describe("water material helpers", () => {
|
|
it("builds contact foam patches for bounds that cross the water surface", () => {
|
|
const patches = collectWaterContactPatches(
|
|
{
|
|
center: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
rotationDegrees: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
size: {
|
|
x: 10,
|
|
y: 2,
|
|
z: 8
|
|
}
|
|
},
|
|
[
|
|
{
|
|
min: {
|
|
x: -1,
|
|
y: 0.8,
|
|
z: -0.75
|
|
},
|
|
max: {
|
|
x: 1,
|
|
y: 1.35,
|
|
z: 0.75
|
|
}
|
|
}
|
|
]
|
|
);
|
|
|
|
expect(patches).toHaveLength(1);
|
|
expect(patches[0]?.shape).toBe("box");
|
|
expect(patches[0]?.x).toBeCloseTo(0, 5);
|
|
expect(patches[0]?.z).toBeCloseTo(0, 5);
|
|
expect(patches[0]?.halfWidth).toBeGreaterThan(0.9);
|
|
expect(patches[0]?.halfDepth).toBeGreaterThan(0.7);
|
|
});
|
|
|
|
it("ignores bounds that do not overlap the water surface band", () => {
|
|
const patches = collectWaterContactPatches(
|
|
{
|
|
center: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
rotationDegrees: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
size: {
|
|
x: 6,
|
|
y: 2,
|
|
z: 6
|
|
}
|
|
},
|
|
[
|
|
{
|
|
min: {
|
|
x: -1,
|
|
y: -3,
|
|
z: -1
|
|
},
|
|
max: {
|
|
x: 1,
|
|
y: -2,
|
|
z: 1
|
|
}
|
|
}
|
|
]
|
|
);
|
|
|
|
expect(patches).toHaveLength(0);
|
|
});
|
|
|
|
it("preserves oriented contact regions for rotated boxes", () => {
|
|
const patches = collectWaterContactPatches(
|
|
{
|
|
center: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
rotationDegrees: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
size: {
|
|
x: 10,
|
|
y: 2,
|
|
z: 10
|
|
}
|
|
},
|
|
[
|
|
{
|
|
kind: "orientedBox",
|
|
center: {
|
|
x: 0,
|
|
y: 1,
|
|
z: 0
|
|
},
|
|
rotationDegrees: {
|
|
x: 0,
|
|
y: 45,
|
|
z: 0
|
|
},
|
|
size: {
|
|
x: 2,
|
|
y: 0.4,
|
|
z: 1
|
|
}
|
|
}
|
|
]
|
|
);
|
|
|
|
expect(patches).toHaveLength(1);
|
|
expect(Math.abs(patches[0]?.axisX ?? 0)).toBeGreaterThan(0.65);
|
|
expect(Math.abs(patches[0]?.axisZ ?? 0)).toBeGreaterThan(0.65);
|
|
});
|
|
|
|
it("clips rotated contact regions to the water footprint", () => {
|
|
const centeredPatch = collectWaterContactPatches(
|
|
{
|
|
center: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
rotationDegrees: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
size: {
|
|
x: 4,
|
|
y: 2,
|
|
z: 4
|
|
}
|
|
},
|
|
[
|
|
{
|
|
kind: "orientedBox",
|
|
center: {
|
|
x: 0,
|
|
y: 1,
|
|
z: 0
|
|
},
|
|
rotationDegrees: {
|
|
x: 0,
|
|
y: 45,
|
|
z: 0
|
|
},
|
|
size: {
|
|
x: 3,
|
|
y: 0.4,
|
|
z: 1
|
|
}
|
|
}
|
|
]
|
|
)[0];
|
|
const clippedPatch = collectWaterContactPatches(
|
|
{
|
|
center: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
rotationDegrees: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
size: {
|
|
x: 4,
|
|
y: 2,
|
|
z: 4
|
|
}
|
|
},
|
|
[
|
|
{
|
|
kind: "orientedBox",
|
|
center: {
|
|
x: 2.2,
|
|
y: 1,
|
|
z: 0
|
|
},
|
|
rotationDegrees: {
|
|
x: 0,
|
|
y: 45,
|
|
z: 0
|
|
},
|
|
size: {
|
|
x: 3,
|
|
y: 0.4,
|
|
z: 1
|
|
}
|
|
}
|
|
]
|
|
)[0];
|
|
|
|
expect(centeredPatch).toBeDefined();
|
|
expect(clippedPatch).toBeDefined();
|
|
expect(clippedPatch?.x ?? 999).toBeLessThan(2);
|
|
expect((clippedPatch?.halfWidth ?? 0) * (clippedPatch?.halfDepth ?? 0)).toBeLessThan(
|
|
(centeredPatch?.halfWidth ?? 0) * (centeredPatch?.halfDepth ?? 0)
|
|
);
|
|
expect(Math.abs(clippedPatch?.axisX ?? 0)).toBeGreaterThan(0.65);
|
|
expect(Math.abs(clippedPatch?.axisZ ?? 0)).toBeGreaterThan(0.65);
|
|
});
|
|
|
|
it("creates a foam patch when a bounds source only touches the water footprint edge", () => {
|
|
const patches = collectWaterContactPatches(
|
|
{
|
|
center: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
rotationDegrees: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
size: {
|
|
x: 4,
|
|
y: 2,
|
|
z: 4
|
|
}
|
|
},
|
|
[
|
|
{
|
|
min: {
|
|
x: 2,
|
|
y: 0.8,
|
|
z: -0.7
|
|
},
|
|
max: {
|
|
x: 3,
|
|
y: 1.2,
|
|
z: 0.7
|
|
}
|
|
}
|
|
]
|
|
);
|
|
|
|
expect(patches).toHaveLength(1);
|
|
expect(patches[0]?.x).toBeCloseTo(2, 5);
|
|
expect(patches[0]?.halfWidth ?? 0).toBeGreaterThan(0.65);
|
|
expect(patches[0]?.halfDepth ?? 0).toBeGreaterThan(0);
|
|
});
|
|
|
|
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).toHaveLength(1);
|
|
expect(patches[0]?.shape).toBe("segment");
|
|
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);
|
|
expect(patches[0]?.halfDepth ?? 1).toBeLessThan(0.3);
|
|
});
|
|
|
|
it("creates foam for triangle mesh waterlines clipped to the water footprint edge", () => {
|
|
const patches = collectWaterContactPatches(
|
|
{
|
|
center: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
rotationDegrees: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
size: {
|
|
x: 4,
|
|
y: 2,
|
|
z: 4
|
|
}
|
|
},
|
|
[
|
|
{
|
|
kind: "triangleMesh",
|
|
vertices: new Float32Array([
|
|
1.6, -0.3, -0.8,
|
|
2.4, 0.3, -0.8,
|
|
2.4, 0.3, 0.8,
|
|
1.6, -0.3, 0.8
|
|
]),
|
|
indices: new Uint32Array([0, 1, 2, 0, 2, 3]),
|
|
transform: {
|
|
position: {
|
|
x: 0,
|
|
y: 1,
|
|
z: 0
|
|
},
|
|
rotationDegrees: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
scale: {
|
|
x: 1,
|
|
y: 1,
|
|
z: 1
|
|
}
|
|
}
|
|
}
|
|
]
|
|
);
|
|
|
|
expect(patches.length).toBeGreaterThan(0);
|
|
expect(patches[0]?.halfDepth ?? 0).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("uses narrow waterline bands for large sloped triangle surfaces", () => {
|
|
const patches = collectWaterContactPatches(
|
|
{
|
|
center: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
rotationDegrees: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
size: {
|
|
x: 12,
|
|
y: 2,
|
|
z: 12
|
|
}
|
|
},
|
|
[
|
|
{
|
|
kind: "triangleMesh",
|
|
mergeProfile: "aggressive",
|
|
vertices: new Float32Array([
|
|
-4, -1, -3,
|
|
4, 1.4, -3,
|
|
4, 1.4, 3,
|
|
-4, -1, 3
|
|
]),
|
|
indices: new Uint32Array([0, 1, 2, 0, 2, 3]),
|
|
transform: {
|
|
position: {
|
|
x: 0,
|
|
y: 1,
|
|
z: 0
|
|
},
|
|
rotationDegrees: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
scale: {
|
|
x: 1,
|
|
y: 1,
|
|
z: 1
|
|
}
|
|
}
|
|
}
|
|
]
|
|
);
|
|
|
|
expect(patches).toHaveLength(1);
|
|
expect(patches[0]?.shape).toBe("segment");
|
|
expect(patches[0]?.halfWidth ?? 0).toBeGreaterThan(1.5);
|
|
expect(patches[0]?.halfDepth ?? 1).toBeLessThan(0.3);
|
|
});
|
|
|
|
it("merges adjacent triangle mesh strips into one longer foam band", () => {
|
|
const patches = collectWaterContactPatches(
|
|
{
|
|
center: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
rotationDegrees: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
size: {
|
|
x: 10,
|
|
y: 2,
|
|
z: 10
|
|
}
|
|
},
|
|
[
|
|
{
|
|
kind: "triangleMesh",
|
|
mergeProfile: "aggressive",
|
|
vertices: new Float32Array([
|
|
-2, 0, -1,
|
|
0, 0, -1,
|
|
0, 0, 1,
|
|
-2, 0, 1,
|
|
2, 0, -1,
|
|
2, 0, 1
|
|
]),
|
|
indices: new Uint32Array([
|
|
0, 1, 2,
|
|
0, 2, 3,
|
|
1, 4, 5,
|
|
1, 5, 2
|
|
]),
|
|
transform: {
|
|
position: {
|
|
x: 0,
|
|
y: 1,
|
|
z: 0
|
|
},
|
|
rotationDegrees: {
|
|
x: 32,
|
|
y: 20,
|
|
z: 12
|
|
},
|
|
scale: {
|
|
x: 1,
|
|
y: 1,
|
|
z: 1
|
|
}
|
|
}
|
|
}
|
|
]
|
|
);
|
|
|
|
expect(patches).toHaveLength(1);
|
|
expect(patches[0]?.halfWidth ?? 0).toBeGreaterThan(1.2);
|
|
expect(patches[0]?.halfDepth ?? 0).toBeGreaterThan(0.05);
|
|
});
|
|
|
|
it("builds shoreline contact patches from authored terrain meshes", () => {
|
|
const terrain = createTerrain({
|
|
id: "terrain-waterline-main",
|
|
position: {
|
|
x: -4,
|
|
y: 0,
|
|
z: -4
|
|
},
|
|
sampleCountX: 5,
|
|
sampleCountZ: 5,
|
|
cellSize: 2,
|
|
heights: [
|
|
-1.2,
|
|
-0.8,
|
|
-0.4,
|
|
0,
|
|
0.4,
|
|
-1,
|
|
-0.6,
|
|
-0.2,
|
|
0.2,
|
|
0.6,
|
|
-0.8,
|
|
-0.4,
|
|
0,
|
|
0.4,
|
|
0.8,
|
|
-0.6,
|
|
-0.2,
|
|
0.2,
|
|
0.6,
|
|
1,
|
|
-0.4,
|
|
0,
|
|
0.4,
|
|
0.8,
|
|
1.2
|
|
]
|
|
});
|
|
const derivedMesh = buildTerrainDerivedMeshData(terrain);
|
|
const patches = collectWaterContactPatches(
|
|
{
|
|
center: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
rotationDegrees: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
size: {
|
|
x: 10,
|
|
y: 2,
|
|
z: 10
|
|
}
|
|
},
|
|
[
|
|
{
|
|
kind: "triangleMesh",
|
|
vertices: derivedMesh.positions,
|
|
indices: derivedMesh.indices,
|
|
mergeProfile: "aggressive",
|
|
transform: {
|
|
position: terrain.position,
|
|
rotationDegrees: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
scale: {
|
|
x: 1,
|
|
y: 1,
|
|
z: 1
|
|
}
|
|
}
|
|
}
|
|
]
|
|
);
|
|
|
|
expect(patches.length).toBeGreaterThan(0);
|
|
expect(patches[0]?.shape).toBe("segment");
|
|
expect(patches[0]?.halfWidth ?? 0).toBeGreaterThan(1);
|
|
});
|
|
|
|
it("keeps foam patches for both long and short edges of a box intersecting the water surface", () => {
|
|
const intersectingBox = createBoxBrush({
|
|
center: {
|
|
x: 0,
|
|
y: 1,
|
|
z: 0
|
|
},
|
|
size: {
|
|
x: 4,
|
|
y: 4,
|
|
z: 2
|
|
}
|
|
});
|
|
const derivedMesh = buildBoxBrushDerivedMeshData(intersectingBox);
|
|
|
|
const patches = collectWaterContactPatches(
|
|
{
|
|
center: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
rotationDegrees: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
size: {
|
|
x: 10,
|
|
y: 2,
|
|
z: 10
|
|
}
|
|
},
|
|
[
|
|
{
|
|
kind: "triangleMesh",
|
|
vertices: derivedMesh.colliderVertices,
|
|
indices: derivedMesh.colliderIndices,
|
|
transform: {
|
|
position: intersectingBox.center,
|
|
rotationDegrees: intersectingBox.rotationDegrees,
|
|
scale: {
|
|
x: 1,
|
|
y: 1,
|
|
z: 1
|
|
}
|
|
}
|
|
}
|
|
]
|
|
);
|
|
|
|
expect(patches).toHaveLength(1);
|
|
expect(patches[0]?.shape).toBe("box");
|
|
expect(patches[0]?.halfWidth ?? 0).toBeGreaterThan(1.7);
|
|
expect(patches[0]?.halfDepth ?? 0).toBeGreaterThan(0.7);
|
|
});
|
|
|
|
it("only uses aggressive merging for explicitly marked triangle meshes", () => {
|
|
const sharedSource = {
|
|
kind: "triangleMesh" as const,
|
|
vertices: new Float32Array([
|
|
-0.4, -0.3, -2,
|
|
-0.25, 0.3, -2,
|
|
-0.25, 0.3, 2,
|
|
-0.4, -0.3, 2,
|
|
0.25, -0.3, -2,
|
|
0.4, 0.3, -2,
|
|
0.4, 0.3, 2,
|
|
0.25, -0.3, 2
|
|
]),
|
|
indices: new Uint32Array([
|
|
0, 1, 2,
|
|
0, 2, 3,
|
|
4, 5, 6,
|
|
4, 6, 7
|
|
]),
|
|
transform: {
|
|
position: {
|
|
x: 0,
|
|
y: 1,
|
|
z: 0
|
|
},
|
|
rotationDegrees: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
scale: {
|
|
x: 1,
|
|
y: 1,
|
|
z: 1
|
|
}
|
|
}
|
|
};
|
|
|
|
const defaultPatches = collectWaterContactPatches(
|
|
{
|
|
center: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
rotationDegrees: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
size: {
|
|
x: 10,
|
|
y: 2,
|
|
z: 10
|
|
}
|
|
},
|
|
[sharedSource]
|
|
);
|
|
const aggressivePatches = collectWaterContactPatches(
|
|
{
|
|
center: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
rotationDegrees: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
size: {
|
|
x: 10,
|
|
y: 2,
|
|
z: 10
|
|
}
|
|
},
|
|
[{
|
|
...sharedSource,
|
|
mergeProfile: "aggressive" as const
|
|
}]
|
|
);
|
|
|
|
expect(defaultPatches.length).toBeGreaterThan(1);
|
|
expect(aggressivePatches).toHaveLength(1);
|
|
});
|
|
|
|
it("does not merge sharply bent triangle mesh strips even in aggressive mode", () => {
|
|
const patches = collectWaterContactPatches(
|
|
{
|
|
center: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
rotationDegrees: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
size: {
|
|
x: 10,
|
|
y: 2,
|
|
z: 10
|
|
}
|
|
},
|
|
[
|
|
{
|
|
kind: "triangleMesh",
|
|
mergeProfile: "aggressive",
|
|
vertices: new Float32Array([
|
|
-2, 0, -1,
|
|
0, 0, -1,
|
|
0, 0, 1,
|
|
-2, 0, 1,
|
|
2, 1.6, -1,
|
|
2, 1.6, 1
|
|
]),
|
|
indices: new Uint32Array([
|
|
0, 1, 2,
|
|
0, 2, 3,
|
|
1, 4, 5,
|
|
1, 5, 2
|
|
]),
|
|
transform: {
|
|
position: {
|
|
x: 0,
|
|
y: 1,
|
|
z: 0
|
|
},
|
|
rotationDegrees: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
scale: {
|
|
x: 1,
|
|
y: 1,
|
|
z: 1
|
|
}
|
|
}
|
|
}
|
|
]
|
|
);
|
|
|
|
expect(patches.length).toBeGreaterThan(1);
|
|
});
|
|
|
|
it("caps the authored foam contact patch count per water surface", () => {
|
|
const patches = collectWaterContactPatches(
|
|
{
|
|
center: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
rotationDegrees: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
size: {
|
|
x: 12,
|
|
y: 2,
|
|
z: 12
|
|
}
|
|
},
|
|
[
|
|
{ min: { x: -5, y: 0.8, z: -1 }, max: { x: -4, y: 1.2, z: 1 } },
|
|
{ min: { x: -3, y: 0.8, z: -1 }, max: { x: -2, y: 1.2, z: 1 } },
|
|
{ min: { x: -1, y: 0.8, z: -1 }, max: { x: 0, y: 1.2, z: 1 } },
|
|
{ min: { x: 1, y: 0.8, z: -1 }, max: { x: 2, y: 1.2, z: 1 } }
|
|
],
|
|
2
|
|
);
|
|
|
|
expect(patches).toHaveLength(2);
|
|
});
|
|
|
|
it("builds a shared quality shader material for visible tinted water", () => {
|
|
const result = createWaterMaterial({
|
|
colorHex: "#4da6d9",
|
|
surfaceOpacity: 0.55,
|
|
waveStrength: 0.35,
|
|
opacity: 0.71,
|
|
quality: true,
|
|
wireframe: false,
|
|
isTopFace: true,
|
|
time: 0,
|
|
halfSize: {
|
|
x: 4,
|
|
z: 4
|
|
},
|
|
contactPatches: [],
|
|
reflection: {
|
|
texture: null,
|
|
enabled: true
|
|
}
|
|
});
|
|
|
|
expect(result.material).toBeInstanceOf(ShaderMaterial);
|
|
|
|
const material = result.material as ShaderMaterial;
|
|
expect(material.transparent).toBe(true);
|
|
expect(material.fog).toBe(true);
|
|
expect(material.uniforms["fogColor"]).toBeDefined();
|
|
expect(material.uniforms["fogDensity"]).toBeDefined();
|
|
expect(material.uniforms["surfaceOpacity"]?.value).toBeGreaterThan(0.14);
|
|
expect(material.uniforms["waveStrength"]?.value).toBe(0.35);
|
|
expect(material.uniforms["surfaceDisplacementEnabled"]?.value).toBe(0);
|
|
expect(material.uniforms["isTopFace"]?.value).toBe(1);
|
|
expect(material.vertexShader).toContain("surfaceDisplacementEnabled");
|
|
expect(result.contactPatchesUniform?.value).toHaveLength(MAX_BOX_BRUSH_WATER_FOAM_CONTACT_LIMIT);
|
|
expect(result.contactPatchShapesUniform?.value).toHaveLength(MAX_BOX_BRUSH_WATER_FOAM_CONTACT_LIMIT);
|
|
expect(result.reflectionTextureUniform).not.toBeNull();
|
|
expect(result.reflectionMatrixUniform).not.toBeNull();
|
|
expect(result.reflectionEnabledUniform?.value).toBe(0);
|
|
expect(result.animationUniform).toBe(material.uniforms["time"]);
|
|
expect(result.reflectionEnabledUniform).toBe(material.uniforms["reflectionEnabled"]);
|
|
|
|
if (result.animationUniform !== null && result.reflectionEnabledUniform !== null) {
|
|
result.animationUniform.value = 2.5;
|
|
result.reflectionEnabledUniform.value = 0.36;
|
|
expect(material.uniforms["time"]?.value).toBe(2.5);
|
|
expect(material.uniforms["reflectionEnabled"]?.value).toBe(0.36);
|
|
}
|
|
});
|
|
});
|