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.