浏览代码

fix error due to imprecise clamping at gimbal lock

TheophileMot 6 年之前
父节点
当前提交
cc30de3472
共有 2 个文件被更改,包括 207 次插入34 次删除
  1. 172 0
      examples/gimbal_lock.html
  2. 35 34
      src/math/Euler.js

+ 172 - 0
examples/gimbal_lock.html

@@ -0,0 +1,172 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - gimbal lock</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 {
+				color: #000;
+				font-family: Monospace;
+				font-size: 13px;
+				text-align: center;
+				font-weight: bold;
+
+				background-color: #fff;
+				margin: 0px;
+				overflow: hidden;
+			}
+
+			#info {
+				color: #000;
+				position: absolute;
+				top: 0px;
+				width: 100%;
+				padding: 5px;
+			}
+
+			a {
+				color: red;
+			}
+		</style>
+	</head>
+
+	<body>
+		<div id="container"></div>
+		<div id="info">
+			<a href="http://threejs.org" target="_blank">three.js</a> - gimbal lock
+			example
+			<p>
+				The object slowly rotates back and forth; one of its Euler angle
+				components is close to pi/2.
+			</p>
+			<p>
+				Every frame, with probability 0.5, the orientation is transformed into a
+				quaternion then back into Euler angles.
+			</p>
+			<p>
+				This causes an observable glitch when the calculation is imprecise due
+				to gimbal lock.
+			</p>
+		</div>
+
+		<script src="../build/three.js"></script>
+
+		<script src="js/controls/OrbitControls.js"></script>
+
+		<script src="js/libs/stats.min.js"></script>
+
+		<script>
+			var stats;
+
+			var camera, controls, scene, renderer, mesh;
+
+			init();
+			render(); // remove when using next line for animation loop (requestAnimationFrame)
+			animate();
+
+			function init() {
+				scene = new THREE.Scene();
+				scene.fog = new THREE.FogExp2(0xcccccc, 0.002);
+
+				renderer = new THREE.WebGLRenderer();
+				renderer.setClearColor(scene.fog.color);
+				renderer.setPixelRatio(window.devicePixelRatio);
+				renderer.setSize(window.innerWidth, window.innerHeight);
+
+				var container = document.getElementById('container');
+				container.appendChild(renderer.domElement);
+
+				camera = new THREE.PerspectiveCamera(
+					60,
+					window.innerWidth / window.innerHeight,
+					1,
+					1000
+				);
+				camera.position.copy(new THREE.Vector3(-10, -22, 17));
+				camera.rotation.copy(new THREE.Euler(0.9, -0.3, 0.4, 'XYZ'));
+
+				controls = new THREE.OrbitControls(camera, renderer.domElement);
+				controls.addEventListener('change', render); // remove when using animation loop
+				// enable animation loop when using damping or autorotation
+				//controls.enableDamping = true;
+				//controls.dampingFactor = 0.25;
+				controls.enableZoom = false;
+
+				// world
+
+				var geometry = new THREE.CylinderGeometry(0, 10, 30, 16, 10);
+				var material = new THREE.MeshPhongMaterial({
+					color: 0xffffff,
+					shading: THREE.FlatShading,
+				});
+
+				mesh = new THREE.Mesh(geometry, material);
+				scene.add(mesh);
+
+				// lights
+
+				light = new THREE.DirectionalLight(0xffffff);
+				light.position.set(1, 1, 1);
+				scene.add(light);
+
+				light = new THREE.DirectionalLight(0x002288);
+				light.position.set(-1, -1, -1);
+				scene.add(light);
+
+				light = new THREE.AmbientLight(0x222222);
+				scene.add(light);
+
+				//
+
+				stats = new Stats();
+				container.appendChild(stats.dom);
+
+				//
+
+				window.addEventListener('resize', onWindowResize, false);
+			}
+
+			function onWindowResize() {
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize(window.innerWidth, window.innerHeight);
+			}
+
+			function animate() {
+				requestAnimationFrame(animate);
+
+				controls.update(); // required if controls.enableDamping = true, or if controls.autoRotate = true
+
+				stats.update();
+
+				render();
+			}
+
+			var startTime = Date.now();
+
+			function render() {
+				var timer = (Date.now() - startTime) * 0.00025;
+				var theta = Math.PI / 2 + Math.sin(timer) * 0.1;
+				// var theta = 1.575;
+
+				var euler = new THREE.Euler(0.7, theta, 0.5, 'ZYX');
+
+				if (Math.random() < 0.5) {
+					let quat = new THREE.Quaternion().setFromEuler(euler);
+					let matrix = new THREE.Matrix4().makeRotationFromQuaternion(quat);
+					var newEuler = new THREE.Euler().setFromRotationMatrix(matrix, 'ZYX');
+
+					euler = newEuler;
+				}
+				mesh.setRotationFromEuler(euler);
+
+				renderer.render(scene, camera);
+			}
+		</script>
+	</body>
+</html>

+ 35 - 34
src/math/Euler.js

@@ -23,9 +23,7 @@ Euler.RotationOrders = [ 'XYZ', 'YZX', 'ZXY', 'XZY', 'YXZ', 'ZYX' ];
 Euler.DefaultOrder = 'XYZ';
 Euler.DefaultOrder = 'XYZ';
 
 
 Object.defineProperties( Euler.prototype, {
 Object.defineProperties( Euler.prototype, {
-
 	x: {
 	x: {
-
 		get: function () {
 		get: function () {
 
 
 			return this._x;
 			return this._x;
@@ -37,12 +35,10 @@ Object.defineProperties( Euler.prototype, {
 			this._x = value;
 			this._x = value;
 			this._onChangeCallback();
 			this._onChangeCallback();
 
 
-		}
-
+		},
 	},
 	},
 
 
 	y: {
 	y: {
-
 		get: function () {
 		get: function () {
 
 
 			return this._y;
 			return this._y;
@@ -54,12 +50,10 @@ Object.defineProperties( Euler.prototype, {
 			this._y = value;
 			this._y = value;
 			this._onChangeCallback();
 			this._onChangeCallback();
 
 
-		}
-
+		},
 	},
 	},
 
 
 	z: {
 	z: {
-
 		get: function () {
 		get: function () {
 
 
 			return this._z;
 			return this._z;
@@ -71,12 +65,10 @@ Object.defineProperties( Euler.prototype, {
 			this._z = value;
 			this._z = value;
 			this._onChangeCallback();
 			this._onChangeCallback();
 
 
-		}
-
+		},
 	},
 	},
 
 
 	order: {
 	order: {
-
 		get: function () {
 		get: function () {
 
 
 			return this._order;
 			return this._order;
@@ -88,14 +80,11 @@ Object.defineProperties( Euler.prototype, {
 			this._order = value;
 			this._order = value;
 			this._onChangeCallback();
 			this._onChangeCallback();
 
 
-		}
-
-	}
-
+		},
+	},
 } );
 } );
 
 
 Object.assign( Euler.prototype, {
 Object.assign( Euler.prototype, {
-
 	isEuler: true,
 	isEuler: true,
 
 
 	set: function ( x, y, z, order ) {
 	set: function ( x, y, z, order ) {
@@ -137,9 +126,15 @@ Object.assign( Euler.prototype, {
 		// assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
 		// assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
 
 
 		var te = m.elements;
 		var te = m.elements;
-		var m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ];
-		var m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ];
-		var m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ];
+		var m11 = te[ 0 ],
+			m12 = te[ 4 ],
+			m13 = te[ 8 ];
+		var m21 = te[ 1 ],
+			m22 = te[ 5 ],
+			m23 = te[ 9 ];
+		var m31 = te[ 2 ],
+			m32 = te[ 6 ],
+			m33 = te[ 10 ];
 
 
 		order = order || this._order;
 		order = order || this._order;
 
 
@@ -147,7 +142,7 @@ Object.assign( Euler.prototype, {
 
 
 			this._y = Math.asin( clamp( m13, - 1, 1 ) );
 			this._y = Math.asin( clamp( m13, - 1, 1 ) );
 
 
-			if ( Math.abs( m13 ) < 0.99999 ) {
+			if ( Math.abs( m13 ) < 1 ) {
 
 
 				this._x = Math.atan2( - m23, m33 );
 				this._x = Math.atan2( - m23, m33 );
 				this._z = Math.atan2( - m12, m11 );
 				this._z = Math.atan2( - m12, m11 );
@@ -163,7 +158,7 @@ Object.assign( Euler.prototype, {
 
 
 			this._x = Math.asin( - clamp( m23, - 1, 1 ) );
 			this._x = Math.asin( - clamp( m23, - 1, 1 ) );
 
 
-			if ( Math.abs( m23 ) < 0.99999 ) {
+			if ( Math.abs( m23 ) < 1 ) {
 
 
 				this._y = Math.atan2( m13, m33 );
 				this._y = Math.atan2( m13, m33 );
 				this._z = Math.atan2( m21, m22 );
 				this._z = Math.atan2( m21, m22 );
@@ -179,7 +174,7 @@ Object.assign( Euler.prototype, {
 
 
 			this._x = Math.asin( clamp( m32, - 1, 1 ) );
 			this._x = Math.asin( clamp( m32, - 1, 1 ) );
 
 
-			if ( Math.abs( m32 ) < 0.99999 ) {
+			if ( Math.abs( m32 ) < 1 ) {
 
 
 				this._y = Math.atan2( - m31, m33 );
 				this._y = Math.atan2( - m31, m33 );
 				this._z = Math.atan2( - m12, m22 );
 				this._z = Math.atan2( - m12, m22 );
@@ -195,7 +190,7 @@ Object.assign( Euler.prototype, {
 
 
 			this._y = Math.asin( - clamp( m31, - 1, 1 ) );
 			this._y = Math.asin( - clamp( m31, - 1, 1 ) );
 
 
-			if ( Math.abs( m31 ) < 0.99999 ) {
+			if ( Math.abs( m31 ) < 1 ) {
 
 
 				this._x = Math.atan2( m32, m33 );
 				this._x = Math.atan2( m32, m33 );
 				this._z = Math.atan2( m21, m11 );
 				this._z = Math.atan2( m21, m11 );
@@ -211,7 +206,7 @@ Object.assign( Euler.prototype, {
 
 
 			this._z = Math.asin( clamp( m21, - 1, 1 ) );
 			this._z = Math.asin( clamp( m21, - 1, 1 ) );
 
 
-			if ( Math.abs( m21 ) < 0.99999 ) {
+			if ( Math.abs( m21 ) < 1 ) {
 
 
 				this._x = Math.atan2( - m23, m22 );
 				this._x = Math.atan2( - m23, m22 );
 				this._y = Math.atan2( - m31, m11 );
 				this._y = Math.atan2( - m31, m11 );
@@ -227,7 +222,7 @@ Object.assign( Euler.prototype, {
 
 
 			this._z = Math.asin( - clamp( m12, - 1, 1 ) );
 			this._z = Math.asin( - clamp( m12, - 1, 1 ) );
 
 
-			if ( Math.abs( m12 ) < 0.99999 ) {
+			if ( Math.abs( m12 ) < 1 ) {
 
 
 				this._x = Math.atan2( m32, m22 );
 				this._x = Math.atan2( m32, m22 );
 				this._y = Math.atan2( m13, m11 );
 				this._y = Math.atan2( m13, m11 );
@@ -241,7 +236,10 @@ Object.assign( Euler.prototype, {
 
 
 		} else {
 		} else {
 
 
-			console.warn( 'THREE.Euler: .setFromRotationMatrix() given unsupported order: ' + order );
+			console.warn(
+				'THREE.Euler: .setFromRotationMatrix() given unsupported order: ' +
+					order
+			);
 
 
 		}
 		}
 
 
@@ -253,7 +251,7 @@ Object.assign( Euler.prototype, {
 
 
 	},
 	},
 
 
-	setFromQuaternion: function () {
+	setFromQuaternion: ( function () {
 
 
 		var matrix = new Matrix4();
 		var matrix = new Matrix4();
 
 
@@ -265,7 +263,7 @@ Object.assign( Euler.prototype, {
 
 
 		};
 		};
 
 
-	}(),
+	} )(),
 
 
 	setFromVector3: function ( v, order ) {
 	setFromVector3: function ( v, order ) {
 
 
@@ -273,7 +271,7 @@ Object.assign( Euler.prototype, {
 
 
 	},
 	},
 
 
-	reorder: function () {
+	reorder: ( function () {
 
 
 		// WARNING: this discards revolution information -bhouston
 		// WARNING: this discards revolution information -bhouston
 
 
@@ -287,11 +285,16 @@ Object.assign( Euler.prototype, {
 
 
 		};
 		};
 
 
-	}(),
+	} )(),
 
 
 	equals: function ( euler ) {
 	equals: function ( euler ) {
 
 
-		return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order );
+		return (
+			euler._x === this._x &&
+			euler._y === this._y &&
+			euler._z === this._z &&
+			euler._order === this._order
+		);
 
 
 	},
 	},
 
 
@@ -344,9 +347,7 @@ Object.assign( Euler.prototype, {
 
 
 	},
 	},
 
 
-	_onChangeCallback: function () {}
-
+	_onChangeCallback: function () {},
 } );
 } );
 
 
-
 export { Euler };
 export { Euler };