Files
webeditor3d/src/rendering/planar-reflection.ts

82 lines
3.5 KiB
TypeScript

import { Euler, Matrix4, PerspectiveCamera, Plane, Quaternion, Vector3, Vector4 } from "three";
import type { Vec3 } from "../core/vector";
export interface PlanarReflectionSurface {
center: Vec3;
rotationDegrees: Vec3;
size: Vec3;
}
const SURFACE_UP = new Vector3(0, 1, 0);
const CAMERA_FORWARD = new Vector3(0, 0, -1);
function createRotationQuaternion(rotationDegrees: Vec3) {
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: PlanarReflectionSurface,
sourceCamera: PerspectiveCamera,
reflectionCamera: PerspectiveCamera,
reflectionMatrix: Matrix4,
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;
}