Skip to content

Commit

Permalink
FBXLoader: Fix rotation discontinuities. (#27057)
Browse files Browse the repository at this point in the history
* 🐛 (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]>
  • Loading branch information
solinehayes and Mugen87 authored Nov 7, 2023
1 parent 79f89cd commit a1906ff
Showing 1 changed file with 105 additions and 52 deletions.
157 changes: 105 additions & 52 deletions examples/jsm/loaders/FBXLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) {
Expand All @@ -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 );

}
Expand Down Expand Up @@ -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 );
Expand All @@ -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 );

}
Expand Down Expand Up @@ -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 ];

}

}
Expand Down Expand Up @@ -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 };

0 comments on commit a1906ff

Please sign in to comment.