Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
104 changes: 93 additions & 11 deletions examples/jsm/loaders/FBXLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,8 @@ class FBXTreeParser {

}

// the transparency handling is implemented based on Blender/Unity's approach: https://github.com/sobotka/blender-addons/blob/7d80f2f97161fc8e353a657b179b9aa1f8e5280b/io_scene_fbx/import_fbx.py#L1444-L1459
// the transparency handling is implemented based on Blender's approach:
// https://github.com/blender/blender/blob/main/scripts/addons_core/io_scene_fbx/import_fbx.py

parameters.opacity = 1 - ( materialNode.TransparencyFactor ? parseFloat( materialNode.TransparencyFactor.value ) : 0 );

Expand All @@ -611,7 +612,10 @@ class FBXTreeParser {

if ( parameters.opacity === null ) {

parameters.opacity = 1 - ( materialNode.TransparentColor ? parseFloat( materialNode.TransparentColor.value[ 0 ] ) : 0 );
// Default to opaque. Some exporters (e.g. 3ds Max) define TransparentColor
// as white (1,1,1) without intending transparency, which makes the Unity-style
// fallback of `1 - TransparentColor.r` produce incorrect zero opacity.
parameters.opacity = 1;

}

Expand Down Expand Up @@ -2779,7 +2783,13 @@ class AnimationParser {

node.transform = child.matrix;

if ( child.userData.transformData ) node.eulerOrder = child.userData.transformData.eulerOrder;
if ( child.userData.transformData ) {

node.eulerOrder = child.userData.transformData.eulerOrder;

if ( child.userData.transformData.rotation ) node.initialRotation = child.userData.transformData.rotation;

}

}

Expand Down Expand Up @@ -2919,7 +2929,7 @@ class AnimationParser {

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

const rotationTrack = this.generateRotationTrack( rawTracks.modelName, rawTracks.R.curves, rawTracks.preRotation, rawTracks.postRotation, rawTracks.eulerOrder );
const rotationTrack = this.generateRotationTrack( rawTracks.modelName, rawTracks.R.curves, rawTracks.preRotation, rawTracks.postRotation, rawTracks.eulerOrder, rawTracks.initialRotation );
if ( rotationTrack !== undefined ) tracks.push( rotationTrack );

}
Expand Down Expand Up @@ -2951,17 +2961,33 @@ class AnimationParser {

}

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

let times;
let values;

if ( curves.x !== undefined && curves.y !== undefined && curves.z !== undefined ) {
if ( curves.x !== undefined || curves.y !== undefined || curves.z !== undefined ) {

// Get merged, sorted, unique times from all available curves
const mergedTimes = this.getTimesForAllAxes( curves );

if ( mergedTimes.length > 0 ) {

const result = this.interpolateRotations( curves.x, curves.y, curves.z, eulerOrder );
const initialRot = initialRotation || [ 0, 0, 0 ];

times = result[ 0 ];
values = result[ 1 ];
// Synchronize all curves to the merged time array.
// Missing axes are filled with constant values from the initial rotation (Lcl Rotation).
// Existing curves at different times are linearly interpolated.
const syncX = this.synchronizeCurve( curves.x, mergedTimes, initialRot[ 0 ] );
const syncY = this.synchronizeCurve( curves.y, mergedTimes, initialRot[ 1 ] );
const syncZ = this.synchronizeCurve( curves.z, mergedTimes, initialRot[ 2 ] );

const result = this.interpolateRotations( syncX, syncY, syncZ, eulerOrder );

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

}

}

Expand Down Expand Up @@ -2993,7 +3019,7 @@ class AnimationParser {

const quaternionValues = [];

if ( ! values || ! times ) return new QuaternionKeyframeTrack( modelName + '.quaternion', [ 0 ], [ 0 ] );
if ( ! values || ! times ) return undefined;

for ( let i = 0; i < values.length; i += 3 ) {

Expand Down Expand Up @@ -3146,6 +3172,62 @@ class AnimationParser {

}

// Synchronize a curve to a target time array using linear interpolation.
// If the curve is undefined (axis not animated), returns constant values from initialValue.
synchronizeCurve( curve, targetTimes, initialValue ) {

if ( curve === undefined ) {

return { times: targetTimes, values: targetTimes.map( () => initialValue ) };

}

// If the curve already has the same number of keyframes as the target, assume times match
if ( curve.times.length === targetTimes.length ) return curve;

// Linearly interpolate curve values at each target time
const values = [];

for ( let i = 0; i < targetTimes.length; i ++ ) {

values.push( this.sampleCurveValue( curve, targetTimes[ i ], initialValue ) );

}

return { times: targetTimes, values: values };

}

// Sample a single value from a curve at a given time using linear interpolation
sampleCurveValue( curve, time, initialValue ) {

const times = curve.times;
const values = curve.values;

// Before first keyframe
if ( time <= times[ 0 ] ) return values[ 0 ];

// After last keyframe
if ( time >= times[ times.length - 1 ] ) return values[ values.length - 1 ];

// Find surrounding keyframes and linearly interpolate
for ( let i = 0; i < times.length - 1; i ++ ) {

if ( time >= times[ i ] && time <= times[ i + 1 ] ) {

if ( times[ i ] === time ) return values[ i ];

const alpha = ( time - times[ i ] ) / ( times[ i + 1 ] - times[ i ] );
return values[ i ] * ( 1 - alpha ) + values[ i + 1 ] * alpha;

}

}

return initialValue;

}

// 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
Expand Down Expand Up @@ -3215,7 +3297,7 @@ class AnimationParser {
const Q2 = new Quaternion().setFromEuler( E2 );

// Check unroll
if ( Q1.dot( Q2 ) ) {
if ( Q1.dot( Q2 ) < 0 ) {

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

Expand Down
Binary file added examples/models/fbx/Head_69.fbx
Binary file not shown.
Binary file added examples/models/fbx/RotationTest.fbx
Binary file not shown.
Binary file added examples/models/fbx/exampleWindow.fbx
Binary file not shown.
4 changes: 4 additions & 0 deletions examples/webgl_loader_fbx.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,16 @@
'warrior/Warrior',
'stanford-bunny',
'mixamo',
'RotationTest',
'exampleWindow',
'Head_69',
];

const scales = new Map();
scales.set( 'warrior/Warrior', 100 );
scales.set( 'archer/ArcherRi01', 100 );
scales.set( 'stanford-bunny', 0.001 );
scales.set( 'Head_69', 100 );

init();

Expand Down
Loading