Explorar el Código

FBXLoader: Fix rotation discontinuities. (#27057)

* :bug: (FBXLoader) Fix interpolation rotation discontinuities

* :recycle: (FBXLoader) lint

* :recycle: (FBXLoader) remove unused function

* Update FBXLoader.js

Clean up.

* :recycle: (FBXLoader) remove initialRotation

* :recycle: (FBXLoader) remove initialRotation unused variable

---------

Co-authored-by: Michael Herzog <[email protected]>
Soline Hayes hace 1 año
padre
commit
a1906ffdf7
Se han modificado 1 ficheros con 105 adiciones y 52 borrados
  1. 105 52
      examples/jsm/loaders/FBXLoader.js

+ 105 - 52
examples/jsm/loaders/FBXLoader.js

@@ -2731,13 +2731,11 @@ class AnimationParser {
 		const tracks = [];
 
 		let initialPosition = new Vector3();
-		let initialRotation = new Quaternion();
 		let initialScale = new Vector3();
 
-		if ( rawTracks.transform ) rawTracks.transform.decompose( initialPosition, initialRotation, initialScale );
+		if ( rawTracks.transform ) rawTracks.transform.decompose( initialPosition, new Quaternion(), initialScale );
 
 		initialPosition = initialPosition.toArray();
-		initialRotation = new Euler().setFromQuaternion( initialRotation, rawTracks.eulerOrder ).toArray();
 		initialScale = initialScale.toArray();
 
 		if ( rawTracks.T !== undefined && Object.keys( rawTracks.T.curves ).length > 0 ) {
@@ -2749,7 +2747,7 @@ class AnimationParser {
 
 		if ( rawTracks.R !== undefined && Object.keys( rawTracks.R.curves ).length > 0 ) {
 
-			const rotationTrack = this.generateRotationTrack( rawTracks.modelName, rawTracks.R.curves, initialRotation, rawTracks.preRotation, rawTracks.postRotation, rawTracks.eulerOrder );
+			const rotationTrack = this.generateRotationTrack( rawTracks.modelName, rawTracks.R.curves, rawTracks.preRotation, rawTracks.postRotation, rawTracks.eulerOrder );
 			if ( rotationTrack !== undefined ) tracks.push( rotationTrack );
 
 		}
@@ -2781,32 +2779,20 @@ class AnimationParser {
 
 	}
 
-	generateRotationTrack( modelName, curves, initialValue, preRotation, postRotation, eulerOrder ) {
+	generateRotationTrack( modelName, curves, preRotation, postRotation, eulerOrder ) {
 
-		if ( curves.x !== undefined ) {
+		let times;
+		let values;
 
-			this.interpolateRotations( curves.x );
-			curves.x.values = curves.x.values.map( MathUtils.degToRad );
+		if ( curves.x !== undefined && curves.y !== undefined && curves.z !== undefined ) {
 
-		}
-
-		if ( curves.y !== undefined ) {
-
-			this.interpolateRotations( curves.y );
-			curves.y.values = curves.y.values.map( MathUtils.degToRad );
-
-		}
-
-		if ( curves.z !== undefined ) {
+			const result = this.interpolateRotations( curves.x, curves.y, curves.z, eulerOrder );
 
-			this.interpolateRotations( curves.z );
-			curves.z.values = curves.z.values.map( MathUtils.degToRad );
+			times = result[ 0 ];
+			values = result[ 1 ];
 
 		}
 
-		const times = this.getTimesForAllAxes( curves );
-		const values = this.getKeyframeTrackValues( times, curves, initialValue );
-
 		if ( preRotation !== undefined ) {
 
 			preRotation = preRotation.map( MathUtils.degToRad );
@@ -2832,15 +2818,32 @@ class AnimationParser {
 
 		const quaternionValues = [];
 
+		if ( ! values || ! times ) return new QuaternionKeyframeTrack( modelName + '.quaternion', [], [] );
+
 		for ( let i = 0; i < values.length; i += 3 ) {
 
 			euler.set( values[ i ], values[ i + 1 ], values[ i + 2 ], eulerOrder );
-
 			quaternion.setFromEuler( euler );
 
 			if ( preRotation !== undefined ) quaternion.premultiply( preRotation );
 			if ( postRotation !== undefined ) quaternion.multiply( postRotation );
 
+			// Check unroll
+			if ( i > 2 ) {
+
+				const prevQuat = new Quaternion().fromArray(
+					quaternionValues,
+					( ( i - 3 ) / 3 ) * 4
+				);
+
+				if ( prevQuat.dot( quaternion ) < 0 ) {
+
+					quaternion.set( - quaternion.x, - quaternion.y, - quaternion.z, - quaternion.w );
+
+				}
+
+			}
+
 			quaternion.toArray( quaternionValues, ( i / 3 ) * 4 );
 
 		}
@@ -2971,47 +2974,103 @@ class AnimationParser {
 	// Rotations are defined as Euler angles which can have values  of any size
 	// These will be converted to quaternions which don't support values greater than
 	// PI, so we'll interpolate large rotations
-	interpolateRotations( curve ) {
+	interpolateRotations( curvex, curvey, curvez, eulerOrder ) {
+
+		const times = [];
+		const values = [];
+		for ( let i = 1; i < curvex.values.length; i ++ ) {
+
+			const initialValue = [
+				curvex.values[ i - 1 ],
+				curvey.values[ i - 1 ],
+				curvez.values[ i - 1 ],
+			];
 
-		for ( let i = 1; i < curve.values.length; i ++ ) {
+			if ( isNaN( initialValue[ 0 ] ) || isNaN( initialValue[ 1 ] ) || isNaN( initialValue[ 2 ] ) ) {
 
-			const initialValue = curve.values[ i - 1 ];
-			const valuesSpan = curve.values[ i ] - initialValue;
+				continue;
 
-			const absoluteSpan = Math.abs( valuesSpan );
+			}
+
+			const initialValueRad = initialValue.map( MathUtils.degToRad );
+
+			const currentValue = [
+				curvex.values[ i ],
+				curvey.values[ i ],
+				curvez.values[ i ],
+			];
+
+			if ( isNaN( currentValue[ 0 ] ) || isNaN( currentValue[ 1 ] ) || isNaN( currentValue[ 2 ] ) ) {
+
+				continue;
+
+			}
+
+			const currentValueRad = currentValue.map( MathUtils.degToRad );
+
+			const valuesSpan = [
+				currentValue[ 0 ] - initialValue[ 0 ],
+				currentValue[ 1 ] - initialValue[ 1 ],
+				currentValue[ 2 ] - initialValue[ 2 ],
+			];
+
+			const absoluteSpan = [
+				Math.abs( valuesSpan[ 0 ] ),
+				Math.abs( valuesSpan[ 1 ] ),
+				Math.abs( valuesSpan[ 2 ] ),
+			];
+
+			if ( absoluteSpan[ 0 ] >= 180 || absoluteSpan[ 1 ] >= 180 || absoluteSpan[ 2 ] >= 180 ) {
 
-			if ( absoluteSpan >= 180 ) {
+				const maxAbsSpan = Math.max( ...absoluteSpan );
 
-				const numSubIntervals = absoluteSpan / 180;
+				const numSubIntervals = maxAbsSpan / 180;
 
-				const step = valuesSpan / numSubIntervals;
-				let nextValue = initialValue + step;
+				const E1 = new Euler( ...initialValueRad, eulerOrder );
+				const E2 = new Euler( ...currentValueRad, eulerOrder );
 
-				const initialTime = curve.times[ i - 1 ];
-				const timeSpan = curve.times[ i ] - initialTime;
-				const interval = timeSpan / numSubIntervals;
-				let nextTime = initialTime + interval;
+				const Q1 = new Quaternion().setFromEuler( E1 );
+				const Q2 = new Quaternion().setFromEuler( E2 );
 
-				const interpolatedTimes = [];
-				const interpolatedValues = [];
+				// Check unroll
+				if ( Q1.dot( Q2 ) ) {
 
-				while ( nextTime < curve.times[ i ] ) {
+					Q2.set( - Q2.x, - Q2.y, - Q2.z, - Q2.w );
 
-					interpolatedTimes.push( nextTime );
-					nextTime += interval;
+				}
+
+				// Interpolate
+				const initialTime = curvex.times[ i - 1 ];
+				const timeSpan = curvex.times[ i ] - initialTime;
+
+				const Q = new Quaternion();
+				const E = new Euler();
+				for ( let t = 0; t < 1; t += 1 / numSubIntervals ) {
 
-					interpolatedValues.push( nextValue );
-					nextValue += step;
+					Q.copy( Q1.clone().slerp( Q2.clone(), t ) );
+
+					times.push( initialTime + t * timeSpan );
+					E.setFromQuaternion( Q, eulerOrder );
+
+					values.push( E.x );
+					values.push( E.y );
+					values.push( E.z );
 
 				}
 
-				curve.times = inject( curve.times, i, interpolatedTimes );
-				curve.values = inject( curve.values, i, interpolatedValues );
+			} else {
+
+				times.push( curvex.times[ i ] );
+				values.push( MathUtils.degToRad( curvex.values[ i ] ) );
+				values.push( MathUtils.degToRad( curvey.values[ i ] ) );
+				values.push( MathUtils.degToRad( curvez.values[ i ] ) );
 
 			}
 
 		}
 
+		return [ times, values ];
+
 	}
 
 }
@@ -4217,11 +4276,5 @@ function slice( a, b, from, to ) {
 
 }
 
-// inject array a2 into array a1 at index
-function inject( a1, index, a2 ) {
-
-	return a1.slice( 0, index ).concat( a2 ).concat( a1.slice( index ) );
-
-}
 
 export { FBXLoader };