Created
March 22, 2013 07:26
-
-
Save YenTheFirst/5219565 to your computer and use it in GitHub Desktop.
recursive portals
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<title>spinnin' cubes! yes! plural!</title> | |
<style>canvas { width: 100%; height: 100% }</style> | |
</head> | |
<body> | |
<script src="https://raw.github.com/mrdoob/three.js/master/build/three.js"></script> | |
<script> | |
//create basic context | |
var main_scene = new THREE.Scene(); | |
var camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000); | |
var renderer = new THREE.WebGLRenderer({stencil: true}); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
document.body.appendChild(renderer.domElement); | |
//create scene | |
//create cube | |
var geometry = new THREE.CubeGeometry(0.5,0.5,0.5); | |
var green_material = new THREE.MeshPhongMaterial({color: 0x00ff00}); | |
var cube = new THREE.Mesh(geometry, green_material); | |
main_scene.add(cube); | |
//create portals | |
var portal_geom = new THREE.Geometry(); | |
portal_geom.vertices.push( new THREE.Vector4( -1, -1) ); | |
portal_geom.vertices.push( new THREE.Vector4( 1, -1) ); | |
portal_geom.vertices.push( new THREE.Vector4( -1, 1) ); | |
portal_geom.vertices.push( new THREE.Vector4( 1, 1) ); | |
portal_geom.faces.push( new THREE.Face3( 0, 1, 2 ) ); | |
portal_geom.faces.push( new THREE.Face3( 2, 1, 3 ) ); | |
portal_geom.computeFaceNormals(); | |
var red_material = new THREE.MeshPhongMaterial({color: 0xFF0000}); | |
var blue_material = new THREE.MeshPhongMaterial({color: 0x0000FF}); | |
var portals = [ | |
new THREE.Mesh(portal_geom, blue_material), | |
new THREE.Mesh(portal_geom, red_material), | |
new THREE.Mesh(portal_geom, green_material) //this one will be a mirror technically | |
]; | |
portals[0].position = new THREE.Vector3(0, 0, -2); | |
portals[1].position = new THREE.Vector3(0, 0, 3); | |
portals[1].rotation = new THREE.Vector3(0, 3.14*1, 0); | |
portals[2].position = new THREE.Vector3(-2,0,0); | |
portals[2].rotation = new THREE.Vector3(0,3.14*0.5,0); | |
main_scene.add(portals[0]); | |
main_scene.add(portals[1]); | |
main_scene.add(portals[2]); | |
//create a set of 'monitor' objects based on the portals | |
var monitors = portals.map(function (obj) { | |
var new_obj = obj.clone(); | |
new_obj.scale.set(0.95,0.95,0.95); | |
new_obj.material = obj.material.clone(); | |
return new_obj; | |
}); | |
//TODO: more straightforward way of having portals + frames | |
monitors[0].position.z+=0.001; | |
monitors[1].position.z-=0.001; | |
monitors[2].position.x+=0.001; | |
monitors[0].calc_view = function(view_matrix) {return portal_view(view_matrix, monitors[0], monitors[1]);}; | |
monitors[1].calc_view = function(view_matrix) {return portal_view(view_matrix, monitors[1], monitors[0]);}; | |
monitors[2].calc_view = function(view_matrix) {return portal_view(view_matrix, monitors[2], monitors[2]);}; //this is not a true mirror. TODO: actual reflection matrix | |
var monitors_scene = new THREE.Scene(); | |
monitors_scene.add(monitors[0]); | |
monitors_scene.add(monitors[1]); | |
monitors_scene.add(monitors[2]); | |
//create the light | |
var light = new THREE.PointLight(0xFFFFFF); | |
light.position.set(0,1,2); | |
main_scene.add(light); | |
camera.position.z = 5; | |
function portal_view(view_matrix, src_portal, dst_portal) { | |
var inverse_view_to_source = new THREE.Matrix4().getInverse(view_matrix).multiply(src_portal.matrix); | |
var new_mat = dst_portal.matrix.clone().multiply(inverse_view_to_source); | |
new_mat.rotateY(3.14); | |
return new_mat; | |
} | |
var gl = renderer.context; | |
//render all potentially recursive monitors. | |
//afterwards, the depth buffer will be filled in with the depth information of the monitors, so the monitor's view isn't overwritten | |
function render_monitors(view_matrix, recurse_level) { | |
if (recurse_level > 2) {return;} //hardcoded for now | |
//for each monitor at the current level | |
monitors.forEach(function(monitor) { | |
//prepare the stencil object for this monitor. (maybe precompute this?) | |
var just_this_monitor = new THREE.Scene(); | |
var stencil_monitor = monitor.clone(); | |
stencil_monitor.material = monitor.material.clone(); | |
stencil_monitor.material.depthWrite=false; | |
stencil_monitor.material.depthTest=false; | |
just_this_monitor.add(stencil_monitor); | |
var monitor_view = monitor.calc_view(view_matrix); | |
//A. increment the stencil buffer for this monitor | |
//we only want to consider adding stencil to where there's already a base | |
gl.colorMask(false,false,false,false); //disable the color buffer | |
camera.matrixWorld=view_matrix; | |
gl.stencilFunc(gl.EQUAL,recurse_level,0xFF); | |
gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); | |
renderer.render(just_this_monitor, camera); | |
//B. render sub-monitors (for hall-of-mirrors, recursive views, etc.) | |
render_monitors(monitor_view, recurse_level+1) //TODO: filter out sub-monitors based on visibility | |
//C. render the main scene as seen by this monitor's view, but only where our stencil is (stencil level = recurse_level+1) | |
gl.colorMask(true,true,true,true); | |
camera.matrixWorld = monitor_view; | |
gl.stencilFunc(gl.EQUAL,recurse_level+1,0xFF); | |
gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); //ideally, I'd like to just DECR on success, but webgl seems not to respect that. | |
renderer.render(main_scene, camera); | |
//D. finally, decrement the stencil buffer for this monitor | |
//but, only where we actually incremented it | |
gl.colorMask(false,false,false,false); //disable the color buffer | |
camera.matrixWorld=view_matrix; | |
gl.stencilFunc(gl.EQUAL,recurse_level+1,0xFF); | |
gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); | |
renderer.render(just_this_monitor, camera); | |
}); | |
//protect the monitors' views at this level by drawing them in the depth buffer, as seen by the outer view. | |
//clear the current depth buffer | |
renderer.clear(false,true,false); | |
//disable color buffer - draw only to depth | |
gl.colorMask(false,false,false,false); | |
//we only care about the depth buffer within the bounds of the outer portal's stencil. don't alter the stencil, though | |
gl.stencilFunc(gl.EQUAL,recurse_level,0xFF); | |
gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); | |
//set the camera back to the original view | |
camera.matrixWorld = view_matrix; | |
renderer.render(monitors_scene, camera); | |
} | |
//render & update loop | |
function render() { | |
//requestAnimationFrame(function(){console.log('ready')}); | |
requestAnimationFrame(render); | |
cube.rotation.x += 0.1; | |
cube.rotation.y += 0.1; | |
//allow camera to have its matrix updated directly | |
camera.updateMatrixWorld(); | |
camera.matrixAutoUpdate=false; | |
//clear the old scene | |
renderer.autoClearColor=false; | |
renderer.autoClearStencil=false; | |
renderer.autoClearDepth=false; | |
renderer.clear(true,true,true); | |
gl.enable(gl.STENCIL_TEST); //render monitors makes use of the stencil test all the way through. | |
render_monitors(camera.matrix,0); | |
gl.colorMask(true,true,true,true); //re-enable the color buffer | |
gl.disable(gl.STENCIL_TEST); //we no longer care about the stencil for the main scene | |
renderer.render(main_scene, camera); | |
camera.matrixAutoUpdate=true; | |
} | |
render(); | |
//simple rotating view based on mouse | |
document.addEventListener('mousemove', function(e) { | |
camera.position.set(0,0,0); | |
elevation = - 0.5*3.14 + 3.14 * e.clientY / window.innerHeight; | |
rotation = -3.14 + 6.28 * e.clientX / window.innerWidth; | |
camera.eulerOrder='YXZ'; | |
camera.rotation.set(elevation,rotation,0); | |
camera.translateZ(4); | |
}); | |
</script> | |
</body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment