|
@@ -36,10 +36,12 @@ import {
|
|
|
Texture,
|
|
|
TextureLoader,
|
|
|
Uint16BufferAttribute,
|
|
|
+ Vector2,
|
|
|
Vector3,
|
|
|
Vector4,
|
|
|
VectorKeyframeTrack,
|
|
|
- SRGBColorSpace
|
|
|
+ SRGBColorSpace,
|
|
|
+ ShapeUtils
|
|
|
} from 'three';
|
|
|
import * as fflate from '../libs/fflate.module.js';
|
|
|
import { NURBSCurve } from '../curves/NURBSCurve.js';
|
|
@@ -443,6 +445,22 @@ class FBXTreeParser {
|
|
|
|
|
|
}
|
|
|
|
|
|
+ } else if ( extension === 'dds' ) {
|
|
|
+
|
|
|
+ const loader = this.manager.getHandler( '.dds' );
|
|
|
+
|
|
|
+ if ( loader === null ) {
|
|
|
+
|
|
|
+ console.warn( 'FBXLoader: DDS loader not found, creating placeholder texture for', textureNode.RelativeFilename );
|
|
|
+ texture = new Texture();
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ loader.setPath( this.textureLoader.path );
|
|
|
+ texture = loader.load( fileName );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
} else if ( extension === 'psd' ) {
|
|
|
|
|
|
console.warn( 'FBXLoader: PSD textures are not supported, creating placeholder texture for', textureNode.RelativeFilename );
|
|
@@ -883,7 +901,7 @@ class FBXTreeParser {
|
|
|
|
|
|
this.bindSkeleton( deformers.skeletons, geometryMap, modelMap );
|
|
|
|
|
|
- this.createAmbientLight();
|
|
|
+ this.addGlobalSceneSettings();
|
|
|
|
|
|
sceneGraph.traverse( function ( node ) {
|
|
|
|
|
@@ -1466,20 +1484,31 @@ class FBXTreeParser {
|
|
|
|
|
|
}
|
|
|
|
|
|
- // Parse ambient color in FBXTree.GlobalSettings - if it's not set to black (default), create an ambient light
|
|
|
- createAmbientLight() {
|
|
|
+ addGlobalSceneSettings() {
|
|
|
+
|
|
|
+ if ( 'GlobalSettings' in fbxTree ) {
|
|
|
|
|
|
- if ( 'GlobalSettings' in fbxTree && 'AmbientColor' in fbxTree.GlobalSettings ) {
|
|
|
+ if ( 'AmbientColor' in fbxTree.GlobalSettings ) {
|
|
|
|
|
|
- const ambientColor = fbxTree.GlobalSettings.AmbientColor.value;
|
|
|
- const r = ambientColor[ 0 ];
|
|
|
- const g = ambientColor[ 1 ];
|
|
|
- const b = ambientColor[ 2 ];
|
|
|
+ // Parse ambient color - if it's not set to black (default), create an ambient light
|
|
|
|
|
|
- if ( r !== 0 || g !== 0 || b !== 0 ) {
|
|
|
+ const ambientColor = fbxTree.GlobalSettings.AmbientColor.value;
|
|
|
+ const r = ambientColor[ 0 ];
|
|
|
+ const g = ambientColor[ 1 ];
|
|
|
+ const b = ambientColor[ 2 ];
|
|
|
|
|
|
- const color = new Color( r, g, b ).convertSRGBToLinear();
|
|
|
- sceneGraph.add( new AmbientLight( color, 1 ) );
|
|
|
+ if ( r !== 0 || g !== 0 || b !== 0 ) {
|
|
|
+
|
|
|
+ const color = new Color( r, g, b ).convertSRGBToLinear();
|
|
|
+ sceneGraph.add( new AmbientLight( color, 1 ) );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( 'UnitScaleFactor' in fbxTree.GlobalSettings ) {
|
|
|
+
|
|
|
+ sceneGraph.userData.unitScaleFactor = fbxTree.GlobalSettings.UnitScaleFactor.value;
|
|
|
|
|
|
}
|
|
|
|
|
@@ -1944,8 +1973,6 @@ class GeometryParser {
|
|
|
|
|
|
if ( endOfFace ) {
|
|
|
|
|
|
- if ( faceLength > 4 ) console.warn( 'THREE.FBXLoader: Polygons with more than four sides are not supported. Make sure to triangulate the geometry during export.' );
|
|
|
-
|
|
|
scope.genFace( buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength );
|
|
|
|
|
|
polygonIndex ++;
|
|
@@ -1967,70 +1994,153 @@ class GeometryParser {
|
|
|
|
|
|
}
|
|
|
|
|
|
+ // See https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal
|
|
|
+ getNormalNewell( vertices ) {
|
|
|
+
|
|
|
+ const normal = new Vector3( 0.0, 0.0, 0.0 );
|
|
|
+
|
|
|
+ for ( let i = 0; i < vertices.length; i ++ ) {
|
|
|
+
|
|
|
+ const current = vertices[ i ];
|
|
|
+ const next = vertices[ ( i + 1 ) % vertices.length ];
|
|
|
+
|
|
|
+ normal.x += ( current.y - next.y ) * ( current.z + next.z );
|
|
|
+ normal.y += ( current.z - next.z ) * ( current.x + next.x );
|
|
|
+ normal.z += ( current.x - next.x ) * ( current.y + next.y );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ normal.normalize();
|
|
|
+
|
|
|
+ return normal;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ getNormalTangentAndBitangent( vertices ) {
|
|
|
+
|
|
|
+ const normalVector = this.getNormalNewell( vertices );
|
|
|
+ // Avoid up being equal or almost equal to normalVector
|
|
|
+ const up = Math.abs( normalVector.z ) > 0.5 ? new Vector3( 0.0, 1.0, 0.0 ) : new Vector3( 0.0, 0.0, 1.0 );
|
|
|
+ const tangent = up.cross( normalVector ).normalize();
|
|
|
+ const bitangent = normalVector.clone().cross( tangent ).normalize();
|
|
|
+
|
|
|
+ return {
|
|
|
+ normal: normalVector,
|
|
|
+ tangent: tangent,
|
|
|
+ bitangent: bitangent
|
|
|
+ };
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ flattenVertex( vertex, normalTangent, normalBitangent ) {
|
|
|
+
|
|
|
+ return new Vector2(
|
|
|
+ vertex.dot( normalTangent ),
|
|
|
+ vertex.dot( normalBitangent )
|
|
|
+ );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
// Generate data for a single face in a geometry. If the face is a quad then split it into 2 tris
|
|
|
genFace( buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength ) {
|
|
|
|
|
|
- for ( let i = 2; i < faceLength; i ++ ) {
|
|
|
+ let triangles;
|
|
|
+
|
|
|
+ if ( faceLength > 3 ) {
|
|
|
+
|
|
|
+ // Triangulate n-gon using earcut
|
|
|
|
|
|
- buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 0 ] ] );
|
|
|
- buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 1 ] ] );
|
|
|
- buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 2 ] ] );
|
|
|
+ const vertices = [];
|
|
|
|
|
|
- buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 ] ] );
|
|
|
- buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 + 1 ] ] );
|
|
|
- buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 + 2 ] ] );
|
|
|
+ for ( let i = 0; i < facePositionIndexes.length; i += 3 ) {
|
|
|
+
|
|
|
+ vertices.push( new Vector3(
|
|
|
+ geoInfo.vertexPositions[ facePositionIndexes[ i ] ],
|
|
|
+ geoInfo.vertexPositions[ facePositionIndexes[ i + 1 ] ],
|
|
|
+ geoInfo.vertexPositions[ facePositionIndexes[ i + 2 ] ]
|
|
|
+ ) );
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
- buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 ] ] );
|
|
|
- buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 + 1 ] ] );
|
|
|
- buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 + 2 ] ] );
|
|
|
+ const { tangent, bitangent } = this.getNormalTangentAndBitangent( vertices );
|
|
|
+ const triangulationInput = [];
|
|
|
+
|
|
|
+ for ( const vertex of vertices ) {
|
|
|
+
|
|
|
+ triangulationInput.push( this.flattenVertex( vertex, tangent, bitangent ) );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ triangles = ShapeUtils.triangulateShape( triangulationInput, [] );
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ // Regular triangle, skip earcut triangulation step
|
|
|
+ triangles = [[ 0, 1, 2 ]];
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ for ( const [ i0, i1, i2 ] of triangles ) {
|
|
|
+
|
|
|
+ buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i0 * 3 ] ] );
|
|
|
+ buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i0 * 3 + 1 ] ] );
|
|
|
+ buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i0 * 3 + 2 ] ] );
|
|
|
+
|
|
|
+ buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i1 * 3 ] ] );
|
|
|
+ buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i1 * 3 + 1 ] ] );
|
|
|
+ buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i1 * 3 + 2 ] ] );
|
|
|
+
|
|
|
+ buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i2 * 3 ] ] );
|
|
|
+ buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i2 * 3 + 1 ] ] );
|
|
|
+ buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i2 * 3 + 2 ] ] );
|
|
|
|
|
|
if ( geoInfo.skeleton ) {
|
|
|
|
|
|
- buffers.vertexWeights.push( faceWeights[ 0 ] );
|
|
|
- buffers.vertexWeights.push( faceWeights[ 1 ] );
|
|
|
- buffers.vertexWeights.push( faceWeights[ 2 ] );
|
|
|
- buffers.vertexWeights.push( faceWeights[ 3 ] );
|
|
|
+ buffers.vertexWeights.push( faceWeights[ i0 * 4 ] );
|
|
|
+ buffers.vertexWeights.push( faceWeights[ i0 * 4 + 1 ] );
|
|
|
+ buffers.vertexWeights.push( faceWeights[ i0 * 4 + 2 ] );
|
|
|
+ buffers.vertexWeights.push( faceWeights[ i0 * 4 + 3 ] );
|
|
|
|
|
|
- buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 ] );
|
|
|
- buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 1 ] );
|
|
|
- buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 2 ] );
|
|
|
- buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 3 ] );
|
|
|
+ buffers.vertexWeights.push( faceWeights[ i1 * 4 ] );
|
|
|
+ buffers.vertexWeights.push( faceWeights[ i1 * 4 + 1 ] );
|
|
|
+ buffers.vertexWeights.push( faceWeights[ i1 * 4 + 2 ] );
|
|
|
+ buffers.vertexWeights.push( faceWeights[ i1 * 4 + 3 ] );
|
|
|
|
|
|
- buffers.vertexWeights.push( faceWeights[ i * 4 ] );
|
|
|
- buffers.vertexWeights.push( faceWeights[ i * 4 + 1 ] );
|
|
|
- buffers.vertexWeights.push( faceWeights[ i * 4 + 2 ] );
|
|
|
- buffers.vertexWeights.push( faceWeights[ i * 4 + 3 ] );
|
|
|
+ buffers.vertexWeights.push( faceWeights[ i2 * 4 ] );
|
|
|
+ buffers.vertexWeights.push( faceWeights[ i2 * 4 + 1 ] );
|
|
|
+ buffers.vertexWeights.push( faceWeights[ i2 * 4 + 2 ] );
|
|
|
+ buffers.vertexWeights.push( faceWeights[ i2 * 4 + 3 ] );
|
|
|
|
|
|
- buffers.weightsIndices.push( faceWeightIndices[ 0 ] );
|
|
|
- buffers.weightsIndices.push( faceWeightIndices[ 1 ] );
|
|
|
- buffers.weightsIndices.push( faceWeightIndices[ 2 ] );
|
|
|
- buffers.weightsIndices.push( faceWeightIndices[ 3 ] );
|
|
|
+ buffers.weightsIndices.push( faceWeightIndices[ i0 * 4 ] );
|
|
|
+ buffers.weightsIndices.push( faceWeightIndices[ i0 * 4 + 1 ] );
|
|
|
+ buffers.weightsIndices.push( faceWeightIndices[ i0 * 4 + 2 ] );
|
|
|
+ buffers.weightsIndices.push( faceWeightIndices[ i0 * 4 + 3 ] );
|
|
|
|
|
|
- buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 ] );
|
|
|
- buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 1 ] );
|
|
|
- buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 2 ] );
|
|
|
- buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 3 ] );
|
|
|
+ buffers.weightsIndices.push( faceWeightIndices[ i1 * 4 ] );
|
|
|
+ buffers.weightsIndices.push( faceWeightIndices[ i1 * 4 + 1 ] );
|
|
|
+ buffers.weightsIndices.push( faceWeightIndices[ i1 * 4 + 2 ] );
|
|
|
+ buffers.weightsIndices.push( faceWeightIndices[ i1 * 4 + 3 ] );
|
|
|
|
|
|
- buffers.weightsIndices.push( faceWeightIndices[ i * 4 ] );
|
|
|
- buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 1 ] );
|
|
|
- buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 2 ] );
|
|
|
- buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 3 ] );
|
|
|
+ buffers.weightsIndices.push( faceWeightIndices[ i2 * 4 ] );
|
|
|
+ buffers.weightsIndices.push( faceWeightIndices[ i2 * 4 + 1 ] );
|
|
|
+ buffers.weightsIndices.push( faceWeightIndices[ i2 * 4 + 2 ] );
|
|
|
+ buffers.weightsIndices.push( faceWeightIndices[ i2 * 4 + 3 ] );
|
|
|
|
|
|
}
|
|
|
|
|
|
if ( geoInfo.color ) {
|
|
|
|
|
|
- buffers.colors.push( faceColors[ 0 ] );
|
|
|
- buffers.colors.push( faceColors[ 1 ] );
|
|
|
- buffers.colors.push( faceColors[ 2 ] );
|
|
|
+ buffers.colors.push( faceColors[ i0 * 3 ] );
|
|
|
+ buffers.colors.push( faceColors[ i0 * 3 + 1 ] );
|
|
|
+ buffers.colors.push( faceColors[ i0 * 3 + 2 ] );
|
|
|
|
|
|
- buffers.colors.push( faceColors[ ( i - 1 ) * 3 ] );
|
|
|
- buffers.colors.push( faceColors[ ( i - 1 ) * 3 + 1 ] );
|
|
|
- buffers.colors.push( faceColors[ ( i - 1 ) * 3 + 2 ] );
|
|
|
+ buffers.colors.push( faceColors[ i1 * 3 ] );
|
|
|
+ buffers.colors.push( faceColors[ i1 * 3 + 1 ] );
|
|
|
+ buffers.colors.push( faceColors[ i1 * 3 + 2 ] );
|
|
|
|
|
|
- buffers.colors.push( faceColors[ i * 3 ] );
|
|
|
- buffers.colors.push( faceColors[ i * 3 + 1 ] );
|
|
|
- buffers.colors.push( faceColors[ i * 3 + 2 ] );
|
|
|
+ buffers.colors.push( faceColors[ i2 * 3 ] );
|
|
|
+ buffers.colors.push( faceColors[ i2 * 3 + 1 ] );
|
|
|
+ buffers.colors.push( faceColors[ i2 * 3 + 2 ] );
|
|
|
|
|
|
}
|
|
|
|
|
@@ -2044,17 +2154,17 @@ class GeometryParser {
|
|
|
|
|
|
if ( geoInfo.normal ) {
|
|
|
|
|
|
- buffers.normal.push( faceNormals[ 0 ] );
|
|
|
- buffers.normal.push( faceNormals[ 1 ] );
|
|
|
- buffers.normal.push( faceNormals[ 2 ] );
|
|
|
+ buffers.normal.push( faceNormals[ i0 * 3 ] );
|
|
|
+ buffers.normal.push( faceNormals[ i0 * 3 + 1 ] );
|
|
|
+ buffers.normal.push( faceNormals[ i0 * 3 + 2 ] );
|
|
|
|
|
|
- buffers.normal.push( faceNormals[ ( i - 1 ) * 3 ] );
|
|
|
- buffers.normal.push( faceNormals[ ( i - 1 ) * 3 + 1 ] );
|
|
|
- buffers.normal.push( faceNormals[ ( i - 1 ) * 3 + 2 ] );
|
|
|
+ buffers.normal.push( faceNormals[ i1 * 3 ] );
|
|
|
+ buffers.normal.push( faceNormals[ i1 * 3 + 1 ] );
|
|
|
+ buffers.normal.push( faceNormals[ i1 * 3 + 2 ] );
|
|
|
|
|
|
- buffers.normal.push( faceNormals[ i * 3 ] );
|
|
|
- buffers.normal.push( faceNormals[ i * 3 + 1 ] );
|
|
|
- buffers.normal.push( faceNormals[ i * 3 + 2 ] );
|
|
|
+ buffers.normal.push( faceNormals[ i2 * 3 ] );
|
|
|
+ buffers.normal.push( faceNormals[ i2 * 3 + 1 ] );
|
|
|
+ buffers.normal.push( faceNormals[ i2 * 3 + 2 ] );
|
|
|
|
|
|
}
|
|
|
|
|
@@ -2064,14 +2174,14 @@ class GeometryParser {
|
|
|
|
|
|
if ( buffers.uvs[ j ] === undefined ) buffers.uvs[ j ] = [];
|
|
|
|
|
|
- buffers.uvs[ j ].push( faceUVs[ j ][ 0 ] );
|
|
|
- buffers.uvs[ j ].push( faceUVs[ j ][ 1 ] );
|
|
|
+ buffers.uvs[ j ].push( faceUVs[ j ][ i0 * 2 ] );
|
|
|
+ buffers.uvs[ j ].push( faceUVs[ j ][ i0 * 2 + 1 ] );
|
|
|
|
|
|
- buffers.uvs[ j ].push( faceUVs[ j ][ ( i - 1 ) * 2 ] );
|
|
|
- buffers.uvs[ j ].push( faceUVs[ j ][ ( i - 1 ) * 2 + 1 ] );
|
|
|
+ buffers.uvs[ j ].push( faceUVs[ j ][ i1 * 2 ] );
|
|
|
+ buffers.uvs[ j ].push( faceUVs[ j ][ i1 * 2 + 1 ] );
|
|
|
|
|
|
- buffers.uvs[ j ].push( faceUVs[ j ][ i * 2 ] );
|
|
|
- buffers.uvs[ j ].push( faceUVs[ j ][ i * 2 + 1 ] );
|
|
|
+ buffers.uvs[ j ].push( faceUVs[ j ][ i2 * 2 ] );
|
|
|
+ buffers.uvs[ j ].push( faceUVs[ j ][ i2 * 2 + 1 ] );
|
|
|
|
|
|
} );
|
|
|
|
|
@@ -2648,13 +2758,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 ) {
|
|
@@ -2666,7 +2774,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 );
|
|
|
|
|
|
}
|
|
@@ -2698,32 +2806,20 @@ class AnimationParser {
|
|
|
|
|
|
}
|
|
|
|
|
|
- generateRotationTrack( modelName, curves, initialValue, preRotation, postRotation, eulerOrder ) {
|
|
|
-
|
|
|
- if ( curves.x !== undefined ) {
|
|
|
+ generateRotationTrack( modelName, curves, preRotation, postRotation, eulerOrder ) {
|
|
|
|
|
|
- this.interpolateRotations( curves.x );
|
|
|
- curves.x.values = curves.x.values.map( MathUtils.degToRad );
|
|
|
-
|
|
|
- }
|
|
|
+ let times;
|
|
|
+ let values;
|
|
|
|
|
|
- if ( curves.y !== undefined ) {
|
|
|
-
|
|
|
- this.interpolateRotations( curves.y );
|
|
|
- curves.y.values = curves.y.values.map( MathUtils.degToRad );
|
|
|
-
|
|
|
- }
|
|
|
+ if ( curves.x !== undefined && curves.y !== undefined && curves.z !== undefined ) {
|
|
|
|
|
|
- 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 );
|
|
@@ -2749,15 +2845,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 );
|
|
|
|
|
|
}
|
|
@@ -2888,47 +3001,110 @@ 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 ) {
|
|
|
|
|
|
- for ( let i = 1; i < curve.values.length; i ++ ) {
|
|
|
+ const times = [];
|
|
|
+ const values = [];
|
|
|
+
|
|
|
+ // Add first frame
|
|
|
+ times.push( curvex.times[ 0 ] );
|
|
|
+ values.push( MathUtils.degToRad( curvex.values[ 0 ] ) );
|
|
|
+ values.push( MathUtils.degToRad( curvey.values[ 0 ] ) );
|
|
|
+ values.push( MathUtils.degToRad( curvez.values[ 0 ] ) );
|
|
|
+
|
|
|
+ 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 );
|
|
|
+
|
|
|
+ const currentValue = [
|
|
|
+ curvex.values[ i ],
|
|
|
+ curvey.values[ i ],
|
|
|
+ curvez.values[ i ],
|
|
|
+ ];
|
|
|
+
|
|
|
+ if ( isNaN( currentValue[ 0 ] ) || isNaN( currentValue[ 1 ] ) || isNaN( currentValue[ 2 ] ) ) {
|
|
|
+
|
|
|
+ continue;
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
- const initialValue = curve.values[ i - 1 ];
|
|
|
- const valuesSpan = curve.values[ i ] - initialValue;
|
|
|
+ const currentValueRad = currentValue.map( MathUtils.degToRad );
|
|
|
|
|
|
- const absoluteSpan = Math.abs( valuesSpan );
|
|
|
+ const valuesSpan = [
|
|
|
+ currentValue[ 0 ] - initialValue[ 0 ],
|
|
|
+ currentValue[ 1 ] - initialValue[ 1 ],
|
|
|
+ currentValue[ 2 ] - initialValue[ 2 ],
|
|
|
+ ];
|
|
|
|
|
|
- if ( absoluteSpan >= 180 ) {
|
|
|
+ const absoluteSpan = [
|
|
|
+ Math.abs( valuesSpan[ 0 ] ),
|
|
|
+ Math.abs( valuesSpan[ 1 ] ),
|
|
|
+ Math.abs( valuesSpan[ 2 ] ),
|
|
|
+ ];
|
|
|
|
|
|
- const numSubIntervals = absoluteSpan / 180;
|
|
|
+ if ( absoluteSpan[ 0 ] >= 180 || absoluteSpan[ 1 ] >= 180 || absoluteSpan[ 2 ] >= 180 ) {
|
|
|
|
|
|
- const step = valuesSpan / numSubIntervals;
|
|
|
- let nextValue = initialValue + step;
|
|
|
+ const maxAbsSpan = Math.max( ...absoluteSpan );
|
|
|
|
|
|
- const initialTime = curve.times[ i - 1 ];
|
|
|
- const timeSpan = curve.times[ i ] - initialTime;
|
|
|
- const interval = timeSpan / numSubIntervals;
|
|
|
- let nextTime = initialTime + interval;
|
|
|
+ const numSubIntervals = maxAbsSpan / 180;
|
|
|
|
|
|
- const interpolatedTimes = [];
|
|
|
- const interpolatedValues = [];
|
|
|
+ const E1 = new Euler( ...initialValueRad, eulerOrder );
|
|
|
+ const E2 = new Euler( ...currentValueRad, eulerOrder );
|
|
|
|
|
|
- while ( nextTime < curve.times[ i ] ) {
|
|
|
+ const Q1 = new Quaternion().setFromEuler( E1 );
|
|
|
+ const Q2 = new Quaternion().setFromEuler( E2 );
|
|
|
|
|
|
- interpolatedTimes.push( nextTime );
|
|
|
- nextTime += interval;
|
|
|
+ // Check unroll
|
|
|
+ if ( Q1.dot( Q2 ) ) {
|
|
|
|
|
|
- interpolatedValues.push( nextValue );
|
|
|
- nextValue += step;
|
|
|
+ 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 ];
|
|
|
+
|
|
|
}
|
|
|
|
|
|
}
|
|
@@ -4134,11 +4310,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 };
|