three.js入门4——2014.5.27让场景动起来

时间:2021-12-11 05:33:56

早前写过一个正方体自己旋转的例子,后想了下让物体随鼠标的方向滚动,让场景动起来有俩种方法

第一种方法是让物体在坐标系里面移动,摄像机不动。第二种方法是让摄像机在坐标系里面移动,物体不动。

这样场景就能够动起来了。摄像机可以理解我们自己的眼睛。但我们通常选择第二种,因为第一种方式对计算机资源的消耗是不可忽视的。

闲话少说,上码。

<!DOCTYPE>

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script src="../build/three.min.js"></script>
<script src="../Demojs/TrackballControls.js"></script>
</head>
<body>
<script type="text/javascript">
//摄像机,场景,渲染器全局变量
var camera, scene, renderer;

//几何体,材质,网格全局变量
var geometry, material, mesh;


//
var controls;
init();
animate();

function init() {//初始化,包括对相机,场景,渲染器,网格的初始化(对网格初始化,对其施加材质等)

//下面设置一个透视相机 近剪裁面为1,远剪裁面为10000;垂直视域为75度角
//相机的可见范围是个四凌锥(削平了尖顶的金子塔)。前、后裁剪面是指相机能看见的最近距离和最远距离。

camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 1, 10000);
//设置相机在z轴位置为1000
camera.position.z = 1000;
//建立场景
scene = new THREE.Scene();
//建立一个立方体,长宽高依次为200,200,200,
geometry = new THREE.CubeGeometry(200, 200, 200);
//建立一个红色的材质,要注意,Three.js提供了很多的材质,其中这个MeshBasicMaterial材质的颜色不会受到漫反射光的染色。
material = new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true });

//建立一个200,200,200的正方体网格,并施加为红色的基础材质
mesh = new THREE.Mesh(geometry, material);

//将网格加入场景,对象加入到场景后,如果不设定位置的话,默认的是在场景中心哦
scene.add(mesh);


//建立渲染器,这里Three.js提供了多种渲染器,WebGLRenderer等,具体差别还在学习中
renderer = new THREE.CanvasRenderer();
//设计渲染的范围
renderer.setSize(window.innerWidth, window.innerHeight);
//将渲染器加入到网页中,其实这里的render.dommElement是一个Canvas标签;
document.body.appendChild(renderer.domElement);

controls = new THREE.TrackballControls(camera);
}
//这是一个动画循环
function animate() {

// note: three.js includes requestAnimationFrame shim

controls.update();
requestAnimationFrame(animate);//基于脚本的动画的计时控制
//每次渲染让网格旋转一定角度
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.02;
//施加渲染
renderer.render(scene, camera);

}
</script>
</body>
</html>
这里引用了除three.min.js外的另一个js文件,文件是由three.js Demo里提供的。

THREE.TrackballControls = function ( object, domElement ) {

THREE.EventDispatcher.call( this );

var _this = this;
var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM: 4, TOUCH_PAN: 5 };

this.object = object;
this.domElement = ( domElement !== undefined ) ? domElement : document;

// API

this.enabled = true;

this.screen = { width: 0, height: 0, offsetLeft: 0, offsetTop: 0 };
this.radius = ( this.screen.width + this.screen.height ) / 4;

this.rotateSpeed = 1.0;
this.zoomSpeed = 1.2;
this.panSpeed = 0.3;

this.noRotate = false;
this.noZoom = false;
this.noPan = false;

this.staticMoving = false;
this.dynamicDampingFactor = 0.2;

this.minDistance = 0;
this.maxDistance = Infinity;

this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ];

// internals

this.target = new THREE.Vector3();

var lastPosition = new THREE.Vector3();

var _state = STATE.NONE,
_prevState = STATE.NONE,

_eye = new THREE.Vector3(),

_rotateStart = new THREE.Vector3(),
_rotateEnd = new THREE.Vector3(),

_zoomStart = new THREE.Vector2(),
_zoomEnd = new THREE.Vector2(),

_touchZoomDistanceStart = 0,
_touchZoomDistanceEnd = 0,

_panStart = new THREE.Vector2(),
_panEnd = new THREE.Vector2();

// for reset

this.target0 = this.target.clone();
this.position0 = this.object.position.clone();
this.up0 = this.object.up.clone();

// events

var changeEvent = { type: 'change' };


// methods

this.handleResize = function () {

this.screen.width = window.innerWidth;
this.screen.height = window.innerHeight;

this.screen.offsetLeft = 0;
this.screen.offsetTop = 0;

this.radius = ( this.screen.width + this.screen.height ) / 4;

};

this.handleEvent = function ( event ) {

if ( typeof this[ event.type ] == 'function' ) {

this[ event.type ]( event );

}

};

this.getMouseOnScreen = function ( clientX, clientY ) {

return new THREE.Vector2(
( clientX - _this.screen.offsetLeft ) / _this.radius * 0.5,
( clientY - _this.screen.offsetTop ) / _this.radius * 0.5
);

};

this.getMouseProjectionOnBall = function ( clientX, clientY ) {

var mouseOnBall = new THREE.Vector3(
( clientX - _this.screen.width * 0.5 - _this.screen.offsetLeft ) / _this.radius,
( _this.screen.height * 0.5 + _this.screen.offsetTop - clientY ) / _this.radius,
0.0
);

var length = mouseOnBall.length();

if ( length > 1.0 ) {

mouseOnBall.normalize();

} else {

mouseOnBall.z = Math.sqrt( 1.0 - length * length );

}

_eye.copy( _this.object.position ).sub( _this.target );

var projection = _this.object.up.clone().setLength( mouseOnBall.y );
projection.add( _this.object.up.clone().cross( _eye ).setLength( mouseOnBall.x ) );
projection.add( _eye.setLength( mouseOnBall.z ) );

return projection;

};

this.rotateCamera = function () {

var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() );

if ( angle ) {

var axis = ( new THREE.Vector3() ).crossVectors( _rotateStart, _rotateEnd ).normalize(),
quaternion = new THREE.Quaternion();

angle *= _this.rotateSpeed;

quaternion.setFromAxisAngle( axis, -angle );

_eye.applyQuaternion( quaternion );
_this.object.up.applyQuaternion( quaternion );

_rotateEnd.applyQuaternion( quaternion );

if ( _this.staticMoving ) {

_rotateStart.copy( _rotateEnd );

} else {

quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) );
_rotateStart.applyQuaternion( quaternion );

}

}

};

this.zoomCamera = function () {

if ( _state === STATE.TOUCH_ZOOM ) {

var factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
_touchZoomDistanceStart = _touchZoomDistanceEnd;
_eye.multiplyScalar( factor );

} else {

var factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed;

if ( factor !== 1.0 && factor > 0.0 ) {

_eye.multiplyScalar( factor );

if ( _this.staticMoving ) {

_zoomStart.copy( _zoomEnd );

} else {

_zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;

}

}

}

};

this.panCamera = function () {

var mouseChange = _panEnd.clone().sub( _panStart );

if ( mouseChange.lengthSq() ) {

mouseChange.multiplyScalar( _eye.length() * _this.panSpeed );

var pan = _eye.clone().cross( _this.object.up ).setLength( mouseChange.x );
pan.add( _this.object.up.clone().setLength( mouseChange.y ) );

_this.object.position.add( pan );
_this.target.add( pan );

if ( _this.staticMoving ) {

_panStart = _panEnd;

} else {

_panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) );

}

}

};

this.checkDistances = function () {

if ( !_this.noZoom || !_this.noPan ) {

if ( _this.object.position.lengthSq() > _this.maxDistance * _this.maxDistance ) {

_this.object.position.setLength( _this.maxDistance );

}

if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) {

_this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) );

}

}

};

this.update = function () {

_eye.subVectors( _this.object.position, _this.target );

if ( !_this.noRotate ) {

_this.rotateCamera();

}

if ( !_this.noZoom ) {

_this.zoomCamera();

}

if ( !_this.noPan ) {

_this.panCamera();

}

_this.object.position.addVectors( _this.target, _eye );

_this.checkDistances();

_this.object.lookAt( _this.target );

if ( lastPosition.distanceToSquared( _this.object.position ) > 0 ) {

_this.dispatchEvent( changeEvent );

lastPosition.copy( _this.object.position );

}

};

this.reset = function () {

_state = STATE.NONE;
_prevState = STATE.NONE;

_this.target.copy( _this.target0 );
_this.object.position.copy( _this.position0 );
_this.object.up.copy( _this.up0 );

_eye.subVectors( _this.object.position, _this.target );

_this.object.lookAt( _this.target );

_this.dispatchEvent( changeEvent );

lastPosition.copy( _this.object.position );

};

// listeners

function keydown( event ) {

if ( _this.enabled === false ) return;

window.removeEventListener( 'keydown', keydown );

_prevState = _state;

if ( _state !== STATE.NONE ) {

return;

} else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && !_this.noRotate ) {

_state = STATE.ROTATE;

} else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && !_this.noZoom ) {

_state = STATE.ZOOM;

} else if ( event.keyCode === _this.keys[ STATE.PAN ] && !_this.noPan ) {

_state = STATE.PAN;

}

}

function keyup( event ) {

if ( _this.enabled === false ) return;

_state = _prevState;

window.addEventListener( 'keydown', keydown, false );

}

function mousedown( event ) {

if ( _this.enabled === false ) return;

event.preventDefault();
event.stopPropagation();

if ( _state === STATE.NONE ) {

_state = event.button;

}

if ( _state === STATE.ROTATE && !_this.noRotate ) {

_rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.clientX, event.clientY );

} else if ( _state === STATE.ZOOM && !_this.noZoom ) {

_zoomStart = _zoomEnd = _this.getMouseOnScreen( event.clientX, event.clientY );

} else if ( _state === STATE.PAN && !_this.noPan ) {

_panStart = _panEnd = _this.getMouseOnScreen( event.clientX, event.clientY );

}

document.addEventListener( 'mousemove', mousemove, false );
document.addEventListener( 'mouseup', mouseup, false );

}

function mousemove( event ) {

if ( _this.enabled === false ) return;

event.preventDefault();
event.stopPropagation();

if ( _state === STATE.ROTATE && !_this.noRotate ) {

_rotateEnd = _this.getMouseProjectionOnBall( event.clientX, event.clientY );

} else if ( _state === STATE.ZOOM && !_this.noZoom ) {

_zoomEnd = _this.getMouseOnScreen( event.clientX, event.clientY );

} else if ( _state === STATE.PAN && !_this.noPan ) {

_panEnd = _this.getMouseOnScreen( event.clientX, event.clientY );

}

}

function mouseup( event ) {

if ( _this.enabled === false ) return;

event.preventDefault();
event.stopPropagation();

_state = STATE.NONE;

document.removeEventListener( 'mousemove', mousemove );
document.removeEventListener( 'mouseup', mouseup );

}

function mousewheel( event ) {

if ( _this.enabled === false ) return;

event.preventDefault();
event.stopPropagation();

var delta = 0;

if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9

delta = event.wheelDelta / 40;

} else if ( event.detail ) { // Firefox

delta = - event.detail / 3;

}

_zoomStart.y += ( 1 / delta ) * 0.05;

}

function touchstart( event ) {

if ( _this.enabled === false ) return;

switch ( event.touches.length ) {

case 1:
_state = STATE.TOUCH_ROTATE;
_rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
break;

case 2:
_state = STATE.TOUCH_ZOOM;
var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
_touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
break;

case 3:
_state = STATE.TOUCH_PAN;
_panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
break;

default:
_state = STATE.NONE;

}

}

function touchmove( event ) {

if ( _this.enabled === false ) return;

event.preventDefault();
event.stopPropagation();

switch ( event.touches.length ) {

case 1:
_rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
break;

case 2:
var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
_touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy )
break;

case 3:
_panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
break;

default:
_state = STATE.NONE;

}

}

function touchend( event ) {

if ( _this.enabled === false ) return;

switch ( event.touches.length ) {

case 1:
_rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
break;

case 2:
_touchZoomDistanceStart = _touchZoomDistanceEnd = 0;
break;

case 3:
_panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
break;

}

_state = STATE.NONE;

}

this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );

this.domElement.addEventListener( 'mousedown', mousedown, false );

this.domElement.addEventListener( 'mousewheel', mousewheel, false );
this.domElement.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox

this.domElement.addEventListener( 'touchstart', touchstart, false );
this.domElement.addEventListener( 'touchend', touchend, false );
this.domElement.addEventListener( 'touchmove', touchmove, false );

window.addEventListener( 'keydown', keydown, false );
window.addEventListener( 'keyup', keyup, false );

this.handleResize();

};


事实上通过改变摄像机来改变场景是非常简单的,关键代码也就是 controls = new THREE.TrackballControls(camera);以及controls.update();