diff --git a/src/rendering/water-material.ts b/src/rendering/water-material.ts index 4aa52aad..84f67413 100644 --- a/src/rendering/water-material.ts +++ b/src/rendering/water-material.ts @@ -100,7 +100,7 @@ function createInverseVolumeRotation(rotationDegrees: Vec3) { .invert(); } -export function collectWaterContactPatches(volume: OrientedWaterVolume, contactBounds: WaterContactBounds[]): WaterContactPatch[] { +export function collectWaterContactPatches(volume: OrientedWaterVolume, contactBounds: WaterContactSource[]): WaterContactPatch[] { const inverseRotation = createInverseVolumeRotation(volume.rotationDegrees); const halfX = Math.max(volume.size.x * 0.5, WATER_CONTACT_EPSILON); const halfY = Math.max(volume.size.y * 0.5, WATER_CONTACT_EPSILON); diff --git a/src/runtime-three/runtime-host.ts b/src/runtime-three/runtime-host.ts index c4a03e57..1eaa90e4 100644 --- a/src/runtime-three/runtime-host.ts +++ b/src/runtime-three/runtime-host.ts @@ -38,7 +38,7 @@ import { resolveBoxVolumeRenderPaths, type ResolvedBoxVolumeRenderPaths } from "../rendering/advanced-rendering"; -import { collectWaterContactPatches, createWaterContactPatchUniformValue, createWaterMaterial } from "../rendering/water-material"; +import { collectWaterContactPatches, createWaterContactPatchAxisUniformValue, createWaterContactPatchUniformValue, createWaterMaterial } from "../rendering/water-material"; import { areAdvancedRenderingSettingsEqual, cloneAdvancedRenderingSettings, @@ -71,6 +71,7 @@ interface LocalLightRenderObjects { interface RuntimeWaterContactUniformBinding { brush: RuntimeBoxBrushInstance; uniform: { value: import("three").Vector4[] }; + axisUniform: { value: import("three").Vector2[] }; staticContactPatches: ReturnType; } @@ -631,10 +632,11 @@ export class RuntimeHost { this.volumeAnimatedUniforms.push(waterMaterial.animationUniform); } - if (faceId === "posY" && waterMaterial.contactPatchesUniform !== null) { + if (faceId === "posY" && waterMaterial.contactPatchesUniform !== null && waterMaterial.contactPatchAxesUniform !== null) { this.runtimeWaterContactUniforms.push({ brush, uniform: waterMaterial.contactPatchesUniform, + axisUniform: waterMaterial.contactPatchAxesUniform, staticContactPatches }); } @@ -811,10 +813,31 @@ export class RuntimeHost { } private collectRuntimeStaticWaterContactPatches(brush: RuntimeBoxBrushInstance) { - const contactBounds = this.runtimeScene?.colliders.map((collider) => ({ - min: collider.worldBounds.min, - max: collider.worldBounds.max - })) ?? []; + const contactBounds: Parameters[1] = []; + + for (const otherBrush of this.runtimeScene?.brushes ?? []) { + if (otherBrush.id === brush.id || otherBrush.volume.mode !== "none") { + 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") { + continue; + } + + contactBounds.push({ + min: collider.worldBounds.min, + max: collider.worldBounds.max + }); + } return collectWaterContactPatches( { @@ -852,9 +875,9 @@ export class RuntimeHost { private updateRuntimeWaterContactUniforms() { for (const binding of this.runtimeWaterContactUniforms) { - binding.uniform.value = createWaterContactPatchUniformValue( - this.mergeRuntimeWaterContactPatches(binding.staticContactPatches, this.collectRuntimePlayerWaterContactPatches(binding.brush)) - ); + const mergedPatches = this.mergeRuntimeWaterContactPatches(binding.staticContactPatches, this.collectRuntimePlayerWaterContactPatches(binding.brush)); + binding.uniform.value = createWaterContactPatchUniformValue(mergedPatches); + binding.axisUniform.value = createWaterContactPatchAxisUniformValue(mergedPatches); } } diff --git a/src/viewport-three/viewport-host.ts b/src/viewport-three/viewport-host.ts index b3089ba5..147cc29b 100644 --- a/src/viewport-three/viewport-host.ts +++ b/src/viewport-three/viewport-host.ts @@ -3007,14 +3007,19 @@ export class ViewportHost { rotationDegrees: Vec3, size: Vec3 ) { - const contactBounds: GeneratedColliderBounds[] = []; + const contactBounds: Parameters[1] = []; for (const brush of Object.values(document.brushes)) { if (brush.id === excludedBrushId || brush.volume.mode !== "none") { continue; } - contactBounds.push(getBoxBrushBounds(brush)); + contactBounds.push({ + kind: "orientedBox", + center: brush.center, + rotationDegrees: brush.rotationDegrees, + size: brush.size + }); } for (const modelInstance of getModelInstances(document.modelInstances)) { diff --git a/tests/domain/water-material.test.ts b/tests/domain/water-material.test.ts index 326a69db..9b3b8144 100644 --- a/tests/domain/water-material.test.ts +++ b/tests/domain/water-material.test.ts @@ -84,6 +84,52 @@ describe("water material helpers", () => { 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: 1, + 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("builds a shared quality shader material for visible tinted water", () => { const result = createWaterMaterial({ colorHex: "#4da6d9",