This is a 3D Boids (flocking behaviour of birds) simulation created using three.js for CME151: Introduction to Data Visualization (Stanford University, Winter 2016).
Last active
October 23, 2023 17:27
-
-
Save kennyng/3763f41bf9f3508ab322 to your computer and use it in GitHub Desktop.
3D Boids Simulation
This file contains 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
/** | |
* @author mrdoob / http://mrdoob.com/ | |
*/ | |
THREE.SpriteCanvasMaterial = function ( parameters ) { | |
THREE.Material.call( this ); | |
this.type = 'SpriteCanvasMaterial'; | |
this.color = new THREE.Color( 0xffffff ); | |
this.program = function ( context, color ) {}; | |
this.setValues( parameters ); | |
}; | |
THREE.SpriteCanvasMaterial.prototype = Object.create( THREE.Material.prototype ); | |
THREE.SpriteCanvasMaterial.prototype.constructor = THREE.SpriteCanvasMaterial; | |
THREE.SpriteCanvasMaterial.prototype.clone = function () { | |
var material = new THREE.SpriteCanvasMaterial(); | |
THREE.Material.prototype.clone.call( this, material ); | |
material.color.copy( this.color ); | |
material.program = this.program; | |
return material; | |
}; | |
// | |
THREE.CanvasRenderer = function ( parameters ) { | |
console.log( 'THREE.CanvasRenderer', THREE.REVISION ); | |
var smoothstep = THREE.Math.smoothstep; | |
parameters = parameters || {}; | |
var _this = this, | |
_renderData, _elements, _lights, | |
_projector = new THREE.Projector(), | |
_canvas = parameters.canvas !== undefined | |
? parameters.canvas | |
: document.createElement( 'canvas' ), | |
_canvasWidth = _canvas.width, | |
_canvasHeight = _canvas.height, | |
_canvasWidthHalf = Math.floor( _canvasWidth / 2 ), | |
_canvasHeightHalf = Math.floor( _canvasHeight / 2 ), | |
_viewportX = 0, | |
_viewportY = 0, | |
_viewportWidth = _canvasWidth, | |
_viewportHeight = _canvasHeight, | |
pixelRatio = 1, | |
_context = _canvas.getContext( '2d', { | |
alpha: parameters.alpha === true | |
} ), | |
_clearColor = new THREE.Color( 0x000000 ), | |
_clearAlpha = parameters.alpha === true ? 0 : 1, | |
_contextGlobalAlpha = 1, | |
_contextGlobalCompositeOperation = 0, | |
_contextStrokeStyle = null, | |
_contextFillStyle = null, | |
_contextLineWidth = null, | |
_contextLineCap = null, | |
_contextLineJoin = null, | |
_contextLineDash = [], | |
_camera, | |
_v1, _v2, _v3, _v4, | |
_v5 = new THREE.RenderableVertex(), | |
_v6 = new THREE.RenderableVertex(), | |
_v1x, _v1y, _v2x, _v2y, _v3x, _v3y, | |
_v4x, _v4y, _v5x, _v5y, _v6x, _v6y, | |
_color = new THREE.Color(), | |
_color1 = new THREE.Color(), | |
_color2 = new THREE.Color(), | |
_color3 = new THREE.Color(), | |
_color4 = new THREE.Color(), | |
_diffuseColor = new THREE.Color(), | |
_emissiveColor = new THREE.Color(), | |
_lightColor = new THREE.Color(), | |
_patterns = {}, | |
_image, _uvs, | |
_uv1x, _uv1y, _uv2x, _uv2y, _uv3x, _uv3y, | |
_clipBox = new THREE.Box2(), | |
_clearBox = new THREE.Box2(), | |
_elemBox = new THREE.Box2(), | |
_ambientLight = new THREE.Color(), | |
_directionalLights = new THREE.Color(), | |
_pointLights = new THREE.Color(), | |
_vector3 = new THREE.Vector3(), // Needed for PointLight | |
_centroid = new THREE.Vector3(), | |
_normal = new THREE.Vector3(), | |
_normalViewMatrix = new THREE.Matrix3(); | |
// dash+gap fallbacks for Firefox and everything else | |
if ( _context.setLineDash === undefined ) { | |
_context.setLineDash = function () {} | |
} | |
this.domElement = _canvas; | |
this.autoClear = true; | |
this.sortObjects = true; | |
this.sortElements = true; | |
this.info = { | |
render: { | |
vertices: 0, | |
faces: 0 | |
} | |
} | |
// WebGLRenderer compatibility | |
this.supportsVertexTextures = function () {}; | |
this.setFaceCulling = function () {}; | |
// | |
this.getPixelRatio = function () { | |
return pixelRatio; | |
}; | |
this.setPixelRatio = function ( value ) { | |
pixelRatio = value; | |
}; | |
this.setSize = function ( width, height, updateStyle ) { | |
_canvasWidth = width * pixelRatio; | |
_canvasHeight = height * pixelRatio; | |
_canvas.width = _canvasWidth; | |
_canvas.height = _canvasHeight; | |
_canvasWidthHalf = Math.floor( _canvasWidth / 2 ); | |
_canvasHeightHalf = Math.floor( _canvasHeight / 2 ); | |
if ( updateStyle !== false ) { | |
_canvas.style.width = width + 'px'; | |
_canvas.style.height = height + 'px'; | |
} | |
_clipBox.min.set( -_canvasWidthHalf, -_canvasHeightHalf ), | |
_clipBox.max.set( _canvasWidthHalf, _canvasHeightHalf ); | |
_clearBox.min.set( - _canvasWidthHalf, - _canvasHeightHalf ); | |
_clearBox.max.set( _canvasWidthHalf, _canvasHeightHalf ); | |
_contextGlobalAlpha = 1; | |
_contextGlobalCompositeOperation = 0; | |
_contextStrokeStyle = null; | |
_contextFillStyle = null; | |
_contextLineWidth = null; | |
_contextLineCap = null; | |
_contextLineJoin = null; | |
this.setViewport( 0, 0, width, height ); | |
}; | |
this.setViewport = function ( x, y, width, height ) { | |
_viewportX = x * pixelRatio; | |
_viewportY = y * pixelRatio; | |
_viewportWidth = width * pixelRatio; | |
_viewportHeight = height * pixelRatio; | |
}; | |
this.setScissor = function () {}; | |
this.enableScissorTest = function () {}; | |
this.setClearColor = function ( color, alpha ) { | |
_clearColor.set( color ); | |
_clearAlpha = alpha !== undefined ? alpha : 1; | |
_clearBox.min.set( - _canvasWidthHalf, - _canvasHeightHalf ); | |
_clearBox.max.set( _canvasWidthHalf, _canvasHeightHalf ); | |
}; | |
this.setClearColorHex = function ( hex, alpha ) { | |
console.warn( 'THREE.CanvasRenderer: .setClearColorHex() is being removed. Use .setClearColor() instead.' ); | |
this.setClearColor( hex, alpha ); | |
}; | |
this.getClearColor = function () { | |
return _clearColor; | |
}; | |
this.getClearAlpha = function () { | |
return _clearAlpha; | |
}; | |
this.getMaxAnisotropy = function () { | |
return 0; | |
}; | |
this.clear = function () { | |
if ( _clearBox.empty() === false ) { | |
_clearBox.intersect( _clipBox ); | |
_clearBox.expandByScalar( 2 ); | |
_clearBox.min.x = _clearBox.min.x + _canvasWidthHalf; | |
_clearBox.min.y = - _clearBox.min.y + _canvasHeightHalf; // higher y value ! | |
_clearBox.max.x = _clearBox.max.x + _canvasWidthHalf; | |
_clearBox.max.y = - _clearBox.max.y + _canvasHeightHalf; // lower y value ! | |
if ( _clearAlpha < 1 ) { | |
_context.clearRect( | |
_clearBox.min.x | 0, | |
_clearBox.max.y | 0, | |
( _clearBox.max.x - _clearBox.min.x ) | 0, | |
( _clearBox.min.y - _clearBox.max.y ) | 0 | |
); | |
} | |
if ( _clearAlpha > 0 ) { | |
setBlending( THREE.NormalBlending ); | |
setOpacity( 1 ); | |
setFillStyle( 'rgba(' + Math.floor( _clearColor.r * 255 ) + ',' + Math.floor( _clearColor.g * 255 ) + ',' + Math.floor( _clearColor.b * 255 ) + ',' + _clearAlpha + ')' ); | |
_context.fillRect( | |
_clearBox.min.x | 0, | |
_clearBox.max.y | 0, | |
( _clearBox.max.x - _clearBox.min.x ) | 0, | |
( _clearBox.min.y - _clearBox.max.y ) | 0 | |
); | |
} | |
_clearBox.makeEmpty(); | |
} | |
}; | |
// compatibility | |
this.clearColor = function () {}; | |
this.clearDepth = function () {}; | |
this.clearStencil = function () {}; | |
this.render = function ( scene, camera ) { | |
if ( camera instanceof THREE.Camera === false ) { | |
console.error( 'THREE.CanvasRenderer.render: camera is not an instance of THREE.Camera.' ); | |
return; | |
} | |
if ( this.autoClear === true ) this.clear(); | |
_this.info.render.vertices = 0; | |
_this.info.render.faces = 0; | |
_context.setTransform( _viewportWidth / _canvasWidth, 0, 0, - _viewportHeight / _canvasHeight, _viewportX, _canvasHeight - _viewportY ); | |
_context.translate( _canvasWidthHalf, _canvasHeightHalf ); | |
_renderData = _projector.projectScene( scene, camera, this.sortObjects, this.sortElements ); | |
_elements = _renderData.elements; | |
_lights = _renderData.lights; | |
_camera = camera; | |
_normalViewMatrix.getNormalMatrix( camera.matrixWorldInverse ); | |
/* DEBUG | |
setFillStyle( 'rgba( 0, 255, 255, 0.5 )' ); | |
_context.fillRect( _clipBox.min.x, _clipBox.min.y, _clipBox.max.x - _clipBox.min.x, _clipBox.max.y - _clipBox.min.y ); | |
*/ | |
calculateLights(); | |
for ( var e = 0, el = _elements.length; e < el; e ++ ) { | |
var element = _elements[ e ]; | |
var material = element.material; | |
if ( material === undefined || material.opacity === 0 ) continue; | |
_elemBox.makeEmpty(); | |
if ( element instanceof THREE.RenderableSprite ) { | |
_v1 = element; | |
_v1.x *= _canvasWidthHalf; _v1.y *= _canvasHeightHalf; | |
renderSprite( _v1, element, material ); | |
} else if ( element instanceof THREE.RenderableLine ) { | |
_v1 = element.v1; _v2 = element.v2; | |
_v1.positionScreen.x *= _canvasWidthHalf; _v1.positionScreen.y *= _canvasHeightHalf; | |
_v2.positionScreen.x *= _canvasWidthHalf; _v2.positionScreen.y *= _canvasHeightHalf; | |
_elemBox.setFromPoints( [ | |
_v1.positionScreen, | |
_v2.positionScreen | |
] ); | |
if ( _clipBox.isIntersectionBox( _elemBox ) === true ) { | |
renderLine( _v1, _v2, element, material ); | |
} | |
} else if ( element instanceof THREE.RenderableFace ) { | |
_v1 = element.v1; _v2 = element.v2; _v3 = element.v3; | |
if ( _v1.positionScreen.z < - 1 || _v1.positionScreen.z > 1 ) continue; | |
if ( _v2.positionScreen.z < - 1 || _v2.positionScreen.z > 1 ) continue; | |
if ( _v3.positionScreen.z < - 1 || _v3.positionScreen.z > 1 ) continue; | |
_v1.positionScreen.x *= _canvasWidthHalf; _v1.positionScreen.y *= _canvasHeightHalf; | |
_v2.positionScreen.x *= _canvasWidthHalf; _v2.positionScreen.y *= _canvasHeightHalf; | |
_v3.positionScreen.x *= _canvasWidthHalf; _v3.positionScreen.y *= _canvasHeightHalf; | |
if ( material.overdraw > 0 ) { | |
expand( _v1.positionScreen, _v2.positionScreen, material.overdraw ); | |
expand( _v2.positionScreen, _v3.positionScreen, material.overdraw ); | |
expand( _v3.positionScreen, _v1.positionScreen, material.overdraw ); | |
} | |
_elemBox.setFromPoints( [ | |
_v1.positionScreen, | |
_v2.positionScreen, | |
_v3.positionScreen | |
] ); | |
if ( _clipBox.isIntersectionBox( _elemBox ) === true ) { | |
renderFace3( _v1, _v2, _v3, 0, 1, 2, element, material ); | |
} | |
} | |
/* DEBUG | |
setLineWidth( 1 ); | |
setStrokeStyle( 'rgba( 0, 255, 0, 0.5 )' ); | |
_context.strokeRect( _elemBox.min.x, _elemBox.min.y, _elemBox.max.x - _elemBox.min.x, _elemBox.max.y - _elemBox.min.y ); | |
*/ | |
_clearBox.union( _elemBox ); | |
} | |
/* DEBUG | |
setLineWidth( 1 ); | |
setStrokeStyle( 'rgba( 255, 0, 0, 0.5 )' ); | |
_context.strokeRect( _clearBox.min.x, _clearBox.min.y, _clearBox.max.x - _clearBox.min.x, _clearBox.max.y - _clearBox.min.y ); | |
*/ | |
_context.setTransform( 1, 0, 0, 1, 0, 0 ); | |
}; | |
// | |
function calculateLights() { | |
_ambientLight.setRGB( 0, 0, 0 ); | |
_directionalLights.setRGB( 0, 0, 0 ); | |
_pointLights.setRGB( 0, 0, 0 ); | |
for ( var l = 0, ll = _lights.length; l < ll; l ++ ) { | |
var light = _lights[ l ]; | |
var lightColor = light.color; | |
if ( light instanceof THREE.AmbientLight ) { | |
_ambientLight.add( lightColor ); | |
} else if ( light instanceof THREE.DirectionalLight ) { | |
// for sprites | |
_directionalLights.add( lightColor ); | |
} else if ( light instanceof THREE.PointLight ) { | |
// for sprites | |
_pointLights.add( lightColor ); | |
} | |
} | |
} | |
function calculateLight( position, normal, color ) { | |
for ( var l = 0, ll = _lights.length; l < ll; l ++ ) { | |
var light = _lights[ l ]; | |
_lightColor.copy( light.color ); | |
if ( light instanceof THREE.DirectionalLight ) { | |
var lightPosition = _vector3.setFromMatrixPosition( light.matrixWorld ).normalize(); | |
var amount = normal.dot( lightPosition ); | |
if ( amount <= 0 ) continue; | |
amount *= light.intensity; | |
color.add( _lightColor.multiplyScalar( amount ) ); | |
} else if ( light instanceof THREE.PointLight ) { | |
var lightPosition = _vector3.setFromMatrixPosition( light.matrixWorld ); | |
var amount = normal.dot( _vector3.subVectors( lightPosition, position ).normalize() ); | |
if ( amount <= 0 ) continue; | |
amount *= light.distance == 0 ? 1 : 1 - Math.min( position.distanceTo( lightPosition ) / light.distance, 1 ); | |
if ( amount == 0 ) continue; | |
amount *= light.intensity; | |
color.add( _lightColor.multiplyScalar( amount ) ); | |
} | |
} | |
} | |
function renderSprite( v1, element, material ) { | |
setOpacity( material.opacity ); | |
setBlending( material.blending ); | |
var scaleX = element.scale.x * _canvasWidthHalf; | |
var scaleY = element.scale.y * _canvasHeightHalf; | |
var dist = 0.5 * Math.sqrt( scaleX * scaleX + scaleY * scaleY ); // allow for rotated sprite | |
_elemBox.min.set( v1.x - dist, v1.y - dist ); | |
_elemBox.max.set( v1.x + dist, v1.y + dist ); | |
if ( material instanceof THREE.SpriteMaterial ) { | |
var texture = material.map; | |
if ( texture !== null && texture.image !== undefined ) { | |
if ( texture.hasEventListener( 'update', onTextureUpdate ) === false ) { | |
if ( texture.image.width > 0 ) { | |
textureToPattern( texture ); | |
} | |
texture.addEventListener( 'update', onTextureUpdate ); | |
} | |
var pattern = _patterns[ texture.id ]; | |
if ( pattern !== undefined ) { | |
setFillStyle( pattern ); | |
} else { | |
setFillStyle( 'rgba( 0, 0, 0, 1 )' ); | |
} | |
// | |
var bitmap = texture.image; | |
var ox = bitmap.width * texture.offset.x; | |
var oy = bitmap.height * texture.offset.y; | |
var sx = bitmap.width * texture.repeat.x; | |
var sy = bitmap.height * texture.repeat.y; | |
var cx = scaleX / sx; | |
var cy = scaleY / sy; | |
_context.save(); | |
_context.translate( v1.x, v1.y ); | |
if ( material.rotation !== 0 ) _context.rotate( material.rotation ); | |
_context.translate( - scaleX / 2, - scaleY / 2 ); | |
_context.scale( cx, cy ); | |
_context.translate( - ox, - oy ); | |
_context.fillRect( ox, oy, sx, sy ); | |
_context.restore(); | |
} else { | |
// no texture | |
setFillStyle( material.color.getStyle() ); | |
_context.save(); | |
_context.translate( v1.x, v1.y ); | |
if ( material.rotation !== 0 ) _context.rotate( material.rotation ); | |
_context.scale( scaleX, - scaleY ); | |
_context.fillRect( - 0.5, - 0.5, 1, 1 ); | |
_context.restore(); | |
} | |
} else if ( material instanceof THREE.SpriteCanvasMaterial ) { | |
setStrokeStyle( material.color.getStyle() ); | |
setFillStyle( material.color.getStyle() ); | |
_context.save(); | |
_context.translate( v1.x, v1.y ); | |
if ( material.rotation !== 0 ) _context.rotate( material.rotation ); | |
_context.scale( scaleX, scaleY ); | |
material.program( _context ); | |
_context.restore(); | |
} | |
/* DEBUG | |
setStrokeStyle( 'rgb(255,255,0)' ); | |
_context.beginPath(); | |
_context.moveTo( v1.x - 10, v1.y ); | |
_context.lineTo( v1.x + 10, v1.y ); | |
_context.moveTo( v1.x, v1.y - 10 ); | |
_context.lineTo( v1.x, v1.y + 10 ); | |
_context.stroke(); | |
*/ | |
} | |
function renderLine( v1, v2, element, material ) { | |
setOpacity( material.opacity ); | |
setBlending( material.blending ); | |
_context.beginPath(); | |
_context.moveTo( v1.positionScreen.x, v1.positionScreen.y ); | |
_context.lineTo( v2.positionScreen.x, v2.positionScreen.y ); | |
if ( material instanceof THREE.LineBasicMaterial ) { | |
setLineWidth( material.linewidth ); | |
setLineCap( material.linecap ); | |
setLineJoin( material.linejoin ); | |
if ( material.vertexColors !== THREE.VertexColors ) { | |
setStrokeStyle( material.color.getStyle() ); | |
} else { | |
var colorStyle1 = element.vertexColors[ 0 ].getStyle(); | |
var colorStyle2 = element.vertexColors[ 1 ].getStyle(); | |
if ( colorStyle1 === colorStyle2 ) { | |
setStrokeStyle( colorStyle1 ); | |
} else { | |
try { | |
var grad = _context.createLinearGradient( | |
v1.positionScreen.x, | |
v1.positionScreen.y, | |
v2.positionScreen.x, | |
v2.positionScreen.y | |
); | |
grad.addColorStop( 0, colorStyle1 ); | |
grad.addColorStop( 1, colorStyle2 ); | |
} catch ( exception ) { | |
grad = colorStyle1; | |
} | |
setStrokeStyle( grad ); | |
} | |
} | |
_context.stroke(); | |
_elemBox.expandByScalar( material.linewidth * 2 ); | |
} else if ( material instanceof THREE.LineDashedMaterial ) { | |
setLineWidth( material.linewidth ); | |
setLineCap( material.linecap ); | |
setLineJoin( material.linejoin ); | |
setStrokeStyle( material.color.getStyle() ); | |
setLineDash( [ material.dashSize, material.gapSize ] ); | |
_context.stroke(); | |
_elemBox.expandByScalar( material.linewidth * 2 ); | |
setLineDash( [] ); | |
} | |
} | |
function renderFace3( v1, v2, v3, uv1, uv2, uv3, element, material ) { | |
_this.info.render.vertices += 3; | |
_this.info.render.faces ++; | |
setOpacity( material.opacity ); | |
setBlending( material.blending ); | |
_v1x = v1.positionScreen.x; _v1y = v1.positionScreen.y; | |
_v2x = v2.positionScreen.x; _v2y = v2.positionScreen.y; | |
_v3x = v3.positionScreen.x; _v3y = v3.positionScreen.y; | |
drawTriangle( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y ); | |
if ( ( material instanceof THREE.MeshLambertMaterial || material instanceof THREE.MeshPhongMaterial ) && material.map === null ) { | |
_diffuseColor.copy( material.color ); | |
_emissiveColor.copy( material.emissive ); | |
if ( material.vertexColors === THREE.FaceColors ) { | |
_diffuseColor.multiply( element.color ); | |
} | |
_color.copy( _ambientLight ); | |
_centroid.copy( v1.positionWorld ).add( v2.positionWorld ).add( v3.positionWorld ).divideScalar( 3 ); | |
calculateLight( _centroid, element.normalModel, _color ); | |
_color.multiply( _diffuseColor ).add( _emissiveColor ); | |
material.wireframe === true | |
? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin ) | |
: fillPath( _color ); | |
} else if ( material instanceof THREE.MeshBasicMaterial || | |
material instanceof THREE.MeshLambertMaterial || | |
material instanceof THREE.MeshPhongMaterial ) { | |
if ( material.map !== null ) { | |
var mapping = material.map.mapping; | |
if ( mapping === THREE.UVMapping ) { | |
_uvs = element.uvs; | |
patternPath( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _uvs[ uv1 ].x, _uvs[ uv1 ].y, _uvs[ uv2 ].x, _uvs[ uv2 ].y, _uvs[ uv3 ].x, _uvs[ uv3 ].y, material.map ); | |
} | |
} else if ( material.envMap !== null ) { | |
if ( material.envMap.mapping === THREE.SphericalReflectionMapping ) { | |
_normal.copy( element.vertexNormalsModel[ uv1 ] ).applyMatrix3( _normalViewMatrix ); | |
_uv1x = 0.5 * _normal.x + 0.5; | |
_uv1y = 0.5 * _normal.y + 0.5; | |
_normal.copy( element.vertexNormalsModel[ uv2 ] ).applyMatrix3( _normalViewMatrix ); | |
_uv2x = 0.5 * _normal.x + 0.5; | |
_uv2y = 0.5 * _normal.y + 0.5; | |
_normal.copy( element.vertexNormalsModel[ uv3 ] ).applyMatrix3( _normalViewMatrix ); | |
_uv3x = 0.5 * _normal.x + 0.5; | |
_uv3y = 0.5 * _normal.y + 0.5; | |
patternPath( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _uv1x, _uv1y, _uv2x, _uv2y, _uv3x, _uv3y, material.envMap ); | |
} | |
} else { | |
_color.copy( material.color ); | |
if ( material.vertexColors === THREE.FaceColors ) { | |
_color.multiply( element.color ); | |
} | |
material.wireframe === true | |
? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin ) | |
: fillPath( _color ); | |
} | |
} else if ( material instanceof THREE.MeshDepthMaterial ) { | |
_color.r = _color.g = _color.b = 1 - smoothstep( v1.positionScreen.z * v1.positionScreen.w, _camera.near, _camera.far ); | |
material.wireframe === true | |
? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin ) | |
: fillPath( _color ); | |
} else if ( material instanceof THREE.MeshNormalMaterial ) { | |
_normal.copy( element.normalModel ).applyMatrix3( _normalViewMatrix ); | |
_color.setRGB( _normal.x, _normal.y, _normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 ); | |
material.wireframe === true | |
? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin ) | |
: fillPath( _color ); | |
} else { | |
_color.setRGB( 1, 1, 1 ); | |
material.wireframe === true | |
? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin ) | |
: fillPath( _color ); | |
} | |
} | |
// | |
function drawTriangle( x0, y0, x1, y1, x2, y2 ) { | |
_context.beginPath(); | |
_context.moveTo( x0, y0 ); | |
_context.lineTo( x1, y1 ); | |
_context.lineTo( x2, y2 ); | |
_context.closePath(); | |
} | |
function strokePath( color, linewidth, linecap, linejoin ) { | |
setLineWidth( linewidth ); | |
setLineCap( linecap ); | |
setLineJoin( linejoin ); | |
setStrokeStyle( color.getStyle() ); | |
_context.stroke(); | |
_elemBox.expandByScalar( linewidth * 2 ); | |
} | |
function fillPath( color ) { | |
setFillStyle( color.getStyle() ); | |
_context.fill(); | |
} | |
function onTextureUpdate ( event ) { | |
textureToPattern( event.target ); | |
} | |
function textureToPattern( texture ) { | |
if ( texture instanceof THREE.CompressedTexture ) return; | |
var repeatX = texture.wrapS === THREE.RepeatWrapping; | |
var repeatY = texture.wrapT === THREE.RepeatWrapping; | |
var image = texture.image; | |
var canvas = document.createElement( 'canvas' ); | |
canvas.width = image.width; | |
canvas.height = image.height; | |
var context = canvas.getContext( '2d' ); | |
context.setTransform( 1, 0, 0, - 1, 0, image.height ); | |
context.drawImage( image, 0, 0 ); | |
_patterns[ texture.id ] = _context.createPattern( | |
canvas, repeatX === true && repeatY === true | |
? 'repeat' | |
: repeatX === true && repeatY === false | |
? 'repeat-x' | |
: repeatX === false && repeatY === true | |
? 'repeat-y' | |
: 'no-repeat' | |
); | |
} | |
function patternPath( x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, texture ) { | |
if ( texture instanceof THREE.DataTexture ) return; | |
if ( texture.hasEventListener( 'update', onTextureUpdate ) === false ) { | |
if ( texture.image !== undefined && texture.image.width > 0 ) { | |
textureToPattern( texture ); | |
} | |
texture.addEventListener( 'update', onTextureUpdate ); | |
} | |
var pattern = _patterns[ texture.id ]; | |
if ( pattern !== undefined ) { | |
setFillStyle( pattern ); | |
} else { | |
setFillStyle( 'rgba(0,0,0,1)' ); | |
_context.fill(); | |
return; | |
} | |
// http://extremelysatisfactorytotalitarianism.com/blog/?p=2120 | |
var a, b, c, d, e, f, det, idet, | |
offsetX = texture.offset.x / texture.repeat.x, | |
offsetY = texture.offset.y / texture.repeat.y, | |
width = texture.image.width * texture.repeat.x, | |
height = texture.image.height * texture.repeat.y; | |
u0 = ( u0 + offsetX ) * width; | |
v0 = ( v0 + offsetY ) * height; | |
u1 = ( u1 + offsetX ) * width; | |
v1 = ( v1 + offsetY ) * height; | |
u2 = ( u2 + offsetX ) * width; | |
v2 = ( v2 + offsetY ) * height; | |
x1 -= x0; y1 -= y0; | |
x2 -= x0; y2 -= y0; | |
u1 -= u0; v1 -= v0; | |
u2 -= u0; v2 -= v0; | |
det = u1 * v2 - u2 * v1; | |
if ( det === 0 ) return; | |
idet = 1 / det; | |
a = ( v2 * x1 - v1 * x2 ) * idet; | |
b = ( v2 * y1 - v1 * y2 ) * idet; | |
c = ( u1 * x2 - u2 * x1 ) * idet; | |
d = ( u1 * y2 - u2 * y1 ) * idet; | |
e = x0 - a * u0 - c * v0; | |
f = y0 - b * u0 - d * v0; | |
_context.save(); | |
_context.transform( a, b, c, d, e, f ); | |
_context.fill(); | |
_context.restore(); | |
} | |
function clipImage( x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, image ) { | |
// http://extremelysatisfactorytotalitarianism.com/blog/?p=2120 | |
var a, b, c, d, e, f, det, idet, | |
width = image.width - 1, | |
height = image.height - 1; | |
u0 *= width; v0 *= height; | |
u1 *= width; v1 *= height; | |
u2 *= width; v2 *= height; | |
x1 -= x0; y1 -= y0; | |
x2 -= x0; y2 -= y0; | |
u1 -= u0; v1 -= v0; | |
u2 -= u0; v2 -= v0; | |
det = u1 * v2 - u2 * v1; | |
idet = 1 / det; | |
a = ( v2 * x1 - v1 * x2 ) * idet; | |
b = ( v2 * y1 - v1 * y2 ) * idet; | |
c = ( u1 * x2 - u2 * x1 ) * idet; | |
d = ( u1 * y2 - u2 * y1 ) * idet; | |
e = x0 - a * u0 - c * v0; | |
f = y0 - b * u0 - d * v0; | |
_context.save(); | |
_context.transform( a, b, c, d, e, f ); | |
_context.clip(); | |
_context.drawImage( image, 0, 0 ); | |
_context.restore(); | |
} | |
// Hide anti-alias gaps | |
function expand( v1, v2, pixels ) { | |
var x = v2.x - v1.x, y = v2.y - v1.y, | |
det = x * x + y * y, idet; | |
if ( det === 0 ) return; | |
idet = pixels / Math.sqrt( det ); | |
x *= idet; y *= idet; | |
v2.x += x; v2.y += y; | |
v1.x -= x; v1.y -= y; | |
} | |
// Context cached methods. | |
function setOpacity( value ) { | |
if ( _contextGlobalAlpha !== value ) { | |
_context.globalAlpha = value; | |
_contextGlobalAlpha = value; | |
} | |
} | |
function setBlending( value ) { | |
if ( _contextGlobalCompositeOperation !== value ) { | |
if ( value === THREE.NormalBlending ) { | |
_context.globalCompositeOperation = 'source-over'; | |
} else if ( value === THREE.AdditiveBlending ) { | |
_context.globalCompositeOperation = 'lighter'; | |
} else if ( value === THREE.SubtractiveBlending ) { | |
_context.globalCompositeOperation = 'darker'; | |
} | |
_contextGlobalCompositeOperation = value; | |
} | |
} | |
function setLineWidth( value ) { | |
if ( _contextLineWidth !== value ) { | |
_context.lineWidth = value; | |
_contextLineWidth = value; | |
} | |
} | |
function setLineCap( value ) { | |
// "butt", "round", "square" | |
if ( _contextLineCap !== value ) { | |
_context.lineCap = value; | |
_contextLineCap = value; | |
} | |
} | |
function setLineJoin( value ) { | |
// "round", "bevel", "miter" | |
if ( _contextLineJoin !== value ) { | |
_context.lineJoin = value; | |
_contextLineJoin = value; | |
} | |
} | |
function setStrokeStyle( value ) { | |
if ( _contextStrokeStyle !== value ) { | |
_context.strokeStyle = value; | |
_contextStrokeStyle = value; | |
} | |
} | |
function setFillStyle( value ) { | |
if ( _contextFillStyle !== value ) { | |
_context.fillStyle = value; | |
_contextFillStyle = value; | |
} | |
} | |
function setLineDash( value ) { | |
if ( _contextLineDash.length !== value.length ) { | |
_context.setLineDash( value ); | |
_contextLineDash = value; | |
} | |
} | |
}; |
This file contains 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
/** | |
* @author qiao / https://github.com/qiao | |
* @author mrdoob / http://mrdoob.com | |
* @author alteredq / http://alteredqualia.com/ | |
* @author WestLangley / http://github.com/WestLangley | |
* @author erich666 / http://erichaines.com | |
*/ | |
/*global THREE, console */ | |
// This set of controls performs orbiting, dollying (zooming), and panning. It maintains | |
// the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is | |
// supported. | |
// | |
// Orbit - left mouse / touch: one finger move | |
// Zoom - middle mouse, or mousewheel / touch: two finger spread or squish | |
// Pan - right mouse, or arrow keys / touch: three finter swipe | |
THREE.OrbitControls = function ( object, domElement ) { | |
this.object = object; | |
this.domElement = ( domElement !== undefined ) ? domElement : document; | |
// API | |
// Set to false to disable this control | |
this.enabled = true; | |
// "target" sets the location of focus, where the control orbits around | |
// and where it pans with respect to. | |
this.target = new THREE.Vector3(); | |
// center is old, deprecated; use "target" instead | |
this.center = this.target; | |
// This option actually enables dollying in and out; left as "zoom" for | |
// backwards compatibility | |
this.noZoom = false; | |
this.zoomSpeed = 1.0; | |
// Limits to how far you can dolly in and out ( PerspectiveCamera only ) | |
this.minDistance = 0; | |
this.maxDistance = Infinity; | |
// Limits to how far you can zoom in and out ( OrthographicCamera only ) | |
this.minZoom = 0; | |
this.maxZoom = Infinity; | |
// Set to true to disable this control | |
this.noRotate = false; | |
this.rotateSpeed = 1.0; | |
// Set to true to disable this control | |
this.noPan = false; | |
this.keyPanSpeed = 7.0; // pixels moved per arrow key push | |
// Set to true to automatically rotate around the target | |
this.autoRotate = false; | |
this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 | |
// How far you can orbit vertically, upper and lower limits. | |
// Range is 0 to Math.PI radians. | |
this.minPolarAngle = 0; // radians | |
this.maxPolarAngle = Math.PI; // radians | |
// How far you can orbit horizontally, upper and lower limits. | |
// If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. | |
this.minAzimuthAngle = - Infinity; // radians | |
this.maxAzimuthAngle = Infinity; // radians | |
// Set to true to disable use of the keys | |
this.noKeys = false; | |
// The four arrow keys | |
this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; | |
// Mouse buttons | |
this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; | |
//////////// | |
// internals | |
var scope = this; | |
var EPS = 0.000001; | |
var rotateStart = new THREE.Vector2(); | |
var rotateEnd = new THREE.Vector2(); | |
var rotateDelta = new THREE.Vector2(); | |
var panStart = new THREE.Vector2(); | |
var panEnd = new THREE.Vector2(); | |
var panDelta = new THREE.Vector2(); | |
var panOffset = new THREE.Vector3(); | |
var offset = new THREE.Vector3(); | |
var dollyStart = new THREE.Vector2(); | |
var dollyEnd = new THREE.Vector2(); | |
var dollyDelta = new THREE.Vector2(); | |
var theta; | |
var phi; | |
var phiDelta = 0; | |
var thetaDelta = 0; | |
var scale = 1; | |
var pan = new THREE.Vector3(); | |
var lastPosition = new THREE.Vector3(); | |
var lastQuaternion = new THREE.Quaternion(); | |
var STATE = { NONE : -1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 }; | |
var state = STATE.NONE; | |
// for reset | |
this.target0 = this.target.clone(); | |
this.position0 = this.object.position.clone(); | |
this.zoom0 = this.object.zoom; | |
// so camera.up is the orbit axis | |
var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); | |
var quatInverse = quat.clone().inverse(); | |
// events | |
var changeEvent = { type: 'change' }; | |
var startEvent = { type: 'start' }; | |
var endEvent = { type: 'end' }; | |
this.rotateLeft = function ( angle ) { | |
if ( angle === undefined ) { | |
angle = getAutoRotationAngle(); | |
} | |
thetaDelta -= angle; | |
}; | |
this.rotateUp = function ( angle ) { | |
if ( angle === undefined ) { | |
angle = getAutoRotationAngle(); | |
} | |
phiDelta -= angle; | |
}; | |
// pass in distance in world space to move left | |
this.panLeft = function ( distance ) { | |
var te = this.object.matrix.elements; | |
// get X column of matrix | |
panOffset.set( te[ 0 ], te[ 1 ], te[ 2 ] ); | |
panOffset.multiplyScalar( - distance ); | |
pan.add( panOffset ); | |
}; | |
// pass in distance in world space to move up | |
this.panUp = function ( distance ) { | |
var te = this.object.matrix.elements; | |
// get Y column of matrix | |
panOffset.set( te[ 4 ], te[ 5 ], te[ 6 ] ); | |
panOffset.multiplyScalar( distance ); | |
pan.add( panOffset ); | |
}; | |
// pass in x,y of change desired in pixel space, | |
// right and down are positive | |
this.pan = function ( deltaX, deltaY ) { | |
var element = scope.domElement === document ? scope.domElement.body : scope.domElement; | |
if ( scope.object instanceof THREE.PerspectiveCamera ) { | |
// perspective | |
var position = scope.object.position; | |
var offset = position.clone().sub( scope.target ); | |
var targetDistance = offset.length(); | |
// half of the fov is center to top of screen | |
targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); | |
// we actually don't use screenWidth, since perspective camera is fixed to screen height | |
scope.panLeft( 2 * deltaX * targetDistance / element.clientHeight ); | |
scope.panUp( 2 * deltaY * targetDistance / element.clientHeight ); | |
} else if ( scope.object instanceof THREE.OrthographicCamera ) { | |
// orthographic | |
scope.panLeft( deltaX * (scope.object.right - scope.object.left) / element.clientWidth ); | |
scope.panUp( deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight ); | |
} else { | |
// camera neither orthographic or perspective | |
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); | |
} | |
}; | |
this.dollyIn = function ( dollyScale ) { | |
if ( dollyScale === undefined ) { | |
dollyScale = getZoomScale(); | |
} | |
if ( scope.object instanceof THREE.PerspectiveCamera ) { | |
scale /= dollyScale; | |
} else if ( scope.object instanceof THREE.OrthographicCamera ) { | |
scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom * dollyScale ) ); | |
scope.object.updateProjectionMatrix(); | |
scope.dispatchEvent( changeEvent ); | |
} else { | |
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); | |
} | |
}; | |
this.dollyOut = function ( dollyScale ) { | |
if ( dollyScale === undefined ) { | |
dollyScale = getZoomScale(); | |
} | |
if ( scope.object instanceof THREE.PerspectiveCamera ) { | |
scale *= dollyScale; | |
} else if ( scope.object instanceof THREE.OrthographicCamera ) { | |
scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / dollyScale ) ); | |
scope.object.updateProjectionMatrix(); | |
scope.dispatchEvent( changeEvent ); | |
} else { | |
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); | |
} | |
}; | |
this.update = function () { | |
var position = this.object.position; | |
offset.copy( position ).sub( this.target ); | |
// rotate offset to "y-axis-is-up" space | |
offset.applyQuaternion( quat ); | |
// angle from z-axis around y-axis | |
theta = Math.atan2( offset.x, offset.z ); | |
// angle from y-axis | |
phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); | |
if ( this.autoRotate && state === STATE.NONE ) { | |
this.rotateLeft( getAutoRotationAngle() ); | |
} | |
theta += thetaDelta; | |
phi += phiDelta; | |
// restrict theta to be between desired limits | |
theta = Math.max( this.minAzimuthAngle, Math.min( this.maxAzimuthAngle, theta ) ); | |
// restrict phi to be between desired limits | |
phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) ); | |
// restrict phi to be betwee EPS and PI-EPS | |
phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) ); | |
var radius = offset.length() * scale; | |
// restrict radius to be between desired limits | |
radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) ); | |
// move target to panned location | |
this.target.add( pan ); | |
offset.x = radius * Math.sin( phi ) * Math.sin( theta ); | |
offset.y = radius * Math.cos( phi ); | |
offset.z = radius * Math.sin( phi ) * Math.cos( theta ); | |
// rotate offset back to "camera-up-vector-is-up" space | |
offset.applyQuaternion( quatInverse ); | |
position.copy( this.target ).add( offset ); | |
this.object.lookAt( this.target ); | |
thetaDelta = 0; | |
phiDelta = 0; | |
scale = 1; | |
pan.set( 0, 0, 0 ); | |
// update condition is: | |
// min(camera displacement, camera rotation in radians)^2 > EPS | |
// using small-angle approximation cos(x/2) = 1 - x^2 / 8 | |
if ( lastPosition.distanceToSquared( this.object.position ) > EPS | |
|| 8 * (1 - lastQuaternion.dot(this.object.quaternion)) > EPS ) { | |
this.dispatchEvent( changeEvent ); | |
lastPosition.copy( this.object.position ); | |
lastQuaternion.copy (this.object.quaternion ); | |
} | |
}; | |
this.reset = function () { | |
state = STATE.NONE; | |
this.target.copy( this.target0 ); | |
this.object.position.copy( this.position0 ); | |
this.object.zoom = this.zoom0; | |
this.object.updateProjectionMatrix(); | |
this.dispatchEvent( changeEvent ); | |
this.update(); | |
}; | |
this.getPolarAngle = function () { | |
return phi; | |
}; | |
this.getAzimuthalAngle = function () { | |
return theta | |
}; | |
function getAutoRotationAngle() { | |
return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; | |
} | |
function getZoomScale() { | |
return Math.pow( 0.95, scope.zoomSpeed ); | |
} | |
function onMouseDown( event ) { | |
if ( scope.enabled === false ) return; | |
event.preventDefault(); | |
if ( event.button === scope.mouseButtons.ORBIT ) { | |
if ( scope.noRotate === true ) return; | |
state = STATE.ROTATE; | |
rotateStart.set( event.clientX, event.clientY ); | |
} else if ( event.button === scope.mouseButtons.ZOOM ) { | |
if ( scope.noZoom === true ) return; | |
state = STATE.DOLLY; | |
dollyStart.set( event.clientX, event.clientY ); | |
} else if ( event.button === scope.mouseButtons.PAN ) { | |
if ( scope.noPan === true ) return; | |
state = STATE.PAN; | |
panStart.set( event.clientX, event.clientY ); | |
} | |
if ( state !== STATE.NONE ) { | |
document.addEventListener( 'mousemove', onMouseMove, false ); | |
document.addEventListener( 'mouseup', onMouseUp, false ); | |
scope.dispatchEvent( startEvent ); | |
} | |
} | |
function onMouseMove( event ) { | |
if ( scope.enabled === false ) return; | |
event.preventDefault(); | |
var element = scope.domElement === document ? scope.domElement.body : scope.domElement; | |
if ( state === STATE.ROTATE ) { | |
if ( scope.noRotate === true ) return; | |
rotateEnd.set( event.clientX, event.clientY ); | |
rotateDelta.subVectors( rotateEnd, rotateStart ); | |
// rotating across whole screen goes 360 degrees around | |
scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); | |
// rotating up and down along whole screen attempts to go 360, but limited to 180 | |
scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); | |
rotateStart.copy( rotateEnd ); | |
} else if ( state === STATE.DOLLY ) { | |
if ( scope.noZoom === true ) return; | |
dollyEnd.set( event.clientX, event.clientY ); | |
dollyDelta.subVectors( dollyEnd, dollyStart ); | |
if ( dollyDelta.y > 0 ) { | |
scope.dollyIn(); | |
} else if ( dollyDelta.y < 0 ) { | |
scope.dollyOut(); | |
} | |
dollyStart.copy( dollyEnd ); | |
} else if ( state === STATE.PAN ) { | |
if ( scope.noPan === true ) return; | |
panEnd.set( event.clientX, event.clientY ); | |
panDelta.subVectors( panEnd, panStart ); | |
scope.pan( panDelta.x, panDelta.y ); | |
panStart.copy( panEnd ); | |
} | |
if ( state !== STATE.NONE ) scope.update(); | |
} | |
function onMouseUp( /* event */ ) { | |
if ( scope.enabled === false ) return; | |
document.removeEventListener( 'mousemove', onMouseMove, false ); | |
document.removeEventListener( 'mouseup', onMouseUp, false ); | |
scope.dispatchEvent( endEvent ); | |
state = STATE.NONE; | |
} | |
function onMouseWheel( event ) { | |
if ( scope.enabled === false || scope.noZoom === true || state !== STATE.NONE ) return; | |
event.preventDefault(); | |
event.stopPropagation(); | |
var delta = 0; | |
if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9 | |
delta = event.wheelDelta; | |
} else if ( event.detail !== undefined ) { // Firefox | |
delta = - event.detail; | |
} | |
if ( delta > 0 ) { | |
scope.dollyOut(); | |
} else if ( delta < 0 ) { | |
scope.dollyIn(); | |
} | |
scope.update(); | |
scope.dispatchEvent( startEvent ); | |
scope.dispatchEvent( endEvent ); | |
} | |
function onKeyDown( event ) { | |
if ( scope.enabled === false || scope.noKeys === true || scope.noPan === true ) return; | |
switch ( event.keyCode ) { | |
case scope.keys.UP: | |
scope.pan( 0, scope.keyPanSpeed ); | |
scope.update(); | |
break; | |
case scope.keys.BOTTOM: | |
scope.pan( 0, - scope.keyPanSpeed ); | |
scope.update(); | |
break; | |
case scope.keys.LEFT: | |
scope.pan( scope.keyPanSpeed, 0 ); | |
scope.update(); | |
break; | |
case scope.keys.RIGHT: | |
scope.pan( - scope.keyPanSpeed, 0 ); | |
scope.update(); | |
break; | |
} | |
} | |
function touchstart( event ) { | |
if ( scope.enabled === false ) return; | |
switch ( event.touches.length ) { | |
case 1: // one-fingered touch: rotate | |
if ( scope.noRotate === true ) return; | |
state = STATE.TOUCH_ROTATE; | |
rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); | |
break; | |
case 2: // two-fingered touch: dolly | |
if ( scope.noZoom === true ) return; | |
state = STATE.TOUCH_DOLLY; | |
var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; | |
var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; | |
var distance = Math.sqrt( dx * dx + dy * dy ); | |
dollyStart.set( 0, distance ); | |
break; | |
case 3: // three-fingered touch: pan | |
if ( scope.noPan === true ) return; | |
state = STATE.TOUCH_PAN; | |
panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); | |
break; | |
default: | |
state = STATE.NONE; | |
} | |
if ( state !== STATE.NONE ) scope.dispatchEvent( startEvent ); | |
} | |
function touchmove( event ) { | |
if ( scope.enabled === false ) return; | |
event.preventDefault(); | |
event.stopPropagation(); | |
var element = scope.domElement === document ? scope.domElement.body : scope.domElement; | |
switch ( event.touches.length ) { | |
case 1: // one-fingered touch: rotate | |
if ( scope.noRotate === true ) return; | |
if ( state !== STATE.TOUCH_ROTATE ) return; | |
rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); | |
rotateDelta.subVectors( rotateEnd, rotateStart ); | |
// rotating across whole screen goes 360 degrees around | |
scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); | |
// rotating up and down along whole screen attempts to go 360, but limited to 180 | |
scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); | |
rotateStart.copy( rotateEnd ); | |
scope.update(); | |
break; | |
case 2: // two-fingered touch: dolly | |
if ( scope.noZoom === true ) return; | |
if ( state !== STATE.TOUCH_DOLLY ) return; | |
var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; | |
var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; | |
var distance = Math.sqrt( dx * dx + dy * dy ); | |
dollyEnd.set( 0, distance ); | |
dollyDelta.subVectors( dollyEnd, dollyStart ); | |
if ( dollyDelta.y > 0 ) { | |
scope.dollyOut(); | |
} else if ( dollyDelta.y < 0 ) { | |
scope.dollyIn(); | |
} | |
dollyStart.copy( dollyEnd ); | |
scope.update(); | |
break; | |
case 3: // three-fingered touch: pan | |
if ( scope.noPan === true ) return; | |
if ( state !== STATE.TOUCH_PAN ) return; | |
panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); | |
panDelta.subVectors( panEnd, panStart ); | |
scope.pan( panDelta.x, panDelta.y ); | |
panStart.copy( panEnd ); | |
scope.update(); | |
break; | |
default: | |
state = STATE.NONE; | |
} | |
} | |
function touchend( /* event */ ) { | |
if ( scope.enabled === false ) return; | |
scope.dispatchEvent( endEvent ); | |
state = STATE.NONE; | |
} | |
this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); | |
this.domElement.addEventListener( 'mousedown', onMouseDown, false ); | |
this.domElement.addEventListener( 'mousewheel', onMouseWheel, false ); | |
this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox | |
this.domElement.addEventListener( 'touchstart', touchstart, false ); | |
this.domElement.addEventListener( 'touchend', touchend, false ); | |
this.domElement.addEventListener( 'touchmove', touchmove, false ); | |
window.addEventListener( 'keydown', onKeyDown, false ); | |
// force an update at start | |
this.update(); | |
}; | |
THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); | |
THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; |
This file contains 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
// Based on http://threejs.org/examples/canvas_geometry_birds.html , | |
// which is based on http://www.openprocessing.org/visuals/?visualID=6910 | |
var Boid = function() { | |
var vector = new THREE.Vector3(), | |
_acceleration, _width = 500, _height = 500, _depth = 200, _goal, _neighborhoodRadius = 100, | |
_maxSpeed = 4, _maxSteerForce = 0.1, _avoidWalls = false; | |
this.position = new THREE.Vector3(); | |
this.velocity = new THREE.Vector3(); | |
_acceleration = new THREE.Vector3(); | |
this.setGoal = function ( target ) { | |
_goal = target; | |
} | |
this.setAvoidWalls = function ( value ) { | |
_avoidWalls = value; | |
} | |
this.setWorldSize = function ( width, height, depth ) { | |
_width = width; | |
_height = height; | |
_depth = depth; | |
} | |
this.position.x = Math.random() * 400 - 200; | |
this.position.y = Math.random() * 400 - 200; | |
this.position.z = Math.random() * 400 - 200; | |
this.velocity.x = Math.random() * 2 - 1; | |
this.velocity.y = Math.random() * 2 - 1; | |
this.velocity.z = Math.random() * 2 - 1; | |
this.setAvoidWalls( true ); | |
this.setWorldSize( 500, 500, 400 ); | |
this.run = function ( boids ) { | |
if ( _avoidWalls ) { | |
vector.set( - _width, this.position.y, this.position.z ); | |
vector = this.avoid( vector ); | |
vector.multiplyScalar( 5 ); | |
_acceleration.add( vector ); | |
vector.set( _width, this.position.y, this.position.z ); | |
vector = this.avoid( vector ); | |
vector.multiplyScalar( 5 ); | |
_acceleration.add( vector ); | |
vector.set( this.position.x, - _height, this.position.z ); | |
vector = this.avoid( vector ); | |
vector.multiplyScalar( 5 ); | |
_acceleration.add( vector ); | |
vector.set( this.position.x, _height, this.position.z ); | |
vector = this.avoid( vector ); | |
vector.multiplyScalar( 5 ); | |
_acceleration.add( vector ); | |
vector.set( this.position.x, this.position.y, - _depth ); | |
vector = this.avoid( vector ); | |
vector.multiplyScalar( 5 ); | |
_acceleration.add( vector ); | |
vector.set( this.position.x, this.position.y, _depth ); | |
vector = this.avoid( vector ); | |
vector.multiplyScalar( 5 ); | |
_acceleration.add( vector ); | |
}/* else { | |
this.checkBounds(); | |
} | |
*/ | |
if ( Math.random() > 0.5 ) { | |
this.flock( boids ); | |
} | |
this.move(); | |
} | |
this.flock = function ( boids ) { | |
if ( _goal ) { | |
_acceleration.add( this.reach( _goal, 0.005 ) ); | |
} | |
_acceleration.add( this.alignment( boids ) ); | |
_acceleration.add( this.cohesion( boids ) ); | |
_acceleration.add( this.separation( boids ) ); | |
} | |
this.move = function () { | |
this.velocity.add( _acceleration ); | |
var l = this.velocity.length(); | |
if ( l > _maxSpeed ) { | |
this.velocity.divideScalar( l / _maxSpeed ); | |
} | |
this.position.add( this.velocity ); | |
_acceleration.set( 0, 0, 0 ); | |
} | |
this.checkBounds = function () { | |
if ( this.position.x > _width ) this.position.x = - _width; | |
if ( this.position.x < - _width ) this.position.x = _width; | |
if ( this.position.y > _height ) this.position.y = - _height; | |
if ( this.position.y < - _height ) this.position.y = _height; | |
if ( this.position.z > _depth ) this.position.z = - _depth; | |
if ( this.position.z < - _depth ) this.position.z = _depth; | |
} | |
this.avoid = function ( target ) { | |
var steer = new THREE.Vector3(); | |
steer.copy( this.position ); | |
steer.sub( target ); | |
steer.multiplyScalar( 1 / this.position.distanceToSquared( target ) ); | |
return steer; | |
} | |
this.repulse = function ( target ) { | |
var distance = this.position.distanceTo( target ); | |
if ( distance < 150 ) { | |
var steer = new THREE.Vector3(); | |
steer.subVectors( this.position, target ); | |
steer.multiplyScalar( 0.5 / distance ); | |
_acceleration.add( steer ); | |
} | |
} | |
this.reach = function ( target, amount ) { | |
var steer = new THREE.Vector3(); | |
steer.subVectors( target, this.position ); | |
steer.multiplyScalar( amount ); | |
return steer; | |
} | |
this.alignment = function ( boids ) { | |
var boid, velSum = new THREE.Vector3(), | |
count = 0; | |
for ( var i = 0, il = boids.length; i < il; i++ ) { | |
if ( Math.random() > 0.6 ) continue; | |
boid = boids[ i ]; | |
distance = boid.position.distanceTo( this.position ); | |
if ( distance > 0 && distance <= _neighborhoodRadius ) { | |
velSum.add( boid.velocity ); | |
count++; | |
} | |
} | |
if ( count > 0 ) { | |
velSum.divideScalar( count ); | |
var l = velSum.length(); | |
if ( l > _maxSteerForce ) { | |
velSum.divideScalar( l / _maxSteerForce ); | |
} | |
} | |
return velSum; | |
} | |
this.cohesion = function ( boids ) { | |
var boid, distance, | |
posSum = new THREE.Vector3(), | |
steer = new THREE.Vector3(), | |
count = 0; | |
for ( var i = 0, il = boids.length; i < il; i ++ ) { | |
if ( Math.random() > 0.6 ) continue; | |
boid = boids[ i ]; | |
distance = boid.position.distanceTo( this.position ); | |
if ( distance > 0 && distance <= _neighborhoodRadius ) { | |
posSum.add( boid.position ); | |
count++; | |
} | |
} | |
if ( count > 0 ) { | |
posSum.divideScalar( count ); | |
} | |
steer.subVectors( posSum, this.position ); | |
var l = steer.length(); | |
if ( l > _maxSteerForce ) { | |
steer.divideScalar( l / _maxSteerForce ); | |
} | |
return steer; | |
} | |
this.separation = function ( boids ) { | |
var boid, distance, | |
posSum = new THREE.Vector3(), | |
repulse = new THREE.Vector3(); | |
for ( var i = 0, il = boids.length; i < il; i ++ ) { | |
if ( Math.random() > 0.6 ) continue; | |
boid = boids[ i ]; | |
distance = boid.position.distanceTo( this.position ); | |
if ( distance > 0 && distance <= _neighborhoodRadius ) { | |
repulse.subVectors( this.position, boid.position ); | |
repulse.normalize(); | |
repulse.divideScalar( distance ); | |
posSum.add( repulse ); | |
} | |
} | |
return posSum; | |
} | |
} |
This file contains 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
/** | |
* @author mrdoob / http://mrdoob.com/ | |
* @author supereggbert / http://www.paulbrunt.co.uk/ | |
* @author julianwa / https://github.com/julianwa | |
*/ | |
THREE.RenderableObject = function () { | |
this.id = 0; | |
this.object = null; | |
this.z = 0; | |
}; | |
// | |
THREE.RenderableFace = function () { | |
this.id = 0; | |
this.v1 = new THREE.RenderableVertex(); | |
this.v2 = new THREE.RenderableVertex(); | |
this.v3 = new THREE.RenderableVertex(); | |
this.normalModel = new THREE.Vector3(); | |
this.vertexNormalsModel = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ]; | |
this.vertexNormalsLength = 0; | |
this.color = new THREE.Color(); | |
this.material = null; | |
this.uvs = [ new THREE.Vector2(), new THREE.Vector2(), new THREE.Vector2() ]; | |
this.z = 0; | |
}; | |
// | |
THREE.RenderableVertex = function () { | |
this.position = new THREE.Vector3(); | |
this.positionWorld = new THREE.Vector3(); | |
this.positionScreen = new THREE.Vector4(); | |
this.visible = true; | |
}; | |
THREE.RenderableVertex.prototype.copy = function ( vertex ) { | |
this.positionWorld.copy( vertex.positionWorld ); | |
this.positionScreen.copy( vertex.positionScreen ); | |
}; | |
// | |
THREE.RenderableLine = function () { | |
this.id = 0; | |
this.v1 = new THREE.RenderableVertex(); | |
this.v2 = new THREE.RenderableVertex(); | |
this.vertexColors = [ new THREE.Color(), new THREE.Color() ]; | |
this.material = null; | |
this.z = 0; | |
}; | |
// | |
THREE.RenderableSprite = function () { | |
this.id = 0; | |
this.object = null; | |
this.x = 0; | |
this.y = 0; | |
this.z = 0; | |
this.rotation = 0; | |
this.scale = new THREE.Vector2(); | |
this.material = null; | |
}; | |
// | |
THREE.Projector = function () { | |
var _object, _objectCount, _objectPool = [], _objectPoolLength = 0, | |
_vertex, _vertexCount, _vertexPool = [], _vertexPoolLength = 0, | |
_face, _faceCount, _facePool = [], _facePoolLength = 0, | |
_line, _lineCount, _linePool = [], _linePoolLength = 0, | |
_sprite, _spriteCount, _spritePool = [], _spritePoolLength = 0, | |
_renderData = { objects: [], lights: [], elements: [] }, | |
_vector3 = new THREE.Vector3(), | |
_vector4 = new THREE.Vector4(), | |
_clipBox = new THREE.Box3( new THREE.Vector3( - 1, - 1, - 1 ), new THREE.Vector3( 1, 1, 1 ) ), | |
_boundingBox = new THREE.Box3(), | |
_points3 = new Array( 3 ), | |
_points4 = new Array( 4 ), | |
_viewMatrix = new THREE.Matrix4(), | |
_viewProjectionMatrix = new THREE.Matrix4(), | |
_modelMatrix, | |
_modelViewProjectionMatrix = new THREE.Matrix4(), | |
_normalMatrix = new THREE.Matrix3(), | |
_frustum = new THREE.Frustum(), | |
_clippedVertex1PositionScreen = new THREE.Vector4(), | |
_clippedVertex2PositionScreen = new THREE.Vector4(); | |
// | |
this.projectVector = function ( vector, camera ) { | |
console.warn( 'THREE.Projector: .projectVector() is now vector.project().' ); | |
vector.project( camera ); | |
}; | |
this.unprojectVector = function ( vector, camera ) { | |
console.warn( 'THREE.Projector: .unprojectVector() is now vector.unproject().' ); | |
vector.unproject( camera ); | |
}; | |
this.pickingRay = function ( vector, camera ) { | |
console.error( 'THREE.Projector: .pickingRay() is now raycaster.setFromCamera().' ); | |
}; | |
// | |
var RenderList = function () { | |
var normals = []; | |
var uvs = []; | |
var object = null; | |
var material = null; | |
var normalMatrix = new THREE.Matrix3(); | |
var setObject = function ( value ) { | |
object = value; | |
material = object.material; | |
normalMatrix.getNormalMatrix( object.matrixWorld ); | |
normals.length = 0; | |
uvs.length = 0; | |
}; | |
var projectVertex = function ( vertex ) { | |
var position = vertex.position; | |
var positionWorld = vertex.positionWorld; | |
var positionScreen = vertex.positionScreen; | |
positionWorld.copy( position ).applyMatrix4( _modelMatrix ); | |
positionScreen.copy( positionWorld ).applyMatrix4( _viewProjectionMatrix ); | |
var invW = 1 / positionScreen.w; | |
positionScreen.x *= invW; | |
positionScreen.y *= invW; | |
positionScreen.z *= invW; | |
vertex.visible = positionScreen.x >= - 1 && positionScreen.x <= 1 && | |
positionScreen.y >= - 1 && positionScreen.y <= 1 && | |
positionScreen.z >= - 1 && positionScreen.z <= 1; | |
}; | |
var pushVertex = function ( x, y, z ) { | |
_vertex = getNextVertexInPool(); | |
_vertex.position.set( x, y, z ); | |
projectVertex( _vertex ); | |
}; | |
var pushNormal = function ( x, y, z ) { | |
normals.push( x, y, z ); | |
}; | |
var pushUv = function ( x, y ) { | |
uvs.push( x, y ); | |
}; | |
var checkTriangleVisibility = function ( v1, v2, v3 ) { | |
if ( v1.visible === true || v2.visible === true || v3.visible === true ) return true; | |
_points3[ 0 ] = v1.positionScreen; | |
_points3[ 1 ] = v2.positionScreen; | |
_points3[ 2 ] = v3.positionScreen; | |
return _clipBox.isIntersectionBox( _boundingBox.setFromPoints( _points3 ) ); | |
}; | |
var checkBackfaceCulling = function ( v1, v2, v3 ) { | |
return ( ( v3.positionScreen.x - v1.positionScreen.x ) * | |
( v2.positionScreen.y - v1.positionScreen.y ) - | |
( v3.positionScreen.y - v1.positionScreen.y ) * | |
( v2.positionScreen.x - v1.positionScreen.x ) ) < 0; | |
}; | |
var pushLine = function ( a, b ) { | |
var v1 = _vertexPool[ a ]; | |
var v2 = _vertexPool[ b ]; | |
_line = getNextLineInPool(); | |
_line.id = object.id; | |
_line.v1.copy( v1 ); | |
_line.v2.copy( v2 ); | |
_line.z = ( v1.positionScreen.z + v2.positionScreen.z ) / 2; | |
_line.material = object.material; | |
_renderData.elements.push( _line ); | |
}; | |
var pushTriangle = function ( a, b, c ) { | |
var v1 = _vertexPool[ a ]; | |
var v2 = _vertexPool[ b ]; | |
var v3 = _vertexPool[ c ]; | |
if ( checkTriangleVisibility( v1, v2, v3 ) === false ) return; | |
if ( material.side === THREE.DoubleSide || checkBackfaceCulling( v1, v2, v3 ) === true ) { | |
_face = getNextFaceInPool(); | |
_face.id = object.id; | |
_face.v1.copy( v1 ); | |
_face.v2.copy( v2 ); | |
_face.v3.copy( v3 ); | |
_face.z = ( v1.positionScreen.z + v2.positionScreen.z + v3.positionScreen.z ) / 3; | |
for ( var i = 0; i < 3; i ++ ) { | |
var offset = arguments[ i ] * 3; | |
var normal = _face.vertexNormalsModel[ i ]; | |
normal.set( normals[ offset ], normals[ offset + 1 ], normals[ offset + 2 ] ); | |
normal.applyMatrix3( normalMatrix ).normalize(); | |
var offset2 = arguments[ i ] * 2; | |
var uv = _face.uvs[ i ]; | |
uv.set( uvs[ offset2 ], uvs[ offset2 + 1 ] ); | |
} | |
_face.vertexNormalsLength = 3; | |
_face.material = object.material; | |
_renderData.elements.push( _face ); | |
} | |
}; | |
return { | |
setObject: setObject, | |
projectVertex: projectVertex, | |
checkTriangleVisibility: checkTriangleVisibility, | |
checkBackfaceCulling: checkBackfaceCulling, | |
pushVertex: pushVertex, | |
pushNormal: pushNormal, | |
pushUv: pushUv, | |
pushLine: pushLine, | |
pushTriangle: pushTriangle | |
} | |
}; | |
var renderList = new RenderList(); | |
this.projectScene = function ( scene, camera, sortObjects, sortElements ) { | |
_faceCount = 0; | |
_lineCount = 0; | |
_spriteCount = 0; | |
_renderData.elements.length = 0; | |
if ( scene.autoUpdate === true ) scene.updateMatrixWorld(); | |
if ( camera.parent === undefined ) camera.updateMatrixWorld(); | |
_viewMatrix.copy( camera.matrixWorldInverse.getInverse( camera.matrixWorld ) ); | |
_viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, _viewMatrix ); | |
_frustum.setFromMatrix( _viewProjectionMatrix ); | |
// | |
_objectCount = 0; | |
_renderData.objects.length = 0; | |
_renderData.lights.length = 0; | |
scene.traverseVisible( function ( object ) { | |
if ( object instanceof THREE.Light ) { | |
_renderData.lights.push( object ); | |
} else if ( object instanceof THREE.Mesh || object instanceof THREE.Line || object instanceof THREE.Sprite ) { | |
if ( object.material.visible === false ) return; | |
if ( object.frustumCulled === false || _frustum.intersectsObject( object ) === true ) { | |
_object = getNextObjectInPool(); | |
_object.id = object.id; | |
_object.object = object; | |
_vector3.setFromMatrixPosition( object.matrixWorld ); | |
_vector3.applyProjection( _viewProjectionMatrix ); | |
_object.z = _vector3.z; | |
_renderData.objects.push( _object ); | |
} | |
} | |
} ); | |
if ( sortObjects === true ) { | |
_renderData.objects.sort( painterSort ); | |
} | |
// | |
for ( var o = 0, ol = _renderData.objects.length; o < ol; o ++ ) { | |
var object = _renderData.objects[ o ].object; | |
var geometry = object.geometry; | |
renderList.setObject( object ); | |
_modelMatrix = object.matrixWorld; | |
_vertexCount = 0; | |
if ( object instanceof THREE.Mesh ) { | |
if ( geometry instanceof THREE.BufferGeometry ) { | |
var attributes = geometry.attributes; | |
var offsets = geometry.offsets; | |
if ( attributes.position === undefined ) continue; | |
var positions = attributes.position.array; | |
for ( var i = 0, l = positions.length; i < l; i += 3 ) { | |
renderList.pushVertex( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] ); | |
} | |
if ( attributes.normal !== undefined ) { | |
var normals = attributes.normal.array; | |
for ( var i = 0, l = normals.length; i < l; i += 3 ) { | |
renderList.pushNormal( normals[ i ], normals[ i + 1 ], normals[ i + 2 ] ); | |
} | |
} | |
if ( attributes.uv !== undefined ) { | |
var uvs = attributes.uv.array; | |
for ( var i = 0, l = uvs.length; i < l; i += 2 ) { | |
renderList.pushUv( uvs[ i ], uvs[ i + 1 ] ); | |
} | |
} | |
if ( attributes.index !== undefined ) { | |
var indices = attributes.index.array; | |
if ( offsets.length > 0 ) { | |
for ( var o = 0; o < offsets.length; o ++ ) { | |
var offset = offsets[ o ]; | |
var index = offset.index; | |
for ( var i = offset.start, l = offset.start + offset.count; i < l; i += 3 ) { | |
renderList.pushTriangle( indices[ i ] + index, indices[ i + 1 ] + index, indices[ i + 2 ] + index ); | |
} | |
} | |
} else { | |
for ( var i = 0, l = indices.length; i < l; i += 3 ) { | |
renderList.pushTriangle( indices[ i ], indices[ i + 1 ], indices[ i + 2 ] ); | |
} | |
} | |
} else { | |
for ( var i = 0, l = positions.length / 3; i < l; i += 3 ) { | |
renderList.pushTriangle( i, i + 1, i + 2 ); | |
} | |
} | |
} else if ( geometry instanceof THREE.Geometry ) { | |
var vertices = geometry.vertices; | |
var faces = geometry.faces; | |
var faceVertexUvs = geometry.faceVertexUvs[ 0 ]; | |
_normalMatrix.getNormalMatrix( _modelMatrix ); | |
var material = object.material; | |
var isFaceMaterial = material instanceof THREE.MeshFaceMaterial; | |
var objectMaterials = isFaceMaterial === true ? object.material : null; | |
for ( var v = 0, vl = vertices.length; v < vl; v ++ ) { | |
var vertex = vertices[ v ]; | |
_vector3.copy( vertex ); | |
if ( material.morphTargets === true ) { | |
var morphTargets = geometry.morphTargets; | |
var morphInfluences = object.morphTargetInfluences; | |
for ( var t = 0, tl = morphTargets.length; t < tl; t ++ ) { | |
var influence = morphInfluences[ t ]; | |
if ( influence === 0 ) continue; | |
var target = morphTargets[ t ]; | |
var targetVertex = target.vertices[ v ]; | |
_vector3.x += ( targetVertex.x - vertex.x ) * influence; | |
_vector3.y += ( targetVertex.y - vertex.y ) * influence; | |
_vector3.z += ( targetVertex.z - vertex.z ) * influence; | |
} | |
} | |
renderList.pushVertex( _vector3.x, _vector3.y, _vector3.z ); | |
} | |
for ( var f = 0, fl = faces.length; f < fl; f ++ ) { | |
var face = faces[ f ]; | |
var material = isFaceMaterial === true | |
? objectMaterials.materials[ face.materialIndex ] | |
: object.material; | |
if ( material === undefined ) continue; | |
var side = material.side; | |
var v1 = _vertexPool[ face.a ]; | |
var v2 = _vertexPool[ face.b ]; | |
var v3 = _vertexPool[ face.c ]; | |
if ( renderList.checkTriangleVisibility( v1, v2, v3 ) === false ) continue; | |
var visible = renderList.checkBackfaceCulling( v1, v2, v3 ); | |
if ( side !== THREE.DoubleSide ) { | |
if ( side === THREE.FrontSide && visible === false ) continue; | |
if ( side === THREE.BackSide && visible === true ) continue; | |
} | |
_face = getNextFaceInPool(); | |
_face.id = object.id; | |
_face.v1.copy( v1 ); | |
_face.v2.copy( v2 ); | |
_face.v3.copy( v3 ); | |
_face.normalModel.copy( face.normal ); | |
if ( visible === false && ( side === THREE.BackSide || side === THREE.DoubleSide ) ) { | |
_face.normalModel.negate(); | |
} | |
_face.normalModel.applyMatrix3( _normalMatrix ).normalize(); | |
var faceVertexNormals = face.vertexNormals; | |
for ( var n = 0, nl = Math.min( faceVertexNormals.length, 3 ); n < nl; n ++ ) { | |
var normalModel = _face.vertexNormalsModel[ n ]; | |
normalModel.copy( faceVertexNormals[ n ] ); | |
if ( visible === false && ( side === THREE.BackSide || side === THREE.DoubleSide ) ) { | |
normalModel.negate(); | |
} | |
normalModel.applyMatrix3( _normalMatrix ).normalize(); | |
} | |
_face.vertexNormalsLength = faceVertexNormals.length; | |
var vertexUvs = faceVertexUvs[ f ]; | |
if ( vertexUvs !== undefined ) { | |
for ( var u = 0; u < 3; u ++ ) { | |
_face.uvs[ u ].copy( vertexUvs[ u ] ); | |
} | |
} | |
_face.color = face.color; | |
_face.material = material; | |
_face.z = ( v1.positionScreen.z + v2.positionScreen.z + v3.positionScreen.z ) / 3; | |
_renderData.elements.push( _face ); | |
} | |
} | |
} else if ( object instanceof THREE.Line ) { | |
if ( geometry instanceof THREE.BufferGeometry ) { | |
var attributes = geometry.attributes; | |
if ( attributes.position !== undefined ) { | |
var positions = attributes.position.array; | |
for ( var i = 0, l = positions.length; i < l; i += 3 ) { | |
renderList.pushVertex( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] ); | |
} | |
if ( attributes.index !== undefined ) { | |
var indices = attributes.index.array; | |
for ( var i = 0, l = indices.length; i < l; i += 2 ) { | |
renderList.pushLine( indices[ i ], indices[ i + 1 ] ); | |
} | |
} else { | |
var step = object.mode === THREE.LinePieces ? 2 : 1; | |
for ( var i = 0, l = ( positions.length / 3 ) - 1; i < l; i += step ) { | |
renderList.pushLine( i, i + 1 ); | |
} | |
} | |
} | |
} else if ( geometry instanceof THREE.Geometry ) { | |
_modelViewProjectionMatrix.multiplyMatrices( _viewProjectionMatrix, _modelMatrix ); | |
var vertices = object.geometry.vertices; | |
if ( vertices.length === 0 ) continue; | |
v1 = getNextVertexInPool(); | |
v1.positionScreen.copy( vertices[ 0 ] ).applyMatrix4( _modelViewProjectionMatrix ); | |
// Handle LineStrip and LinePieces | |
var step = object.mode === THREE.LinePieces ? 2 : 1; | |
for ( var v = 1, vl = vertices.length; v < vl; v ++ ) { | |
v1 = getNextVertexInPool(); | |
v1.positionScreen.copy( vertices[ v ] ).applyMatrix4( _modelViewProjectionMatrix ); | |
if ( ( v + 1 ) % step > 0 ) continue; | |
v2 = _vertexPool[ _vertexCount - 2 ]; | |
_clippedVertex1PositionScreen.copy( v1.positionScreen ); | |
_clippedVertex2PositionScreen.copy( v2.positionScreen ); | |
if ( clipLine( _clippedVertex1PositionScreen, _clippedVertex2PositionScreen ) === true ) { | |
// Perform the perspective divide | |
_clippedVertex1PositionScreen.multiplyScalar( 1 / _clippedVertex1PositionScreen.w ); | |
_clippedVertex2PositionScreen.multiplyScalar( 1 / _clippedVertex2PositionScreen.w ); | |
_line = getNextLineInPool(); | |
_line.id = object.id; | |
_line.v1.positionScreen.copy( _clippedVertex1PositionScreen ); | |
_line.v2.positionScreen.copy( _clippedVertex2PositionScreen ); | |
_line.z = Math.max( _clippedVertex1PositionScreen.z, _clippedVertex2PositionScreen.z ); | |
_line.material = object.material; | |
if ( object.material.vertexColors === THREE.VertexColors ) { | |
_line.vertexColors[ 0 ].copy( object.geometry.colors[ v ] ); | |
_line.vertexColors[ 1 ].copy( object.geometry.colors[ v - 1 ] ); | |
} | |
_renderData.elements.push( _line ); | |
} | |
} | |
} | |
} else if ( object instanceof THREE.Sprite ) { | |
_vector4.set( _modelMatrix.elements[ 12 ], _modelMatrix.elements[ 13 ], _modelMatrix.elements[ 14 ], 1 ); | |
_vector4.applyMatrix4( _viewProjectionMatrix ); | |
var invW = 1 / _vector4.w; | |
_vector4.z *= invW; | |
if ( _vector4.z >= - 1 && _vector4.z <= 1 ) { | |
_sprite = getNextSpriteInPool(); | |
_sprite.id = object.id; | |
_sprite.x = _vector4.x * invW; | |
_sprite.y = _vector4.y * invW; | |
_sprite.z = _vector4.z; | |
_sprite.object = object; | |
_sprite.rotation = object.rotation; | |
_sprite.scale.x = object.scale.x * Math.abs( _sprite.x - ( _vector4.x + camera.projectionMatrix.elements[ 0 ] ) / ( _vector4.w + camera.projectionMatrix.elements[ 12 ] ) ); | |
_sprite.scale.y = object.scale.y * Math.abs( _sprite.y - ( _vector4.y + camera.projectionMatrix.elements[ 5 ] ) / ( _vector4.w + camera.projectionMatrix.elements[ 13 ] ) ); | |
_sprite.material = object.material; | |
_renderData.elements.push( _sprite ); | |
} | |
} | |
} | |
if ( sortElements === true ) { | |
_renderData.elements.sort( painterSort ); | |
} | |
return _renderData; | |
}; | |
// Pools | |
function getNextObjectInPool() { | |
if ( _objectCount === _objectPoolLength ) { | |
var object = new THREE.RenderableObject(); | |
_objectPool.push( object ); | |
_objectPoolLength ++; | |
_objectCount ++; | |
return object; | |
} | |
return _objectPool[ _objectCount ++ ]; | |
} | |
function getNextVertexInPool() { | |
if ( _vertexCount === _vertexPoolLength ) { | |
var vertex = new THREE.RenderableVertex(); | |
_vertexPool.push( vertex ); | |
_vertexPoolLength ++; | |
_vertexCount ++; | |
return vertex; | |
} | |
return _vertexPool[ _vertexCount ++ ]; | |
} | |
function getNextFaceInPool() { | |
if ( _faceCount === _facePoolLength ) { | |
var face = new THREE.RenderableFace(); | |
_facePool.push( face ); | |
_facePoolLength ++; | |
_faceCount ++; | |
return face; | |
} | |
return _facePool[ _faceCount ++ ]; | |
} | |
function getNextLineInPool() { | |
if ( _lineCount === _linePoolLength ) { | |
var line = new THREE.RenderableLine(); | |
_linePool.push( line ); | |
_linePoolLength ++; | |
_lineCount ++ | |
return line; | |
} | |
return _linePool[ _lineCount ++ ]; | |
} | |
function getNextSpriteInPool() { | |
if ( _spriteCount === _spritePoolLength ) { | |
var sprite = new THREE.RenderableSprite(); | |
_spritePool.push( sprite ); | |
_spritePoolLength ++; | |
_spriteCount ++ | |
return sprite; | |
} | |
return _spritePool[ _spriteCount ++ ]; | |
} | |
// | |
function painterSort( a, b ) { | |
if ( a.z !== b.z ) { | |
return b.z - a.z; | |
} else if ( a.id !== b.id ) { | |
return a.id - b.id; | |
} else { | |
return 0; | |
} | |
} | |
function clipLine( s1, s2 ) { | |
var alpha1 = 0, alpha2 = 1, | |
// Calculate the boundary coordinate of each vertex for the near and far clip planes, | |
// Z = -1 and Z = +1, respectively. | |
bc1near = s1.z + s1.w, | |
bc2near = s2.z + s2.w, | |
bc1far = - s1.z + s1.w, | |
bc2far = - s2.z + s2.w; | |
if ( bc1near >= 0 && bc2near >= 0 && bc1far >= 0 && bc2far >= 0 ) { | |
// Both vertices lie entirely within all clip planes. | |
return true; | |
} else if ( ( bc1near < 0 && bc2near < 0 ) || ( bc1far < 0 && bc2far < 0 ) ) { | |
// Both vertices lie entirely outside one of the clip planes. | |
return false; | |
} else { | |
// The line segment spans at least one clip plane. | |
if ( bc1near < 0 ) { | |
// v1 lies outside the near plane, v2 inside | |
alpha1 = Math.max( alpha1, bc1near / ( bc1near - bc2near ) ); | |
} else if ( bc2near < 0 ) { | |
// v2 lies outside the near plane, v1 inside | |
alpha2 = Math.min( alpha2, bc1near / ( bc1near - bc2near ) ); | |
} | |
if ( bc1far < 0 ) { | |
// v1 lies outside the far plane, v2 inside | |
alpha1 = Math.max( alpha1, bc1far / ( bc1far - bc2far ) ); | |
} else if ( bc2far < 0 ) { | |
// v2 lies outside the far plane, v2 inside | |
alpha2 = Math.min( alpha2, bc1far / ( bc1far - bc2far ) ); | |
} | |
if ( alpha2 < alpha1 ) { | |
// The line segment spans two boundaries, but is outside both of them. | |
// (This can't happen when we're only clipping against just near/far but good | |
// to leave the check here for future usage if other clip planes are added.) | |
return false; | |
} else { | |
// Update the s1 and s2 vertices to match the clipped line segment. | |
s1.lerp( s2, alpha1 ); | |
s2.lerp( s1, 1 - alpha2 ); | |
return true; | |
} | |
} | |
} | |
}; |
This file contains 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
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
body { | |
color: #808080; | |
font-family:Monospace; | |
font-size:13px; | |
text-align:center; | |
background-color: black; | |
margin: 0px; | |
overflow: hidden; | |
} | |
</style> | |
<body> | |
<div id="dat_gui_container"></div> | |
<canvas id="three_boid"></canvas> | |
<!-- JavaScript --> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r74/three.min.js"> | |
</script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5.1/dat.gui.min.js"> | |
</script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/stats.js/r14/Stats.min.js"> | |
</script> | |
<script src="README.Projector.js"></script> | |
<script src="README.CanvasRenderer.js"></script> | |
<script src="README.OrbitControls.js"></script> | |
<script src="README.physics_boid.js"></script> | |
<script src="render_three_boid.js"></script> | |
</body> |
This file contains 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
// Initialization | |
// --------------- | |
var SCENE_WIDTH = SCENE_HEIGHT = 720; | |
// Get canvas from document. | |
var canvas = document.getElementById("three_boid"); | |
// Initialize Webgl renderer. | |
var renderer = new THREE.WebGLRenderer({canvas: canvas, antialias: true}); | |
// Resize the output canvas to (width, height). | |
// Set the viewport to fit that size, starting in (0, 0). | |
renderer.setSize(SCENE_WIDTH, SCENE_HEIGHT); | |
// Initialize scene (where we put our models). | |
var scene = new THREE.Scene(); | |
// Initialize camera (how we look at our scene). | |
var camera = new THREE.PerspectiveCamera(80, SCENE_WIDTH / SCENE_HEIGHT, 1, 10000); | |
camera.position.set(SCENE_WIDTH, SCENE_HEIGHT/2, 2000); | |
// Initialize orbit controls (how we use our mouse to move the camera). | |
var controls = new THREE.OrbitControls(camera, canvas); | |
controls.addEventListener('change', render); | |
// Initialize parent object (like a sub-scene). | |
var parent = new THREE.Object3D(); | |
// Add Axes | |
// --------- | |
// Reference: http://soledadpenades.com/articles/three-js-tutorials/drawing-the-coordinate-axes/ | |
function buildAxes(length) { | |
var axes = new THREE.Object3D(); | |
var axisPosX = buildAxis(new THREE.Vector3(0,0,0), | |
new THREE.Vector3(length, 0, 0), 0xFF0000, false); | |
var axisNegX = buildAxis(new THREE.Vector3(0, 0, 0), | |
new THREE.Vector3(-length, 0, 0), 0xFF0000, true); | |
axes.add(axisPosX); // +X | |
axes.add(axisNegX); // -X | |
var axisPosY = buildAxis(new THREE.Vector3(0, 0, 0), | |
new THREE.Vector3(0, length, 0), 0x00FF00, false); | |
var axisNegY = buildAxis(new THREE.Vector3(0, 0, 0), | |
new THREE.Vector3(0, -length, 0 ), 0x00FF00, true); | |
axes.add(axisPosY); // +Y | |
axes.add(axisNegY); // -Y | |
var axisPosZ = buildAxis(new THREE.Vector3(0, 0, 0), | |
new THREE.Vector3(0, 0, length), 0x0000FF, false); | |
var axisNegZ = buildAxis(new THREE.Vector3(0, 0, 0), | |
new THREE.Vector3( 0, 0, -length), 0x0000FF, true); | |
axes.add(axisPosZ); // +Z | |
axes.add(axisNegZ); // -Z | |
return axes; | |
} | |
function buildAxis(src, dst, colorHex, dashed) { | |
var geom = new THREE.Geometry(), | |
mat; | |
if (dashed) { | |
mat = new THREE.LineDashedMaterial({linewidth:3, color: colorHex, dashSize:3, gapSize:3}); | |
} else { | |
mat = new THREE.LineBasicMaterial({linewidth:3, color:colorHex}); | |
} | |
geom.vertices.push(src.clone()); | |
geom.vertices.push(dst.clone()); | |
// This is IMPORTANT, otherwise dashed lines will appear as plain lines. | |
geom.computeLineDistances(); | |
var axis = new THREE.Line(geom, mat, THREE.LineSegments); | |
return axis; | |
} | |
axes = buildAxes(SCENE_WIDTH); | |
parent.add(axes); | |
// Add Bounding Box | |
// ----------------- | |
// http://threejs.org/docs/#Reference/Extras.Helpers/BoundingBoxHelper | |
var boundingBox = new THREE.BoundingBoxHelper(parent); | |
boundingBox.update(); | |
parent.add(boundingBox); | |
// Particle Render Prototype Methods | |
// ---------------------------------- | |
Boid.prototype.set_color = function() { | |
// http://threejs.org/docs/#Reference/Math/Color | |
this.color = new THREE.Color(); | |
this.color.setHSL(Math.random(), 0.85, 0.5); | |
} | |
Boid.prototype.set_radius = function() {this.radius = Math.random() * 50;} | |
Boid.prototype.create_geometry = function() { | |
// http://threejs.org/docs/#Reference/Extras.Geometries/SphereGeometry | |
//this.geometry = new THREE.SphereGeometry( | |
// this.radius, // radius — sphere radius. Default: 50. | |
// 25, // widthSegments — number of horizontal segments. Minimum: 3; Default: 8. | |
// 25 // heightSegments — number of vertical segments. Minimum: 2; Default: 6. | |
//); | |
// http://threejs.org/docs/#Reference/Extras.Geometries/BoxGeometry | |
this.geometry = new THREE.BoxGeometry(this.radius, this.radius, this.radius); | |
} | |
Boid.prototype.create_material = function() { | |
// http://threejs.org/docs/#Reference/Materials/MeshPhongMaterial | |
this.material = new THREE.MeshPhongMaterial({ | |
color: this.color, | |
specular: 0x333333, | |
shininess: 100 | |
}); | |
this.material.transparent = true; | |
this.material.opacity = 0.80; | |
} | |
Boid.prototype.create_mesh = function() { | |
// http://threejs.org/docs/#Reference/Objects/Mesh | |
this.mesh = new THREE.Mesh( | |
this.geometry, | |
this.material | |
); | |
this.mesh.position.set(this.position.x, this.position.y, this.position.z); | |
//console.log(this.mesh); | |
} | |
Boid.prototype.init_mesh_obj = function() { | |
this.create_geometry(); | |
this.create_material(); | |
this.create_mesh(); | |
} | |
Boid.prototype.set_rotation = function() { | |
this.rotation = new THREE.Vector3(); | |
this.rotation.x = this.rotation.y = this.rotation.z = 0; | |
this.rotation_v = new THREE.Vector3(); | |
this.rotation_v.x = Math.random() / 10; | |
this.rotation_v.y = Math.random() / 10; | |
this.rotation_v.z = Math.random() / 10; | |
} | |
Boid.prototype.update_boids = function() { | |
this.mesh.position.set(this.position.x, this.position.y, this.position.z); | |
// Calculate momentum and apply it to the color. | |
var momentum = this.velocity.length() * this.radius; | |
var intensity = momentum / 150; | |
if (intensity < 1) intensity = -1; | |
if (intensity > 1) intensity = 1; | |
this.mesh.material.color.offsetHSL(intensity * 0.0001, intensity * 0.0001, | |
intensity * 0.0001); | |
} | |
// Add boids. | |
var n = 300, data = []; | |
for (var i = 0; i < n; i++) { | |
var b = new Boid() | |
b.set_color(); | |
b.set_radius(); | |
b.init_mesh_obj(); | |
b.set_rotation(); | |
b.setWorldSize(SCENE_WIDTH, SCENE_HEIGHT, SCENE_HEIGHT); | |
data.push(b); | |
parent.add(b.mesh); | |
} | |
scene.add(parent); | |
// Add Light | |
// ---------- | |
var ambientLight = new THREE.AmbientLight(0x444444); | |
scene.add(ambientLight); | |
var directionalLight = new THREE.DirectionalLight(0xffffff); | |
directionalLight.position.set(10, 10, 10).normalize(); | |
scene.add(directionalLight); | |
var directionalLight2 = new THREE.DirectionalLight(0xffffff); | |
directionalLight2.position.set(-10, -10, -10).normalize(); | |
scene.add(directionalLight2); | |
// Add FPS using Stats.js | |
// ----------------------- | |
var stats = new Stats(); | |
stats.setMode(0); // 0: fps, 1: ms | |
document.getElementById('dat_gui_container').appendChild(stats.domElement); | |
// Align to the right of dat.gui. | |
stats.domElement.style.float = 'right'; | |
// Add Controls and GUI | |
// --------------------- | |
var controls = new function() { | |
// Add params here. | |
this.x_rot_v = 0.02; | |
this.y_rot_v = 0.02; | |
this.z_rot_v = 0.02; | |
this.p_x_rot_v = 0; | |
this.p_y_rot_v = 0.01; | |
this.p_z_rot_v = 0; | |
this.ambient_light = true; | |
this.direction_light = true; | |
this.direction_light_2 = true; | |
} | |
var gui = new dat.GUI(); | |
document.getElementById('dat_gui_container').appendChild(gui.domElement); | |
gui.add(controls, 'x_rot_v', 0, 0.5); | |
gui.add(controls, 'y_rot_v', 0, 0.5); | |
gui.add(controls, 'z_rot_v', 0, 0.5); | |
gui.add(controls, 'p_x_rot_v', 0, 0.5); | |
gui.add(controls, 'p_y_rot_v', 0, 0.5); | |
gui.add(controls, 'p_z_rot_v', 0, 0.5); | |
gui.close(); | |
ambient_light = gui.add(controls, 'ambient_light'); | |
ambient_light.onChange(function(value) { | |
if (value) { | |
scene.add(ambientLight); | |
} else { | |
scene.remove(ambientLight); | |
} | |
}); | |
direction_light = gui.add(controls, 'direction_light'); | |
direction_light.onChange(function(value) { | |
if (value) { | |
scene.add(directionalLight); | |
} else { | |
scene.remove(directionalLight); | |
} | |
}); | |
direction_light_2 = gui.add(controls, 'direction_light_2'); | |
direction_light_2.onChange(function(value) { | |
if (value) { | |
scene.add(directionalLight2); | |
} else { | |
scene.remove(directionalLight2); | |
} | |
}); | |
// Draw Loop | |
// ----------- | |
function draw() { | |
// Start recording statistics. | |
stats.begin(); | |
for (var i = 0; i <n; i++) { | |
data[i].run(data); | |
data[i].update_boids(); | |
} | |
parent.rotation.x += controls.p_x_rot_v; | |
parent.rotation.y += controls.p_y_rot_v; | |
parent.rotation.z += controls.p_z_rot_v; | |
// Render scene. | |
renderer.render(scene, camera); | |
// End recording statistics. | |
stats.end(); | |
// Run draw again. | |
requestAnimationFrame(draw); | |
} | |
function render() { | |
renderer.render(scene, camera); | |
} | |
// Start Animation | |
// ----------------- | |
requestAnimationFrame(draw); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment