@@ -2779,7 +2779,13 @@ class AnimationParser {
27792779
27802780 node . transform = child . matrix ;
27812781
2782- if ( child . userData . transformData ) node . eulerOrder = child . userData . transformData . eulerOrder ;
2782+ if ( child . userData . transformData ) {
2783+
2784+ node . eulerOrder = child . userData . transformData . eulerOrder ;
2785+
2786+ if ( child . userData . transformData . rotation ) node . initialRotation = child . userData . transformData . rotation ;
2787+
2788+ }
27832789
27842790 }
27852791
@@ -2919,7 +2925,7 @@ class AnimationParser {
29192925
29202926 if ( rawTracks . R !== undefined && Object . keys ( rawTracks . R . curves ) . length > 0 ) {
29212927
2922- const rotationTrack = this . generateRotationTrack ( rawTracks . modelName , rawTracks . R . curves , rawTracks . preRotation , rawTracks . postRotation , rawTracks . eulerOrder ) ;
2928+ const rotationTrack = this . generateRotationTrack ( rawTracks . modelName , rawTracks . R . curves , rawTracks . preRotation , rawTracks . postRotation , rawTracks . eulerOrder , rawTracks . initialRotation ) ;
29232929 if ( rotationTrack !== undefined ) tracks . push ( rotationTrack ) ;
29242930
29252931 }
@@ -2951,17 +2957,33 @@ class AnimationParser {
29512957
29522958 }
29532959
2954- generateRotationTrack ( modelName , curves , preRotation , postRotation , eulerOrder ) {
2960+ generateRotationTrack ( modelName , curves , preRotation , postRotation , eulerOrder , initialRotation ) {
29552961
29562962 let times ;
29572963 let values ;
29582964
2959- if ( curves . x !== undefined && curves . y !== undefined && curves . z !== undefined ) {
2965+ if ( curves . x !== undefined || curves . y !== undefined || curves . z !== undefined ) {
2966+
2967+ // Get merged, sorted, unique times from all available curves
2968+ const mergedTimes = this . getTimesForAllAxes ( curves ) ;
2969+
2970+ if ( mergedTimes . length > 0 ) {
29602971
2961- const result = this . interpolateRotations ( curves . x , curves . y , curves . z , eulerOrder ) ;
2972+ const initialRot = initialRotation || [ 0 , 0 , 0 ] ;
29622973
2963- times = result [ 0 ] ;
2964- values = result [ 1 ] ;
2974+ // Synchronize all curves to the merged time array.
2975+ // Missing axes are filled with constant values from the initial rotation (Lcl Rotation).
2976+ // Existing curves at different times are linearly interpolated.
2977+ const syncX = this . synchronizeCurve ( curves . x , mergedTimes , initialRot [ 0 ] ) ;
2978+ const syncY = this . synchronizeCurve ( curves . y , mergedTimes , initialRot [ 1 ] ) ;
2979+ const syncZ = this . synchronizeCurve ( curves . z , mergedTimes , initialRot [ 2 ] ) ;
2980+
2981+ const result = this . interpolateRotations ( syncX , syncY , syncZ , eulerOrder ) ;
2982+
2983+ times = result [ 0 ] ;
2984+ values = result [ 1 ] ;
2985+
2986+ }
29652987
29662988 }
29672989
@@ -2993,7 +3015,7 @@ class AnimationParser {
29933015
29943016 const quaternionValues = [ ] ;
29953017
2996- if ( ! values || ! times ) return new QuaternionKeyframeTrack ( modelName + '.quaternion' , [ 0 ] , [ 0 ] ) ;
3018+ if ( ! values || ! times ) return undefined ;
29973019
29983020 for ( let i = 0 ; i < values . length ; i += 3 ) {
29993021
@@ -3146,6 +3168,62 @@ class AnimationParser {
31463168
31473169 }
31483170
3171+ // Synchronize a curve to a target time array using linear interpolation.
3172+ // If the curve is undefined (axis not animated), returns constant values from initialValue.
3173+ synchronizeCurve ( curve , targetTimes , initialValue ) {
3174+
3175+ if ( curve === undefined ) {
3176+
3177+ return { times : targetTimes , values : targetTimes . map ( ( ) => initialValue ) } ;
3178+
3179+ }
3180+
3181+ // If the curve already has the same number of keyframes as the target, assume times match
3182+ if ( curve . times . length === targetTimes . length ) return curve ;
3183+
3184+ // Linearly interpolate curve values at each target time
3185+ const values = [ ] ;
3186+
3187+ for ( let i = 0 ; i < targetTimes . length ; i ++ ) {
3188+
3189+ values . push ( this . sampleCurveValue ( curve , targetTimes [ i ] , initialValue ) ) ;
3190+
3191+ }
3192+
3193+ return { times : targetTimes , values : values } ;
3194+
3195+ }
3196+
3197+ // Sample a single value from a curve at a given time using linear interpolation
3198+ sampleCurveValue ( curve , time , initialValue ) {
3199+
3200+ const times = curve . times ;
3201+ const values = curve . values ;
3202+
3203+ // Before first keyframe
3204+ if ( time <= times [ 0 ] ) return values [ 0 ] ;
3205+
3206+ // After last keyframe
3207+ if ( time >= times [ times . length - 1 ] ) return values [ values . length - 1 ] ;
3208+
3209+ // Find surrounding keyframes and linearly interpolate
3210+ for ( let i = 0 ; i < times . length - 1 ; i ++ ) {
3211+
3212+ if ( time >= times [ i ] && time <= times [ i + 1 ] ) {
3213+
3214+ if ( times [ i ] === time ) return values [ i ] ;
3215+
3216+ const alpha = ( time - times [ i ] ) / ( times [ i + 1 ] - times [ i ] ) ;
3217+ return values [ i ] * ( 1 - alpha ) + values [ i + 1 ] * alpha ;
3218+
3219+ }
3220+
3221+ }
3222+
3223+ return initialValue ;
3224+
3225+ }
3226+
31493227 // Rotations are defined as Euler angles which can have values of any size
31503228 // These will be converted to quaternions which don't support values greater than
31513229 // PI, so we'll interpolate large rotations
@@ -3215,7 +3293,7 @@ class AnimationParser {
32153293 const Q2 = new Quaternion ( ) . setFromEuler ( E2 ) ;
32163294
32173295 // Check unroll
3218- if ( Q1 . dot ( Q2 ) ) {
3296+ if ( Q1 . dot ( Q2 ) < 0 ) {
32193297
32203298 Q2 . set ( - Q2 . x , - Q2 . y , - Q2 . z , - Q2 . w ) ;
32213299
0 commit comments