This example illustrates using OrbitControls to move a three.js Object3D (a PerspectiveCamera in this case).
Three.js "tutorials by example"
Author: Lee Stemkoski
Date: July 2013
export interface Renderer { | |
setSize: (width: number, height: number) => any; | |
} | |
export interface Camera { | |
aspect: number; | |
updateProjectionMatrix: () => any; | |
} | |
/** | |
* Update renderer and camera when the window is resized | |
* | |
* renderer -- the renderer to update | |
* camera -- the camera to update | |
*/ | |
export default function bindResize(renderer: Renderer, camera: Camera) { | |
/** | |
* | |
*/ | |
const listener = function() { | |
// Notify the renderer of the size change. | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
// Update the camera. | |
camera.aspect = window.innerWidth / window.innerHeight; | |
camera.updateProjectionMatrix(); | |
} | |
const useCapture = false; | |
window.addEventListener('resize', listener, useCapture); | |
// return .stop() the function to stop watching window resize | |
return { | |
/** | |
* Stop watching window resize | |
*/ | |
stop : function(){ | |
window.removeEventListener('resize', listener, useCapture); | |
} | |
}; | |
} |
/** | |
* @author alteredq / http://alteredqualia.com/ | |
* @author mr.doob / http://mrdoob.com/ | |
*/ | |
const Detector = { | |
canvas: !! window['CanvasRenderingContext2D'], | |
webgl: ( function () { try { return !! window['WebGLRenderingContext'] && !! document.createElement( 'canvas' ).getContext( 'experimental-webgl' ); } catch( e ) { return false; } } )(), | |
workers: !! window['Worker'], | |
fileapi: window['File'] && window['FileReader'] && window['FileList'] && window['Blob'], | |
getWebGLErrorMessage: function () { | |
var element = document.createElement( 'div' ); | |
element.id = 'webgl-error-message'; | |
element.style.fontFamily = 'monospace'; | |
element.style.fontSize = '13px'; | |
element.style.fontWeight = 'normal'; | |
element.style.textAlign = 'center'; | |
element.style.background = '#fff'; | |
element.style.color = '#000'; | |
element.style.padding = '1.5em'; | |
element.style.width = '400px'; | |
element.style.margin = '5em auto 0'; | |
if ( ! this.webgl ) { | |
element.innerHTML = window['WebGLRenderingContext'] ? [ | |
'Your graphics card does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation" style="color:#000">WebGL</a>.<br />', | |
'Find out how to get it <a href="http://get.webgl.org/" style="color:#000">here</a>.' | |
].join( '\n' ) : [ | |
'Your browser does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation" style="color:#000">WebGL</a>.<br/>', | |
'Find out how to get it <a href="http://get.webgl.org/" style="color:#000">here</a>.' | |
].join( '\n' ); | |
} | |
return element; | |
}, | |
addGetWebGLMessage: function ( parameters ) { | |
var parent, id, element; | |
parameters = parameters || {}; | |
parent = parameters.parent !== undefined ? parameters.parent : document.body; | |
id = parameters.id !== undefined ? parameters.id : 'oldie'; | |
element = Detector.getWebGLErrorMessage(); | |
element.id = id; | |
parent.appendChild( element ); | |
} | |
}; | |
export default Detector; |
// This THREEx helper makes it easy to handle the fullscreen API | |
// * it hides the prefix for each browser | |
// * it hides the little discrepencies of the various vendor API | |
// * at the time of this writing (nov 2011) it is available in | |
// [firefox nightly](http://blog.pearce.org.nz/2011/11/firefoxs-html-full-screen-api-enabled.html), | |
// [webkit nightly](http://peter.sh/2011/01/javascript-full-screen-api-navigation-timing-and-repeating-css-gradients/) and | |
// [chrome stable](http://updates.html5rocks.com/2011/10/Let-Your-Content-Do-the-Talking-Fullscreen-API). | |
// internal constants to know which fullscreen API implementation is available | |
function cancelFullScreen(el) { | |
var requestMethod = el.cancelFullScreen || el.webkitCancelFullScreen || el.mozCancelFullScreen || el.exitFullscreen; | |
if (requestMethod) { // cancel full screen. | |
requestMethod.call(el); | |
} | |
else if (typeof window['ActiveXObject'] !== "undefined") { // Older IE. | |
var wscript = new ActiveXObject("WScript.Shell"); | |
if (wscript !== null) { | |
wscript.SendKeys("{F11}"); | |
} | |
} | |
} | |
function requestFullScreen(el) { | |
// Supports most browsers and their versions. | |
var requestMethod = el.requestFullScreen || el.webkitRequestFullScreen(el.ALLOW_KEYBOARD_INPUT) || el.mozRequestFullScreen || el.msRequestFullScreen; | |
if (requestMethod) { // Native full screen. | |
requestMethod.call(el); | |
} | |
else if (typeof window['ActiveXObject'] !== "undefined") { // Older IE. | |
var wscript = new ActiveXObject("WScript.Shell"); | |
if (wscript !== null) { | |
wscript.SendKeys("{F11}"); | |
} | |
} | |
return false | |
} | |
function toggleFull() { | |
var elem = document.body; // Make the body go full screen. | |
var isInFullScreen = (document['fullScreenElement'] && document['fullScreenElement'] !== null) || (document['mozFullScreen'] || document['webkitIsFullScreen']); | |
if (isInFullScreen) { | |
cancelFullScreen(document); | |
} else { | |
requestFullScreen(elem); | |
} | |
return false; | |
} | |
const hasWebkitFullScreen = 'webkitCancelFullScreen' in document ? true : false; | |
// console.log(`hasWebkitFullScreen => ${hasWebkitFullScreen}`); | |
const hasMozFullScreen = 'mozCancelFullScreen' in document ? true : false; | |
// console.log(`hasMozFullScreen => ${hasMozFullScreen}`); | |
const FullScreen = { | |
/** | |
* test if it is possible to have fullscreen | |
* | |
* @returns {Boolean} true if fullscreen API is available, false otherwise | |
*/ | |
available: function() { | |
return hasWebkitFullScreen || hasMozFullScreen; | |
}, | |
/** | |
* test if fullscreen is currently activated | |
* | |
* @returns {Boolean} true if fullscreen is currently activated, false otherwise | |
*/ | |
activated: function(): boolean { | |
if( hasWebkitFullScreen ) { | |
return document['webkitIsFullScreen']; | |
} | |
else if( hasMozFullScreen ) { | |
return document['mozFullScreen']; | |
} | |
else { | |
console.assert(false); | |
} | |
}, | |
/** | |
* Request fullscreen on a given element | |
* @param {DomElement} element to make fullscreen. optional. default to document.body | |
*/ | |
request: function(element: HTMLElement) { | |
element = element || document.body; | |
if( hasWebkitFullScreen ) { | |
element['webkitRequestFullScreen'](Element['ALLOW_KEYBOARD_INPUT']); | |
} | |
else if( this._hasMozFullScreen ) { | |
element['mozRequestFullScreen'](); | |
} | |
else { | |
console.assert(false); | |
} | |
}, | |
/** | |
* Cancel fullscreen | |
*/ | |
cancel: function() { | |
if( hasWebkitFullScreen ) { | |
document['webkitCancelFullScreen'](); | |
} | |
else if( this._hasMozFullScreen ) { | |
document['mozCancelFullScreen'](); | |
} | |
else { | |
console.assert(false); | |
} | |
}, | |
/** | |
* Bind a key to renderer screenshot | |
* usage: THREEx.FullScreen.bindKey({ charCode : 'a'.charCodeAt(0) }); | |
*/ | |
bindKey: function(opts){ | |
opts = opts || {}; | |
var charCode = opts.charCode || 'f'.charCodeAt(0); | |
var dblclick = opts.dblclick !== undefined ? opts.dblclick : false; | |
var element = opts.element | |
var toggle = function() { | |
console.log("toggle") | |
if( FullScreen.activated() ) { | |
FullScreen.cancel(); | |
} | |
else { | |
FullScreen.request(element); | |
} | |
} | |
var onKeyPress = function(event){ | |
if( event.which !== charCode ) return; | |
toggle(); | |
}.bind(this); | |
document.addEventListener('keypress', onKeyPress, false); | |
dblclick && document.addEventListener('dblclick', toggle, false); | |
return { | |
unbind : function(){ | |
document.removeEventListener('keypress', onKeyPress, false); | |
dblclick && document.removeEventListener('dblclick', toggle, false); | |
} | |
}; | |
} | |
}; | |
export default FullScreen; | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<style> | |
/* STYLE-MARKER */ | |
</style> | |
<!-- SCRIPTS-MARKER --> | |
<script src="https://jspm.io/system.js"></script> | |
</head> | |
<body> | |
<div id="ThreeJS" style="position: absolute; left:0px; top:0px"></div> | |
<script> | |
// CODE-MARKER | |
</script> | |
<script>System.import('./index.js')</script> | |
</body> | |
</html> |
import Detector from './Detector'; | |
import FullScreen from './FullScreen'; | |
import KeyboardState from './KeyboardState'; | |
import OrbitControls from './OrbitControls'; | |
import bindResize from './bindResize'; | |
let scene: THREE.Scene; | |
let camera: THREE.PerspectiveCamera; | |
let renderer: THREE.WebGLRenderer; | |
let container: HTMLElement; | |
let stats: Stats; | |
let controls: OrbitControls; | |
const keyboard = new KeyboardState(); | |
DomReady.ready(function() { | |
init(); | |
animate(); | |
}); | |
function init() | |
{ | |
scene = new THREE.Scene(); | |
const SCREEN_WIDTH = window.innerWidth, SCREEN_HEIGHT = window.innerHeight; | |
const VIEW_ANGLE = 45, ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT, NEAR = 0.1, FAR = 20000; | |
camera = new THREE.PerspectiveCamera( VIEW_ANGLE, ASPECT, NEAR, FAR); | |
scene.add(camera); | |
camera.position.set(0,150,400); | |
camera.lookAt(scene.position); | |
if (Detector.webgl) { | |
renderer = new THREE.WebGLRenderer( {antialias:true} ); | |
} | |
else { | |
throw new Error("WeBGL is not supported"); | |
} | |
renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT); | |
container = document.getElementById( 'ThreeJS' ); | |
container.appendChild( renderer.domElement ); | |
bindResize(renderer, camera); | |
FullScreen.bindKey({ charCode : 'm'.charCodeAt(0) }); | |
controls = new OrbitControls( camera, renderer.domElement ); | |
stats = new Stats(); | |
stats.domElement.style.position = 'absolute'; | |
stats.domElement.style.left = '0px'; | |
stats.domElement.style.top = '0px'; | |
stats.domElement.style.zIndex = '100'; | |
container.appendChild( stats.domElement ); | |
const light = new THREE.PointLight(0xffffff); | |
light.position.set(100,250,100); | |
scene.add(light); | |
const loader = new THREE.TextureLoader(); | |
loader.load('img/textures/checkerboard.jpg', | |
function (texture: THREE.Texture) { | |
texture.wrapS = texture.wrapT = THREE.RepeatWrapping; | |
texture.repeat.set( 10, 10 ); | |
const floorMaterial = new THREE.MeshBasicMaterial( { map: texture, side: THREE.DoubleSide } ); | |
const floorGeometry = new THREE.PlaneGeometry(1000, 1000, 10, 10); | |
const floor = new THREE.Mesh(floorGeometry, floorMaterial); | |
floor.position.y = -0.5; | |
floor.rotation.x = Math.PI / 2; | |
scene.add(floor); | |
}, | |
function(progress) { | |
// console.log((progress.loaded / progress.total * 100) + '% loaded' ); | |
}, | |
function(e) { | |
console.log( `An error happened ${JSON.stringify(e)}` ); | |
} | |
); | |
var skyBoxGeometry = new THREE.CubeGeometry( 10000, 10000, 10000 ); | |
var skyBoxMaterial = new THREE.MeshBasicMaterial( { color: 0x9999ff, side: THREE.BackSide } ); | |
var skyBox = new THREE.Mesh( skyBoxGeometry, skyBoxMaterial ); | |
scene.add(skyBox); | |
const geometry = new THREE.SphereGeometry(30, 32, 16); | |
const material = new THREE.MeshLambertMaterial({ color: 0x000088 }); | |
const mesh = new THREE.Mesh( geometry, material ); | |
mesh.position.set(0, 40, 0); | |
scene.add(mesh); | |
} | |
function animate() { | |
render(); | |
update(); | |
requestAnimationFrame( animate ); | |
} | |
function update() { | |
if ( keyboard.pressed("z") ) { | |
// do something | |
} | |
controls.update(); | |
stats.update(); | |
} | |
function render() { | |
renderer.render( scene, camera ); | |
} | |
/** | |
* @author Lee Stemkoski | |
* @author David Geo Holmes (TypeScript migration) | |
* | |
* Usage: | |
* (1) create a global variable: | |
* var keyboard = new KeyboardState(); | |
* (2) during main loop: | |
* keyboard.update(); | |
* (3) check state of keys: | |
* keyboard.down("A") -- true for one update cycle after key is pressed | |
* keyboard.pressed("A") -- true as long as key is being pressed | |
* keyboard.up("A") -- true for one update cycle after key is released | |
* | |
* See KeyboardState.k object data below for names of keys whose state can be polled | |
*/ | |
export default class KeyboardState { | |
constructor() { | |
// bind keyEvents | |
document.addEventListener("keydown", KeyboardState.onKeyDown, false); | |
document.addEventListener("keyup", KeyboardState.onKeyUp, false); | |
} | |
update() | |
{ | |
for (var key in KeyboardState.status) | |
{ | |
// ensure that every keypress has "down" status exactly once | |
if (!KeyboardState.status[key].updatedPreviously) | |
{ | |
KeyboardState.status[key].down = true; | |
KeyboardState.status[key].pressed = true; | |
KeyboardState.status[key].updatedPreviously = true; | |
} | |
else // updated previously | |
{ | |
KeyboardState.status[key].down = false; | |
} | |
// key has been flagged as "up" since last update | |
if ( KeyboardState.status[key].up ) | |
{ | |
delete KeyboardState.status[key]; | |
continue; // move on to next key | |
} | |
if ( !KeyboardState.status[key].pressed ) // key released | |
KeyboardState.status[key].up = true; | |
} | |
} | |
down(keyName: string): boolean { | |
return (KeyboardState.status[keyName] && KeyboardState.status[keyName].down); | |
} | |
pressed(keyName: string): boolean { | |
return (KeyboardState.status[keyName] && KeyboardState.status[keyName].pressed); | |
} | |
up(keyName: string): boolean { | |
return (KeyboardState.status[keyName] && KeyboardState.status[keyName].up); | |
} | |
debug() { | |
var list = "Keys active: "; | |
for (var arg in KeyboardState.status) | |
list += " " + arg | |
console.log(list); | |
} | |
static k: {[keyCode: number]: string} = { | |
8: "backspace", 9: "tab", 13: "enter", 16: "shift", | |
17: "ctrl", 18: "alt", 27: "esc", 32: "space", | |
33: "pageup", 34: "pagedown", 35: "end", 36: "home", | |
37: "left", 38: "up", 39: "right", 40: "down", | |
45: "insert", 46: "delete", 186: ";", 187: "=", | |
188: ",", 189: "-", 190: ".", 191: "/", | |
219: "[", 220: "\\", 221: "]", 222: "'" | |
}; | |
static status: {[key: string]: {down: boolean; pressed: boolean; up: boolean; updatedPreviously: boolean}} = {}; | |
static keyName(keyCode: number): string { | |
return ( KeyboardState.k[keyCode] != null ) ? KeyboardState.k[keyCode] : String.fromCharCode(keyCode); | |
} | |
static onKeyUp(event: KeyboardEvent) { | |
const key = KeyboardState.keyName(event.keyCode); | |
if ( KeyboardState.status[key] ) | |
KeyboardState.status[key].pressed = false; | |
} | |
static onKeyDown(event: KeyboardEvent) { | |
const key = KeyboardState.keyName(event.keyCode); | |
if (!KeyboardState.status[key]) { | |
KeyboardState.status[key] = { down: false, pressed: false, up: false, updatedPreviously: false }; | |
} | |
} | |
} |
/** | |
* @author qiao / https://github.com/qiao | |
* @author mrdoob / http://mrdoob.com | |
* @author alteredq / http://alteredqualia.com/ | |
* @author WestLangley / http://github.com/WestLangley | |
* @author David Geo Holmes (TypeScript migration) | |
*/ | |
const EPS = 0.000001; | |
// 65 /*A*/, 83 /*S*/, 68 /*D*/ | |
const keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40, ROTATE: 65, ZOOM: 83, PAN: 68 }; | |
export default class OrbitControls { | |
private object: THREE.Object3D; | |
private domElement; | |
public enabled = true; | |
public center = new THREE.Vector3(); | |
public userZoom = true; | |
public userZoomSpeed = 1.0; | |
public userRotate = true; | |
public userRotateSpeed = 1.0; | |
public userPan = true; | |
public userPanSpeed = 2.0; | |
public autoRotate = false; | |
public autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 | |
public minPolarAngle = 0; // radians | |
public maxPolarAngle = Math.PI; // radians | |
public minDistance = 0; | |
public maxDistance = Infinity; | |
private phiDelta = 0; | |
private thetaDelta = 0; | |
private scale = 1; | |
private lastPosition = new THREE.Vector3(); | |
constructor( object: THREE.Object3D , domElement: HTMLCanvasElement ) { | |
this.object = object; | |
this.domElement = ( domElement !== undefined ) ? domElement : document; | |
var PIXELS_PER_ROUND = 1800; | |
var rotateStart = new THREE.Vector2(); | |
var rotateEnd = new THREE.Vector2(); | |
var rotateDelta = new THREE.Vector2(); | |
var zoomStart = new THREE.Vector2(); | |
var zoomEnd = new THREE.Vector2(); | |
var zoomDelta = new THREE.Vector2(); | |
var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2 }; | |
var state = STATE.NONE; | |
// events | |
var changeEvent = { type: 'change' }; | |
this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); | |
const onMouseDown = (event: MouseEvent) => { | |
if (this.enabled === false ) return; | |
if (this.userRotate === false ) return; | |
event.preventDefault(); | |
if ( state === STATE.NONE ) | |
{ | |
if ( event.button === 0 ) | |
state = STATE.ROTATE; | |
if ( event.button === 1 ) | |
state = STATE.ZOOM; | |
if ( event.button === 2 ) | |
state = STATE.PAN; | |
} | |
if ( state === STATE.ROTATE ) { | |
//state = STATE.ROTATE; | |
rotateStart.set( event.clientX, event.clientY ); | |
} | |
else if ( state === STATE.ZOOM ) { | |
//state = STATE.ZOOM; | |
zoomStart.set( event.clientX, event.clientY ); | |
} | |
else if ( state === STATE.PAN ) { | |
//state = STATE.PAN; | |
} | |
document.addEventListener( 'mousemove', onMouseMove, false ); | |
document.addEventListener( 'mouseup', onMouseUp, false ); | |
} | |
this.domElement.addEventListener( 'mousedown', onMouseDown, false ); | |
const onMouseMove = (event: MouseEvent) => { | |
if (this.enabled === false ) return; | |
event.preventDefault(); | |
if ( state === STATE.ROTATE ) { | |
rotateEnd.set( event.clientX, event.clientY ); | |
rotateDelta.subVectors( rotateEnd, rotateStart ); | |
this.rotateLeft( 2 * Math.PI * rotateDelta.x / PIXELS_PER_ROUND * this.userRotateSpeed ); | |
this.rotateUp( 2 * Math.PI * rotateDelta.y / PIXELS_PER_ROUND * this.userRotateSpeed ); | |
rotateStart.copy( rotateEnd ); | |
} | |
else if ( state === STATE.ZOOM ) { | |
zoomEnd.set( event.clientX, event.clientY ); | |
zoomDelta.subVectors( zoomEnd, zoomStart ); | |
if ( zoomDelta.y > 0 ) { | |
this.zoomIn(); | |
} | |
else { | |
this.zoomOut(); | |
} | |
zoomStart.copy( zoomEnd ); | |
} else if ( state === STATE.PAN ) { | |
const movementX: number = event['movementX'] || event['mozMovementX'] || event['webkitMovementX'] || 0; | |
const movementY = event['movementY'] || event['mozMovementY'] || event['webkitMovementY'] || 0; | |
this.pan( new THREE.Vector3( - movementX, movementY, 0 ) ); | |
} | |
} | |
const onMouseUp = (event: MouseEvent) => { | |
if (this.enabled === false) return; | |
if (this.userRotate === false) return; | |
document.removeEventListener( 'mousemove', onMouseMove, false ); | |
document.removeEventListener( 'mouseup', onMouseUp, false ); | |
state = STATE.NONE; | |
} | |
const onMouseWheel = (event: MouseWheelEvent) => { | |
if (this.enabled === false ) return; | |
if (this.userZoom === false ) return; | |
let delta = 0; | |
if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9 | |
delta = event.wheelDelta; | |
} | |
else if ( event.detail ) { // Firefox | |
delta = - event.detail; | |
} | |
if ( delta > 0 ) { | |
this.zoomOut(); | |
} | |
else { | |
this.zoomIn(); | |
} | |
} | |
this.domElement.addEventListener( 'mousewheel', onMouseWheel, false ); | |
this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox | |
const onKeyDown = (event: KeyboardEvent) => { | |
if (this.enabled === false) return; | |
if (this.userPan === false) return; | |
switch ( event.keyCode ) { | |
/*case scope.keys.UP: | |
scope.pan( new THREE.Vector3( 0, 1, 0 ) ); | |
break; | |
case scope.keys.BOTTOM: | |
scope.pan( new THREE.Vector3( 0, - 1, 0 ) ); | |
break; | |
case scope.keys.LEFT: | |
scope.pan( new THREE.Vector3( - 1, 0, 0 ) ); | |
break; | |
case scope.keys.RIGHT: | |
scope.pan( new THREE.Vector3( 1, 0, 0 ) ); | |
break; | |
*/ | |
case keys.ROTATE: | |
state = STATE.ROTATE; | |
break; | |
case keys.ZOOM: | |
state = STATE.ZOOM; | |
break; | |
case keys.PAN: | |
state = STATE.PAN; | |
break; | |
} | |
} | |
const onKeyUp = (event: KeyboardEvent) => { | |
switch (event.keyCode) { | |
case keys.ROTATE: | |
case keys.ZOOM: | |
case keys.PAN: | |
state = STATE.NONE; | |
break; | |
} | |
} | |
window.addEventListener( 'keydown', onKeyDown, false ); | |
window.addEventListener( 'keyup', onKeyUp, false ); | |
} | |
private getAutoRotationAngle(): number { | |
return 2 * Math.PI / 60 / 60 * this.autoRotateSpeed; | |
} | |
private getZoomScale(): number { | |
return Math.pow( 0.95, this.userZoomSpeed ); | |
} | |
rotateLeft(angle?: number): void { | |
if ( angle === undefined ) { | |
angle = this.getAutoRotationAngle(); | |
} | |
this.thetaDelta -= angle; | |
} | |
rotateRight(angle?: number): void { | |
if ( angle === undefined ) { | |
angle = this.getAutoRotationAngle(); | |
} | |
this.thetaDelta += angle; | |
} | |
rotateUp(angle?: number) { | |
if ( angle === undefined ) { | |
angle = this.getAutoRotationAngle(); | |
} | |
this.phiDelta -= angle; | |
} | |
rotateDown(angle?: number) { | |
if ( angle === undefined ) { | |
angle = this.getAutoRotationAngle(); | |
} | |
this.phiDelta += angle; | |
} | |
zoomIn(zoomScale?: number): void { | |
if ( zoomScale === undefined ) { | |
zoomScale = this.getZoomScale(); | |
} | |
this.scale /= zoomScale; | |
} | |
zoomOut(zoomScale?: number): void { | |
if (zoomScale === undefined) { | |
zoomScale = this.getZoomScale(); | |
} | |
this.scale *= zoomScale; | |
} | |
pan( distance: THREE.Vector3 ) { | |
distance.transformDirection( this.object.matrix ); | |
distance.multiplyScalar( this.userPanSpeed ); | |
this.object.position.add( distance ); | |
this.center.add( distance ); | |
} | |
update() { | |
const position = this.object.position; | |
const offset = position.clone().sub( this.center ); | |
// angle from z-axis around y-axis | |
let theta = Math.atan2( offset.x, offset.z ); | |
// angle from y-axis | |
let phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); | |
if (this.autoRotate) { | |
this.rotateLeft(this.getAutoRotationAngle()); | |
} | |
theta += this.thetaDelta; | |
phi += this.phiDelta; | |
// 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() * this.scale; | |
// restrict radius to be between desired limits | |
radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) ); | |
offset.x = radius * Math.sin( phi ) * Math.sin( theta ); | |
offset.y = radius * Math.cos( phi ); | |
offset.z = radius * Math.sin( phi ) * Math.cos( theta ); | |
position.copy( this.center ).add( offset ); | |
this.object.lookAt( this.center ); | |
this.thetaDelta = 0; | |
this.phiDelta = 0; | |
this.scale = 1; | |
if ( this.lastPosition.distanceTo( this.object.position ) > 0 ) { | |
// this.dispatchEvent( changeEvent ); | |
this.lastPosition.copy( this.object.position ); | |
} | |
} | |
} |
{ | |
"description": "three.js template", | |
"name": "copy-of-", | |
"version": "0.1.0", | |
"dependencies": { | |
"DomReady": "1.0.0", | |
"three.js": "0.82.0", | |
"stats.js": "0.16.0" | |
}, | |
"keywords": [ | |
"THREE", | |
"three.js" | |
] | |
} |
// |
// This THREEx helper makes it easy to handle window resize. | |
// It will update renderer and camera when window is resized. | |
// | |
// # Usage | |
// | |
// **Step 1**: Start updating renderer and camera | |
// | |
// ```var windowResize = THREEx.WindowResize(aRenderer, aCamera)``` | |
// | |
// **Step 2**: Start updating renderer and camera | |
// | |
// ```windowResize.stop()``` | |
// # Code | |
/** | |
* Update renderer and camera when the window is resized | |
* | |
* @param {Object} renderer the renderer to update | |
* @param {Object} Camera the camera to update | |
*/ | |
export default function WindowResize(renderer, camera){ | |
var callback = function(){ | |
// notify the renderer of the size change | |
renderer.setSize( window.innerWidth, window.innerHeight ); | |
// update the camera | |
camera.aspect = window.innerWidth / window.innerHeight; | |
camera.updateProjectionMatrix(); | |
} | |
// bind the resize event | |
window.addEventListener('resize', callback, false); | |
// return .stop() the function to stop watching window resize | |
return { | |
/** | |
* Stop watching window resize | |
*/ | |
stop : function(){ | |
window.removeEventListener('resize', callback); | |
} | |
}; | |
} |