diff --git a/src/rendering/planar-reflection.js b/src/rendering/planar-reflection.js new file mode 100644 index 00000000..ee8869a7 --- /dev/null +++ b/src/rendering/planar-reflection.js @@ -0,0 +1,50 @@ +import { Euler, Matrix4, PerspectiveCamera, Plane, Quaternion, Vector3, Vector4 } from "three"; + +const SURFACE_UP = new Vector3(0, 1, 0); +const CAMERA_FORWARD = new Vector3(0, 0, -1); + +function createRotationQuaternion(rotationDegrees) { + return new Quaternion().setFromEuler(new Euler((rotationDegrees.x * Math.PI) / 180, (rotationDegrees.y * Math.PI) / 180, (rotationDegrees.z * Math.PI) / 180, "XYZ")); +} + +export function updatePlanarReflectionCamera(surface, sourceCamera, reflectionCamera, reflectionMatrix, clipBias = 0.003) { + const rotation = createRotationQuaternion(surface.rotationDegrees); + const surfaceNormal = SURFACE_UP.clone().applyQuaternion(rotation).normalize(); + const surfaceCenter = new Vector3(surface.center.x, surface.center.y, surface.center.z).add(surfaceNormal.clone().multiplyScalar(surface.size.y * 0.5)); + const cameraWorldPosition = new Vector3().setFromMatrixPosition(sourceCamera.matrixWorld); + const sourceRotationMatrix = new Matrix4().extractRotation(sourceCamera.matrixWorld); + const lookAtPosition = CAMERA_FORWARD.clone().applyMatrix4(sourceRotationMatrix).add(cameraWorldPosition); + const reflectedViewPosition = surfaceCenter.clone().sub(cameraWorldPosition); + if (reflectedViewPosition.dot(surfaceNormal) > 0) { + return false; + } + reflectedViewPosition.reflect(surfaceNormal).negate(); + reflectedViewPosition.add(surfaceCenter); + const reflectedTarget = surfaceCenter.clone().sub(lookAtPosition); + reflectedTarget.reflect(surfaceNormal).negate(); + reflectedTarget.add(surfaceCenter); + reflectionCamera.position.copy(reflectedViewPosition); + reflectionCamera.up.set(0, 1, 0).applyMatrix4(sourceRotationMatrix).reflect(surfaceNormal); + reflectionCamera.near = sourceCamera.near; + reflectionCamera.far = sourceCamera.far; + reflectionCamera.aspect = sourceCamera.aspect; + reflectionCamera.projectionMatrix.copy(sourceCamera.projectionMatrix); + reflectionCamera.projectionMatrixInverse.copy(sourceCamera.projectionMatrixInverse); + reflectionCamera.lookAt(reflectedTarget); + reflectionCamera.updateMatrixWorld(); + reflectionCamera.matrixWorldInverse.copy(reflectionCamera.matrixWorld).invert(); + reflectionMatrix.set(0.5, 0, 0, 0.5, 0, 0.5, 0, 0.5, 0, 0, 0.5, 0.5, 0, 0, 0, 1); + reflectionMatrix.multiply(reflectionCamera.projectionMatrix); + reflectionMatrix.multiply(reflectionCamera.matrixWorldInverse); + const clipPlane = new Plane().setFromNormalAndCoplanarPoint(surfaceNormal, surfaceCenter).applyMatrix4(reflectionCamera.matrixWorldInverse); + const clipVector = new Vector4(clipPlane.normal.x, clipPlane.normal.y, clipPlane.normal.z, clipPlane.constant); + const projectionElements = reflectionCamera.projectionMatrix.elements; + const q = new Vector4((Math.sign(clipVector.x) + projectionElements[8]) / projectionElements[0], (Math.sign(clipVector.y) + projectionElements[9]) / projectionElements[5], -1, (1 + projectionElements[10]) / projectionElements[14]); + clipVector.multiplyScalar(2 / clipVector.dot(q)); + projectionElements[2] = clipVector.x; + projectionElements[6] = clipVector.y; + projectionElements[10] = clipVector.z + 1 - clipBias; + projectionElements[14] = clipVector.w; + reflectionCamera.projectionMatrixInverse.copy(reflectionCamera.projectionMatrix).invert(); + return true; +} \ No newline at end of file