├── img ├── 1.jpg ├── map.png ├── t1.png └── t2.png ├── index.html └── js ├── app.js ├── app2.js └── vendor ├── OrbitControls.js ├── postprocessing ├── BloomPass.js ├── EffectComposer.js ├── RenderPass.js ├── ShaderPass.js └── UnrealBloomPass.js ├── shader ├── ConvolutionShader.js └── CopyShader.js └── three.min.js /img/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imokya/threejs-tunnel-effect/8e53428cebf87c98f635db59e678fc86c8142c35/img/1.jpg -------------------------------------------------------------------------------- /img/map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imokya/threejs-tunnel-effect/8e53428cebf87c98f635db59e678fc86c8142c35/img/map.png -------------------------------------------------------------------------------- /img/t1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imokya/threejs-tunnel-effect/8e53428cebf87c98f635db59e678fc86c8142c35/img/t1.png -------------------------------------------------------------------------------- /img/t2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imokya/threejs-tunnel-effect/8e53428cebf87c98f635db59e678fc86c8142c35/img/t2.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Three.js Tunnel 8 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | class App { 2 | 3 | constructor() { 4 | this.init() 5 | } 6 | 7 | init() { 8 | this._initScene() 9 | this._initTube() 10 | this._initPlanes() 11 | this._bindEvent() 12 | this._initVars() 13 | this._render() 14 | } 15 | 16 | _bindEvent() { 17 | window.addEventListener('resize', this._onResize.bind(this)) 18 | document.addEventListener('wheel', e => { 19 | this.speed += e.deltaY * 0.002 20 | }) 21 | } 22 | 23 | _onResize() { 24 | this.ww = window.innerWidth 25 | this.wh = window.innerHeight 26 | this.renderer.setSize(this.ww, this.wh) 27 | this.camera.aspect = this.ww / this.wh 28 | this.camera.updateProjectionMatrix() 29 | } 30 | 31 | _initTube() { 32 | function CustomSinCurve(scale) { 33 | THREE.Curve.call( this ) 34 | this.scale = ( scale === undefined ) ? 1 : scale 35 | } 36 | CustomSinCurve.prototype = Object.create(THREE.Curve.prototype) 37 | CustomSinCurve.prototype.constructor = CustomSinCurve 38 | CustomSinCurve.prototype.getPoint = function (t) { 39 | const tx = Math.cos(2 * Math.PI * t) 40 | const ty = Math.sin(2 * Math.PI * t) 41 | const tz = 0.1 * Math.sin(8 * Math.PI * t) 42 | 43 | // t = t * 4 * Math.PI 44 | // const a = this.radius / 2 45 | // const tx = a * (1 + Math.cos(t)) 46 | // const ty = a * Math.sin(t) 47 | // const tz = 2 * a * Math.sin(t / 2) 48 | 49 | return new THREE.Vector3(tx, ty, tz).multiplyScalar(this.scale) 50 | } 51 | const path = new CustomSinCurve(40) 52 | this.tubeGeometry = new THREE.TubeGeometry(path, 200, 1, 8, false) 53 | const material = new THREE.MeshBasicMaterial({ 54 | side: THREE.DoubleSide, 55 | map: new THREE.TextureLoader().load('img/map.png') 56 | }) 57 | material.map.wrapS = THREE.RepeatWrapping; 58 | material.map.wrapT= THREE.RepeatWrapping; 59 | material.map.repeat.set(10, 1) 60 | const mesh = new THREE.Mesh(this.tubeGeometry, material) 61 | this.scene.add(mesh) 62 | } 63 | 64 | _initScene() { 65 | this.ww = window.innerWidth 66 | this.wh = window.innerHeight 67 | this.camera = new THREE.PerspectiveCamera(45, this.ww/this.wh, 0.1, 1000) 68 | this.scene = new THREE.Scene() 69 | this.renderer = new THREE.WebGLRenderer({ 70 | antialias: true 71 | }) 72 | this.renderer.setSize(this.ww, this.wh) 73 | document.body.appendChild(this.renderer.domElement) 74 | 75 | this.camera.position.z = 150 76 | } 77 | 78 | _initPlanes() { 79 | this.planes = [] 80 | const tex1 = new THREE.TextureLoader().load('img/t1.png') 81 | const tex2 = new THREE.TextureLoader().load('img/t2.png') 82 | const geo = new THREE.PlaneBufferGeometry(0.5, 0.5) 83 | const mat1 = new THREE.MeshBasicMaterial({ 84 | transparent: true, 85 | map: tex1 86 | }) 87 | const mat2 = new THREE.MeshBasicMaterial({ 88 | transparent: true, 89 | map: tex2 90 | }) 91 | geo.center() 92 | const mesh = new THREE.Mesh(geo, mat1) 93 | for(let i = 0; i < 20; i++) { 94 | const plane = mesh.clone() 95 | if(i % 2 == 0) { 96 | plane.material = mat1 97 | } else { 98 | plane.material = mat2 99 | } 100 | plane.position.copy(this.tubeGeometry.parameters.path.getPointAt(i * 0.04)) 101 | this.planes.push(plane) 102 | this.scene.add(plane) 103 | } 104 | 105 | 106 | } 107 | 108 | 109 | _initVars() { 110 | //this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement) 111 | this.binormal = new THREE.Vector3() 112 | this.normal = new THREE.Vector3() 113 | this.time = 0 114 | this.position = 0 115 | this.speed = 0 116 | } 117 | 118 | _render() { 119 | //this.time = this.position * 100 120 | this.time += 10 121 | let looptime = 20 * 1000 122 | let t = ( this.time % looptime ) / looptime 123 | let pos = this.tubeGeometry.parameters.path.getPointAt(t) 124 | 125 | let segments = this.tubeGeometry.tangents.length 126 | let pickt = t * segments 127 | let pick = Math.floor(pickt) 128 | let pickNext = (pick + 1) % segments 129 | let offset = 0 130 | 131 | this.binormal.subVectors(this.tubeGeometry.binormals[pickNext], this.tubeGeometry.binormals[pick]) 132 | this.binormal.multiplyScalar(pickt - pick).add(this.tubeGeometry.binormals[pick]) 133 | let dir = this.tubeGeometry.parameters.path.getTangentAt(t) 134 | 135 | this.normal.copy(this.binormal).cross(dir) 136 | pos.add(this.normal.clone().multiplyScalar(offset)) 137 | this.camera.position.copy(pos) 138 | let lookAt = this.tubeGeometry.parameters.path.getPointAt((t+1/this.tubeGeometry.parameters.path.getLength())%1) 139 | this.camera.matrix.lookAt(this.camera.position, lookAt, this.normal) 140 | this.camera.rotation.setFromRotationMatrix(this.camera.matrix, this.camera.rotation.order) 141 | 142 | this.position += this.speed 143 | this.position = this.position < 0 ? 0 : this.position 144 | this.speed *= 0.9 145 | 146 | this.planes.forEach(plane => { 147 | plane.quaternion.copy(this.camera.quaternion) 148 | }) 149 | 150 | 151 | this.renderer.render(this.scene, this.camera) 152 | requestAnimationFrame(this._render.bind(this)) 153 | } 154 | 155 | } 156 | 157 | const app = new App() -------------------------------------------------------------------------------- /js/app2.js: -------------------------------------------------------------------------------- 1 | class App { 2 | 3 | constructor() { 4 | this.init() 5 | } 6 | 7 | init() { 8 | this._initScene() 9 | this._initTube() 10 | this._initPlanes() 11 | this._bindEvent() 12 | this._initVars() 13 | this._render() 14 | } 15 | 16 | _bindEvent() { 17 | window.addEventListener('resize', this._onResize.bind(this)) 18 | document.addEventListener('wheel', e => { 19 | this.speed += e.deltaY * 0.002 20 | }) 21 | } 22 | 23 | _onResize() { 24 | this.ww = window.innerWidth 25 | this.wh = window.innerHeight 26 | this.renderer.setSize(this.ww, this.wh) 27 | this.camera.aspect = this.ww / this.wh 28 | this.camera.updateProjectionMatrix() 29 | } 30 | 31 | _initTube() { 32 | function CustomSinCurve(scale) { 33 | THREE.Curve.call( this ) 34 | this.scale = ( scale === undefined ) ? 1 : scale 35 | } 36 | CustomSinCurve.prototype = Object.create(THREE.Curve.prototype) 37 | CustomSinCurve.prototype.constructor = CustomSinCurve 38 | CustomSinCurve.prototype.getPoint = function (t) { 39 | const tx = Math.cos(2 * Math.PI * t) 40 | const ty = Math.sin(2 * Math.PI * t) 41 | const tz = 0.1 * Math.sin(8 * Math.PI * t) 42 | 43 | // t = t * 4 * Math.PI 44 | // const a = this.radius / 2 45 | // const tx = a * (1 + Math.cos(t)) 46 | // const ty = a * Math.sin(t) 47 | // const tz = 2 * a * Math.sin(t / 2) 48 | 49 | return new THREE.Vector3(tx, ty, tz).multiplyScalar(this.scale) 50 | } 51 | const path = new CustomSinCurve(40) 52 | this.tubeGeometry = new THREE.TubeGeometry(path, 200, 5, 8, false) 53 | const material = new THREE.MeshBasicMaterial({ 54 | side: THREE.DoubleSide, 55 | map: new THREE.TextureLoader().load('img/map.png') 56 | }) 57 | material.map.wrapS = THREE.RepeatWrapping; 58 | material.map.wrapT= THREE.RepeatWrapping; 59 | material.map.repeat.set(10, 1) 60 | const mesh = new THREE.Mesh(this.tubeGeometry, material) 61 | this.scene.add(mesh) 62 | } 63 | 64 | _initScene() { 65 | this.ww = window.innerWidth 66 | this.wh = window.innerHeight 67 | this.camera = new THREE.PerspectiveCamera(45, this.ww/this.wh, 0.1, 1000) 68 | this.scene = new THREE.Scene() 69 | this.renderer = new THREE.WebGLRenderer({ 70 | antialias: true 71 | }) 72 | this.renderer.setSize(this.ww, this.wh) 73 | document.body.appendChild(this.renderer.domElement) 74 | 75 | this.camera.position.z = 150 76 | } 77 | 78 | _initPlanes() { 79 | this.planes = [] 80 | const tex1 = new THREE.TextureLoader().load('img/t1.png') 81 | const tex2 = new THREE.TextureLoader().load('img/t2.png') 82 | const geo = new THREE.PlaneBufferGeometry(0.5, 0.5) 83 | const mat1 = new THREE.MeshBasicMaterial({ 84 | transparent: true, 85 | map: tex1 86 | }) 87 | const mat2 = new THREE.MeshBasicMaterial({ 88 | transparent: true, 89 | map: tex2 90 | }) 91 | geo.center() 92 | const mesh = new THREE.Mesh(geo, mat1) 93 | for(let i = 0; i < 20; i++) { 94 | const plane = mesh.clone() 95 | if(i % 2 == 0) { 96 | plane.material = mat1 97 | } else { 98 | plane.material = mat2 99 | } 100 | plane.position.copy(this.tubeGeometry.parameters.path.getPointAt(i * 0.04)) 101 | this.planes.push(plane) 102 | this.scene.add(plane) 103 | } 104 | 105 | 106 | } 107 | 108 | 109 | _initVars() { 110 | this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement) 111 | this.binormal = new THREE.Vector3() 112 | this.normal = new THREE.Vector3() 113 | this.time = 0 114 | this.position = 0 115 | this.speed = 0 116 | } 117 | 118 | _render() { 119 | //this.time = this.position * 100 120 | // this.time += 10 121 | // let looptime = 20 * 1000 122 | // let t = ( this.time % looptime ) / looptime 123 | // let pos = this.tubeGeometry.parameters.path.getPointAt(t) 124 | 125 | // let segments = this.tubeGeometry.tangents.length 126 | // let pickt = t * segments 127 | // let pick = Math.floor(pickt) 128 | // let pickNext = (pick + 1) % segments 129 | 130 | // this.binormal.subVectors(this.tubeGeometry.binormals[pickNext], this.tubeGeometry.binormals[pick]) 131 | // this.binormal.multiplyScalar(pickt - pick).add(this.tubeGeometry.binormals[pick]) 132 | // let dir = this.tubeGeometry.parameters.path.getTangentAt(t) 133 | 134 | // let offset = 0 135 | 136 | // this.normal.copy(this.binormal).cross(dir) 137 | // pos.add(this.normal.clone().multiplyScalar(offset)) 138 | // this.camera.position.copy(pos) 139 | 140 | // let lookAt = this.tubeGeometry.parameters.path.getPointAt((t+1/this.tubeGeometry.parameters.path.getLength())%1) 141 | // this.camera.matrix.lookAt(this.camera.position, lookAt, this.normal) 142 | // this.camera.rotation.setFromRotationMatrix(this.camera.matrix, this.camera.rotation.order) 143 | 144 | // this.position += this.speed 145 | // this.position = this.position < 0 ? 0 : this.position 146 | // this.speed *= 0.9 147 | 148 | // this.planes.forEach(plane => { 149 | // plane.quaternion.copy(this.camera.quaternion) 150 | // }) 151 | 152 | 153 | this.renderer.render(this.scene, this.camera) 154 | requestAnimationFrame(this._render.bind(this)) 155 | } 156 | 157 | } 158 | 159 | const app = new App() -------------------------------------------------------------------------------- /js/vendor/OrbitControls.js: -------------------------------------------------------------------------------- 1 | console.warn( "THREE.OrbitControls: As part of the transition to ES6 Modules, the files in 'examples/js' were deprecated in May 2020 (r117) and will be deleted in December 2020 (r124). You can find more information about developing using ES6 Modules in https://threejs.org/docs/#manual/en/introduction/Installation." ); 2 | /** 3 | * @author qiao / https://github.com/qiao 4 | * @author mrdoob / http://mrdoob.com 5 | * @author alteredq / http://alteredqualia.com/ 6 | * @author WestLangley / http://github.com/WestLangley 7 | * @author erich666 / http://erichaines.com 8 | * @author ScieCode / http://github.com/sciecode 9 | */ 10 | 11 | // This set of controls performs orbiting, dollying (zooming), and panning. 12 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 13 | // 14 | // Orbit - left mouse / touch: one-finger move 15 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish 16 | // Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move 17 | 18 | THREE.OrbitControls = function ( object, domElement ) { 19 | 20 | if ( domElement === undefined ) console.warn( 'THREE.OrbitControls: The second parameter "domElement" is now mandatory.' ); 21 | if ( domElement === document ) console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' ); 22 | 23 | this.object = object; 24 | this.domElement = domElement; 25 | 26 | // Set to false to disable this control 27 | this.enabled = true; 28 | 29 | // "target" sets the location of focus, where the object orbits around 30 | this.target = new THREE.Vector3(); 31 | 32 | // How far you can dolly in and out ( PerspectiveCamera only ) 33 | this.minDistance = 0; 34 | this.maxDistance = Infinity; 35 | 36 | // How far you can zoom in and out ( OrthographicCamera only ) 37 | this.minZoom = 0; 38 | this.maxZoom = Infinity; 39 | 40 | // How far you can orbit vertically, upper and lower limits. 41 | // Range is 0 to Math.PI radians. 42 | this.minPolarAngle = 0; // radians 43 | this.maxPolarAngle = Math.PI; // radians 44 | 45 | // How far you can orbit horizontally, upper and lower limits. 46 | // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI ) 47 | this.minAzimuthAngle = - Infinity; // radians 48 | this.maxAzimuthAngle = Infinity; // radians 49 | 50 | // Set to true to enable damping (inertia) 51 | // If damping is enabled, you must call controls.update() in your animation loop 52 | this.enableDamping = false; 53 | this.dampingFactor = 0.05; 54 | 55 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. 56 | // Set to false to disable zooming 57 | this.enableZoom = true; 58 | this.zoomSpeed = 1.0; 59 | 60 | // Set to false to disable rotating 61 | this.enableRotate = true; 62 | this.rotateSpeed = 1.0; 63 | 64 | // Set to false to disable panning 65 | this.enablePan = true; 66 | this.panSpeed = 1.0; 67 | this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up 68 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 69 | 70 | // Set to true to automatically rotate around the target 71 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 72 | this.autoRotate = false; 73 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 74 | 75 | // Set to false to disable use of the keys 76 | this.enableKeys = true; 77 | 78 | // The four arrow keys 79 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 80 | 81 | // Mouse buttons 82 | this.mouseButtons = { LEFT: THREE.MOUSE.ROTATE, MIDDLE: THREE.MOUSE.DOLLY, RIGHT: THREE.MOUSE.PAN }; 83 | 84 | // Touch fingers 85 | this.touches = { ONE: THREE.TOUCH.ROTATE, TWO: THREE.TOUCH.DOLLY_PAN }; 86 | 87 | // for reset 88 | this.target0 = this.target.clone(); 89 | this.position0 = this.object.position.clone(); 90 | this.zoom0 = this.object.zoom; 91 | 92 | // 93 | // public methods 94 | // 95 | 96 | this.getPolarAngle = function () { 97 | 98 | return spherical.phi; 99 | 100 | }; 101 | 102 | this.getAzimuthalAngle = function () { 103 | 104 | return spherical.theta; 105 | 106 | }; 107 | 108 | this.saveState = function () { 109 | 110 | scope.target0.copy( scope.target ); 111 | scope.position0.copy( scope.object.position ); 112 | scope.zoom0 = scope.object.zoom; 113 | 114 | }; 115 | 116 | this.reset = function () { 117 | 118 | scope.target.copy( scope.target0 ); 119 | scope.object.position.copy( scope.position0 ); 120 | scope.object.zoom = scope.zoom0; 121 | 122 | scope.object.updateProjectionMatrix(); 123 | scope.dispatchEvent( changeEvent ); 124 | 125 | scope.update(); 126 | 127 | state = STATE.NONE; 128 | 129 | }; 130 | 131 | // this method is exposed, but perhaps it would be better if we can make it private... 132 | this.update = function () { 133 | 134 | var offset = new THREE.Vector3(); 135 | 136 | // so camera.up is the orbit axis 137 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 138 | var quatInverse = quat.clone().inverse(); 139 | 140 | var lastPosition = new THREE.Vector3(); 141 | var lastQuaternion = new THREE.Quaternion(); 142 | 143 | var twoPI = 2 * Math.PI; 144 | 145 | return function update() { 146 | 147 | var position = scope.object.position; 148 | 149 | offset.copy( position ).sub( scope.target ); 150 | 151 | // rotate offset to "y-axis-is-up" space 152 | offset.applyQuaternion( quat ); 153 | 154 | // angle from z-axis around y-axis 155 | spherical.setFromVector3( offset ); 156 | 157 | if ( scope.autoRotate && state === STATE.NONE ) { 158 | 159 | rotateLeft( getAutoRotationAngle() ); 160 | 161 | } 162 | 163 | if ( scope.enableDamping ) { 164 | 165 | spherical.theta += sphericalDelta.theta * scope.dampingFactor; 166 | spherical.phi += sphericalDelta.phi * scope.dampingFactor; 167 | 168 | } else { 169 | 170 | spherical.theta += sphericalDelta.theta; 171 | spherical.phi += sphericalDelta.phi; 172 | 173 | } 174 | 175 | // restrict theta to be between desired limits 176 | 177 | var min = scope.minAzimuthAngle; 178 | var max = scope.maxAzimuthAngle; 179 | 180 | if ( isFinite( min ) && isFinite( max ) ) { 181 | 182 | if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI; 183 | 184 | if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI; 185 | 186 | if ( min < max ) { 187 | 188 | spherical.theta = Math.max( min, Math.min( max, spherical.theta ) ); 189 | 190 | } else { 191 | 192 | spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ? 193 | Math.max( min, spherical.theta ) : 194 | Math.min( max, spherical.theta ); 195 | 196 | } 197 | 198 | } 199 | 200 | // restrict phi to be between desired limits 201 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); 202 | 203 | spherical.makeSafe(); 204 | 205 | 206 | spherical.radius *= scale; 207 | 208 | // restrict radius to be between desired limits 209 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); 210 | 211 | // move target to panned location 212 | 213 | if ( scope.enableDamping === true ) { 214 | 215 | scope.target.addScaledVector( panOffset, scope.dampingFactor ); 216 | 217 | } else { 218 | 219 | scope.target.add( panOffset ); 220 | 221 | } 222 | 223 | offset.setFromSpherical( spherical ); 224 | 225 | // rotate offset back to "camera-up-vector-is-up" space 226 | offset.applyQuaternion( quatInverse ); 227 | 228 | position.copy( scope.target ).add( offset ); 229 | 230 | scope.object.lookAt( scope.target ); 231 | 232 | if ( scope.enableDamping === true ) { 233 | 234 | sphericalDelta.theta *= ( 1 - scope.dampingFactor ); 235 | sphericalDelta.phi *= ( 1 - scope.dampingFactor ); 236 | 237 | panOffset.multiplyScalar( 1 - scope.dampingFactor ); 238 | 239 | } else { 240 | 241 | sphericalDelta.set( 0, 0, 0 ); 242 | 243 | panOffset.set( 0, 0, 0 ); 244 | 245 | } 246 | 247 | scale = 1; 248 | 249 | // update condition is: 250 | // min(camera displacement, camera rotation in radians)^2 > EPS 251 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 252 | 253 | if ( zoomChanged || 254 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 255 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 256 | 257 | scope.dispatchEvent( changeEvent ); 258 | 259 | lastPosition.copy( scope.object.position ); 260 | lastQuaternion.copy( scope.object.quaternion ); 261 | zoomChanged = false; 262 | 263 | return true; 264 | 265 | } 266 | 267 | return false; 268 | 269 | }; 270 | 271 | }(); 272 | 273 | this.dispose = function () { 274 | 275 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 276 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 277 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); 278 | 279 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 280 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 281 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 282 | 283 | scope.domElement.ownerDocument.removeEventListener( 'mousemove', onMouseMove, false ); 284 | scope.domElement.ownerDocument.removeEventListener( 'mouseup', onMouseUp, false ); 285 | 286 | scope.domElement.removeEventListener( 'keydown', onKeyDown, false ); 287 | 288 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 289 | 290 | }; 291 | 292 | // 293 | // internals 294 | // 295 | 296 | var scope = this; 297 | 298 | var changeEvent = { type: 'change' }; 299 | var startEvent = { type: 'start' }; 300 | var endEvent = { type: 'end' }; 301 | 302 | var STATE = { 303 | NONE: - 1, 304 | ROTATE: 0, 305 | DOLLY: 1, 306 | PAN: 2, 307 | TOUCH_ROTATE: 3, 308 | TOUCH_PAN: 4, 309 | TOUCH_DOLLY_PAN: 5, 310 | TOUCH_DOLLY_ROTATE: 6 311 | }; 312 | 313 | var state = STATE.NONE; 314 | 315 | var EPS = 0.000001; 316 | 317 | // current position in spherical coordinates 318 | var spherical = new THREE.Spherical(); 319 | var sphericalDelta = new THREE.Spherical(); 320 | 321 | var scale = 1; 322 | var panOffset = new THREE.Vector3(); 323 | var zoomChanged = false; 324 | 325 | var rotateStart = new THREE.Vector2(); 326 | var rotateEnd = new THREE.Vector2(); 327 | var rotateDelta = new THREE.Vector2(); 328 | 329 | var panStart = new THREE.Vector2(); 330 | var panEnd = new THREE.Vector2(); 331 | var panDelta = new THREE.Vector2(); 332 | 333 | var dollyStart = new THREE.Vector2(); 334 | var dollyEnd = new THREE.Vector2(); 335 | var dollyDelta = new THREE.Vector2(); 336 | 337 | function getAutoRotationAngle() { 338 | 339 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 340 | 341 | } 342 | 343 | function getZoomScale() { 344 | 345 | return Math.pow( 0.95, scope.zoomSpeed ); 346 | 347 | } 348 | 349 | function rotateLeft( angle ) { 350 | 351 | sphericalDelta.theta -= angle; 352 | 353 | } 354 | 355 | function rotateUp( angle ) { 356 | 357 | sphericalDelta.phi -= angle; 358 | 359 | } 360 | 361 | var panLeft = function () { 362 | 363 | var v = new THREE.Vector3(); 364 | 365 | return function panLeft( distance, objectMatrix ) { 366 | 367 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix 368 | v.multiplyScalar( - distance ); 369 | 370 | panOffset.add( v ); 371 | 372 | }; 373 | 374 | }(); 375 | 376 | var panUp = function () { 377 | 378 | var v = new THREE.Vector3(); 379 | 380 | return function panUp( distance, objectMatrix ) { 381 | 382 | if ( scope.screenSpacePanning === true ) { 383 | 384 | v.setFromMatrixColumn( objectMatrix, 1 ); 385 | 386 | } else { 387 | 388 | v.setFromMatrixColumn( objectMatrix, 0 ); 389 | v.crossVectors( scope.object.up, v ); 390 | 391 | } 392 | 393 | v.multiplyScalar( distance ); 394 | 395 | panOffset.add( v ); 396 | 397 | }; 398 | 399 | }(); 400 | 401 | // deltaX and deltaY are in pixels; right and down are positive 402 | var pan = function () { 403 | 404 | var offset = new THREE.Vector3(); 405 | 406 | return function pan( deltaX, deltaY ) { 407 | 408 | var element = scope.domElement; 409 | 410 | if ( scope.object.isPerspectiveCamera ) { 411 | 412 | // perspective 413 | var position = scope.object.position; 414 | offset.copy( position ).sub( scope.target ); 415 | var targetDistance = offset.length(); 416 | 417 | // half of the fov is center to top of screen 418 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 419 | 420 | // we use only clientHeight here so aspect ratio does not distort speed 421 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 422 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 423 | 424 | } else if ( scope.object.isOrthographicCamera ) { 425 | 426 | // orthographic 427 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); 428 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); 429 | 430 | } else { 431 | 432 | // camera neither orthographic nor perspective 433 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 434 | scope.enablePan = false; 435 | 436 | } 437 | 438 | }; 439 | 440 | }(); 441 | 442 | function dollyOut( dollyScale ) { 443 | 444 | if ( scope.object.isPerspectiveCamera ) { 445 | 446 | scale /= dollyScale; 447 | 448 | } else if ( scope.object.isOrthographicCamera ) { 449 | 450 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 451 | scope.object.updateProjectionMatrix(); 452 | zoomChanged = true; 453 | 454 | } else { 455 | 456 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 457 | scope.enableZoom = false; 458 | 459 | } 460 | 461 | } 462 | 463 | function dollyIn( dollyScale ) { 464 | 465 | if ( scope.object.isPerspectiveCamera ) { 466 | 467 | scale *= dollyScale; 468 | 469 | } else if ( scope.object.isOrthographicCamera ) { 470 | 471 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 472 | scope.object.updateProjectionMatrix(); 473 | zoomChanged = true; 474 | 475 | } else { 476 | 477 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 478 | scope.enableZoom = false; 479 | 480 | } 481 | 482 | } 483 | 484 | // 485 | // event callbacks - update the object state 486 | // 487 | 488 | function handleMouseDownRotate( event ) { 489 | 490 | rotateStart.set( event.clientX, event.clientY ); 491 | 492 | } 493 | 494 | function handleMouseDownDolly( event ) { 495 | 496 | dollyStart.set( event.clientX, event.clientY ); 497 | 498 | } 499 | 500 | function handleMouseDownPan( event ) { 501 | 502 | panStart.set( event.clientX, event.clientY ); 503 | 504 | } 505 | 506 | function handleMouseMoveRotate( event ) { 507 | 508 | rotateEnd.set( event.clientX, event.clientY ); 509 | 510 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 511 | 512 | var element = scope.domElement; 513 | 514 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height 515 | 516 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 517 | 518 | rotateStart.copy( rotateEnd ); 519 | 520 | scope.update(); 521 | 522 | } 523 | 524 | function handleMouseMoveDolly( event ) { 525 | 526 | dollyEnd.set( event.clientX, event.clientY ); 527 | 528 | dollyDelta.subVectors( dollyEnd, dollyStart ); 529 | 530 | if ( dollyDelta.y > 0 ) { 531 | 532 | dollyOut( getZoomScale() ); 533 | 534 | } else if ( dollyDelta.y < 0 ) { 535 | 536 | dollyIn( getZoomScale() ); 537 | 538 | } 539 | 540 | dollyStart.copy( dollyEnd ); 541 | 542 | scope.update(); 543 | 544 | } 545 | 546 | function handleMouseMovePan( event ) { 547 | 548 | panEnd.set( event.clientX, event.clientY ); 549 | 550 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 551 | 552 | pan( panDelta.x, panDelta.y ); 553 | 554 | panStart.copy( panEnd ); 555 | 556 | scope.update(); 557 | 558 | } 559 | 560 | function handleMouseUp( /*event*/ ) { 561 | 562 | // no-op 563 | 564 | } 565 | 566 | function handleMouseWheel( event ) { 567 | 568 | if ( event.deltaY < 0 ) { 569 | 570 | dollyIn( getZoomScale() ); 571 | 572 | } else if ( event.deltaY > 0 ) { 573 | 574 | dollyOut( getZoomScale() ); 575 | 576 | } 577 | 578 | scope.update(); 579 | 580 | } 581 | 582 | function handleKeyDown( event ) { 583 | 584 | var needsUpdate = false; 585 | 586 | switch ( event.keyCode ) { 587 | 588 | case scope.keys.UP: 589 | pan( 0, scope.keyPanSpeed ); 590 | needsUpdate = true; 591 | break; 592 | 593 | case scope.keys.BOTTOM: 594 | pan( 0, - scope.keyPanSpeed ); 595 | needsUpdate = true; 596 | break; 597 | 598 | case scope.keys.LEFT: 599 | pan( scope.keyPanSpeed, 0 ); 600 | needsUpdate = true; 601 | break; 602 | 603 | case scope.keys.RIGHT: 604 | pan( - scope.keyPanSpeed, 0 ); 605 | needsUpdate = true; 606 | break; 607 | 608 | } 609 | 610 | if ( needsUpdate ) { 611 | 612 | // prevent the browser from scrolling on cursor keys 613 | event.preventDefault(); 614 | 615 | scope.update(); 616 | 617 | } 618 | 619 | 620 | } 621 | 622 | function handleTouchStartRotate( event ) { 623 | 624 | if ( event.touches.length == 1 ) { 625 | 626 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 627 | 628 | } else { 629 | 630 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 631 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 632 | 633 | rotateStart.set( x, y ); 634 | 635 | } 636 | 637 | } 638 | 639 | function handleTouchStartPan( event ) { 640 | 641 | if ( event.touches.length == 1 ) { 642 | 643 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 644 | 645 | } else { 646 | 647 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 648 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 649 | 650 | panStart.set( x, y ); 651 | 652 | } 653 | 654 | } 655 | 656 | function handleTouchStartDolly( event ) { 657 | 658 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 659 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 660 | 661 | var distance = Math.sqrt( dx * dx + dy * dy ); 662 | 663 | dollyStart.set( 0, distance ); 664 | 665 | } 666 | 667 | function handleTouchStartDollyPan( event ) { 668 | 669 | if ( scope.enableZoom ) handleTouchStartDolly( event ); 670 | 671 | if ( scope.enablePan ) handleTouchStartPan( event ); 672 | 673 | } 674 | 675 | function handleTouchStartDollyRotate( event ) { 676 | 677 | if ( scope.enableZoom ) handleTouchStartDolly( event ); 678 | 679 | if ( scope.enableRotate ) handleTouchStartRotate( event ); 680 | 681 | } 682 | 683 | function handleTouchMoveRotate( event ) { 684 | 685 | if ( event.touches.length == 1 ) { 686 | 687 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 688 | 689 | } else { 690 | 691 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 692 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 693 | 694 | rotateEnd.set( x, y ); 695 | 696 | } 697 | 698 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 699 | 700 | var element = scope.domElement; 701 | 702 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height 703 | 704 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 705 | 706 | rotateStart.copy( rotateEnd ); 707 | 708 | } 709 | 710 | function handleTouchMovePan( event ) { 711 | 712 | if ( event.touches.length == 1 ) { 713 | 714 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 715 | 716 | } else { 717 | 718 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 719 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 720 | 721 | panEnd.set( x, y ); 722 | 723 | } 724 | 725 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 726 | 727 | pan( panDelta.x, panDelta.y ); 728 | 729 | panStart.copy( panEnd ); 730 | 731 | } 732 | 733 | function handleTouchMoveDolly( event ) { 734 | 735 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 736 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 737 | 738 | var distance = Math.sqrt( dx * dx + dy * dy ); 739 | 740 | dollyEnd.set( 0, distance ); 741 | 742 | dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); 743 | 744 | dollyOut( dollyDelta.y ); 745 | 746 | dollyStart.copy( dollyEnd ); 747 | 748 | } 749 | 750 | function handleTouchMoveDollyPan( event ) { 751 | 752 | if ( scope.enableZoom ) handleTouchMoveDolly( event ); 753 | 754 | if ( scope.enablePan ) handleTouchMovePan( event ); 755 | 756 | } 757 | 758 | function handleTouchMoveDollyRotate( event ) { 759 | 760 | if ( scope.enableZoom ) handleTouchMoveDolly( event ); 761 | 762 | if ( scope.enableRotate ) handleTouchMoveRotate( event ); 763 | 764 | } 765 | 766 | function handleTouchEnd( /*event*/ ) { 767 | 768 | // no-op 769 | 770 | } 771 | 772 | // 773 | // event handlers - FSM: listen for events and reset state 774 | // 775 | 776 | function onMouseDown( event ) { 777 | 778 | if ( scope.enabled === false ) return; 779 | 780 | // Prevent the browser from scrolling. 781 | event.preventDefault(); 782 | 783 | // Manually set the focus since calling preventDefault above 784 | // prevents the browser from setting it automatically. 785 | 786 | scope.domElement.focus ? scope.domElement.focus() : window.focus(); 787 | 788 | var mouseAction; 789 | 790 | switch ( event.button ) { 791 | 792 | case 0: 793 | 794 | mouseAction = scope.mouseButtons.LEFT; 795 | break; 796 | 797 | case 1: 798 | 799 | mouseAction = scope.mouseButtons.MIDDLE; 800 | break; 801 | 802 | case 2: 803 | 804 | mouseAction = scope.mouseButtons.RIGHT; 805 | break; 806 | 807 | default: 808 | 809 | mouseAction = - 1; 810 | 811 | } 812 | 813 | switch ( mouseAction ) { 814 | 815 | case THREE.MOUSE.DOLLY: 816 | 817 | if ( scope.enableZoom === false ) return; 818 | 819 | handleMouseDownDolly( event ); 820 | 821 | state = STATE.DOLLY; 822 | 823 | break; 824 | 825 | case THREE.MOUSE.ROTATE: 826 | 827 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) { 828 | 829 | if ( scope.enablePan === false ) return; 830 | 831 | handleMouseDownPan( event ); 832 | 833 | state = STATE.PAN; 834 | 835 | } else { 836 | 837 | if ( scope.enableRotate === false ) return; 838 | 839 | handleMouseDownRotate( event ); 840 | 841 | state = STATE.ROTATE; 842 | 843 | } 844 | 845 | break; 846 | 847 | case THREE.MOUSE.PAN: 848 | 849 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) { 850 | 851 | if ( scope.enableRotate === false ) return; 852 | 853 | handleMouseDownRotate( event ); 854 | 855 | state = STATE.ROTATE; 856 | 857 | } else { 858 | 859 | if ( scope.enablePan === false ) return; 860 | 861 | handleMouseDownPan( event ); 862 | 863 | state = STATE.PAN; 864 | 865 | } 866 | 867 | break; 868 | 869 | default: 870 | 871 | state = STATE.NONE; 872 | 873 | } 874 | 875 | if ( state !== STATE.NONE ) { 876 | 877 | scope.domElement.ownerDocument.addEventListener( 'mousemove', onMouseMove, false ); 878 | scope.domElement.ownerDocument.addEventListener( 'mouseup', onMouseUp, false ); 879 | 880 | scope.dispatchEvent( startEvent ); 881 | 882 | } 883 | 884 | } 885 | 886 | function onMouseMove( event ) { 887 | 888 | if ( scope.enabled === false ) return; 889 | 890 | event.preventDefault(); 891 | 892 | switch ( state ) { 893 | 894 | case STATE.ROTATE: 895 | 896 | if ( scope.enableRotate === false ) return; 897 | 898 | handleMouseMoveRotate( event ); 899 | 900 | break; 901 | 902 | case STATE.DOLLY: 903 | 904 | if ( scope.enableZoom === false ) return; 905 | 906 | handleMouseMoveDolly( event ); 907 | 908 | break; 909 | 910 | case STATE.PAN: 911 | 912 | if ( scope.enablePan === false ) return; 913 | 914 | handleMouseMovePan( event ); 915 | 916 | break; 917 | 918 | } 919 | 920 | } 921 | 922 | function onMouseUp( event ) { 923 | 924 | if ( scope.enabled === false ) return; 925 | 926 | handleMouseUp( event ); 927 | 928 | scope.domElement.ownerDocument.removeEventListener( 'mousemove', onMouseMove, false ); 929 | scope.domElement.ownerDocument.removeEventListener( 'mouseup', onMouseUp, false ); 930 | 931 | scope.dispatchEvent( endEvent ); 932 | 933 | state = STATE.NONE; 934 | 935 | } 936 | 937 | function onMouseWheel( event ) { 938 | 939 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; 940 | 941 | event.preventDefault(); 942 | event.stopPropagation(); 943 | 944 | scope.dispatchEvent( startEvent ); 945 | 946 | handleMouseWheel( event ); 947 | 948 | scope.dispatchEvent( endEvent ); 949 | 950 | } 951 | 952 | function onKeyDown( event ) { 953 | 954 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 955 | 956 | handleKeyDown( event ); 957 | 958 | } 959 | 960 | function onTouchStart( event ) { 961 | 962 | if ( scope.enabled === false ) return; 963 | 964 | event.preventDefault(); // prevent scrolling 965 | 966 | switch ( event.touches.length ) { 967 | 968 | case 1: 969 | 970 | switch ( scope.touches.ONE ) { 971 | 972 | case THREE.TOUCH.ROTATE: 973 | 974 | if ( scope.enableRotate === false ) return; 975 | 976 | handleTouchStartRotate( event ); 977 | 978 | state = STATE.TOUCH_ROTATE; 979 | 980 | break; 981 | 982 | case THREE.TOUCH.PAN: 983 | 984 | if ( scope.enablePan === false ) return; 985 | 986 | handleTouchStartPan( event ); 987 | 988 | state = STATE.TOUCH_PAN; 989 | 990 | break; 991 | 992 | default: 993 | 994 | state = STATE.NONE; 995 | 996 | } 997 | 998 | break; 999 | 1000 | case 2: 1001 | 1002 | switch ( scope.touches.TWO ) { 1003 | 1004 | case THREE.TOUCH.DOLLY_PAN: 1005 | 1006 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 1007 | 1008 | handleTouchStartDollyPan( event ); 1009 | 1010 | state = STATE.TOUCH_DOLLY_PAN; 1011 | 1012 | break; 1013 | 1014 | case THREE.TOUCH.DOLLY_ROTATE: 1015 | 1016 | if ( scope.enableZoom === false && scope.enableRotate === false ) return; 1017 | 1018 | handleTouchStartDollyRotate( event ); 1019 | 1020 | state = STATE.TOUCH_DOLLY_ROTATE; 1021 | 1022 | break; 1023 | 1024 | default: 1025 | 1026 | state = STATE.NONE; 1027 | 1028 | } 1029 | 1030 | break; 1031 | 1032 | default: 1033 | 1034 | state = STATE.NONE; 1035 | 1036 | } 1037 | 1038 | if ( state !== STATE.NONE ) { 1039 | 1040 | scope.dispatchEvent( startEvent ); 1041 | 1042 | } 1043 | 1044 | } 1045 | 1046 | function onTouchMove( event ) { 1047 | 1048 | if ( scope.enabled === false ) return; 1049 | 1050 | event.preventDefault(); // prevent scrolling 1051 | event.stopPropagation(); 1052 | 1053 | switch ( state ) { 1054 | 1055 | case STATE.TOUCH_ROTATE: 1056 | 1057 | if ( scope.enableRotate === false ) return; 1058 | 1059 | handleTouchMoveRotate( event ); 1060 | 1061 | scope.update(); 1062 | 1063 | break; 1064 | 1065 | case STATE.TOUCH_PAN: 1066 | 1067 | if ( scope.enablePan === false ) return; 1068 | 1069 | handleTouchMovePan( event ); 1070 | 1071 | scope.update(); 1072 | 1073 | break; 1074 | 1075 | case STATE.TOUCH_DOLLY_PAN: 1076 | 1077 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 1078 | 1079 | handleTouchMoveDollyPan( event ); 1080 | 1081 | scope.update(); 1082 | 1083 | break; 1084 | 1085 | case STATE.TOUCH_DOLLY_ROTATE: 1086 | 1087 | if ( scope.enableZoom === false && scope.enableRotate === false ) return; 1088 | 1089 | handleTouchMoveDollyRotate( event ); 1090 | 1091 | scope.update(); 1092 | 1093 | break; 1094 | 1095 | default: 1096 | 1097 | state = STATE.NONE; 1098 | 1099 | } 1100 | 1101 | } 1102 | 1103 | function onTouchEnd( event ) { 1104 | 1105 | if ( scope.enabled === false ) return; 1106 | 1107 | handleTouchEnd( event ); 1108 | 1109 | scope.dispatchEvent( endEvent ); 1110 | 1111 | state = STATE.NONE; 1112 | 1113 | } 1114 | 1115 | function onContextMenu( event ) { 1116 | 1117 | if ( scope.enabled === false ) return; 1118 | 1119 | event.preventDefault(); 1120 | 1121 | } 1122 | 1123 | // 1124 | 1125 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 1126 | 1127 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); 1128 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); 1129 | 1130 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 1131 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 1132 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 1133 | 1134 | scope.domElement.addEventListener( 'keydown', onKeyDown, false ); 1135 | 1136 | // make sure element can receive keys. 1137 | 1138 | if ( scope.domElement.tabIndex === - 1 ) { 1139 | 1140 | scope.domElement.tabIndex = 0; 1141 | 1142 | } 1143 | 1144 | // force an update at start 1145 | 1146 | this.update(); 1147 | 1148 | }; 1149 | 1150 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 1151 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 1152 | 1153 | 1154 | // This set of controls performs orbiting, dollying (zooming), and panning. 1155 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 1156 | // This is very similar to OrbitControls, another set of touch behavior 1157 | // 1158 | // Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate 1159 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish 1160 | // Pan - left mouse, or arrow keys / touch: one-finger move 1161 | 1162 | THREE.MapControls = function ( object, domElement ) { 1163 | 1164 | THREE.OrbitControls.call( this, object, domElement ); 1165 | 1166 | this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up 1167 | 1168 | this.mouseButtons.LEFT = THREE.MOUSE.PAN; 1169 | this.mouseButtons.RIGHT = THREE.MOUSE.ROTATE; 1170 | 1171 | this.touches.ONE = THREE.TOUCH.PAN; 1172 | this.touches.TWO = THREE.TOUCH.DOLLY_ROTATE; 1173 | 1174 | }; 1175 | 1176 | THREE.MapControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 1177 | THREE.MapControls.prototype.constructor = THREE.MapControls; 1178 | -------------------------------------------------------------------------------- /js/vendor/postprocessing/BloomPass.js: -------------------------------------------------------------------------------- 1 | console.warn( "THREE.BloomPass: As part of the transition to ES6 Modules, the files in 'examples/js' were deprecated in May 2020 (r117) and will be deleted in December 2020 (r124). You can find more information about developing using ES6 Modules in https://threejs.org/docs/#manual/en/introduction/Installation." ); 2 | /** 3 | * @author alteredq / http://alteredqualia.com/ 4 | */ 5 | 6 | THREE.BloomPass = function ( strength, kernelSize, sigma, resolution ) { 7 | 8 | THREE.Pass.call( this ); 9 | 10 | strength = ( strength !== undefined ) ? strength : 1; 11 | kernelSize = ( kernelSize !== undefined ) ? kernelSize : 25; 12 | sigma = ( sigma !== undefined ) ? sigma : 4.0; 13 | resolution = ( resolution !== undefined ) ? resolution : 256; 14 | 15 | // render targets 16 | 17 | var pars = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat }; 18 | 19 | this.renderTargetX = new THREE.WebGLRenderTarget( resolution, resolution, pars ); 20 | this.renderTargetX.texture.name = "BloomPass.x"; 21 | this.renderTargetY = new THREE.WebGLRenderTarget( resolution, resolution, pars ); 22 | this.renderTargetY.texture.name = "BloomPass.y"; 23 | 24 | // copy material 25 | 26 | if ( THREE.CopyShader === undefined ) 27 | console.error( "THREE.BloomPass relies on THREE.CopyShader" ); 28 | 29 | var copyShader = THREE.CopyShader; 30 | 31 | this.copyUniforms = THREE.UniformsUtils.clone( copyShader.uniforms ); 32 | 33 | this.copyUniforms[ "opacity" ].value = strength; 34 | 35 | this.materialCopy = new THREE.ShaderMaterial( { 36 | 37 | uniforms: this.copyUniforms, 38 | vertexShader: copyShader.vertexShader, 39 | fragmentShader: copyShader.fragmentShader, 40 | blending: THREE.AdditiveBlending, 41 | transparent: true 42 | 43 | } ); 44 | 45 | // convolution material 46 | 47 | if ( THREE.ConvolutionShader === undefined ) 48 | console.error( "THREE.BloomPass relies on THREE.ConvolutionShader" ); 49 | 50 | var convolutionShader = THREE.ConvolutionShader; 51 | 52 | this.convolutionUniforms = THREE.UniformsUtils.clone( convolutionShader.uniforms ); 53 | 54 | this.convolutionUniforms[ "uImageIncrement" ].value = THREE.BloomPass.blurX; 55 | this.convolutionUniforms[ "cKernel" ].value = THREE.ConvolutionShader.buildKernel( sigma ); 56 | 57 | this.materialConvolution = new THREE.ShaderMaterial( { 58 | 59 | uniforms: this.convolutionUniforms, 60 | vertexShader: convolutionShader.vertexShader, 61 | fragmentShader: convolutionShader.fragmentShader, 62 | defines: { 63 | "KERNEL_SIZE_FLOAT": kernelSize.toFixed( 1 ), 64 | "KERNEL_SIZE_INT": kernelSize.toFixed( 0 ) 65 | } 66 | 67 | } ); 68 | 69 | this.needsSwap = false; 70 | 71 | this.fsQuad = new THREE.Pass.FullScreenQuad( null ); 72 | 73 | }; 74 | 75 | THREE.BloomPass.prototype = Object.assign( Object.create( THREE.Pass.prototype ), { 76 | 77 | constructor: THREE.BloomPass, 78 | 79 | render: function ( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { 80 | 81 | if ( maskActive ) renderer.state.buffers.stencil.setTest( false ); 82 | 83 | // Render quad with blured scene into texture (convolution pass 1) 84 | 85 | this.fsQuad.material = this.materialConvolution; 86 | 87 | this.convolutionUniforms[ "tDiffuse" ].value = readBuffer.texture; 88 | this.convolutionUniforms[ "uImageIncrement" ].value = THREE.BloomPass.blurX; 89 | 90 | renderer.setRenderTarget( this.renderTargetX ); 91 | renderer.clear(); 92 | this.fsQuad.render( renderer ); 93 | 94 | 95 | // Render quad with blured scene into texture (convolution pass 2) 96 | 97 | this.convolutionUniforms[ "tDiffuse" ].value = this.renderTargetX.texture; 98 | this.convolutionUniforms[ "uImageIncrement" ].value = THREE.BloomPass.blurY; 99 | 100 | renderer.setRenderTarget( this.renderTargetY ); 101 | renderer.clear(); 102 | this.fsQuad.render( renderer ); 103 | 104 | // Render original scene with superimposed blur to texture 105 | 106 | this.fsQuad.material = this.materialCopy; 107 | 108 | this.copyUniforms[ "tDiffuse" ].value = this.renderTargetY.texture; 109 | 110 | if ( maskActive ) renderer.state.buffers.stencil.setTest( true ); 111 | 112 | renderer.setRenderTarget( readBuffer ); 113 | if ( this.clear ) renderer.clear(); 114 | this.fsQuad.render( renderer ); 115 | 116 | } 117 | 118 | } ); 119 | 120 | THREE.BloomPass.blurX = new THREE.Vector2( 0.001953125, 0.0 ); 121 | THREE.BloomPass.blurY = new THREE.Vector2( 0.0, 0.001953125 ); 122 | -------------------------------------------------------------------------------- /js/vendor/postprocessing/EffectComposer.js: -------------------------------------------------------------------------------- 1 | console.warn( "THREE.EffectComposer: As part of the transition to ES6 Modules, the files in 'examples/js' were deprecated in May 2020 (r117) and will be deleted in December 2020 (r124). You can find more information about developing using ES6 Modules in https://threejs.org/docs/#manual/en/introduction/Installation." ); 2 | /** 3 | * @author alteredq / http://alteredqualia.com/ 4 | */ 5 | 6 | THREE.EffectComposer = function ( renderer, renderTarget ) { 7 | 8 | this.renderer = renderer; 9 | 10 | if ( renderTarget === undefined ) { 11 | 12 | var parameters = { 13 | minFilter: THREE.LinearFilter, 14 | magFilter: THREE.LinearFilter, 15 | format: THREE.RGBAFormat, 16 | stencilBuffer: false 17 | }; 18 | 19 | var size = renderer.getSize( new THREE.Vector2() ); 20 | this._pixelRatio = renderer.getPixelRatio(); 21 | this._width = size.width; 22 | this._height = size.height; 23 | 24 | renderTarget = new THREE.WebGLRenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, parameters ); 25 | renderTarget.texture.name = 'EffectComposer.rt1'; 26 | 27 | } else { 28 | 29 | this._pixelRatio = 1; 30 | this._width = renderTarget.width; 31 | this._height = renderTarget.height; 32 | 33 | } 34 | 35 | this.renderTarget1 = renderTarget; 36 | this.renderTarget2 = renderTarget.clone(); 37 | this.renderTarget2.texture.name = 'EffectComposer.rt2'; 38 | 39 | this.writeBuffer = this.renderTarget1; 40 | this.readBuffer = this.renderTarget2; 41 | 42 | this.renderToScreen = true; 43 | 44 | this.passes = []; 45 | 46 | // dependencies 47 | 48 | if ( THREE.CopyShader === undefined ) { 49 | 50 | console.error( 'THREE.EffectComposer relies on THREE.CopyShader' ); 51 | 52 | } 53 | 54 | if ( THREE.ShaderPass === undefined ) { 55 | 56 | console.error( 'THREE.EffectComposer relies on THREE.ShaderPass' ); 57 | 58 | } 59 | 60 | this.copyPass = new THREE.ShaderPass( THREE.CopyShader ); 61 | 62 | this.clock = new THREE.Clock(); 63 | 64 | }; 65 | 66 | Object.assign( THREE.EffectComposer.prototype, { 67 | 68 | swapBuffers: function () { 69 | 70 | var tmp = this.readBuffer; 71 | this.readBuffer = this.writeBuffer; 72 | this.writeBuffer = tmp; 73 | 74 | }, 75 | 76 | addPass: function ( pass ) { 77 | 78 | this.passes.push( pass ); 79 | pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio ); 80 | 81 | }, 82 | 83 | insertPass: function ( pass, index ) { 84 | 85 | this.passes.splice( index, 0, pass ); 86 | pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio ); 87 | 88 | }, 89 | 90 | isLastEnabledPass: function ( passIndex ) { 91 | 92 | for ( var i = passIndex + 1; i < this.passes.length; i ++ ) { 93 | 94 | if ( this.passes[ i ].enabled ) { 95 | 96 | return false; 97 | 98 | } 99 | 100 | } 101 | 102 | return true; 103 | 104 | }, 105 | 106 | render: function ( deltaTime ) { 107 | 108 | // deltaTime value is in seconds 109 | 110 | if ( deltaTime === undefined ) { 111 | 112 | deltaTime = this.clock.getDelta(); 113 | 114 | } 115 | 116 | var currentRenderTarget = this.renderer.getRenderTarget(); 117 | 118 | var maskActive = false; 119 | 120 | var pass, i, il = this.passes.length; 121 | 122 | for ( i = 0; i < il; i ++ ) { 123 | 124 | pass = this.passes[ i ]; 125 | 126 | if ( pass.enabled === false ) continue; 127 | 128 | pass.renderToScreen = ( this.renderToScreen && this.isLastEnabledPass( i ) ); 129 | pass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime, maskActive ); 130 | 131 | if ( pass.needsSwap ) { 132 | 133 | if ( maskActive ) { 134 | 135 | var context = this.renderer.getContext(); 136 | var stencil = this.renderer.state.buffers.stencil; 137 | 138 | //context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff ); 139 | stencil.setFunc( context.NOTEQUAL, 1, 0xffffffff ); 140 | 141 | this.copyPass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime ); 142 | 143 | //context.stencilFunc( context.EQUAL, 1, 0xffffffff ); 144 | stencil.setFunc( context.EQUAL, 1, 0xffffffff ); 145 | 146 | } 147 | 148 | this.swapBuffers(); 149 | 150 | } 151 | 152 | if ( THREE.MaskPass !== undefined ) { 153 | 154 | if ( pass instanceof THREE.MaskPass ) { 155 | 156 | maskActive = true; 157 | 158 | } else if ( pass instanceof THREE.ClearMaskPass ) { 159 | 160 | maskActive = false; 161 | 162 | } 163 | 164 | } 165 | 166 | } 167 | 168 | this.renderer.setRenderTarget( currentRenderTarget ); 169 | 170 | }, 171 | 172 | reset: function ( renderTarget ) { 173 | 174 | if ( renderTarget === undefined ) { 175 | 176 | var size = this.renderer.getSize( new THREE.Vector2() ); 177 | this._pixelRatio = this.renderer.getPixelRatio(); 178 | this._width = size.width; 179 | this._height = size.height; 180 | 181 | renderTarget = this.renderTarget1.clone(); 182 | renderTarget.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio ); 183 | 184 | } 185 | 186 | this.renderTarget1.dispose(); 187 | this.renderTarget2.dispose(); 188 | this.renderTarget1 = renderTarget; 189 | this.renderTarget2 = renderTarget.clone(); 190 | 191 | this.writeBuffer = this.renderTarget1; 192 | this.readBuffer = this.renderTarget2; 193 | 194 | }, 195 | 196 | setSize: function ( width, height ) { 197 | 198 | this._width = width; 199 | this._height = height; 200 | 201 | var effectiveWidth = this._width * this._pixelRatio; 202 | var effectiveHeight = this._height * this._pixelRatio; 203 | 204 | this.renderTarget1.setSize( effectiveWidth, effectiveHeight ); 205 | this.renderTarget2.setSize( effectiveWidth, effectiveHeight ); 206 | 207 | for ( var i = 0; i < this.passes.length; i ++ ) { 208 | 209 | this.passes[ i ].setSize( effectiveWidth, effectiveHeight ); 210 | 211 | } 212 | 213 | }, 214 | 215 | setPixelRatio: function ( pixelRatio ) { 216 | 217 | this._pixelRatio = pixelRatio; 218 | 219 | this.setSize( this._width, this._height ); 220 | 221 | } 222 | 223 | } ); 224 | 225 | 226 | THREE.Pass = function () { 227 | 228 | // if set to true, the pass is processed by the composer 229 | this.enabled = true; 230 | 231 | // if set to true, the pass indicates to swap read and write buffer after rendering 232 | this.needsSwap = true; 233 | 234 | // if set to true, the pass clears its buffer before rendering 235 | this.clear = false; 236 | 237 | // if set to true, the result of the pass is rendered to screen. This is set automatically by EffectComposer. 238 | this.renderToScreen = false; 239 | 240 | }; 241 | 242 | Object.assign( THREE.Pass.prototype, { 243 | 244 | setSize: function ( /* width, height */ ) {}, 245 | 246 | render: function ( /* renderer, writeBuffer, readBuffer, deltaTime, maskActive */ ) { 247 | 248 | console.error( 'THREE.Pass: .render() must be implemented in derived pass.' ); 249 | 250 | } 251 | 252 | } ); 253 | 254 | // Helper for passes that need to fill the viewport with a single quad. 255 | THREE.Pass.FullScreenQuad = ( function () { 256 | 257 | var camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); 258 | var geometry = new THREE.PlaneBufferGeometry( 2, 2 ); 259 | 260 | var FullScreenQuad = function ( material ) { 261 | 262 | this._mesh = new THREE.Mesh( geometry, material ); 263 | 264 | }; 265 | 266 | Object.defineProperty( FullScreenQuad.prototype, 'material', { 267 | 268 | get: function () { 269 | 270 | return this._mesh.material; 271 | 272 | }, 273 | 274 | set: function ( value ) { 275 | 276 | this._mesh.material = value; 277 | 278 | } 279 | 280 | } ); 281 | 282 | Object.assign( FullScreenQuad.prototype, { 283 | 284 | dispose: function () { 285 | 286 | this._mesh.geometry.dispose(); 287 | 288 | }, 289 | 290 | render: function ( renderer ) { 291 | 292 | renderer.render( this._mesh, camera ); 293 | 294 | } 295 | 296 | } ); 297 | 298 | return FullScreenQuad; 299 | 300 | } )(); 301 | -------------------------------------------------------------------------------- /js/vendor/postprocessing/RenderPass.js: -------------------------------------------------------------------------------- 1 | console.warn( "THREE.RenderPass: As part of the transition to ES6 Modules, the files in 'examples/js' were deprecated in May 2020 (r117) and will be deleted in December 2020 (r124). You can find more information about developing using ES6 Modules in https://threejs.org/docs/#manual/en/introduction/Installation." ); 2 | /** 3 | * @author alteredq / http://alteredqualia.com/ 4 | */ 5 | 6 | 7 | THREE.RenderPass = function ( scene, camera, overrideMaterial, clearColor, clearAlpha ) { 8 | 9 | THREE.Pass.call( this ); 10 | 11 | this.scene = scene; 12 | this.camera = camera; 13 | 14 | this.overrideMaterial = overrideMaterial; 15 | 16 | this.clearColor = clearColor; 17 | this.clearAlpha = ( clearAlpha !== undefined ) ? clearAlpha : 0; 18 | 19 | this.clear = true; 20 | this.clearDepth = false; 21 | this.needsSwap = false; 22 | 23 | }; 24 | 25 | THREE.RenderPass.prototype = Object.assign( Object.create( THREE.Pass.prototype ), { 26 | 27 | constructor: THREE.RenderPass, 28 | 29 | render: function ( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { 30 | 31 | var oldAutoClear = renderer.autoClear; 32 | renderer.autoClear = false; 33 | 34 | var oldClearColor, oldClearAlpha, oldOverrideMaterial; 35 | 36 | if ( this.overrideMaterial !== undefined ) { 37 | 38 | oldOverrideMaterial = this.scene.overrideMaterial; 39 | 40 | this.scene.overrideMaterial = this.overrideMaterial; 41 | 42 | } 43 | 44 | if ( this.clearColor ) { 45 | 46 | oldClearColor = renderer.getClearColor().getHex(); 47 | oldClearAlpha = renderer.getClearAlpha(); 48 | 49 | renderer.setClearColor( this.clearColor, this.clearAlpha ); 50 | 51 | } 52 | 53 | if ( this.clearDepth ) { 54 | 55 | renderer.clearDepth(); 56 | 57 | } 58 | 59 | renderer.setRenderTarget( this.renderToScreen ? null : readBuffer ); 60 | 61 | // TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600 62 | if ( this.clear ) renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); 63 | renderer.render( this.scene, this.camera ); 64 | 65 | if ( this.clearColor ) { 66 | 67 | renderer.setClearColor( oldClearColor, oldClearAlpha ); 68 | 69 | } 70 | 71 | if ( this.overrideMaterial !== undefined ) { 72 | 73 | this.scene.overrideMaterial = oldOverrideMaterial; 74 | 75 | } 76 | 77 | renderer.autoClear = oldAutoClear; 78 | 79 | } 80 | 81 | } ); 82 | -------------------------------------------------------------------------------- /js/vendor/postprocessing/ShaderPass.js: -------------------------------------------------------------------------------- 1 | console.warn( "THREE.ShaderPass: As part of the transition to ES6 Modules, the files in 'examples/js' were deprecated in May 2020 (r117) and will be deleted in December 2020 (r124). You can find more information about developing using ES6 Modules in https://threejs.org/docs/#manual/en/introduction/Installation." ); 2 | /** 3 | * @author alteredq / http://alteredqualia.com/ 4 | */ 5 | 6 | THREE.ShaderPass = function ( shader, textureID ) { 7 | 8 | THREE.Pass.call( this ); 9 | 10 | this.textureID = ( textureID !== undefined ) ? textureID : "tDiffuse"; 11 | 12 | if ( shader instanceof THREE.ShaderMaterial ) { 13 | 14 | this.uniforms = shader.uniforms; 15 | 16 | this.material = shader; 17 | 18 | } else if ( shader ) { 19 | 20 | this.uniforms = THREE.UniformsUtils.clone( shader.uniforms ); 21 | 22 | this.material = new THREE.ShaderMaterial( { 23 | 24 | defines: Object.assign( {}, shader.defines ), 25 | uniforms: this.uniforms, 26 | vertexShader: shader.vertexShader, 27 | fragmentShader: shader.fragmentShader 28 | 29 | } ); 30 | 31 | } 32 | 33 | this.fsQuad = new THREE.Pass.FullScreenQuad( this.material ); 34 | 35 | }; 36 | 37 | THREE.ShaderPass.prototype = Object.assign( Object.create( THREE.Pass.prototype ), { 38 | 39 | constructor: THREE.ShaderPass, 40 | 41 | render: function ( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { 42 | 43 | if ( this.uniforms[ this.textureID ] ) { 44 | 45 | this.uniforms[ this.textureID ].value = readBuffer.texture; 46 | 47 | } 48 | 49 | this.fsQuad.material = this.material; 50 | 51 | if ( this.renderToScreen ) { 52 | 53 | renderer.setRenderTarget( null ); 54 | this.fsQuad.render( renderer ); 55 | 56 | } else { 57 | 58 | renderer.setRenderTarget( writeBuffer ); 59 | // TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600 60 | if ( this.clear ) renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); 61 | this.fsQuad.render( renderer ); 62 | 63 | } 64 | 65 | } 66 | 67 | } ); 68 | -------------------------------------------------------------------------------- /js/vendor/postprocessing/UnrealBloomPass.js: -------------------------------------------------------------------------------- 1 | console.warn( "THREE.UnrealBloomPass: As part of the transition to ES6 Modules, the files in 'examples/js' were deprecated in May 2020 (r117) and will be deleted in December 2020 (r124). You can find more information about developing using ES6 Modules in https://threejs.org/docs/#manual/en/introduction/Installation." ); 2 | /** 3 | * @author spidersharma / http://eduperiment.com/ 4 | */ 5 | 6 | /** 7 | * UnrealBloomPass is inspired by the bloom pass of Unreal Engine. It creates a 8 | * mip map chain of bloom textures and blurs them with different radii. Because 9 | * of the weighted combination of mips, and because larger blurs are done on 10 | * higher mips, this effect provides good quality and performance. 11 | * 12 | * Reference: 13 | * - https://docs.unrealengine.com/latest/INT/Engine/Rendering/PostProcessEffects/Bloom/ 14 | */ 15 | THREE.UnrealBloomPass = function ( resolution, strength, radius, threshold ) { 16 | 17 | THREE.Pass.call( this ); 18 | 19 | this.strength = ( strength !== undefined ) ? strength : 1; 20 | this.radius = radius; 21 | this.threshold = threshold; 22 | this.resolution = ( resolution !== undefined ) ? new THREE.Vector2( resolution.x, resolution.y ) : new THREE.Vector2( 256, 256 ); 23 | 24 | // create color only once here, reuse it later inside the render function 25 | this.clearColor = new THREE.Color( 0, 0, 0 ); 26 | 27 | // render targets 28 | var pars = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat }; 29 | this.renderTargetsHorizontal = []; 30 | this.renderTargetsVertical = []; 31 | this.nMips = 5; 32 | var resx = Math.round( this.resolution.x / 2 ); 33 | var resy = Math.round( this.resolution.y / 2 ); 34 | 35 | this.renderTargetBright = new THREE.WebGLRenderTarget( resx, resy, pars ); 36 | this.renderTargetBright.texture.name = "UnrealBloomPass.bright"; 37 | this.renderTargetBright.texture.generateMipmaps = false; 38 | 39 | for ( var i = 0; i < this.nMips; i ++ ) { 40 | 41 | var renderTargetHorizonal = new THREE.WebGLRenderTarget( resx, resy, pars ); 42 | 43 | renderTargetHorizonal.texture.name = "UnrealBloomPass.h" + i; 44 | renderTargetHorizonal.texture.generateMipmaps = false; 45 | 46 | this.renderTargetsHorizontal.push( renderTargetHorizonal ); 47 | 48 | var renderTargetVertical = new THREE.WebGLRenderTarget( resx, resy, pars ); 49 | 50 | renderTargetVertical.texture.name = "UnrealBloomPass.v" + i; 51 | renderTargetVertical.texture.generateMipmaps = false; 52 | 53 | this.renderTargetsVertical.push( renderTargetVertical ); 54 | 55 | resx = Math.round( resx / 2 ); 56 | 57 | resy = Math.round( resy / 2 ); 58 | 59 | } 60 | 61 | // luminosity high pass material 62 | 63 | if ( THREE.LuminosityHighPassShader === undefined ) 64 | console.error( "THREE.UnrealBloomPass relies on THREE.LuminosityHighPassShader" ); 65 | 66 | var highPassShader = THREE.LuminosityHighPassShader; 67 | this.highPassUniforms = THREE.UniformsUtils.clone( highPassShader.uniforms ); 68 | 69 | this.highPassUniforms[ "luminosityThreshold" ].value = threshold; 70 | this.highPassUniforms[ "smoothWidth" ].value = 0.01; 71 | 72 | this.materialHighPassFilter = new THREE.ShaderMaterial( { 73 | uniforms: this.highPassUniforms, 74 | vertexShader: highPassShader.vertexShader, 75 | fragmentShader: highPassShader.fragmentShader, 76 | defines: {} 77 | } ); 78 | 79 | // Gaussian Blur Materials 80 | this.separableBlurMaterials = []; 81 | var kernelSizeArray = [ 3, 5, 7, 9, 11 ]; 82 | var resx = Math.round( this.resolution.x / 2 ); 83 | var resy = Math.round( this.resolution.y / 2 ); 84 | 85 | for ( var i = 0; i < this.nMips; i ++ ) { 86 | 87 | this.separableBlurMaterials.push( this.getSeperableBlurMaterial( kernelSizeArray[ i ] ) ); 88 | 89 | this.separableBlurMaterials[ i ].uniforms[ "texSize" ].value = new THREE.Vector2( resx, resy ); 90 | 91 | resx = Math.round( resx / 2 ); 92 | 93 | resy = Math.round( resy / 2 ); 94 | 95 | } 96 | 97 | // Composite material 98 | this.compositeMaterial = this.getCompositeMaterial( this.nMips ); 99 | this.compositeMaterial.uniforms[ "blurTexture1" ].value = this.renderTargetsVertical[ 0 ].texture; 100 | this.compositeMaterial.uniforms[ "blurTexture2" ].value = this.renderTargetsVertical[ 1 ].texture; 101 | this.compositeMaterial.uniforms[ "blurTexture3" ].value = this.renderTargetsVertical[ 2 ].texture; 102 | this.compositeMaterial.uniforms[ "blurTexture4" ].value = this.renderTargetsVertical[ 3 ].texture; 103 | this.compositeMaterial.uniforms[ "blurTexture5" ].value = this.renderTargetsVertical[ 4 ].texture; 104 | this.compositeMaterial.uniforms[ "bloomStrength" ].value = strength; 105 | this.compositeMaterial.uniforms[ "bloomRadius" ].value = 0.1; 106 | this.compositeMaterial.needsUpdate = true; 107 | 108 | var bloomFactors = [ 1.0, 0.8, 0.6, 0.4, 0.2 ]; 109 | this.compositeMaterial.uniforms[ "bloomFactors" ].value = bloomFactors; 110 | this.bloomTintColors = [ new THREE.Vector3( 1, 1, 1 ), new THREE.Vector3( 1, 1, 1 ), new THREE.Vector3( 1, 1, 1 ), 111 | new THREE.Vector3( 1, 1, 1 ), new THREE.Vector3( 1, 1, 1 ) ]; 112 | this.compositeMaterial.uniforms[ "bloomTintColors" ].value = this.bloomTintColors; 113 | 114 | // copy material 115 | if ( THREE.CopyShader === undefined ) { 116 | 117 | console.error( "THREE.UnrealBloomPass relies on THREE.CopyShader" ); 118 | 119 | } 120 | 121 | var copyShader = THREE.CopyShader; 122 | 123 | this.copyUniforms = THREE.UniformsUtils.clone( copyShader.uniforms ); 124 | this.copyUniforms[ "opacity" ].value = 1.0; 125 | 126 | this.materialCopy = new THREE.ShaderMaterial( { 127 | uniforms: this.copyUniforms, 128 | vertexShader: copyShader.vertexShader, 129 | fragmentShader: copyShader.fragmentShader, 130 | blending: THREE.AdditiveBlending, 131 | depthTest: false, 132 | depthWrite: false, 133 | transparent: true 134 | } ); 135 | 136 | this.enabled = true; 137 | this.needsSwap = false; 138 | 139 | this.oldClearColor = new THREE.Color(); 140 | this.oldClearAlpha = 1; 141 | 142 | this.basic = new THREE.MeshBasicMaterial(); 143 | 144 | this.fsQuad = new THREE.Pass.FullScreenQuad( null ); 145 | 146 | }; 147 | 148 | THREE.UnrealBloomPass.prototype = Object.assign( Object.create( THREE.Pass.prototype ), { 149 | 150 | constructor: THREE.UnrealBloomPass, 151 | 152 | dispose: function () { 153 | 154 | for ( var i = 0; i < this.renderTargetsHorizontal.length; i ++ ) { 155 | 156 | this.renderTargetsHorizontal[ i ].dispose(); 157 | 158 | } 159 | 160 | for ( var i = 0; i < this.renderTargetsVertical.length; i ++ ) { 161 | 162 | this.renderTargetsVertical[ i ].dispose(); 163 | 164 | } 165 | 166 | this.renderTargetBright.dispose(); 167 | 168 | }, 169 | 170 | setSize: function ( width, height ) { 171 | 172 | var resx = Math.round( width / 2 ); 173 | var resy = Math.round( height / 2 ); 174 | 175 | this.renderTargetBright.setSize( resx, resy ); 176 | 177 | for ( var i = 0; i < this.nMips; i ++ ) { 178 | 179 | this.renderTargetsHorizontal[ i ].setSize( resx, resy ); 180 | this.renderTargetsVertical[ i ].setSize( resx, resy ); 181 | 182 | this.separableBlurMaterials[ i ].uniforms[ "texSize" ].value = new THREE.Vector2( resx, resy ); 183 | 184 | resx = Math.round( resx / 2 ); 185 | resy = Math.round( resy / 2 ); 186 | 187 | } 188 | 189 | }, 190 | 191 | render: function ( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { 192 | 193 | this.oldClearColor.copy( renderer.getClearColor() ); 194 | this.oldClearAlpha = renderer.getClearAlpha(); 195 | var oldAutoClear = renderer.autoClear; 196 | renderer.autoClear = false; 197 | 198 | renderer.setClearColor( this.clearColor, 0 ); 199 | 200 | if ( maskActive ) renderer.state.buffers.stencil.setTest( false ); 201 | 202 | // Render input to screen 203 | 204 | if ( this.renderToScreen ) { 205 | 206 | this.fsQuad.material = this.basic; 207 | this.basic.map = readBuffer.texture; 208 | 209 | renderer.setRenderTarget( null ); 210 | renderer.clear(); 211 | this.fsQuad.render( renderer ); 212 | 213 | } 214 | 215 | // 1. Extract Bright Areas 216 | 217 | this.highPassUniforms[ "tDiffuse" ].value = readBuffer.texture; 218 | this.highPassUniforms[ "luminosityThreshold" ].value = this.threshold; 219 | this.fsQuad.material = this.materialHighPassFilter; 220 | 221 | renderer.setRenderTarget( this.renderTargetBright ); 222 | renderer.clear(); 223 | this.fsQuad.render( renderer ); 224 | 225 | // 2. Blur All the mips progressively 226 | 227 | var inputRenderTarget = this.renderTargetBright; 228 | 229 | for ( var i = 0; i < this.nMips; i ++ ) { 230 | 231 | this.fsQuad.material = this.separableBlurMaterials[ i ]; 232 | 233 | this.separableBlurMaterials[ i ].uniforms[ "colorTexture" ].value = inputRenderTarget.texture; 234 | this.separableBlurMaterials[ i ].uniforms[ "direction" ].value = THREE.UnrealBloomPass.BlurDirectionX; 235 | renderer.setRenderTarget( this.renderTargetsHorizontal[ i ] ); 236 | renderer.clear(); 237 | this.fsQuad.render( renderer ); 238 | 239 | this.separableBlurMaterials[ i ].uniforms[ "colorTexture" ].value = this.renderTargetsHorizontal[ i ].texture; 240 | this.separableBlurMaterials[ i ].uniforms[ "direction" ].value = THREE.UnrealBloomPass.BlurDirectionY; 241 | renderer.setRenderTarget( this.renderTargetsVertical[ i ] ); 242 | renderer.clear(); 243 | this.fsQuad.render( renderer ); 244 | 245 | inputRenderTarget = this.renderTargetsVertical[ i ]; 246 | 247 | } 248 | 249 | // Composite All the mips 250 | 251 | this.fsQuad.material = this.compositeMaterial; 252 | this.compositeMaterial.uniforms[ "bloomStrength" ].value = this.strength; 253 | this.compositeMaterial.uniforms[ "bloomRadius" ].value = this.radius; 254 | this.compositeMaterial.uniforms[ "bloomTintColors" ].value = this.bloomTintColors; 255 | 256 | renderer.setRenderTarget( this.renderTargetsHorizontal[ 0 ] ); 257 | renderer.clear(); 258 | this.fsQuad.render( renderer ); 259 | 260 | // Blend it additively over the input texture 261 | 262 | this.fsQuad.material = this.materialCopy; 263 | this.copyUniforms[ "tDiffuse" ].value = this.renderTargetsHorizontal[ 0 ].texture; 264 | 265 | if ( maskActive ) renderer.state.buffers.stencil.setTest( true ); 266 | 267 | if ( this.renderToScreen ) { 268 | 269 | renderer.setRenderTarget( null ); 270 | this.fsQuad.render( renderer ); 271 | 272 | } else { 273 | 274 | renderer.setRenderTarget( readBuffer ); 275 | this.fsQuad.render( renderer ); 276 | 277 | } 278 | 279 | // Restore renderer settings 280 | 281 | renderer.setClearColor( this.oldClearColor, this.oldClearAlpha ); 282 | renderer.autoClear = oldAutoClear; 283 | 284 | }, 285 | 286 | getSeperableBlurMaterial: function ( kernelRadius ) { 287 | 288 | return new THREE.ShaderMaterial( { 289 | 290 | defines: { 291 | "KERNEL_RADIUS": kernelRadius, 292 | "SIGMA": kernelRadius 293 | }, 294 | 295 | uniforms: { 296 | "colorTexture": { value: null }, 297 | "texSize": { value: new THREE.Vector2( 0.5, 0.5 ) }, 298 | "direction": { value: new THREE.Vector2( 0.5, 0.5 ) } 299 | }, 300 | 301 | vertexShader: 302 | "varying vec2 vUv;\n\ 303 | void main() {\n\ 304 | vUv = uv;\n\ 305 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\ 306 | }", 307 | 308 | fragmentShader: 309 | "#include \ 310 | varying vec2 vUv;\n\ 311 | uniform sampler2D colorTexture;\n\ 312 | uniform vec2 texSize;\ 313 | uniform vec2 direction;\ 314 | \ 315 | float gaussianPdf(in float x, in float sigma) {\ 316 | return 0.39894 * exp( -0.5 * x * x/( sigma * sigma))/sigma;\ 317 | }\ 318 | void main() {\n\ 319 | vec2 invSize = 1.0 / texSize;\ 320 | float fSigma = float(SIGMA);\ 321 | float weightSum = gaussianPdf(0.0, fSigma);\ 322 | vec3 diffuseSum = texture2D( colorTexture, vUv).rgb * weightSum;\ 323 | for( int i = 1; i < KERNEL_RADIUS; i ++ ) {\ 324 | float x = float(i);\ 325 | float w = gaussianPdf(x, fSigma);\ 326 | vec2 uvOffset = direction * invSize * x;\ 327 | vec3 sample1 = texture2D( colorTexture, vUv + uvOffset).rgb;\ 328 | vec3 sample2 = texture2D( colorTexture, vUv - uvOffset).rgb;\ 329 | diffuseSum += (sample1 + sample2) * w;\ 330 | weightSum += 2.0 * w;\ 331 | }\ 332 | gl_FragColor = vec4(diffuseSum/weightSum, 1.0);\n\ 333 | }" 334 | } ); 335 | 336 | }, 337 | 338 | getCompositeMaterial: function ( nMips ) { 339 | 340 | return new THREE.ShaderMaterial( { 341 | 342 | defines: { 343 | "NUM_MIPS": nMips 344 | }, 345 | 346 | uniforms: { 347 | "blurTexture1": { value: null }, 348 | "blurTexture2": { value: null }, 349 | "blurTexture3": { value: null }, 350 | "blurTexture4": { value: null }, 351 | "blurTexture5": { value: null }, 352 | "dirtTexture": { value: null }, 353 | "bloomStrength": { value: 1.0 }, 354 | "bloomFactors": { value: null }, 355 | "bloomTintColors": { value: null }, 356 | "bloomRadius": { value: 0.0 } 357 | }, 358 | 359 | vertexShader: 360 | "varying vec2 vUv;\n\ 361 | void main() {\n\ 362 | vUv = uv;\n\ 363 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\ 364 | }", 365 | 366 | fragmentShader: 367 | "varying vec2 vUv;\ 368 | uniform sampler2D blurTexture1;\ 369 | uniform sampler2D blurTexture2;\ 370 | uniform sampler2D blurTexture3;\ 371 | uniform sampler2D blurTexture4;\ 372 | uniform sampler2D blurTexture5;\ 373 | uniform sampler2D dirtTexture;\ 374 | uniform float bloomStrength;\ 375 | uniform float bloomRadius;\ 376 | uniform float bloomFactors[NUM_MIPS];\ 377 | uniform vec3 bloomTintColors[NUM_MIPS];\ 378 | \ 379 | float lerpBloomFactor(const in float factor) { \ 380 | float mirrorFactor = 1.2 - factor;\ 381 | return mix(factor, mirrorFactor, bloomRadius);\ 382 | }\ 383 | \ 384 | void main() {\ 385 | gl_FragColor = bloomStrength * ( lerpBloomFactor(bloomFactors[0]) * vec4(bloomTintColors[0], 1.0) * texture2D(blurTexture1, vUv) + \ 386 | lerpBloomFactor(bloomFactors[1]) * vec4(bloomTintColors[1], 1.0) * texture2D(blurTexture2, vUv) + \ 387 | lerpBloomFactor(bloomFactors[2]) * vec4(bloomTintColors[2], 1.0) * texture2D(blurTexture3, vUv) + \ 388 | lerpBloomFactor(bloomFactors[3]) * vec4(bloomTintColors[3], 1.0) * texture2D(blurTexture4, vUv) + \ 389 | lerpBloomFactor(bloomFactors[4]) * vec4(bloomTintColors[4], 1.0) * texture2D(blurTexture5, vUv) );\ 390 | }" 391 | } ); 392 | 393 | } 394 | 395 | } ); 396 | 397 | THREE.UnrealBloomPass.BlurDirectionX = new THREE.Vector2( 1.0, 0.0 ); 398 | THREE.UnrealBloomPass.BlurDirectionY = new THREE.Vector2( 0.0, 1.0 ); 399 | -------------------------------------------------------------------------------- /js/vendor/shader/ConvolutionShader.js: -------------------------------------------------------------------------------- 1 | console.warn( "THREE.ConvolutionShader: As part of the transition to ES6 Modules, the files in 'examples/js' were deprecated in May 2020 (r117) and will be deleted in December 2020 (r124). You can find more information about developing using ES6 Modules in https://threejs.org/docs/#manual/en/introduction/Installation." ); 2 | /** 3 | * @author alteredq / http://alteredqualia.com/ 4 | * 5 | * Convolution shader 6 | * ported from o3d sample to WebGL / GLSL 7 | * http://o3d.googlecode.com/svn/trunk/samples/convolution.html 8 | */ 9 | 10 | THREE.ConvolutionShader = { 11 | 12 | defines: { 13 | 14 | "KERNEL_SIZE_FLOAT": "25.0", 15 | "KERNEL_SIZE_INT": "25" 16 | 17 | }, 18 | 19 | uniforms: { 20 | 21 | "tDiffuse": { value: null }, 22 | "uImageIncrement": { value: new THREE.Vector2( 0.001953125, 0.0 ) }, 23 | "cKernel": { value: [] } 24 | 25 | }, 26 | 27 | vertexShader: [ 28 | 29 | "uniform vec2 uImageIncrement;", 30 | 31 | "varying vec2 vUv;", 32 | 33 | "void main() {", 34 | 35 | " vUv = uv - ( ( KERNEL_SIZE_FLOAT - 1.0 ) / 2.0 ) * uImageIncrement;", 36 | " gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 37 | 38 | "}" 39 | 40 | ].join( "\n" ), 41 | 42 | fragmentShader: [ 43 | 44 | "uniform float cKernel[ KERNEL_SIZE_INT ];", 45 | 46 | "uniform sampler2D tDiffuse;", 47 | "uniform vec2 uImageIncrement;", 48 | 49 | "varying vec2 vUv;", 50 | 51 | "void main() {", 52 | 53 | " vec2 imageCoord = vUv;", 54 | " vec4 sum = vec4( 0.0, 0.0, 0.0, 0.0 );", 55 | 56 | " for( int i = 0; i < KERNEL_SIZE_INT; i ++ ) {", 57 | 58 | " sum += texture2D( tDiffuse, imageCoord ) * cKernel[ i ];", 59 | " imageCoord += uImageIncrement;", 60 | 61 | " }", 62 | 63 | " gl_FragColor = sum;", 64 | 65 | "}" 66 | 67 | 68 | ].join( "\n" ), 69 | 70 | buildKernel: function ( sigma ) { 71 | 72 | // We lop off the sqrt(2 * pi) * sigma term, since we're going to normalize anyway. 73 | 74 | function gauss( x, sigma ) { 75 | 76 | return Math.exp( - ( x * x ) / ( 2.0 * sigma * sigma ) ); 77 | 78 | } 79 | 80 | var i, values, sum, halfWidth, kMaxKernelSize = 25, kernelSize = 2 * Math.ceil( sigma * 3.0 ) + 1; 81 | 82 | if ( kernelSize > kMaxKernelSize ) kernelSize = kMaxKernelSize; 83 | halfWidth = ( kernelSize - 1 ) * 0.5; 84 | 85 | values = new Array( kernelSize ); 86 | sum = 0.0; 87 | for ( i = 0; i < kernelSize; ++ i ) { 88 | 89 | values[ i ] = gauss( i - halfWidth, sigma ); 90 | sum += values[ i ]; 91 | 92 | } 93 | 94 | // normalize the kernel 95 | 96 | for ( i = 0; i < kernelSize; ++ i ) values[ i ] /= sum; 97 | 98 | return values; 99 | 100 | } 101 | 102 | }; 103 | -------------------------------------------------------------------------------- /js/vendor/shader/CopyShader.js: -------------------------------------------------------------------------------- 1 | console.warn( "THREE.CopyShader: As part of the transition to ES6 Modules, the files in 'examples/js' were deprecated in May 2020 (r117) and will be deleted in December 2020 (r124). You can find more information about developing using ES6 Modules in https://threejs.org/docs/#manual/en/introduction/Installation." ); 2 | /** 3 | * @author alteredq / http://alteredqualia.com/ 4 | * 5 | * Full-screen textured quad shader 6 | */ 7 | 8 | THREE.CopyShader = { 9 | 10 | uniforms: { 11 | 12 | "tDiffuse": { value: null }, 13 | "opacity": { value: 1.0 } 14 | 15 | }, 16 | 17 | vertexShader: [ 18 | 19 | "varying vec2 vUv;", 20 | 21 | "void main() {", 22 | 23 | " vUv = uv;", 24 | " gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 25 | 26 | "}" 27 | 28 | ].join( "\n" ), 29 | 30 | fragmentShader: [ 31 | 32 | "uniform float opacity;", 33 | 34 | "uniform sampler2D tDiffuse;", 35 | 36 | "varying vec2 vUv;", 37 | 38 | "void main() {", 39 | 40 | " vec4 texel = texture2D( tDiffuse, vUv );", 41 | " gl_FragColor = opacity * texel;", 42 | 43 | "}" 44 | 45 | ].join( "\n" ) 46 | 47 | }; 48 | --------------------------------------------------------------------------------