Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FBXLoader: Fix rotation discontinuities. #27057

Merged
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 146 additions & 53 deletions examples/jsm/loaders/FBXLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -2698,32 +2698,35 @@ class AnimationParser {

}

generateRotationTrack( modelName, curves, initialValue, preRotation, postRotation, eulerOrder ) {

if ( curves.x !== undefined ) {

this.interpolateRotations( curves.x );
curves.x.values = curves.x.values.map( MathUtils.degToRad );

}

if ( curves.y !== undefined ) {

this.interpolateRotations( curves.y );
curves.y.values = curves.y.values.map( MathUtils.degToRad );
generateRotationTrack(
modelName,
curves,
initialValue,
preRotation,
postRotation,
eulerOrder
) {
Mugen87 marked this conversation as resolved.
Show resolved Hide resolved

let times;
Mugen87 marked this conversation as resolved.
Show resolved Hide resolved
let values;
if (
curves.x !== undefined &&
curves.y !== undefined &&
curves.z !== undefined
) {

const result = this.interpolateRotations(
curves.x,
curves.y,
curves.z,
eulerOrder
);

times = result[ 0 ];
values = result[ 1 ];

}

if ( curves.z !== undefined ) {

this.interpolateRotations( curves.z );
curves.z.values = curves.z.values.map( MathUtils.degToRad );

}

const times = this.getTimesForAllAxes( curves );
const values = this.getKeyframeTrackValues( times, curves, initialValue );

if ( preRotation !== undefined ) {

preRotation = preRotation.map( MathUtils.degToRad );
Expand All @@ -2749,20 +2752,49 @@ 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 );

}

return new QuaternionKeyframeTrack( modelName + '.quaternion', times, quaternionValues );
return new QuaternionKeyframeTrack(
modelName + '.quaternion',
times,
quaternionValues
);

}

Expand Down Expand Up @@ -2888,47 +2920,114 @@ 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 ],
];
if (
isNaN( initialValue[ 0 ] ) ||
isNaN( initialValue[ 1 ] ) ||
isNaN( initialValue[ 2 ] )
) {

continue;

}

const initialValueRad = initialValue.map( MathUtils.degToRad );

for ( let i = 1; i < curve.values.length; i ++ ) {
const currentValue = [
curvex.values[ i ],
curvey.values[ i ],
curvez.values[ i ],
];

const initialValue = curve.values[ i - 1 ];
const valuesSpan = curve.values[ i ] - initialValue;
if (
isNaN( currentValue[ 0 ] ) ||
isNaN( currentValue[ 1 ] ) ||
isNaN( currentValue[ 2 ] )
) {

const absoluteSpan = Math.abs( valuesSpan );
continue;

if ( absoluteSpan >= 180 ) {
}

const currentValueRad = currentValue.map( MathUtils.degToRad );

const valuesSpan = [
currentValue[ 0 ] - initialValue[ 0 ],
currentValue[ 1 ] - initialValue[ 1 ],
currentValue[ 2 ] - initialValue[ 2 ],
];

const numSubIntervals = absoluteSpan / 180;
const absoluteSpan = [
Math.abs( valuesSpan[ 0 ] ),
Math.abs( valuesSpan[ 1 ] ),
Math.abs( valuesSpan[ 2 ] ),
];

const step = valuesSpan / numSubIntervals;
let nextValue = initialValue + step;
if (
absoluteSpan[ 0 ] >= 180 ||
absoluteSpan[ 1 ] >= 180 ||
absoluteSpan[ 2 ] >= 180
) {

const initialTime = curve.times[ i - 1 ];
const timeSpan = curve.times[ i ] - initialTime;
const interval = timeSpan / numSubIntervals;
let nextTime = initialTime + interval;
const maxAbsSpan = Math.max( ...absoluteSpan );

const interpolatedTimes = [];
const interpolatedValues = [];
const numSubIntervals = maxAbsSpan / 180;

while ( nextTime < curve.times[ i ] ) {
const E1 = new Euler( ...initialValueRad, eulerOrder );
const E2 = new Euler( ...currentValueRad, eulerOrder );

interpolatedTimes.push( nextTime );
nextTime += interval;
const Q1 = new Quaternion().setFromEuler( E1 );
const Q2 = new Quaternion().setFromEuler( E2 );

interpolatedValues.push( nextValue );
nextValue += step;
// Check unroll
if ( Q1.dot( Q2 ) ) {

Q2.set( - Q2.x, - Q2.y, - Q2.z, - Q2.w );

}

curve.times = inject( curve.times, i, interpolatedTimes );
curve.values = inject( curve.values, i, interpolatedValues );
// 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 ) {

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 );

}

} 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 @@ -4134,11 +4233,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 };