diff --git a/js/three.js/three.min.js b/js/three.js/three.min.js index a955ba13d67da5143fdadcb2d67312e16e1e8c8f..473282c571a672478ae4e0a82b9d2c51becdd770 100644 --- a/js/three.js/three.min.js +++ b/js/three.js/three.min.js @@ -991,3 +991,1054 @@ THREE.MorphBlendMesh.prototype.setAnimationDuration=function(a,b){var c=this.ani THREE.MorphBlendMesh.prototype.getAnimationDuration=function(a){var b=-1;if(a=this.animationsMap[a])b=a.duration;return b};THREE.MorphBlendMesh.prototype.playAnimation=function(a){var b=this.animationsMap[a];b?(b.time=0,b.active=!0):console.warn("THREE.MorphBlendMesh: animation["+a+"] undefined in .playAnimation()")};THREE.MorphBlendMesh.prototype.stopAnimation=function(a){if(a=this.animationsMap[a])a.active=!1}; THREE.MorphBlendMesh.prototype.update=function(a){for(var b=0,c=this.animationsList.length;b<c;b++){var d=this.animationsList[b];if(d.active){var e=d.duration/d.length;d.time+=d.direction*a;if(d.mirroredLoop){if(d.time>d.duration||0>d.time)d.direction*=-1,d.time>d.duration&&(d.time=d.duration,d.directionBackwards=!0),0>d.time&&(d.time=0,d.directionBackwards=!1)}else d.time%=d.duration,0>d.time&&(d.time+=d.duration);var f=d.start+THREE.Math.clamp(Math.floor(d.time/e),0,d.length-1),g=d.weight;f!==d.currentFrame&& (this.morphTargetInfluences[d.lastFrame]=0,this.morphTargetInfluences[d.currentFrame]=1*g,this.morphTargetInfluences[f]=0,d.lastFrame=d.currentFrame,d.currentFrame=f);e=d.time%e/e;d.directionBackwards&&(e=1-e);d.currentFrame!==d.lastFrame?(this.morphTargetInfluences[d.currentFrame]=e*g,this.morphTargetInfluences[d.lastFrame]=(1-e)*g):this.morphTargetInfluences[d.currentFrame]=g}}}; +/** + * @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 + */ + +// This set of controls performs orbiting, dollying (zooming), and panning. +// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). +// +// Orbit - left mouse / touch: one-finger move +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - right mouse, or left mouse + ctrl/metaKey, or arrow keys / touch: two-finger move + +THREE.OrbitControls = function ( object, domElement ) { + + this.object = object; + + this.domElement = ( domElement !== undefined ) ? domElement : document; + + // Set to false to disable this control + this.enabled = true; + + // "target" sets the location of focus, where the object orbits around + this.target = new THREE.Vector3(); + + // How far you can dolly in and out ( PerspectiveCamera only ) + this.minDistance = 0; + this.maxDistance = Infinity; + + // How far you can zoom in and out ( OrthographicCamera only ) + this.minZoom = 0; + this.maxZoom = Infinity; + + // 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 enable damping (inertia) + // If damping is enabled, you must call controls.update() in your animation loop + this.enableDamping = false; + this.dampingFactor = 0.25; + + // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. + // Set to false to disable zooming + this.enableZoom = true; + this.zoomSpeed = 1.0; + + // Set to false to disable rotating + this.enableRotate = true; + this.rotateSpeed = 1.0; + + // Set to false to disable panning + this.enablePan = true; + this.panSpeed = 1.0; + this.screenSpacePanning = false; // if true, pan in screen-space + this.keyPanSpeed = 7.0; // pixels moved per arrow key push + + // Set to true to automatically rotate around the target + // If auto-rotate is enabled, you must call controls.update() in your animation loop + this.autoRotate = false; + this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 + + // Set to false to disable use of the keys + this.enableKeys = true; + + // The four arrow keys + this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; + + // Mouse buttons + this.mouseButtons = { LEFT: THREE.MOUSE.LEFT, MIDDLE: THREE.MOUSE.MIDDLE, RIGHT: THREE.MOUSE.RIGHT }; + + // for reset + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + this.zoom0 = this.object.zoom; + + // + // public methods + // + + this.getPolarAngle = function () { + + return spherical.phi; + + }; + + this.getAzimuthalAngle = function () { + + return spherical.theta; + + }; + + this.saveState = function () { + + scope.target0.copy( scope.target ); + scope.position0.copy( scope.object.position ); + scope.zoom0 = scope.object.zoom; + + }; + + this.reset = function () { + + scope.target.copy( scope.target0 ); + scope.object.position.copy( scope.position0 ); + scope.object.zoom = scope.zoom0; + + scope.object.updateProjectionMatrix(); + scope.dispatchEvent( changeEvent ); + + scope.update(); + + state = STATE.NONE; + + }; + + // this method is exposed, but perhaps it would be better if we can make it private... + this.update = function () { + + var offset = new THREE.Vector3(); + + // 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(); + + var lastPosition = new THREE.Vector3(); + var lastQuaternion = new THREE.Quaternion(); + + return function update() { + + var position = scope.object.position; + + offset.copy( position ).sub( scope.target ); + + // rotate offset to "y-axis-is-up" space + offset.applyQuaternion( quat ); + + // angle from z-axis around y-axis + spherical.setFromVector3( offset ); + + if ( scope.autoRotate && state === STATE.NONE ) { + + rotateLeft( getAutoRotationAngle() ); + + } + + spherical.theta += sphericalDelta.theta; + spherical.phi += sphericalDelta.phi; + + // restrict theta to be between desired limits + spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); + + // restrict phi to be between desired limits + spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); + + spherical.makeSafe(); + + + spherical.radius *= scale; + + // restrict radius to be between desired limits + spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); + + // move target to panned location + scope.target.add( panOffset ); + + offset.setFromSpherical( spherical ); + + // rotate offset back to "camera-up-vector-is-up" space + offset.applyQuaternion( quatInverse ); + + position.copy( scope.target ).add( offset ); + + scope.object.lookAt( scope.target ); + + if ( scope.enableDamping === true ) { + + sphericalDelta.theta *= ( 1 - scope.dampingFactor ); + sphericalDelta.phi *= ( 1 - scope.dampingFactor ); + + panOffset.multiplyScalar( 1 - scope.dampingFactor ); + + } else { + + sphericalDelta.set( 0, 0, 0 ); + + panOffset.set( 0, 0, 0 ); + + } + + scale = 1; + + // update condition is: + // min(camera displacement, camera rotation in radians)^2 > EPS + // using small-angle approximation cos(x/2) = 1 - x^2 / 8 + + if ( zoomChanged || + lastPosition.distanceToSquared( scope.object.position ) > EPS || + 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { + + scope.dispatchEvent( changeEvent ); + + lastPosition.copy( scope.object.position ); + lastQuaternion.copy( scope.object.quaternion ); + zoomChanged = false; + + return true; + + } + + return false; + + }; + + }(); + + this.dispose = function () { + + scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); + scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); + scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); + + scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); + scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); + scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); + + document.removeEventListener( 'mousemove', onMouseMove, false ); + document.removeEventListener( 'mouseup', onMouseUp, false ); + + window.removeEventListener( 'keydown', onKeyDown, false ); + + //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? + + }; + + // + // internals + // + + var scope = this; + + var changeEvent = { type: 'change' }; + var startEvent = { type: 'start' }; + var endEvent = { type: 'end' }; + + var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY_PAN: 4 }; + + var state = STATE.NONE; + + var EPS = 0.000001; + + // current position in spherical coordinates + var spherical = new THREE.Spherical(); + var sphericalDelta = new THREE.Spherical(); + + var scale = 1; + var panOffset = new THREE.Vector3(); + var zoomChanged = false; + + 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 dollyStart = new THREE.Vector2(); + var dollyEnd = new THREE.Vector2(); + var dollyDelta = new THREE.Vector2(); + + function getAutoRotationAngle() { + + return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; + + } + + function getZoomScale() { + + return Math.pow( 0.95, scope.zoomSpeed ); + + } + + function rotateLeft( angle ) { + + sphericalDelta.theta -= angle; + + } + + function rotateUp( angle ) { + + sphericalDelta.phi -= angle; + + } + + var panLeft = function () { + + var v = new THREE.Vector3(); + + return function panLeft( distance, objectMatrix ) { + + v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix + v.multiplyScalar( - distance ); + + panOffset.add( v ); + + }; + + }(); + + var panUp = function () { + + var v = new THREE.Vector3(); + + return function panUp( distance, objectMatrix ) { + + if ( scope.screenSpacePanning === true ) { + + v.setFromMatrixColumn( objectMatrix, 1 ); + + } else { + + v.setFromMatrixColumn( objectMatrix, 0 ); + v.crossVectors( scope.object.up, v ); + + } + + v.multiplyScalar( distance ); + + panOffset.add( v ); + + }; + + }(); + + // deltaX and deltaY are in pixels; right and down are positive + var pan = function () { + + var offset = new THREE.Vector3(); + + return function pan( deltaX, deltaY ) { + + var element = scope.domElement === document ? scope.domElement.body : scope.domElement; + + if ( scope.object.isPerspectiveCamera ) { + + // perspective + var position = scope.object.position; + offset.copy( position ).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 use only clientHeight here so aspect ratio does not distort speed + panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); + panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); + + } else if ( scope.object.isOrthographicCamera ) { + + // orthographic + panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); + panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); + + } else { + + // camera neither orthographic nor perspective + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); + scope.enablePan = false; + + } + + }; + + }(); + + function dollyIn( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale /= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + function dollyOut( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale *= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + // + // event callbacks - update the object state + // + + function handleMouseDownRotate( event ) { + + //console.log( 'handleMouseDownRotate' ); + + rotateStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownDolly( event ) { + + //console.log( 'handleMouseDownDolly' ); + + dollyStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownPan( event ) { + + //console.log( 'handleMouseDownPan' ); + + panStart.set( event.clientX, event.clientY ); + + } + + function handleMouseMoveRotate( event ) { + + //console.log( 'handleMouseMoveRotate' ); + + rotateEnd.set( event.clientX, event.clientY ); + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + + var element = scope.domElement === document ? scope.domElement.body : scope.domElement; + + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + + rotateStart.copy( rotateEnd ); + + scope.update(); + + } + + function handleMouseMoveDolly( event ) { + + //console.log( 'handleMouseMoveDolly' ); + + dollyEnd.set( event.clientX, event.clientY ); + + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y > 0 ) { + + dollyIn( getZoomScale() ); + + } else if ( dollyDelta.y < 0 ) { + + dollyOut( getZoomScale() ); + + } + + dollyStart.copy( dollyEnd ); + + scope.update(); + + } + + function handleMouseMovePan( event ) { + + //console.log( 'handleMouseMovePan' ); + + panEnd.set( event.clientX, event.clientY ); + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + scope.update(); + + } + + function handleMouseUp( event ) { + + // console.log( 'handleMouseUp' ); + + } + + function handleMouseWheel( event ) { + + // console.log( 'handleMouseWheel' ); + + if ( event.deltaY < 0 ) { + + dollyOut( getZoomScale() ); + + } else if ( event.deltaY > 0 ) { + + dollyIn( getZoomScale() ); + + } + + scope.update(); + + } + + function handleKeyDown( event ) { + + //console.log( 'handleKeyDown' ); + + switch ( event.keyCode ) { + + case scope.keys.UP: + pan( 0, scope.keyPanSpeed ); + scope.update(); + break; + + case scope.keys.BOTTOM: + pan( 0, - scope.keyPanSpeed ); + scope.update(); + break; + + case scope.keys.LEFT: + pan( scope.keyPanSpeed, 0 ); + scope.update(); + break; + + case scope.keys.RIGHT: + pan( - scope.keyPanSpeed, 0 ); + scope.update(); + break; + + } + + } + + function handleTouchStartRotate( event ) { + + //console.log( 'handleTouchStartRotate' ); + + rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } + + function handleTouchStartDollyPan( event ) { + + //console.log( 'handleTouchStartDollyPan' ); + + if ( scope.enableZoom ) { + + 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 ); + + } + + if ( scope.enablePan ) { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + panStart.set( x, y ); + + } + + } + + function handleTouchMoveRotate( event ) { + + //console.log( 'handleTouchMoveRotate' ); + + rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + + var element = scope.domElement === document ? scope.domElement.body : scope.domElement; + + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + + rotateStart.copy( rotateEnd ); + + scope.update(); + + } + + function handleTouchMoveDollyPan( event ) { + + //console.log( 'handleTouchMoveDollyPan' ); + + if ( scope.enableZoom ) { + + 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.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); + + dollyIn( dollyDelta.y ); + + dollyStart.copy( dollyEnd ); + + } + + if ( scope.enablePan ) { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + panEnd.set( x, y ); + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + } + + scope.update(); + + } + + function handleTouchEnd( event ) { + + //console.log( 'handleTouchEnd' ); + + } + + // + // event handlers - FSM: listen for events and reset state + // + + function onMouseDown( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + switch ( event.button ) { + + case scope.mouseButtons.LEFT: + + if ( event.ctrlKey || event.metaKey ) { + + if ( scope.enablePan === false ) return; + + handleMouseDownPan( event ); + + state = STATE.PAN; + + } else { + + if ( scope.enableRotate === false ) return; + + handleMouseDownRotate( event ); + + state = STATE.ROTATE; + + } + + break; + + case scope.mouseButtons.MIDDLE: + + if ( scope.enableZoom === false ) return; + + handleMouseDownDolly( event ); + + state = STATE.DOLLY; + + break; + + case scope.mouseButtons.RIGHT: + + if ( scope.enablePan === false ) return; + + handleMouseDownPan( event ); + + state = STATE.PAN; + + break; + + } + + 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(); + + switch ( state ) { + + case STATE.ROTATE: + + if ( scope.enableRotate === false ) return; + + handleMouseMoveRotate( event ); + + break; + + case STATE.DOLLY: + + if ( scope.enableZoom === false ) return; + + handleMouseMoveDolly( event ); + + break; + + case STATE.PAN: + + if ( scope.enablePan === false ) return; + + handleMouseMovePan( event ); + + break; + + } + + } + + function onMouseUp( event ) { + + if ( scope.enabled === false ) return; + + handleMouseUp( event ); + + document.removeEventListener( 'mousemove', onMouseMove, false ); + document.removeEventListener( 'mouseup', onMouseUp, false ); + + scope.dispatchEvent( endEvent ); + + state = STATE.NONE; + + } + + function onMouseWheel( event ) { + + if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; + + event.preventDefault(); + event.stopPropagation(); + + scope.dispatchEvent( startEvent ); + + handleMouseWheel( event ); + + scope.dispatchEvent( endEvent ); + + } + + function onKeyDown( event ) { + + if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; + + handleKeyDown( event ); + + } + + function onTouchStart( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + switch ( event.touches.length ) { + + case 1: // one-fingered touch: rotate + + if ( scope.enableRotate === false ) return; + + handleTouchStartRotate( event ); + + state = STATE.TOUCH_ROTATE; + + break; + + case 2: // two-fingered touch: dolly-pan + + if ( scope.enableZoom === false && scope.enablePan === false ) return; + + handleTouchStartDollyPan( event ); + + state = STATE.TOUCH_DOLLY_PAN; + + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + scope.dispatchEvent( startEvent ); + + } + + } + + function onTouchMove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + event.stopPropagation(); + + switch ( event.touches.length ) { + + case 1: // one-fingered touch: rotate + + if ( scope.enableRotate === false ) return; + if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed? + + handleTouchMoveRotate( event ); + + break; + + case 2: // two-fingered touch: dolly-pan + + if ( scope.enableZoom === false && scope.enablePan === false ) return; + if ( state !== STATE.TOUCH_DOLLY_PAN ) return; // is this needed? + + handleTouchMoveDollyPan( event ); + + break; + + default: + + state = STATE.NONE; + + } + + } + + function onTouchEnd( event ) { + + if ( scope.enabled === false ) return; + + handleTouchEnd( event ); + + scope.dispatchEvent( endEvent ); + + state = STATE.NONE; + + } + + function onContextMenu( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + } + + // + + scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); + + scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); + scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); + + scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); + scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); + scope.domElement.addEventListener( 'touchmove', onTouchMove, 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; + +Object.defineProperties( THREE.OrbitControls.prototype, { + + center: { + + get: function () { + + console.warn( 'THREE.OrbitControls: .center has been renamed to .target' ); + return this.target; + + } + + }, + + // backward compatibility + + noZoom: { + + get: function () { + + console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); + return ! this.enableZoom; + + }, + + set: function ( value ) { + + console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); + this.enableZoom = ! value; + + } + + }, + + noRotate: { + + get: function () { + + console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); + return ! this.enableRotate; + + }, + + set: function ( value ) { + + console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); + this.enableRotate = ! value; + + } + + }, + + noPan: { + + get: function () { + + console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); + return ! this.enablePan; + + }, + + set: function ( value ) { + + console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); + this.enablePan = ! value; + + } + + }, + + noKeys: { + + get: function () { + + console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); + return ! this.enableKeys; + + }, + + set: function ( value ) { + + console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); + this.enableKeys = ! value; + + } + + }, + + staticMoving: { + + get: function () { + + console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); + return ! this.enableDamping; + + }, + + set: function ( value ) { + + console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); + this.enableDamping = ! value; + + } + + }, + + dynamicDampingFactor: { + + get: function () { + + console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); + return this.dampingFactor; + + }, + + set: function ( value ) { + + console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); + this.dampingFactor = value; + + } + + } + +} ); diff --git a/modules/hardware/pot b/modules/hardware/pot index 5386b80f8b556c5d5661303df80fb4921c840db3..3e6ffe92d8d7da75252b26a66274628531925d4f 100644 --- a/modules/hardware/pot +++ b/modules/hardware/pot @@ -39,13 +39,14 @@ // inputs // var inputs = { - // gcode: { - // type: 'string', - // label: 'gcode', - // event: function(evt) { - // parseGCode(evt.detail) - // } - // } + points: { + type: 'object', + label: 'points', + event: function(e) { + mod.points = e.detail; + updateGeometry(); + } + } } // // outputs @@ -67,18 +68,37 @@ // Three.js viewer div var viewer = document.createElement('div'); - viewer.style.width = '300px'; - viewer.style.height = '200px'; + viewer.style.width = '400px'; + viewer.style.height = '400px'; div.appendChild(viewer); - mod.viewer = viewer; - // - // inputs - // + // Parameters + mod.widthInput = make_text_input(div, "width scale", 24); + mod.widthInput.value = 20; + mod.widthInput.addEventListener('input', function(e) { + updateGeometry(); + }); + + mod.heightInput = make_text_input(div, "height scale", 24); + mod.heightInput.value = 20; + mod.heightInput.addEventListener('input', function(e) { + updateGeometry(); + }); + + mod.extrusionInput = make_text_input(div, "extrusion rate", 24); + mod.extrusionInput.value = 20; + + mod.layersInput = make_text_input(div, "number of layers", 24); + mod.layersInput.value = 50; + mod.layersInput.addEventListener('input', function(e) { + updateGeometry(); + }); + + // Output event button mod.sendButton = make_button_input(div, 'send') mod.sendButton.addEventListener('click', function() { - outputs.instructions.event(mod.instructionArray); + generateInstructions(); }) } @@ -86,6 +106,13 @@ return Math.sqrt( ((v1.x - v0.x) * (v1.x - v0.x)) + ((v1.y - v0.y) * (v1.y - v0.y)) ); } + function lerp(pt1, pt2, pct) { + return { + x: (1 - pct) * pt1.x + pct * pt2.x, + y: (1 - pct) * pt1.y + pct * pt2.y + } + } + function threeJsLoaded() { var renderer = new THREE.WebGLRenderer(); renderer.setSize( mod.viewer.clientWidth, mod.viewer.clientHeight ); @@ -93,92 +120,114 @@ var scene = new THREE.Scene(); var camera = new THREE.PerspectiveCamera( 75, mod.viewer.clientWidth / mod.viewer.clientHeight, 0.1, 1000 ); - // var controls = new THREE.OrbitControls( camera ); + var controls = new THREE.OrbitControls( camera, mod.viewer ); + + mod.scene = scene; camera.position.z = 50; - var points = []; - for ( var i = 0; i < 20; i ++ ) { - points.push( new THREE.Vector2( Math.sin( i * 0.1 ) * 10 + 5, i) ); + // Begin rendering + var animate = function () { + requestAnimationFrame( animate ); + controls.update(); + renderer.render( scene, camera ); + }; + animate(); + } + + function updateGeometry() { + if (!mod.points || mod.points.length == 0) { return; } + + // Construct an updated profile given the number of layers specified + // using linear interpolation + var geomPoints = []; + var layers = mod.layersInput.value; + for ( var i = 0; i < layers; ++i ) { + var scaledLayerIndex = (i / layers) * (mod.points.length - 1); + var pt1 = mod.points[Math.floor(scaledLayerIndex)]; + var pt2 = mod.points[Math.ceil(scaledLayerIndex)]; + var pct = scaledLayerIndex - Math.floor(scaledLayerIndex); + var lerpedPt = lerp(pt1, pt2, pct); + if (lerpedPt.x == 0) { + lerpedPt.x += 0.001; + } + geomPoints.push( new THREE.Vector2( lerpedPt.x * +(mod.widthInput.value), lerpedPt.y * +(mod.heightInput.value)) ); } + mod.geomPoints = geomPoints; // Create revolved geometry - var geometry = new THREE.LatheGeometry( points ); - var material = new THREE.MeshBasicMaterial( { color: 0x0000ff } ); - var pot = new THREE.Mesh( geometry, material ); + var geometry = new THREE.LatheGeometry( geomPoints ); - // Rotate the vertex order so that we spiral up from the bottom - var potVerts = pot.geometry.vertices; - var clayGeometry = new THREE.Geometry(); + // Construct the extrusion path vertices + // + // NOTE: We rotate the vertex order so that we spiral up from the bottom + var potVerts = geometry.vertices; + mod.clayGeometry = new THREE.Geometry(); var segments = geometry.parameters.segments || 12; - for ( var pt = 0; pt < points.length; ++pt ) { + for ( var pt = 0; pt < geomPoints.length; ++pt ) { for ( var seg = 0; seg < segments; ++seg ) { - var vert = potVerts[(points.length * seg) + pt]; - clayGeometry.vertices.push(vert); + var vert = potVerts[(geomPoints.length * seg) + pt]; + mod.clayGeometry.vertices.push(vert); } } var pathMat = new THREE.LineBasicMaterial( { color: 0x00ff00 } ); - var path = new THREE.Line(clayGeometry, pathMat); - // scene.add(pot); - scene.add(path); - - /************************************************************ - * - * Generate move instructions - * - * NOTE: We rotate the coordinate system from Y-up to Z-up - * - ************************************************************/ - var verts = clayGeometry.vertices; - var totalExtrusion = 0; - const EXTRUSION_RATE = 20; - var instructions = []; - - // Add the first instruction manually - instructions.push({ - type: "move", - speed: EXTRUSION_RATE, - position: { - X: verts[0].x, - Y: verts[0].z, - Z: verts[0].y, - E: 0 + var path = new THREE.Line(mod.clayGeometry, pathMat); + if (mod.path) { + mod.scene.remove(mod.path); } - }); + mod.scene.add(path); + mod.path = path; + } - for (var i = 1; i < verts.length; ++i) { - var currentVert = verts[i]; - var previousVert = verts[i - 1]; - totalExtrusion += distance(currentVert, previousVert); + function generateInstructions() { + /************************************************************ + * + * Generate move instructions + * + * NOTE: We rotate the coordinate system from Y-up to Z-up + * + ************************************************************/ + if (!mod.points || !mod.clayGeometry || mod.clayGeometry.vertices.length == 0) { return; } + + var verts = mod.clayGeometry.vertices; + var totalExtrusion = 0; + const rate = +(mod.extrusionInput.value); + var instructions = []; + + // Add the first instruction manually instructions.push({ - type: "move", - speed: EXTRUSION_RATE, - position: { - X: verts[i].x, - Y: verts[i].z, - Z: verts[i].y, - E: totalExtrusion - } - }) - } - - mod.instructionArray = instructions; - - - // Render - - var animate = function () { - requestAnimationFrame( animate ); - - renderer.render( scene, camera ); - }; + type: "move", + speed: rate, + position: { + X: verts[0].x, + Y: verts[0].z, + Z: verts[0].y, + E: 0 + } + }); + + for (var i = 1; i < verts.length; ++i) { + var currentVert = verts[i]; + var previousVert = verts[i - 1]; + totalExtrusion += distance(currentVert, previousVert); + instructions.push({ + type: "move", + speed: rate, + position: { + X: verts[i].x, + Y: verts[i].z, + Z: verts[i].y, + E: totalExtrusion + } + }); + } - animate(); + outputs.instructions.event(instructions); } - /* - UI helpers - */ + // /* + // UI helpers + // */ function make_text_input(div, name, size) { div.appendChild(document.createElement('br')) div.appendChild(document.createTextNode(name + ': ')) diff --git a/modules/hardware/profile b/modules/hardware/profile new file mode 100644 index 0000000000000000000000000000000000000000..e72da1638af61169239a1841cb285a4b5bb7c4ac --- /dev/null +++ b/modules/hardware/profile @@ -0,0 +1,217 @@ +// +// print a pot +// +// Sean Hickey +// (c) Massachusetts Institute of Technology 2018 +// +// This work may be reproduced, modified, distributed, performed, and +// displayed for any purpose, but must acknowledge the mods +// project. Copyright is retained and must be preserved. The work is +// provided as is; no warranty is provided, and users accept all +// liability. + + +// +// closure +// +(function() { + // + // module globals + // + var mod = {} + // + // name + // + var name = 'profile' + // + // initialization + // + var init = function() { + mod.isDrawing = false; + } + // + // inputs + // + var inputs = { + + } + // + // outputs + // + var outputs = { + points: { + type: 'object', + label: 'points', + event: function(obj) { + mods.output(mod, 'points', obj) + } + } + } + // + // interface + // + var interface = function(div) { + mod.div = div + + // Drawing canvas + var canvas = div.appendChild(document.createElement('canvas')); + canvas.setAttribute('width', 400); + canvas.setAttribute('height', 600); + canvas.style.width = '400px'; + canvas.style.height = '600px'; + div.appendChild(canvas); + + canvas.addEventListener('mousedown', mouseDown, false); + canvas.addEventListener('mouseup', mouseUp, false); + canvas.addEventListener('mousemove', mouseMove, false); + + mod.canvas = canvas; + + var points = []; + + const TOTAL_POINTS = 25; + + // Prepopulate points with a basic profile + points.push({ + x: 0, + y: 0 + }) + for (var i = 1; i < TOTAL_POINTS; ++i) { + points.push({ + x: (400 * (i / TOTAL_POINTS)), + y: (600 * (i / TOTAL_POINTS) * (i / TOTAL_POINTS)) + }) + } + + mod.points = points; + + redraw(); + } + + function mouseDown() { + mod.isDrawing = true; + } + + function mouseUp() { + if (mod.isDrawing) { + mod.isDrawing = false; + redraw(); + var normalizedPoints = []; + for (var i = 0; i < mod.points.length; ++i) { + var pt = mod.points[i]; + normalizedPoints.push({ + x: pt.x / 400, + y: pt.y / 600 + }) + } + outputs.points.event(normalizedPoints); + } + } + + function mouseMove(e) { + if (mod.isDrawing) { + var mousePoint = { + x: e.offsetX, + y: 600 - e.offsetY // Flip Y + } + for (var i = 0; i < mod.points.length; ++i) { + var pt = mod.points[i] + var dist = distance(mousePoint, pt); + if (dist > 50) { continue; } + + // Falloff function + var move = Math.sqrt(dist / 20) * ((Math.PI / 2) - Math.atan(dist / 20)); + + if (pt.x > mousePoint.x) { + mod.points[i].x -= move; + } + else { + mod.points[i].x += move; + } + } + } + redraw(); + } + + function distance(v0, v1) { + return Math.sqrt( ((v1.x - v0.x) * (v1.x - v0.x)) + ((v1.y - v0.y) * (v1.y - v0.y)) ); + } + + function redraw() { + // NOTE: Flip Y here + if (mod.canvas.getContext) { + var ctx = mod.canvas.getContext('2d'); + ctx.clearRect(0, 0, mod.canvas.width, mod.canvas.height); + + ctx.beginPath(); + + var first = mod.points[0]; + ctx.moveTo(first.x, 600 - first.y); + + for (var i = 1; i < mod.points.length; ++i) { + var pt = mod.points[i]; + ctx.lineTo(pt.x, 600 - pt.y); + } + + ctx.stroke(); + } + } + + /* + UI helpers + */ + function make_text_input(div, name, size) { + div.appendChild(document.createElement('br')) + div.appendChild(document.createTextNode(name + ': ')) + var input = document.createElement('input') + input.type = 'text' + input.size = size + div.appendChild(input) + + return input + } + + function make_button_input(div, text) { + div.appendChild(document.createElement('br')) + var button = document.createElement('button') + button.style.padding = mods.ui.padding + button.style.margin = 1 + button.appendChild(document.createTextNode(text)) + div.appendChild(button) + + return button + } + + function make_checkbox_input(div, prefix) { + div.appendChild(document.createElement('br')) + div.appendChild(document.createTextNode(prefix + ': ')) + var checkbox = document.createElement('input') + checkbox.type = 'checkbox' + div.appendChild(checkbox) + + return checkbox + } + + function make_text_display(div, prefix) { + div.appendChild(document.createElement('br')) + div.appendChild(document.createTextNode(prefix + ': ')) + var span = document.createElement('span') + span.innerHTML = '' + div.appendChild(span) + + return span + } + + + // + // return values + // + return ({ + mod: mod, + name: name, + init: init, + inputs: inputs, + outputs: outputs, + interface: interface + }) +}()) \ No newline at end of file