import * as THREE from "https://cdn.jsdelivr.net/npm/three@0.119.1/build/three.module.js";
import { OrbitControls } from 'https://cdn.jsdelivr.net/npm/three@0.119.1/examples/jsm/controls/OrbitControls.js';
// Reflector class definition
var Reflector = function ( geometry, options ) {
THREE.Mesh.call( this, geometry );
this.type = 'Reflector';
var scope = this;
options = options || {};
var color = ( options.color !== undefined ) ? new THREE.Color( options.color ) : new THREE.Color( 0x7F7F7F );
var textureWidth = options.textureWidth || 512;
var textureHeight = options.textureHeight || 512;
var clipBias = options.clipBias || 0;
var shader = options.shader || Reflector.ReflectorShader;
var reflectorPlane = new THREE.Plane();
var normal = new THREE.Vector3();
var reflectorWorldPosition = new THREE.Vector3();
var cameraWorldPosition = new THREE.Vector3();
var rotationMatrix = new THREE.Matrix4();
var lookAtPosition = new THREE.Vector3( 0, 0, - 1 );
var clipPlane = new THREE.Vector4();
var view = new THREE.Vector3();
var target = new THREE.Vector3();
var q = new THREE.Vector4();
var textureMatrix = new THREE.Matrix4();
var virtualCamera = new THREE.PerspectiveCamera();
var parameters = {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
format: THREE.RGBFormat,
stencilBuffer: false
};
var renderTarget = new THREE.WebGLRenderTarget( textureWidth, textureHeight, parameters );
if ( ! THREE.MathUtils.isPowerOfTwo( textureWidth ) || ! THREE.MathUtils.isPowerOfTwo( textureHeight ) ) {
renderTarget.texture.generateMipmaps = false;
}
var material = new THREE.ShaderMaterial( {
uniforms: THREE.UniformsUtils.merge( [
{
opacity: {
value: 1.0 // Set opacity to 1.0 (fully opaque)
}
},
shader.uniforms
] ),
fragmentShader: shader.fragmentShader,
vertexShader: shader.vertexShader
} );
material.uniforms[ "tDiffuse" ].value = renderTarget.texture;
material.uniforms[ "color" ].value = color;
material.uniforms[ "textureMatrix" ].value = textureMatrix;
this.material = material;
this.onBeforeRender = function ( renderer, scene, camera ) {
reflectorWorldPosition.setFromMatrixPosition( scope.matrixWorld );
cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld );
rotationMatrix.extractRotation( scope.matrixWorld );
normal.set( 0, 0, 1 );
normal.applyMatrix4( rotationMatrix );
view.subVectors( reflectorWorldPosition, cameraWorldPosition );
if ( view.dot( normal ) > 0 ) return;
view.reflect( normal ).negate();
view.add( reflectorWorldPosition );
rotationMatrix.extractRotation( camera.matrixWorld );
lookAtPosition.set( 0, 0, - 1 );
lookAtPosition.applyMatrix4( rotationMatrix );
lookAtPosition.add( cameraWorldPosition );
target.subVectors( reflectorWorldPosition, lookAtPosition );
target.reflect( normal ).negate();
target.add( reflectorWorldPosition );
virtualCamera.position.copy( view );
virtualCamera.up.set( 0, 1, 0 );
virtualCamera.up.applyMatrix4( rotationMatrix );
virtualCamera.up.reflect( normal );
virtualCamera.lookAt( target );
virtualCamera.far = camera.far;
virtualCamera.updateMatrixWorld();
virtualCamera.projectionMatrix.copy( camera.projectionMatrix );
textureMatrix.set(
0.5, 0.0, 0.0, 0.5,
0.0, 0.5, 0.0, 0.5,
0.0, 0.0, 0.5, 0.5,
0.0, 0.0, 0.0, 1.0
);
textureMatrix.multiply( virtualCamera.projectionMatrix );
textureMatrix.multiply( virtualCamera.matrixWorldInverse );
textureMatrix.multiply( scope.matrixWorld );
reflectorPlane.setFromNormalAndCoplanarPoint( normal, reflectorWorldPosition );
reflectorPlane.applyMatrix4( virtualCamera.matrixWorldInverse );
clipPlane.set( reflectorPlane.normal.x, reflectorPlane.normal.y, reflectorPlane.normal.z, reflectorPlane.constant );
var projectionMatrix = virtualCamera.projectionMatrix;
q.x = ( Math.sign( clipPlane.x ) + projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ];
q.y = ( Math.sign( clipPlane.y ) + projectionMatrix.elements[ 9 ] ) / projectionMatrix.elements[ 5 ];
q.z = - 1.0;
q.w = ( 1.0 + projectionMatrix.elements[ 10 ] ) / projectionMatrix.elements[ 14 ];
clipPlane.multiplyScalar( 2.0 / clipPlane.dot( q ) );
projectionMatrix.elements[ 2 ] = clipPlane.x;
projectionMatrix.elements[ 6 ] = clipPlane.y;
projectionMatrix.elements[ 10 ] = clipPlane.z + 1.0 - clipBias;
projectionMatrix.elements[ 14 ] = clipPlane.w;
renderTarget.texture.encoding = renderer.outputEncoding;
scope.visible = false;
var currentRenderTarget = renderer.getRenderTarget();
var currentXrEnabled = renderer.xr.enabled;
var currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
renderer.xr.enabled = false;
renderer.shadowMap.autoUpdate = false;
renderer.setRenderTarget( renderTarget );
renderer.state.buffers.depth.setMask( true );
if ( renderer.autoClear === false ) renderer.clear();
renderer.render( scene, virtualCamera );
renderer.xr.enabled = currentXrEnabled;
renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
renderer.setRenderTarget( currentRenderTarget );
var viewport = camera.viewport;
if ( viewport !== undefined ) {
renderer.state.viewport( viewport );
}
scope.visible = true;
};
this.getRenderTarget = function () {
return renderTarget;
};
};
Reflector.prototype = Object.create( THREE.Mesh.prototype );
Reflector.prototype.constructor = Reflector;
Reflector.ReflectorShader = {
uniforms: {
'color': { value: null },
'tDiffuse': { value: null },
'textureMatrix': { value: null }
},
vertexShader: [
'uniform mat4 textureMatrix;',
'varying vec4 vUv;',
'void main() {',
' vUv = textureMatrix * vec4( position, 1.0 );',
' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
'}'
].join( '\n' ),
fragmentShader: [
'uniform vec3 color;',
'uniform float opacity;',
'uniform sampler2D tDiffuse;',
'varying vec4 vUv;',
'void main() {',
' vec4 base = texture2DProj( tDiffuse, vUv );',
' gl_FragColor = vec4( base.rgb * color, base.a * opacity );',
'}'
].join( '\n' )
};
// Initialize the scene and camera
var WIDTH = window.innerWidth;
var HEIGHT = window.innerHeight;
var camera, scene, renderer;
var cameraControls;
var sphereGroup, smallSphere;
init();
animate();
function init() {
var container = document.getElementById( 'container' );
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( WIDTH, HEIGHT );
container.appendChild( renderer.domElement );
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 45, WIDTH / HEIGHT, 1, 500 );
camera.position.set( 0, 75, 160 );
cameraControls = new OrbitControls( camera, renderer.domElement );
cameraControls.target.set( 0, 40, 0 );
cameraControls.maxDistance = 400;
cameraControls.minDistance = 10;
cameraControls.update();
var planeGeo = new THREE.PlaneBufferGeometry( 100.1, 100.1 );
var geometry = new THREE.CircleBufferGeometry( 40, 64 );
var groundMirror = new Reflector( geometry, {
clipBias: 0.003,
textureWidth: WIDTH * window.devicePixelRatio,
textureHeight: HEIGHT * window.devicePixelRatio,
color: 0x777777
} );
groundMirror.position.y = 0.5;
groundMirror.rotateX( - Math.PI / 2 );
scene.add( groundMirror );
var geometry = new THREE.PlaneBufferGeometry( 100, 100 );
var verticalMirror = new Reflector( geometry, {
clipBias: 0.003,
textureWidth: WIDTH * window.devicePixelRatio,
textureHeight: HEIGHT * window.devicePixelRatio,
color: 0x889999
} );
verticalMirror.position.y = 50;
verticalMirror.position.z = - 50;
verticalMirror.material.transparent = true;
verticalMirror.material.uniforms.opacity.value = 1.0; // Set opacity to 1.0 (fully opaque)
scene.add( verticalMirror );
sphereGroup = new THREE.Object3D();
scene.add( sphereGroup );
var geometry = new THREE.SphereBufferGeometry( 15, 24, 24 );
var material = new THREE.MeshPhongMaterial( { color: 0xffffff, emissive: 0x444444 } );
var halfSphere = new THREE.Mesh( geometry, material );
sphereGroup.add( halfSphere );
var geometry = new THREE.IcosahedronBufferGeometry( 5, 0 );
var material = new THREE.MeshPhongMaterial( { color: 0xffffff, emissive: 0x333333, flatShading: true } );
smallSphere = new THREE.Mesh( geometry, material );
scene.add( smallSphere );
var mainLight = new THREE.PointLight( 0xcccccc, 1.5, 250 );
mainLight.position.y = 60;
scene.add( mainLight );
}
function animate() {
requestAnimationFrame( animate );
var timer = Date.now() * 0.01;
sphereGroup.rotation.y -= 0.002;
smallSphere.position.set(
Math.cos( timer * 0.1 ) * 30,
Math.abs( Math.cos( timer * 0.2 ) ) * 20 + 5,
Math.sin( timer * 0.1 ) * 30
);
smallSphere.rotation.y = ( Math.PI / 2 ) - timer * 0.1;
smallSphere.rotation.z = timer * 0.8;
renderer.render( scene, camera );
}