Skip to content

Commit a1906ff

Browse files
solinehayesMugen87
andauthored
FBXLoader: Fix rotation discontinuities. (#27057)
* 🐛 (FBXLoader) Fix interpolation rotation discontinuities * ♻️ (FBXLoader) lint * ♻️ (FBXLoader) remove unused function * Update FBXLoader.js Clean up. * ♻️ (FBXLoader) remove initialRotation * ♻️ (FBXLoader) remove initialRotation unused variable --------- Co-authored-by: Michael Herzog <[email protected]>
1 parent 79f89cd commit a1906ff

File tree

1 file changed

+105
-52
lines changed

1 file changed

+105
-52
lines changed

examples/jsm/loaders/FBXLoader.js

Lines changed: 105 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2731,13 +2731,11 @@ class AnimationParser {
27312731
const tracks = [];
27322732

27332733
let initialPosition = new Vector3();
2734-
let initialRotation = new Quaternion();
27352734
let initialScale = new Vector3();
27362735

2737-
if ( rawTracks.transform ) rawTracks.transform.decompose( initialPosition, initialRotation, initialScale );
2736+
if ( rawTracks.transform ) rawTracks.transform.decompose( initialPosition, new Quaternion(), initialScale );
27382737

27392738
initialPosition = initialPosition.toArray();
2740-
initialRotation = new Euler().setFromQuaternion( initialRotation, rawTracks.eulerOrder ).toArray();
27412739
initialScale = initialScale.toArray();
27422740

27432741
if ( rawTracks.T !== undefined && Object.keys( rawTracks.T.curves ).length > 0 ) {
@@ -2749,7 +2747,7 @@ class AnimationParser {
27492747

27502748
if ( rawTracks.R !== undefined && Object.keys( rawTracks.R.curves ).length > 0 ) {
27512749

2752-
const rotationTrack = this.generateRotationTrack( rawTracks.modelName, rawTracks.R.curves, initialRotation, rawTracks.preRotation, rawTracks.postRotation, rawTracks.eulerOrder );
2750+
const rotationTrack = this.generateRotationTrack( rawTracks.modelName, rawTracks.R.curves, rawTracks.preRotation, rawTracks.postRotation, rawTracks.eulerOrder );
27532751
if ( rotationTrack !== undefined ) tracks.push( rotationTrack );
27542752

27552753
}
@@ -2781,32 +2779,20 @@ class AnimationParser {
27812779

27822780
}
27832781

2784-
generateRotationTrack( modelName, curves, initialValue, preRotation, postRotation, eulerOrder ) {
2782+
generateRotationTrack( modelName, curves, preRotation, postRotation, eulerOrder ) {
27852783

2786-
if ( curves.x !== undefined ) {
2784+
let times;
2785+
let values;
27872786

2788-
this.interpolateRotations( curves.x );
2789-
curves.x.values = curves.x.values.map( MathUtils.degToRad );
2787+
if ( curves.x !== undefined && curves.y !== undefined && curves.z !== undefined ) {
27902788

2791-
}
2792-
2793-
if ( curves.y !== undefined ) {
2794-
2795-
this.interpolateRotations( curves.y );
2796-
curves.y.values = curves.y.values.map( MathUtils.degToRad );
2797-
2798-
}
2799-
2800-
if ( curves.z !== undefined ) {
2789+
const result = this.interpolateRotations( curves.x, curves.y, curves.z, eulerOrder );
28012790

2802-
this.interpolateRotations( curves.z );
2803-
curves.z.values = curves.z.values.map( MathUtils.degToRad );
2791+
times = result[ 0 ];
2792+
values = result[ 1 ];
28042793

28052794
}
28062795

2807-
const times = this.getTimesForAllAxes( curves );
2808-
const values = this.getKeyframeTrackValues( times, curves, initialValue );
2809-
28102796
if ( preRotation !== undefined ) {
28112797

28122798
preRotation = preRotation.map( MathUtils.degToRad );
@@ -2832,15 +2818,32 @@ class AnimationParser {
28322818

28332819
const quaternionValues = [];
28342820

2821+
if ( ! values || ! times ) return new QuaternionKeyframeTrack( modelName + '.quaternion', [], [] );
2822+
28352823
for ( let i = 0; i < values.length; i += 3 ) {
28362824

28372825
euler.set( values[ i ], values[ i + 1 ], values[ i + 2 ], eulerOrder );
2838-
28392826
quaternion.setFromEuler( euler );
28402827

28412828
if ( preRotation !== undefined ) quaternion.premultiply( preRotation );
28422829
if ( postRotation !== undefined ) quaternion.multiply( postRotation );
28432830

2831+
// Check unroll
2832+
if ( i > 2 ) {
2833+
2834+
const prevQuat = new Quaternion().fromArray(
2835+
quaternionValues,
2836+
( ( i - 3 ) / 3 ) * 4
2837+
);
2838+
2839+
if ( prevQuat.dot( quaternion ) < 0 ) {
2840+
2841+
quaternion.set( - quaternion.x, - quaternion.y, - quaternion.z, - quaternion.w );
2842+
2843+
}
2844+
2845+
}
2846+
28442847
quaternion.toArray( quaternionValues, ( i / 3 ) * 4 );
28452848

28462849
}
@@ -2971,47 +2974,103 @@ class AnimationParser {
29712974
// Rotations are defined as Euler angles which can have values of any size
29722975
// These will be converted to quaternions which don't support values greater than
29732976
// PI, so we'll interpolate large rotations
2974-
interpolateRotations( curve ) {
2977+
interpolateRotations( curvex, curvey, curvez, eulerOrder ) {
2978+
2979+
const times = [];
2980+
const values = [];
2981+
for ( let i = 1; i < curvex.values.length; i ++ ) {
2982+
2983+
const initialValue = [
2984+
curvex.values[ i - 1 ],
2985+
curvey.values[ i - 1 ],
2986+
curvez.values[ i - 1 ],
2987+
];
29752988

2976-
for ( let i = 1; i < curve.values.length; i ++ ) {
2989+
if ( isNaN( initialValue[ 0 ] ) || isNaN( initialValue[ 1 ] ) || isNaN( initialValue[ 2 ] ) ) {
29772990

2978-
const initialValue = curve.values[ i - 1 ];
2979-
const valuesSpan = curve.values[ i ] - initialValue;
2991+
continue;
29802992

2981-
const absoluteSpan = Math.abs( valuesSpan );
2993+
}
2994+
2995+
const initialValueRad = initialValue.map( MathUtils.degToRad );
2996+
2997+
const currentValue = [
2998+
curvex.values[ i ],
2999+
curvey.values[ i ],
3000+
curvez.values[ i ],
3001+
];
3002+
3003+
if ( isNaN( currentValue[ 0 ] ) || isNaN( currentValue[ 1 ] ) || isNaN( currentValue[ 2 ] ) ) {
3004+
3005+
continue;
3006+
3007+
}
3008+
3009+
const currentValueRad = currentValue.map( MathUtils.degToRad );
3010+
3011+
const valuesSpan = [
3012+
currentValue[ 0 ] - initialValue[ 0 ],
3013+
currentValue[ 1 ] - initialValue[ 1 ],
3014+
currentValue[ 2 ] - initialValue[ 2 ],
3015+
];
3016+
3017+
const absoluteSpan = [
3018+
Math.abs( valuesSpan[ 0 ] ),
3019+
Math.abs( valuesSpan[ 1 ] ),
3020+
Math.abs( valuesSpan[ 2 ] ),
3021+
];
3022+
3023+
if ( absoluteSpan[ 0 ] >= 180 || absoluteSpan[ 1 ] >= 180 || absoluteSpan[ 2 ] >= 180 ) {
29823024

2983-
if ( absoluteSpan >= 180 ) {
3025+
const maxAbsSpan = Math.max( ...absoluteSpan );
29843026

2985-
const numSubIntervals = absoluteSpan / 180;
3027+
const numSubIntervals = maxAbsSpan / 180;
29863028

2987-
const step = valuesSpan / numSubIntervals;
2988-
let nextValue = initialValue + step;
3029+
const E1 = new Euler( ...initialValueRad, eulerOrder );
3030+
const E2 = new Euler( ...currentValueRad, eulerOrder );
29893031

2990-
const initialTime = curve.times[ i - 1 ];
2991-
const timeSpan = curve.times[ i ] - initialTime;
2992-
const interval = timeSpan / numSubIntervals;
2993-
let nextTime = initialTime + interval;
3032+
const Q1 = new Quaternion().setFromEuler( E1 );
3033+
const Q2 = new Quaternion().setFromEuler( E2 );
29943034

2995-
const interpolatedTimes = [];
2996-
const interpolatedValues = [];
3035+
// Check unroll
3036+
if ( Q1.dot( Q2 ) ) {
29973037

2998-
while ( nextTime < curve.times[ i ] ) {
3038+
Q2.set( - Q2.x, - Q2.y, - Q2.z, - Q2.w );
29993039

3000-
interpolatedTimes.push( nextTime );
3001-
nextTime += interval;
3040+
}
3041+
3042+
// Interpolate
3043+
const initialTime = curvex.times[ i - 1 ];
3044+
const timeSpan = curvex.times[ i ] - initialTime;
3045+
3046+
const Q = new Quaternion();
3047+
const E = new Euler();
3048+
for ( let t = 0; t < 1; t += 1 / numSubIntervals ) {
30023049

3003-
interpolatedValues.push( nextValue );
3004-
nextValue += step;
3050+
Q.copy( Q1.clone().slerp( Q2.clone(), t ) );
3051+
3052+
times.push( initialTime + t * timeSpan );
3053+
E.setFromQuaternion( Q, eulerOrder );
3054+
3055+
values.push( E.x );
3056+
values.push( E.y );
3057+
values.push( E.z );
30053058

30063059
}
30073060

3008-
curve.times = inject( curve.times, i, interpolatedTimes );
3009-
curve.values = inject( curve.values, i, interpolatedValues );
3061+
} else {
3062+
3063+
times.push( curvex.times[ i ] );
3064+
values.push( MathUtils.degToRad( curvex.values[ i ] ) );
3065+
values.push( MathUtils.degToRad( curvey.values[ i ] ) );
3066+
values.push( MathUtils.degToRad( curvez.values[ i ] ) );
30103067

30113068
}
30123069

30133070
}
30143071

3072+
return [ times, values ];
3073+
30153074
}
30163075

30173076
}
@@ -4217,11 +4276,5 @@ function slice( a, b, from, to ) {
42174276

42184277
}
42194278

4220-
// inject array a2 into array a1 at index
4221-
function inject( a1, index, a2 ) {
4222-
4223-
return a1.slice( 0, index ).concat( a2 ).concat( a1.slice( index ) );
4224-
4225-
}
42264279

42274280
export { FBXLoader };

0 commit comments

Comments
 (0)