Inspired by this pen: http://codepen.io/golle404/pen/zqvRLG and this demo: http://threejs.org/examples/#webgl_mirror, I tried to create a real praxinoscope.
A Pen by Andreas Borgen on CodePen.
| #container |
| "use strict"; | |
| console.clear(); | |
| // camera | |
| var VIEW_ANGLE = 45; | |
| var NEAR = 1; | |
| var FAR = 500; | |
| //var FRAMES_URL = 'http://crossorigin.me/https://docs.coronalabs.com/images/simulator/sprites-cat-running.png', | |
| // FRAMES_X = 4, FRAMES_Y = 2; | |
| var FRAMES_URL = | |
| '//programming.enthuses.me/assets/codepen/Muybridge%20-%20three.js.jpg', | |
| //'https://i.imgur.com/EUWJA2s.jpg', | |
| //'https://upload.wikimedia.org/wikipedia/commons/4/4a/Muybridge_race_horse_gallop.jpg', | |
| FRAMES_X = 4, FRAMES_Y = 4; | |
| var camera, scene, renderer; | |
| var cameraControls, stats; | |
| var praxinoGroup, | |
| magniSphere, magniCamera; | |
| function init() { | |
| // renderer | |
| renderer = new THREE.WebGLRenderer(); | |
| renderer.setPixelRatio( window.devicePixelRatio ); | |
| // scene | |
| scene = new THREE.Scene(); | |
| // camera | |
| camera = new THREE.PerspectiveCamera(VIEW_ANGLE, 1, NEAR, FAR); | |
| camera.position.set( 0, 45, 150 ); | |
| function resize() { | |
| var aspect = window.innerWidth / window.innerHeight; | |
| camera.aspect = aspect; | |
| camera.fov = VIEW_ANGLE/((aspect+1)/2); | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| } | |
| window.addEventListener('resize', resize, false); | |
| resize(); | |
| cameraControls = new THREE.OrbitControls(camera, renderer.domElement); | |
| cameraControls.target.set(0, 0, 0); | |
| cameraControls.maxDistance = 500; | |
| //cameraControls.minDistance = 30; | |
| cameraControls.update(); | |
| var container = document.getElementById( 'container' ); | |
| container.appendChild( renderer.domElement ); | |
| //https://github.com/mrdoob/stats.js/ | |
| stats = new Stats(); | |
| stats.setMode(0); // 0: fps, 1: ms, 2: mb | |
| stats.domElement.style.position = 'absolute'; | |
| stats.domElement.style.left = '0px'; | |
| stats.domElement.style.top = '0px'; | |
| container.appendChild( stats.domElement ); | |
| } | |
| function fillScene() { | |
| var boxMaterial = new THREE.MeshPhongMaterial( { color: 0xffffff/*, side: THREE.DoubleSide*/ } ), | |
| imgCount = FRAMES_X * FRAMES_Y, | |
| mirrorRad = 20, | |
| imgRad = mirrorRad * 2; | |
| var imgAngle = Math.PI*2 / imgCount, | |
| //https://www.mathsisfun.com/sine-cosine-tangent.html | |
| //tan(a) = opposite/adjacent | |
| //tan(mirrorAngle/2) = mirrorWidth/2 / mirrorsRad | |
| mirrorWidth = Math.tan(imgAngle/2) * 2 * mirrorRad, | |
| mirrorHeight = mirrorRad, | |
| mirrorY = mirrorHeight/4; | |
| praxinoGroup = new THREE.Object3D(); | |
| scene.add(praxinoGroup); | |
| var planeBottom = new THREE.Mesh( new THREE.PlaneBufferGeometry( imgRad*2.2, imgRad*2.2), boxMaterial ); | |
| //planeBack.position.z = -50; | |
| planeBottom.position.y = -mirrorHeight/2; | |
| planeBottom.rotateX( -Math.PI/2 ); | |
| praxinoGroup.add( planeBottom ); | |
| //http://stackoverflow.com/questions/13223168/open-ended-cylinder-not-showing-inside-material | |
| var frameMaterial = boxMaterial.clone(); | |
| frameMaterial.side = THREE.DoubleSide; | |
| var outerFrame = new THREE.Mesh( | |
| new THREE.CylinderGeometry(imgRad*1.1, imgRad*1.1, | |
| mirrorHeight, 20, 1, true), | |
| frameMaterial | |
| ); | |
| praxinoGroup.add(outerFrame); | |
| var mirrorsLid = new THREE.Mesh( | |
| new THREE.CylinderGeometry(mirrorRad, mirrorRad, | |
| .1, 20), | |
| boxMaterial | |
| ); | |
| mirrorsLid.position.y = mirrorHeight/2 + mirrorY; | |
| praxinoGroup.add(mirrorsLid); | |
| //Mirrors and images | |
| function addMirrorAndImage(i) { | |
| var theta = imgAngle * i, | |
| x = mirrorRad * Math.sin(theta), | |
| z = mirrorRad * Math.cos(theta); | |
| //console.log(i, imgAngle, theta); | |
| //Mirror | |
| var mirror = new THREE.Reflector( | |
| mirrorWidth, | |
| mirrorHeight, | |
| { clipBias: 0.0001, textureWidth: 1024, textureHeight: 1024, color: 0x777777 } | |
| ); | |
| mirror.position.y = mirrorY; | |
| mirror.position.x = x; | |
| mirror.position.z = z; | |
| mirror.rotateY(theta); | |
| praxinoGroup.add(mirror); | |
| //Image | |
| //https://threejs.org/docs/#Reference/Loaders/TextureLoader | |
| // instantiate a loader | |
| var loader = new THREE.TextureLoader(); | |
| loader.crossOrigin = ''; | |
| // load a resource | |
| loader.load( | |
| // resource URL | |
| FRAMES_URL, | |
| // Function when resource is loaded | |
| function ( imgTexture ) { | |
| //https://github.com/mrdoob/three.js/issues/1847 | |
| imgTexture.repeat.x = 1/FRAMES_X; | |
| imgTexture.repeat.y = 1/FRAMES_Y; | |
| //.75 -> 0 | |
| imgTexture.offset.y = 1 - (1 + Math.floor(i / FRAMES_X))/FRAMES_Y; | |
| imgTexture.offset.x = (1/FRAMES_X) * (i % FRAMES_X); | |
| //console.log(i, imgTexture.offset.y, imgTexture.offset.x) | |
| /* | |
| //http://stackoverflow.com/questions/24087757/three-js-and-loading-a-cross-domain-image | |
| //http://jsfiddle.net/greggman/MZpx8/ | |
| var imgMaterial = new THREE.MeshPhongMaterial({ | |
| map: imgTexture, | |
| //specular: 0xFFFFFF, | |
| //shininess: 5, | |
| shading: THREE.FlatShading, | |
| }); | |
| */ | |
| var imgMaterial = new THREE.MeshBasicMaterial({ | |
| map: imgTexture | |
| }); | |
| var img = new THREE.Mesh( new THREE.PlaneBufferGeometry( mirrorWidth*1.5, mirrorWidth ), imgMaterial /*new THREE.MeshPhongMaterial( { color: 0x00ff00 } )*/ ); | |
| //img.position.y = mirrorY; | |
| img.position.x = x*2; | |
| img.position.z = -z*2; | |
| img.rotateY(-theta); | |
| praxinoGroup.add( img ); | |
| } | |
| ); | |
| } | |
| for(var i=0; i<imgCount; i++) { | |
| addMirrorAndImage(i); | |
| } | |
| //Magnifying glass | |
| var magnifierGroup = new THREE.Object3D(); | |
| //https://www.script-tutorials.com/webgl-with-three-js-lesson-5/ | |
| magniCamera = new THREE.CubeCamera(NEAR, FAR, 500); | |
| magniCamera.renderTarget.mapping = THREE.CubeRefractionMapping; | |
| // create refracting material and spherical mesh | |
| var magniMaterial = new THREE.MeshBasicMaterial({ | |
| color: 0xffffff, | |
| envMap: magniCamera.renderTarget, | |
| refractionRatio: 1, | |
| reflectivity: 0.9 | |
| }); | |
| //magniSphere = new THREE.Mesh( new THREE.SphereGeometry(10, 32, 32), magniMaterial); | |
| magniSphere = new THREE.Mesh( new THREE.CylinderGeometry( 14, 14, 2, 30 ), magniMaterial); | |
| var magniFrame = new THREE.Mesh( new THREE.CylinderGeometry( 15, 15, 1.9, 30 ), boxMaterial); | |
| var magniHandle = new THREE.Mesh( new THREE.CylinderGeometry( 1, 1.5, 18, 8 ), boxMaterial); | |
| magniHandle.rotateZ(Math.PI/2); | |
| magniHandle.position.set(24,0,0); | |
| magnifierGroup.add(magniSphere); | |
| magnifierGroup.add(magniFrame); | |
| magnifierGroup.add(magniHandle); | |
| magnifierGroup.position.set(0, 21, 70); | |
| magnifierGroup.rotateX(1); | |
| magnifierGroup.rotateY(-1); | |
| //magniSphere.position/2 - The closer, the more magnification: | |
| magniCamera.position.set(0, 10, 35); | |
| scene.add(magniCamera); | |
| scene.add(magnifierGroup); | |
| // lights | |
| var mainLight = new THREE.PointLight( 0xffff00, .5, 250 ); | |
| mainLight.position.y = 60; | |
| //scene.add( mainLight ); | |
| var spotLight = new THREE.PointLight( 0xffaa00, 2, 250 ); | |
| spotLight.position.y = 60; | |
| spotLight.position.z = 100; | |
| scene.add( spotLight ); | |
| } | |
| function render() { | |
| // update refracting object | |
| magniSphere.visible = false; | |
| magniCamera.update(renderer, scene); | |
| magniSphere.visible = true; | |
| renderer.render(scene, camera); | |
| } | |
| function update() { | |
| requestAnimationFrame( update ); | |
| var timer = Date.now() * 0.001; | |
| //camera.position.x = Math.cos( timer/2 ) * 200; | |
| //camera.position.z = Math.sin( timer/2 ) * 200; | |
| //camera.lookAt( scene.position ); | |
| praxinoGroup.rotation.y = (timer*2); | |
| cameraControls.update(); | |
| stats.update(); | |
| render(); | |
| } | |
| init(); | |
| fillScene(); | |
| update(); |
| <script src="https://threejs.org/examples/js/libs/stats.min.js"></script> | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/three.js/88/three.min.js"></script> | |
| <script src="//cdn.rawgit.com/mrdoob/three.js/r88/examples/js/objects/Reflector.js"></script> | |
| <script src="//cdn.rawgit.com/mrdoob/three.js/r88/examples/js/controls/OrbitControls.js"></script> |
| body { | |
| background-color: #000; | |
| margin: 0px; | |
| overflow: hidden; | |
| } |
Inspired by this pen: http://codepen.io/golle404/pen/zqvRLG and this demo: http://threejs.org/examples/#webgl_mirror, I tried to create a real praxinoscope.
A Pen by Andreas Borgen on CodePen.