浏览代码

Quaternion: Added .transformTo()

Mugen87 7 年之前
父节点
当前提交
b8d290ef86

+ 9 - 0
docs/api/math/Quaternion.html

@@ -169,6 +169,15 @@
 		<h3>[method:Quaternion premultiply]( [param:Quaternion q] )</h3>
 		<p>Pre-multiplies this quaternion by [page:Quaternion q].</p>
 
+		<h3>[method:Quaternion transformTo]( [param:Quaternion q], [param:Float step] )</h3>
+		<p>
+			[page:Quaternion q] - The target quaternion.<br />
+			[page:float step] - The angular step in radians.<br /><br />
+
+			Transforms this quaternion by a given angular step to the defined quaternion *q*.
+			The method ensures that the final quaternion will not overshoot *q*.
+		</p>
+
 		<h3>[method:Quaternion slerp]( [param:Quaternion qb], [param:float t] )</h3>
 		<p>
 			[page:Quaternion qb] - The other quaternion rotation<br />

+ 1 - 0
examples/files.js

@@ -183,6 +183,7 @@ var files = {
 		"webgl_materials_video",
 		"webgl_materials_video_webcam",
 		"webgl_materials_wireframe",
+		"webgl_math_orientation_transform",
 		"webgl_mirror",
 		"webgl_mirror_nodes",
 		"webgl_modifier_simplifier",

+ 156 - 0
examples/webgl_math_orientation_transform.html

@@ -0,0 +1,156 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js transform orientation</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				background:#777;
+				padding:0;
+				margin:0;
+				font-weight: bold;
+				overflow:hidden;
+			}
+
+			#info {
+				position: absolute;
+				top: 0px;
+				width: 100%;
+				color: #ffffff;
+				padding: 5px;
+				font-family:Monospace;
+				font-size:13px;
+				text-align:center;
+			}
+
+			a {
+				color: #ffffff;
+			}
+		</style>
+
+		<script src="../build/three.js"></script>
+		<script src="js/controls/OrbitControls.js"></script>
+		<script src="js/Detector.js"></script>
+
+	</head>
+	<body>
+
+		<div id="container"></div>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener noreferrer">three.js</a> - gradually transform an orientation to a target orientation
+		</div>
+
+		<script>
+
+		if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
+
+		var camera, scene, renderer, mesh, target;
+
+		var spherical = new THREE.Spherical();
+		var rotationMatrix = new THREE.Matrix4();
+		var targetRotation = new THREE.Quaternion();
+		var clock = new THREE.Clock();
+		var speed = 2;
+
+		init();
+		animate();
+
+		function init() {
+
+			camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.01, 10 );
+			camera.position.z = 5;
+
+			scene = new THREE.Scene();
+
+			var geometry = new THREE.ConeBufferGeometry( 0.2, 1, 8 );
+			geometry.rotateX( Math.PI * 0.5 );
+			var material = new THREE.MeshNormalMaterial();
+
+			mesh = new THREE.Mesh( geometry, material );
+			scene.add( mesh );
+
+			//
+
+			var targetGeometry = new THREE.SphereBufferGeometry( 0.05 );
+			var targetMaterial = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
+			target = new THREE.Mesh( targetGeometry, targetMaterial );
+			scene.add( target );
+
+			//
+
+			var sphereGeometry = new THREE.SphereBufferGeometry( 2, 32, 32 );
+			var sphereMaterial = new THREE.MeshBasicMaterial( { color: 0xcccccc, wireframe: true, transparent: true, opacity: 0.3 } );
+			var sphere = new THREE.Mesh( sphereGeometry, sphereMaterial );
+			scene.add( sphere );
+
+			//
+
+			renderer = new THREE.WebGLRenderer( { antialias: true } );
+			renderer.setPixelRatio( window.devicePixelRatio );
+			renderer.setSize( window.innerWidth, window.innerHeight );
+			document.body.appendChild( renderer.domElement );
+
+			//
+
+			window.addEventListener( 'resize', onResize, false );
+
+			//
+
+			var controls = new THREE.OrbitControls( camera, renderer.domElement );
+
+			//
+
+			generateTarget();
+
+		}
+
+		function onResize() {
+
+			camera.aspect = window.innerWidth / window.innerHeight;
+			camera.updateProjectionMatrix();
+
+			renderer.setSize( window.innerWidth, window.innerHeight );
+
+		}
+
+		function animate() {
+
+			requestAnimationFrame( animate );
+
+			var delta = clock.getDelta();
+
+			if ( ! mesh.quaternion.equals( targetRotation ) ) {
+
+				var step = speed * delta;
+				mesh.quaternion.transformTo( targetRotation, step );
+
+			}
+
+			renderer.render( scene, camera );
+
+		}
+
+		function generateTarget() {
+
+			// generate a random points on a sphere
+
+			spherical.theta = Math.random() * Math.PI * 2;
+			spherical.phi = Math.acos( ( 2 * Math.random() ) - 1 );
+			spherical.radius = 2;
+
+			target.position.setFromSpherical( spherical );
+
+			// compute target rotation
+
+			rotationMatrix.lookAt( target.position, mesh.position, mesh.up );
+			targetRotation.setFromRotationMatrix( rotationMatrix );
+
+			setTimeout( generateTarget, 2000 );
+
+		}
+
+	</script>
+
+</body>
+</html>

+ 14 - 0
src/math/Quaternion.js

@@ -400,6 +400,20 @@ Object.assign( Quaternion.prototype, {
 
 	},
 
+	transformTo: function	( q, step ) {
+
+		var angle = this.angleTo( q );
+
+		if ( angle === 0 ) return this;
+
+		var t = Math.min( 1, step / angle );
+
+		this.slerp( q, t );
+
+		return this;
+
+	},
+
 	inverse: function () {
 
 		// quaternion is assumed to have unit length

+ 20 - 0
test/unit/src/math/Quaternion.tests.js

@@ -403,6 +403,26 @@ export default QUnit.module( 'Maths', () => {
 
 		} );
 
+		QUnit.test( "transformTo", ( assert ) => {
+
+			var a = new Quaternion();
+			var b = new Quaternion().setFromEuler( new Euler( 0, Math.PI, 0 ) );
+			var c = new Quaternion();
+
+			var halfPI = Math.PI * 0.5;
+
+			a.transformTo( b, 0 );
+			assert.ok( a.equals( a ) === true, "Passed!" );
+
+			a.transformTo( b, Math.PI * 2 ); // test overshoot
+			assert.ok( a.equals( b ) === true, "Passed!" );
+
+			a.set( 0, 0, 0, 1 );
+			a.transformTo( b, halfPI );
+			assert.ok( a.angleTo( c ) - halfPI <= eps, "Passed!" );
+
+		} );
+
 		QUnit.test( "inverse/conjugate", ( assert ) => {
 
 			var a = new Quaternion( x, y, z, w );