auto-git:
[unlink] playwright.config.js [unlink] src/app/App.js [unlink] src/app/editor-store.js [unlink] src/app/use-editor-store.js [unlink] src/assets/audio-assets.js [unlink] src/assets/gltf-model-import.js [unlink] src/assets/image-assets.js [unlink] src/assets/model-instance-labels.js [unlink] src/assets/model-instance-rendering.js [unlink] src/assets/model-instances.js [unlink] src/assets/project-asset-storage.js [unlink] src/assets/project-assets.js [unlink] src/commands/brush-command-helpers.js [unlink] src/commands/command-history.js [unlink] src/commands/command.js [unlink] src/commands/commit-transform-session-command.js [unlink] src/commands/create-box-brush-command.js [unlink] src/commands/delete-box-brush-command.js [unlink] src/commands/delete-entity-command.js [unlink] src/commands/delete-interaction-link-command.js [unlink] src/commands/delete-model-instance-command.js [unlink] src/commands/duplicate-selection-command.js [unlink] src/commands/import-audio-asset-command.js [unlink] src/commands/import-background-image-asset-command.js [unlink] src/commands/import-model-asset-command.js [unlink] src/commands/move-box-brush-command.js [unlink] src/commands/resize-box-brush-command.js [unlink] src/commands/rotate-box-brush-command.js [unlink] src/commands/set-box-brush-face-material-command.js [unlink] src/commands/set-box-brush-face-uv-state-command.js [unlink] src/commands/set-box-brush-name-command.js [unlink] src/commands/set-box-brush-transform-command.js [unlink] src/commands/set-box-brush-volume-settings-command.js [unlink] src/commands/set-entity-name-command.js [unlink] src/commands/set-model-instance-name-command.js [unlink] src/commands/set-player-start-command.js [unlink] src/commands/set-scene-name-command.js [unlink] src/commands/set-world-settings-command.js [unlink] src/commands/upsert-entity-command.js [unlink] src/commands/upsert-interaction-link-command.js [unlink] src/commands/upsert-model-instance-command.js [unlink] src/core/ids.js [unlink] src/core/selection.js [unlink] src/core/tool-mode.js [unlink] src/core/transform-session.js [unlink] src/core/vector.js [unlink] src/core/whitebox-selection-feedback.js [unlink] src/core/whitebox-selection-mode.js [unlink] src/document/brushes.js [unlink] src/document/migrate-scene-document.js [unlink] src/document/scene-document-validation.js [unlink] src/document/scene-document.js [unlink] src/document/world-settings.js [unlink] src/entities/entity-instances.js [unlink] src/entities/entity-labels.js [unlink] src/geometry/box-brush-components.js [unlink] src/geometry/box-brush-mesh.js [unlink] src/geometry/box-brush.js [unlink] src/geometry/box-face-uvs.js [unlink] src/geometry/grid-snapping.js [unlink] src/geometry/model-instance-collider-debug-mesh.js [unlink] src/geometry/model-instance-collider-generation.js [unlink] src/interactions/interaction-links.js [unlink] src/main.js [unlink] src/materials/starter-material-library.js [unlink] src/materials/starter-material-textures.js [unlink] src/rendering/advanced-rendering.js [unlink] src/rendering/fog-material.js [unlink] src/rendering/planar-reflection.js [unlink] src/rendering/water-material.js [unlink] src/runner-web/RunnerCanvas.js [unlink] src/runtime-three/first-person-navigation-controller.js [unlink] src/runtime-three/navigation-controller.js [unlink] src/runtime-three/orbit-visitor-navigation-controller.js [unlink] src/runtime-three/player-collision.js [unlink] src/runtime-three/rapier-collision-world.js [unlink] src/runtime-three/runtime-audio-system.js [unlink] src/runtime-three/runtime-host.js [unlink] src/runtime-three/runtime-interaction-system.js [unlink] src/runtime-three/runtime-scene-build.js [unlink] src/runtime-three/runtime-scene-validation.js [unlink] src/runtime-three/underwater-fog.js [unlink] src/serialization/local-draft-storage.js [unlink] src/serialization/scene-document-json.js [unlink] src/shared-ui/HierarchicalMenu.js [unlink] src/shared-ui/Panel.js [unlink] src/shared-ui/world-background-style.js [unlink] src/viewport-three/ViewportCanvas.js [unlink] src/viewport-three/ViewportPanel.js [unlink] src/viewport-three/viewport-entity-markers.js [unlink] src/viewport-three/viewport-focus.js [unlink] src/viewport-three/viewport-host.js [unlink] src/viewport-three/viewport-layout.js [unlink] src/viewport-three/viewport-transient-state.js [unlink] src/viewport-three/viewport-view-modes.js [unlink] vite.config.js [unlink] vitest.config.js
This commit is contained in:
@@ -1,136 +0,0 @@
|
||||
import { BasicShadowMap, DirectionalLight, HalfFloatType, Mesh, NoToneMapping, PCFShadowMap, PCFSoftShadowMap, PointLight, SpotLight, UnsignedByteType } from "three";
|
||||
import { BloomEffect, DepthOfFieldEffect, EffectComposer, EffectPass, NormalPass, RenderPass, SMAAEffect, SMAAPreset, SSAOEffect, ToneMappingEffect, ToneMappingMode } from "postprocessing";
|
||||
const AMBIENT_OCCLUSION_LUMINANCE_INFLUENCE = 0.15;
|
||||
const MIN_AMBIENT_OCCLUSION_EFFECT_RADIUS = 0.02;
|
||||
const MAX_AMBIENT_OCCLUSION_EFFECT_RADIUS = 0.2;
|
||||
const MIN_AMBIENT_OCCLUSION_SAMPLES = 12;
|
||||
const COARSE_AMBIENT_OCCLUSION_RESOLUTION_SCALE = 0.5;
|
||||
const DETAIL_AMBIENT_OCCLUSION_RESOLUTION_SCALE = 0.75;
|
||||
const DETAIL_AMBIENT_OCCLUSION_RADIUS_SCALE = 0.35;
|
||||
const COARSE_AMBIENT_OCCLUSION_INTENSITY_SCALE = 0.45;
|
||||
const DETAIL_AMBIENT_OCCLUSION_INTENSITY_SCALE = 0.35;
|
||||
export function resolveBoxVolumeRenderPaths(settings) {
|
||||
if (!settings.enabled) {
|
||||
return {
|
||||
fog: "performance",
|
||||
water: "performance"
|
||||
};
|
||||
}
|
||||
return {
|
||||
fog: settings.fogPath,
|
||||
water: settings.waterPath
|
||||
};
|
||||
}
|
||||
export function getAdvancedRenderingShadowMapType(shadowType) {
|
||||
switch (shadowType) {
|
||||
case "basic":
|
||||
return BasicShadowMap;
|
||||
case "pcf":
|
||||
return PCFShadowMap;
|
||||
case "pcfSoft":
|
||||
return PCFSoftShadowMap;
|
||||
}
|
||||
}
|
||||
export function getAdvancedRenderingToneMappingMode(mode) {
|
||||
switch (mode) {
|
||||
case "none":
|
||||
return ToneMappingMode.LINEAR;
|
||||
case "linear":
|
||||
return ToneMappingMode.LINEAR;
|
||||
case "reinhard":
|
||||
return ToneMappingMode.REINHARD;
|
||||
case "cineon":
|
||||
return ToneMappingMode.CINEON;
|
||||
case "acesFilmic":
|
||||
return ToneMappingMode.ACES_FILMIC;
|
||||
}
|
||||
}
|
||||
export function configureAdvancedRenderingRenderer(renderer, settings) {
|
||||
renderer.shadowMap.enabled = settings.enabled && settings.shadows.enabled;
|
||||
renderer.shadowMap.type = getAdvancedRenderingShadowMapType(settings.shadows.type);
|
||||
renderer.toneMapping = NoToneMapping;
|
||||
renderer.toneMappingExposure = settings.toneMapping.exposure;
|
||||
}
|
||||
function clampAmbientOcclusionEffectRadius(radius) {
|
||||
return Math.min(Math.max(radius, MIN_AMBIENT_OCCLUSION_EFFECT_RADIUS), MAX_AMBIENT_OCCLUSION_EFFECT_RADIUS);
|
||||
}
|
||||
function getAmbientOcclusionSampleCount(samples) {
|
||||
return Math.max(samples, MIN_AMBIENT_OCCLUSION_SAMPLES);
|
||||
}
|
||||
export function createAdvancedRenderingComposer(renderer, scene, camera, settings) {
|
||||
// The scene is always rendered into the composer's offscreen targets first,
|
||||
// so those targets need depth for correct visibility even when no effect samples it.
|
||||
const composer = new EffectComposer(renderer, {
|
||||
depthBuffer: true,
|
||||
stencilBuffer: false,
|
||||
multisampling: 0,
|
||||
frameBufferType: renderer.capabilities.isWebGL2 ? HalfFloatType : UnsignedByteType
|
||||
});
|
||||
composer.addPass(new RenderPass(scene, camera));
|
||||
const effects = [];
|
||||
if (settings.ambientOcclusion.enabled) {
|
||||
// postprocessing's internal depth-downsampling path writes zero normals unless
|
||||
// a real normal buffer is supplied, which turns SSAO into speckled noise.
|
||||
const normalPass = new NormalPass(scene, camera);
|
||||
composer.addPass(normalPass);
|
||||
const ambientOcclusionRadius = clampAmbientOcclusionEffectRadius(settings.ambientOcclusion.radius);
|
||||
const ambientOcclusionSamples = getAmbientOcclusionSampleCount(settings.ambientOcclusion.samples);
|
||||
const detailAmbientOcclusionRadius = Math.max(ambientOcclusionRadius * DETAIL_AMBIENT_OCCLUSION_RADIUS_SCALE, MIN_AMBIENT_OCCLUSION_EFFECT_RADIUS);
|
||||
composer.addPass(new EffectPass(camera, new SSAOEffect(camera, normalPass.texture, {
|
||||
depthAwareUpsampling: true,
|
||||
luminanceInfluence: AMBIENT_OCCLUSION_LUMINANCE_INFLUENCE,
|
||||
resolutionScale: COARSE_AMBIENT_OCCLUSION_RESOLUTION_SCALE,
|
||||
samples: ambientOcclusionSamples,
|
||||
radius: ambientOcclusionRadius,
|
||||
intensity: settings.ambientOcclusion.intensity * COARSE_AMBIENT_OCCLUSION_INTENSITY_SCALE
|
||||
}), new SSAOEffect(camera, normalPass.texture, {
|
||||
depthAwareUpsampling: true,
|
||||
luminanceInfluence: AMBIENT_OCCLUSION_LUMINANCE_INFLUENCE,
|
||||
resolutionScale: DETAIL_AMBIENT_OCCLUSION_RESOLUTION_SCALE,
|
||||
samples: ambientOcclusionSamples,
|
||||
radius: detailAmbientOcclusionRadius,
|
||||
intensity: settings.ambientOcclusion.intensity * DETAIL_AMBIENT_OCCLUSION_INTENSITY_SCALE
|
||||
})));
|
||||
}
|
||||
if (settings.bloom.enabled) {
|
||||
effects.push(new BloomEffect({
|
||||
intensity: settings.bloom.intensity,
|
||||
luminanceThreshold: settings.bloom.threshold,
|
||||
radius: settings.bloom.radius
|
||||
}));
|
||||
}
|
||||
if (settings.depthOfField.enabled) {
|
||||
effects.push(new DepthOfFieldEffect(camera, {
|
||||
focusDistance: settings.depthOfField.focusDistance,
|
||||
focalLength: settings.depthOfField.focalLength,
|
||||
bokehScale: settings.depthOfField.bokehScale
|
||||
}));
|
||||
}
|
||||
effects.push(new ToneMappingEffect({
|
||||
mode: getAdvancedRenderingToneMappingMode(settings.toneMapping.mode)
|
||||
}));
|
||||
effects.push(new SMAAEffect({
|
||||
preset: SMAAPreset.MEDIUM
|
||||
}));
|
||||
composer.addPass(new EffectPass(camera, ...effects));
|
||||
return composer;
|
||||
}
|
||||
export function applyAdvancedRenderingRenderableShadowFlags(root, enabled) {
|
||||
root.traverse((object) => {
|
||||
if (object.isMesh === true && object.userData.shadowIgnored !== true) {
|
||||
const mesh = object;
|
||||
mesh.castShadow = enabled;
|
||||
mesh.receiveShadow = enabled;
|
||||
}
|
||||
});
|
||||
}
|
||||
export function applyAdvancedRenderingLightShadowFlags(root, settings) {
|
||||
const shadowEnabled = settings.enabled && settings.shadows.enabled;
|
||||
root.traverse((object) => {
|
||||
if (object instanceof DirectionalLight || object instanceof PointLight || object instanceof SpotLight) {
|
||||
object.castShadow = shadowEnabled;
|
||||
object.shadow.bias = settings.shadows.bias;
|
||||
object.shadow.mapSize.set(settings.shadows.mapSize, settings.shadows.mapSize);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
import { BackSide, Color, ShaderMaterial, UniformsLib, UniformsUtils, Vector3 } from "three";
|
||||
const MIN_FOG_HALF_SIZE = 0.05;
|
||||
export function createFogQualityMaterial(options) {
|
||||
const halfSize = new Vector3(Math.max(MIN_FOG_HALF_SIZE, options.halfSize.x), Math.max(MIN_FOG_HALF_SIZE, options.halfSize.y), Math.max(MIN_FOG_HALF_SIZE, options.halfSize.z));
|
||||
const minHalfExtent = Math.min(halfSize.x, halfSize.y, halfSize.z);
|
||||
const padding = Math.max(0, Math.min(options.padding, minHalfExtent * 0.82));
|
||||
const animationUniform = { value: options.time };
|
||||
const uniforms = UniformsUtils.clone(UniformsLib.fog);
|
||||
uniforms["time"] = animationUniform;
|
||||
uniforms["volumeFogColor"] = { value: new Color(options.colorHex) };
|
||||
uniforms["volumeFogDensity"] = { value: Math.max(0, options.density) };
|
||||
uniforms["volumeHalfSize"] = { value: halfSize };
|
||||
uniforms["volumePadding"] = { value: padding };
|
||||
uniforms["opacityMultiplier"] = { value: Math.max(0.6, Math.min(1.5, options.opacityMultiplier ?? 1)) };
|
||||
uniforms["colorLift"] = { value: Math.max(0, Math.min(0.22, options.colorLift ?? 0)) };
|
||||
uniforms["localCameraPosition"] = { value: new Vector3() };
|
||||
const vertexShader = /* glsl */ `
|
||||
varying vec3 vLocalPosition;
|
||||
#include <fog_pars_vertex>
|
||||
|
||||
void main() {
|
||||
vLocalPosition = position;
|
||||
vec4 worldPosition = modelMatrix * vec4(position, 1.0);
|
||||
vec4 mvPosition = viewMatrix * worldPosition;
|
||||
gl_Position = projectionMatrix * mvPosition;
|
||||
#include <fog_vertex>
|
||||
}
|
||||
`;
|
||||
const fragmentShader = /* glsl */ `
|
||||
uniform vec3 volumeFogColor;
|
||||
uniform float volumeFogDensity;
|
||||
uniform vec3 volumeHalfSize;
|
||||
uniform float volumePadding;
|
||||
uniform float opacityMultiplier;
|
||||
uniform float colorLift;
|
||||
uniform float time;
|
||||
uniform vec3 localCameraPosition;
|
||||
|
||||
varying vec3 vLocalPosition;
|
||||
#include <fog_pars_fragment>
|
||||
|
||||
#define FOG_STEPS 10
|
||||
|
||||
float hash13(vec3 point) {
|
||||
point = fract(point * 0.1031);
|
||||
point += dot(point, point.yzx + 33.33);
|
||||
return fract((point.x + point.y) * point.z);
|
||||
}
|
||||
|
||||
float noise3(vec3 point) {
|
||||
vec3 cell = floor(point);
|
||||
vec3 local = fract(point);
|
||||
vec3 smoothLocal = local * local * (3.0 - 2.0 * local);
|
||||
|
||||
float n000 = hash13(cell + vec3(0.0, 0.0, 0.0));
|
||||
float n100 = hash13(cell + vec3(1.0, 0.0, 0.0));
|
||||
float n010 = hash13(cell + vec3(0.0, 1.0, 0.0));
|
||||
float n110 = hash13(cell + vec3(1.0, 1.0, 0.0));
|
||||
float n001 = hash13(cell + vec3(0.0, 0.0, 1.0));
|
||||
float n101 = hash13(cell + vec3(1.0, 0.0, 1.0));
|
||||
float n011 = hash13(cell + vec3(0.0, 1.0, 1.0));
|
||||
float n111 = hash13(cell + vec3(1.0, 1.0, 1.0));
|
||||
|
||||
float nx00 = mix(n000, n100, smoothLocal.x);
|
||||
float nx10 = mix(n010, n110, smoothLocal.x);
|
||||
float nx01 = mix(n001, n101, smoothLocal.x);
|
||||
float nx11 = mix(n011, n111, smoothLocal.x);
|
||||
float nxy0 = mix(nx00, nx10, smoothLocal.y);
|
||||
float nxy1 = mix(nx01, nx11, smoothLocal.y);
|
||||
return mix(nxy0, nxy1, smoothLocal.z);
|
||||
}
|
||||
|
||||
float fbm(vec3 point) {
|
||||
float value = 0.0;
|
||||
float amplitude = 0.5;
|
||||
|
||||
for (int octave = 0; octave < 3; octave += 1) {
|
||||
value += amplitude * noise3(point);
|
||||
point = point * 2.04 + vec3(17.1, 31.7, 9.2);
|
||||
amplitude *= 0.5;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
vec2 intersectBox(vec3 rayOrigin, vec3 rayDirection, vec3 halfSize) {
|
||||
vec3 safeDirection = sign(rayDirection) * max(abs(rayDirection), vec3(1e-4));
|
||||
vec3 invDirection = 1.0 / safeDirection;
|
||||
vec3 t0 = (-halfSize - rayOrigin) * invDirection;
|
||||
vec3 t1 = (halfSize - rayOrigin) * invDirection;
|
||||
vec3 tMin = min(t0, t1);
|
||||
vec3 tMax = max(t0, t1);
|
||||
float nearHit = max(max(tMin.x, tMin.y), tMin.z);
|
||||
float farHit = min(min(tMax.x, tMax.y), tMax.z);
|
||||
return vec2(nearHit, farHit);
|
||||
}
|
||||
|
||||
float sampleShape(vec3 samplePosition) {
|
||||
float minHalfExtent = min(min(volumeHalfSize.x, volumeHalfSize.y), volumeHalfSize.z);
|
||||
float edgeSoftness = max(0.08, min(volumePadding + minHalfExtent * 0.16, minHalfExtent * 0.72));
|
||||
vec3 innerHalfSize = max(volumeHalfSize - vec3(edgeSoftness), vec3(minHalfExtent * 0.18));
|
||||
vec3 distanceToCore = abs(samplePosition) - innerHalfSize;
|
||||
float outsideDistance = length(max(distanceToCore, 0.0));
|
||||
float insideDistance = min(max(distanceToCore.x, max(distanceToCore.y, distanceToCore.z)), 0.0);
|
||||
float roundedBoxDistance = outsideDistance + insideDistance;
|
||||
float edgeMask = 1.0 - smoothstep(-edgeSoftness * 0.7, edgeSoftness * 1.35, roundedBoxDistance);
|
||||
|
||||
vec3 ellipsoidPosition = samplePosition / max(volumeHalfSize - vec3(edgeSoftness * 0.18), vec3(1e-3));
|
||||
float roundedMask = 1.0 - smoothstep(0.54, 1.03, length(ellipsoidPosition * vec3(0.96, 1.08, 0.96)));
|
||||
|
||||
return edgeMask * mix(0.42, 1.0, roundedMask);
|
||||
}
|
||||
|
||||
float sampleVolumeDensity(vec3 samplePosition) {
|
||||
vec3 normalizedPosition = samplePosition / max(volumeHalfSize, vec3(1e-3));
|
||||
float shape = sampleShape(samplePosition);
|
||||
|
||||
if (shape <= 1e-3) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
vec3 drift = vec3(time * 0.1, time * 0.04, -time * 0.065);
|
||||
float primary = fbm(samplePosition * 0.58 + drift);
|
||||
float secondary = fbm(samplePosition * 1.18 - drift * 1.45 + vec3(4.3, 9.7, 2.1));
|
||||
float wisps = noise3(samplePosition * 2.15 + vec3(0.0, time * 0.08, 0.0));
|
||||
float cloud = smoothstep(0.34, 0.92, primary * 0.68 + secondary * 0.24 + wisps * 0.08);
|
||||
float centerBias = 1.0 - smoothstep(0.18, 1.08, length(normalizedPosition * vec3(1.05, 0.92, 1.05)));
|
||||
float verticalBias = mix(0.9, 1.08, smoothstep(-0.75, 0.35, normalizedPosition.y));
|
||||
float carvedCloud = mix(0.42, 1.04, cloud) * mix(0.72, 1.0, centerBias);
|
||||
|
||||
return volumeFogDensity * shape * carvedCloud * verticalBias;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec3 rayDirection = normalize(vLocalPosition - localCameraPosition);
|
||||
vec2 hitRange = intersectBox(localCameraPosition, rayDirection, volumeHalfSize);
|
||||
float startDistance = max(hitRange.x, 0.0);
|
||||
float endDistance = hitRange.y;
|
||||
|
||||
if (endDistance <= startDistance) {
|
||||
discard;
|
||||
}
|
||||
|
||||
float rayLength = endDistance - startDistance;
|
||||
float stepLength = rayLength / float(FOG_STEPS);
|
||||
float jitter = hash13(vLocalPosition * 1.73 + vec3(time * 0.17)) - 0.5;
|
||||
float transmittance = 1.0;
|
||||
vec3 accumulatedColor = vec3(0.0);
|
||||
|
||||
for (int stepIndex = 0; stepIndex < FOG_STEPS; stepIndex += 1) {
|
||||
float sampleDistance = startDistance + (float(stepIndex) + 0.5 + jitter * 0.35) * stepLength;
|
||||
vec3 samplePosition = localCameraPosition + rayDirection * sampleDistance;
|
||||
float sampleDensity = sampleVolumeDensity(samplePosition);
|
||||
|
||||
if (sampleDensity <= 1e-4) {
|
||||
continue;
|
||||
}
|
||||
|
||||
vec3 normalizedPosition = samplePosition / max(volumeHalfSize, vec3(1e-3));
|
||||
float forwardScatter = 1.0 - abs(dot(rayDirection, normalize(samplePosition + vec3(1e-3, 2e-3, -1e-3))));
|
||||
float topLight = smoothstep(-0.2, 0.95, normalizedPosition.y);
|
||||
float coolShadow = smoothstep(0.18, 0.88, noise3(samplePosition * 0.88 - vec3(time * 0.08, 0.0, time * 0.05)));
|
||||
vec3 sampleColor = mix(volumeFogColor * 0.76, vec3(1.0), 0.06 + topLight * 0.12 + forwardScatter * 0.12);
|
||||
sampleColor = mix(sampleColor * 0.92, sampleColor, coolShadow);
|
||||
|
||||
float extinction = sampleDensity * stepLength * 1.5;
|
||||
float sampleAlpha = 1.0 - exp(-extinction);
|
||||
accumulatedColor += transmittance * sampleColor * sampleAlpha;
|
||||
transmittance *= 1.0 - sampleAlpha;
|
||||
|
||||
if (transmittance < 0.03) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
float baseAlpha = 1.0 - transmittance;
|
||||
float alpha = clamp(baseAlpha * opacityMultiplier, 0.0, 0.96);
|
||||
|
||||
if (alpha <= 0.01) {
|
||||
discard;
|
||||
}
|
||||
|
||||
vec3 color = accumulatedColor / max(baseAlpha, 1e-4);
|
||||
color = mix(color, vec3(1.0), colorLift);
|
||||
|
||||
gl_FragColor = vec4(color, alpha);
|
||||
#include <fog_fragment>
|
||||
}
|
||||
`;
|
||||
return {
|
||||
material: new ShaderMaterial({
|
||||
vertexShader,
|
||||
fragmentShader,
|
||||
uniforms,
|
||||
transparent: true,
|
||||
depthWrite: false,
|
||||
fog: true,
|
||||
side: BackSide
|
||||
}),
|
||||
animationUniform
|
||||
};
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
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;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user