This updates Three.js to 0.183.2 and MapLibre GL to 5.19.0, and rewires GLTFLoader utility imports to local vendored modules for browser module compatibility. Made-with: Cursor
This commit is contained in:
Vendored
+386
-273
File diff suppressed because it is too large
Load Diff
Vendored
+3317
-37928
File diff suppressed because one or more lines are too long
+91
-29
@@ -11,6 +11,30 @@ import {
|
||||
Vector3,
|
||||
} from '../three.module.js';
|
||||
|
||||
/**
|
||||
* @module BufferGeometryUtils
|
||||
* @three_import import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
|
||||
*/
|
||||
|
||||
/**
|
||||
* Computes vertex tangents using the MikkTSpace algorithm. MikkTSpace generates the same tangents consistently,
|
||||
* and is used in most modelling tools and normal map bakers. Use MikkTSpace for materials with normal maps,
|
||||
* because inconsistent tangents may lead to subtle visual issues in the normal map, particularly around mirrored
|
||||
* UV seams.
|
||||
*
|
||||
* In comparison to this method, {@link BufferGeometry#computeTangents} (a custom algorithm) generates tangents that
|
||||
* probably will not match the tangents in other software. The custom algorithm is sufficient for general use with a
|
||||
* custom material, and may be faster than MikkTSpace.
|
||||
*
|
||||
* Returns the original BufferGeometry. Indexed geometries will be de-indexed. Requires position, normal, and uv attributes.
|
||||
*
|
||||
* @param {BufferGeometry} geometry - The geometry to compute tangents for.
|
||||
* @param {Object} MikkTSpace - Instance of `examples/jsm/libs/mikktspace.module.js`, or `mikktspace` npm package.
|
||||
* Await `MikkTSpace.ready` before use.
|
||||
* @param {boolean} [negateSign=true] - Whether to negate the sign component (.w) of each tangent.
|
||||
* Required for normal map conventions in some formats, including glTF.
|
||||
* @return {BufferGeometry} The updated geometry.
|
||||
*/
|
||||
function computeMikkTSpaceTangents( geometry, MikkTSpace, negateSign = true ) {
|
||||
|
||||
if ( ! MikkTSpace || ! MikkTSpace.isReady ) {
|
||||
@@ -100,9 +124,11 @@ function computeMikkTSpaceTangents( geometry, MikkTSpace, negateSign = true ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array<BufferGeometry>} geometries
|
||||
* @param {Boolean} useGroups
|
||||
* @return {BufferGeometry}
|
||||
* Merges a set of geometries into a single instance. All geometries must have compatible attributes.
|
||||
*
|
||||
* @param {Array<BufferGeometry>} geometries - The geometries to merge.
|
||||
* @param {boolean} [useGroups=false] - Whether to use groups or not.
|
||||
* @return {?BufferGeometry} The merged geometry. Returns `null` if the merge does not succeed.
|
||||
*/
|
||||
function mergeGeometries( geometries, useGroups = false ) {
|
||||
|
||||
@@ -296,8 +322,11 @@ function mergeGeometries( geometries, useGroups = false ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array<BufferAttribute>} attributes
|
||||
* @return {BufferAttribute}
|
||||
* Merges a set of attributes into a single instance. All attributes must have compatible properties and types.
|
||||
* Instances of {@link InterleavedBufferAttribute} are not supported.
|
||||
*
|
||||
* @param {Array<BufferAttribute>} attributes - The attributes to merge.
|
||||
* @return {?BufferAttribute} The merged attribute. Returns `null` if the merge does not succeed.
|
||||
*/
|
||||
function mergeAttributes( attributes ) {
|
||||
|
||||
@@ -389,10 +418,12 @@ function mergeAttributes( attributes ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BufferAttribute}
|
||||
* @return {BufferAttribute}
|
||||
* Performs a deep clone of the given buffer attribute.
|
||||
*
|
||||
* @param {BufferAttribute} attribute - The attribute to clone.
|
||||
* @return {BufferAttribute} The cloned attribute.
|
||||
*/
|
||||
export function deepCloneAttribute( attribute ) {
|
||||
function deepCloneAttribute( attribute ) {
|
||||
|
||||
if ( attribute.isInstancedInterleavedBufferAttribute || attribute.isInterleavedBufferAttribute ) {
|
||||
|
||||
@@ -411,8 +442,11 @@ export function deepCloneAttribute( attribute ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array<BufferAttribute>} attributes
|
||||
* @return {Array<InterleavedBufferAttribute>}
|
||||
* Interleaves a set of attributes and returns a new array of corresponding attributes that share a
|
||||
* single {@link InterleavedBuffer} instance. All attributes must have compatible types.
|
||||
*
|
||||
* @param {Array<BufferAttribute>} attributes - The attributes to interleave.
|
||||
* @return {?Array<InterleavedBufferAttribute>} An array of interleaved attributes. If interleave does not succeed, the method returns `null`.
|
||||
*/
|
||||
function interleaveAttributes( attributes ) {
|
||||
|
||||
@@ -475,8 +509,13 @@ function interleaveAttributes( attributes ) {
|
||||
|
||||
}
|
||||
|
||||
// returns a new, non-interleaved version of the provided attribute
|
||||
export function deinterleaveAttribute( attribute ) {
|
||||
/**
|
||||
* Returns a new, non-interleaved version of the given attribute.
|
||||
*
|
||||
* @param {InterleavedBufferAttribute} attribute - The interleaved attribute.
|
||||
* @return {BufferAttribute} The non-interleaved attribute.
|
||||
*/
|
||||
function deinterleaveAttribute( attribute ) {
|
||||
|
||||
const cons = attribute.data.array.constructor;
|
||||
const count = attribute.count;
|
||||
@@ -523,8 +562,12 @@ export function deinterleaveAttribute( attribute ) {
|
||||
|
||||
}
|
||||
|
||||
// deinterleaves all attributes on the geometry
|
||||
export function deinterleaveGeometry( geometry ) {
|
||||
/**
|
||||
* Deinterleaves all attributes on the given geometry.
|
||||
*
|
||||
* @param {BufferGeometry} geometry - The geometry to deinterleave.
|
||||
*/
|
||||
function deinterleaveGeometry( geometry ) {
|
||||
|
||||
const attributes = geometry.attributes;
|
||||
const morphTargets = geometry.morphTargets;
|
||||
@@ -567,8 +610,10 @@ export function deinterleaveGeometry( geometry ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BufferGeometry} geometry
|
||||
* @return {number}
|
||||
* Returns the amount of bytes used by all attributes to represent the geometry.
|
||||
*
|
||||
* @param {BufferGeometry} geometry - The geometry.
|
||||
* @return {number} The estimate bytes used.
|
||||
*/
|
||||
function estimateBytesUsed( geometry ) {
|
||||
|
||||
@@ -590,9 +635,11 @@ function estimateBytesUsed( geometry ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BufferGeometry} geometry
|
||||
* @param {number} tolerance
|
||||
* @return {BufferGeometry}
|
||||
* Returns a new geometry with vertices for which all similar vertex attributes (within tolerance) are merged.
|
||||
*
|
||||
* @param {BufferGeometry} geometry - The geometry to merge vertices for.
|
||||
* @param {number} [tolerance=1e-4] - The tolerance value.
|
||||
* @return {BufferGeometry} - The new geometry with merged vertices.
|
||||
*/
|
||||
function mergeVertices( geometry, tolerance = 1e-4 ) {
|
||||
|
||||
@@ -753,9 +800,12 @@ function mergeVertices( geometry, tolerance = 1e-4 ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BufferGeometry} geometry
|
||||
* @param {number} drawMode
|
||||
* @return {BufferGeometry}
|
||||
* Returns a new indexed geometry based on `TrianglesDrawMode` draw mode.
|
||||
* This mode corresponds to the `gl.TRIANGLES` primitive in WebGL.
|
||||
*
|
||||
* @param {BufferGeometry} geometry - The geometry to convert.
|
||||
* @param {number} drawMode - The current draw mode.
|
||||
* @return {BufferGeometry} The new geometry using `TrianglesDrawMode`.
|
||||
*/
|
||||
function toTrianglesDrawMode( geometry, drawMode ) {
|
||||
|
||||
@@ -864,9 +914,13 @@ function toTrianglesDrawMode( geometry, drawMode ) {
|
||||
|
||||
/**
|
||||
* Calculates the morphed attributes of a morphed/skinned BufferGeometry.
|
||||
* Helpful for Raytracing or Decals.
|
||||
* @param {Mesh | Line | Points} object An instance of Mesh, Line or Points.
|
||||
* @return {Object} An Object with original position/normal attributes and morphed ones.
|
||||
*
|
||||
* Helpful for Raytracing or Decals (i.e. a `DecalGeometry` applied to a morphed Object with a `BufferGeometry`
|
||||
* will use the original `BufferGeometry`, not the morphed/skinned one, generating an incorrect result.
|
||||
* Using this function to create a shadow `Object3`D the `DecalGeometry` can be correctly generated).
|
||||
*
|
||||
* @param {Mesh|Line|Points} object - The 3D object to compute morph attributes for.
|
||||
* @return {Object} An object with original position/normal attributes and morphed ones.
|
||||
*/
|
||||
function computeMorphedAttributes( object ) {
|
||||
|
||||
@@ -1142,6 +1196,12 @@ function computeMorphedAttributes( object ) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the {@link BufferGeometry#groups} for the given geometry.
|
||||
*
|
||||
* @param {BufferGeometry} geometry - The geometry to modify.
|
||||
* @return {BufferGeometry} - The updated geometry
|
||||
*/
|
||||
function mergeGroups( geometry ) {
|
||||
|
||||
if ( geometry.groups.length === 0 ) {
|
||||
@@ -1244,15 +1304,14 @@ function mergeGroups( geometry ) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Modifies the supplied geometry if it is non-indexed, otherwise creates a new,
|
||||
* non-indexed geometry. Returns the geometry with smooth normals everywhere except
|
||||
* faces that meet at an angle greater than the crease angle.
|
||||
*
|
||||
* @param {BufferGeometry} geometry
|
||||
* @param {number} [creaseAngle]
|
||||
* @return {BufferGeometry}
|
||||
* @param {BufferGeometry} geometry - The geometry to modify.
|
||||
* @param {number} [creaseAngle=Math.PI/3] - The crease angle in radians.
|
||||
* @return {BufferGeometry} - The updated geometry
|
||||
*/
|
||||
function toCreasedNormals( geometry, creaseAngle = Math.PI / 3 /* 60 degrees */ ) {
|
||||
|
||||
@@ -1363,6 +1422,9 @@ export {
|
||||
computeMikkTSpaceTangents,
|
||||
mergeGeometries,
|
||||
mergeAttributes,
|
||||
deepCloneAttribute,
|
||||
deinterleaveAttribute,
|
||||
deinterleaveGeometry,
|
||||
interleaveAttributes,
|
||||
estimateBytesUsed,
|
||||
mergeVertices,
|
||||
|
||||
+490
@@ -0,0 +1,490 @@
|
||||
import {
|
||||
AnimationClip,
|
||||
AnimationMixer,
|
||||
Matrix4,
|
||||
Quaternion,
|
||||
QuaternionKeyframeTrack,
|
||||
SkeletonHelper,
|
||||
Vector3,
|
||||
VectorKeyframeTrack
|
||||
} from '../three.module.js';
|
||||
|
||||
/**
|
||||
* @module SkeletonUtils
|
||||
* @three_import import * as SkeletonUtils from 'three/addons/utils/SkeletonUtils.js';
|
||||
*/
|
||||
|
||||
function getBoneName( bone, options ) {
|
||||
|
||||
if ( options.getBoneName !== undefined ) {
|
||||
|
||||
return options.getBoneName( bone );
|
||||
|
||||
}
|
||||
|
||||
return options.names[ bone.name ];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retargets the skeleton from the given source 3D object to the
|
||||
* target 3D object.
|
||||
*
|
||||
* @param {Object3D} target - The target 3D object.
|
||||
* @param {Object3D} source - The source 3D object.
|
||||
* @param {module:SkeletonUtils~RetargetOptions} options - The options.
|
||||
*/
|
||||
function retarget( target, source, options = {} ) {
|
||||
|
||||
const quat = new Quaternion(),
|
||||
scale = new Vector3(),
|
||||
relativeMatrix = new Matrix4(),
|
||||
globalMatrix = new Matrix4();
|
||||
|
||||
options.preserveBoneMatrix = options.preserveBoneMatrix !== undefined ? options.preserveBoneMatrix : true;
|
||||
options.preserveBonePositions = options.preserveBonePositions !== undefined ? options.preserveBonePositions : true;
|
||||
options.useTargetMatrix = options.useTargetMatrix !== undefined ? options.useTargetMatrix : false;
|
||||
options.hip = options.hip !== undefined ? options.hip : 'hip';
|
||||
options.hipInfluence = options.hipInfluence !== undefined ? options.hipInfluence : new Vector3( 1, 1, 1 );
|
||||
options.scale = options.scale !== undefined ? options.scale : 1;
|
||||
options.names = options.names || {};
|
||||
|
||||
const sourceBones = source.isObject3D ? source.skeleton.bones : getBones( source ),
|
||||
bones = target.isObject3D ? target.skeleton.bones : getBones( target );
|
||||
|
||||
let bone, name, boneTo,
|
||||
bonesPosition;
|
||||
|
||||
// reset bones
|
||||
|
||||
if ( target.isObject3D ) {
|
||||
|
||||
target.skeleton.pose();
|
||||
|
||||
} else {
|
||||
|
||||
options.useTargetMatrix = true;
|
||||
options.preserveBoneMatrix = false;
|
||||
|
||||
}
|
||||
|
||||
if ( options.preserveBonePositions ) {
|
||||
|
||||
bonesPosition = [];
|
||||
|
||||
for ( let i = 0; i < bones.length; i ++ ) {
|
||||
|
||||
bonesPosition.push( bones[ i ].position.clone() );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( options.preserveBoneMatrix ) {
|
||||
|
||||
// reset matrix
|
||||
|
||||
target.updateMatrixWorld();
|
||||
|
||||
target.matrixWorld.identity();
|
||||
|
||||
// reset children matrix
|
||||
|
||||
for ( let i = 0; i < target.children.length; ++ i ) {
|
||||
|
||||
target.children[ i ].updateMatrixWorld( true );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for ( let i = 0; i < bones.length; ++ i ) {
|
||||
|
||||
bone = bones[ i ];
|
||||
name = getBoneName( bone, options );
|
||||
|
||||
boneTo = getBoneByName( name, sourceBones );
|
||||
|
||||
globalMatrix.copy( bone.matrixWorld );
|
||||
|
||||
if ( boneTo ) {
|
||||
|
||||
boneTo.updateMatrixWorld();
|
||||
|
||||
if ( options.useTargetMatrix ) {
|
||||
|
||||
relativeMatrix.copy( boneTo.matrixWorld );
|
||||
|
||||
} else {
|
||||
|
||||
relativeMatrix.copy( target.matrixWorld ).invert();
|
||||
relativeMatrix.multiply( boneTo.matrixWorld );
|
||||
|
||||
}
|
||||
|
||||
// ignore scale to extract rotation
|
||||
|
||||
scale.setFromMatrixScale( relativeMatrix );
|
||||
relativeMatrix.scale( scale.set( 1 / scale.x, 1 / scale.y, 1 / scale.z ) );
|
||||
|
||||
// apply to global matrix
|
||||
|
||||
globalMatrix.makeRotationFromQuaternion( quat.setFromRotationMatrix( relativeMatrix ) );
|
||||
|
||||
if ( target.isObject3D ) {
|
||||
|
||||
if ( options.localOffsets ) {
|
||||
|
||||
if ( options.localOffsets[ bone.name ] ) {
|
||||
|
||||
globalMatrix.multiply( options.localOffsets[ bone.name ] );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
globalMatrix.copyPosition( relativeMatrix );
|
||||
|
||||
}
|
||||
|
||||
if ( name === options.hip ) {
|
||||
|
||||
globalMatrix.elements[ 12 ] *= options.scale * options.hipInfluence.x;
|
||||
globalMatrix.elements[ 13 ] *= options.scale * options.hipInfluence.y;
|
||||
globalMatrix.elements[ 14 ] *= options.scale * options.hipInfluence.z;
|
||||
|
||||
if ( options.hipPosition !== undefined ) {
|
||||
|
||||
globalMatrix.elements[ 12 ] += options.hipPosition.x * options.scale;
|
||||
globalMatrix.elements[ 13 ] += options.hipPosition.y * options.scale;
|
||||
globalMatrix.elements[ 14 ] += options.hipPosition.z * options.scale;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( bone.parent ) {
|
||||
|
||||
bone.matrix.copy( bone.parent.matrixWorld ).invert();
|
||||
bone.matrix.multiply( globalMatrix );
|
||||
|
||||
} else {
|
||||
|
||||
bone.matrix.copy( globalMatrix );
|
||||
|
||||
}
|
||||
|
||||
bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
|
||||
|
||||
bone.updateMatrixWorld();
|
||||
|
||||
}
|
||||
|
||||
if ( options.preserveBonePositions ) {
|
||||
|
||||
for ( let i = 0; i < bones.length; ++ i ) {
|
||||
|
||||
bone = bones[ i ];
|
||||
name = getBoneName( bone, options ) || bone.name;
|
||||
|
||||
if ( name !== options.hip ) {
|
||||
|
||||
bone.position.copy( bonesPosition[ i ] );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( options.preserveBoneMatrix ) {
|
||||
|
||||
// restore matrix
|
||||
|
||||
target.updateMatrixWorld( true );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retargets the animation clip of the source object to the
|
||||
* target 3D object.
|
||||
*
|
||||
* @param {Object3D} target - The target 3D object.
|
||||
* @param {Object3D} source - The source 3D object.
|
||||
* @param {AnimationClip} clip - The animation clip.
|
||||
* @param {module:SkeletonUtils~RetargetOptions} options - The options.
|
||||
* @return {AnimationClip} The retargeted animation clip.
|
||||
*/
|
||||
function retargetClip( target, source, clip, options = {} ) {
|
||||
|
||||
options.useFirstFramePosition = options.useFirstFramePosition !== undefined ? options.useFirstFramePosition : false;
|
||||
|
||||
// Calculate the fps from the source clip based on the track with the most frames, unless fps is already provided.
|
||||
options.fps = options.fps !== undefined ? options.fps : ( Math.max( ...clip.tracks.map( track => track.times.length ) ) / clip.duration );
|
||||
options.names = options.names || [];
|
||||
|
||||
if ( ! source.isObject3D ) {
|
||||
|
||||
source = getHelperFromSkeleton( source );
|
||||
|
||||
}
|
||||
|
||||
const numFrames = Math.round( clip.duration * ( options.fps / 1000 ) * 1000 ),
|
||||
delta = clip.duration / ( numFrames - 1 ),
|
||||
convertedTracks = [],
|
||||
mixer = new AnimationMixer( source ),
|
||||
bones = getBones( target.skeleton ),
|
||||
boneDatas = [];
|
||||
|
||||
let positionOffset,
|
||||
bone, boneTo, boneData,
|
||||
name;
|
||||
|
||||
mixer.clipAction( clip ).play();
|
||||
|
||||
// trim
|
||||
|
||||
let start = 0, end = numFrames;
|
||||
|
||||
if ( options.trim !== undefined ) {
|
||||
|
||||
start = Math.round( options.trim[ 0 ] * options.fps );
|
||||
end = Math.min( Math.round( options.trim[ 1 ] * options.fps ), numFrames ) - start;
|
||||
|
||||
mixer.update( options.trim[ 0 ] );
|
||||
|
||||
} else {
|
||||
|
||||
mixer.update( 0 );
|
||||
|
||||
}
|
||||
|
||||
source.updateMatrixWorld();
|
||||
|
||||
//
|
||||
|
||||
for ( let frame = 0; frame < end; ++ frame ) {
|
||||
|
||||
const time = frame * delta;
|
||||
|
||||
retarget( target, source, options );
|
||||
|
||||
for ( let j = 0; j < bones.length; ++ j ) {
|
||||
|
||||
bone = bones[ j ];
|
||||
name = getBoneName( bone, options ) || bone.name;
|
||||
boneTo = getBoneByName( name, source.skeleton );
|
||||
|
||||
if ( boneTo ) {
|
||||
|
||||
boneData = boneDatas[ j ] = boneDatas[ j ] || { bone: bone };
|
||||
|
||||
if ( options.hip === name ) {
|
||||
|
||||
if ( ! boneData.pos ) {
|
||||
|
||||
boneData.pos = {
|
||||
times: new Float32Array( end ),
|
||||
values: new Float32Array( end * 3 )
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
if ( options.useFirstFramePosition ) {
|
||||
|
||||
if ( frame === 0 ) {
|
||||
|
||||
positionOffset = bone.position.clone();
|
||||
|
||||
}
|
||||
|
||||
bone.position.sub( positionOffset );
|
||||
|
||||
}
|
||||
|
||||
boneData.pos.times[ frame ] = time;
|
||||
|
||||
bone.position.toArray( boneData.pos.values, frame * 3 );
|
||||
|
||||
}
|
||||
|
||||
if ( ! boneData.quat ) {
|
||||
|
||||
boneData.quat = {
|
||||
times: new Float32Array( end ),
|
||||
values: new Float32Array( end * 4 )
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
boneData.quat.times[ frame ] = time;
|
||||
|
||||
bone.quaternion.toArray( boneData.quat.values, frame * 4 );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( frame === end - 2 ) {
|
||||
|
||||
// last mixer update before final loop iteration
|
||||
// make sure we do not go over or equal to clip duration
|
||||
mixer.update( delta - 0.0000001 );
|
||||
|
||||
} else {
|
||||
|
||||
mixer.update( delta );
|
||||
|
||||
}
|
||||
|
||||
source.updateMatrixWorld();
|
||||
|
||||
}
|
||||
|
||||
for ( let i = 0; i < boneDatas.length; ++ i ) {
|
||||
|
||||
boneData = boneDatas[ i ];
|
||||
|
||||
if ( boneData ) {
|
||||
|
||||
if ( boneData.pos ) {
|
||||
|
||||
convertedTracks.push( new VectorKeyframeTrack(
|
||||
'.bones[' + boneData.bone.name + '].position',
|
||||
boneData.pos.times,
|
||||
boneData.pos.values
|
||||
) );
|
||||
|
||||
}
|
||||
|
||||
convertedTracks.push( new QuaternionKeyframeTrack(
|
||||
'.bones[' + boneData.bone.name + '].quaternion',
|
||||
boneData.quat.times,
|
||||
boneData.quat.values
|
||||
) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
mixer.uncacheAction( clip );
|
||||
|
||||
return new AnimationClip( clip.name, - 1, convertedTracks );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the given 3D object and its descendants, ensuring that any `SkinnedMesh` instances are
|
||||
* correctly associated with their bones. Bones are also cloned, and must be descendants of the
|
||||
* object passed to this method. Other data, like geometries and materials, are reused by reference.
|
||||
*
|
||||
* @param {Object3D} source - The 3D object to clone.
|
||||
* @return {Object3D} The cloned 3D object.
|
||||
*/
|
||||
function clone( source ) {
|
||||
|
||||
const sourceLookup = new Map();
|
||||
const cloneLookup = new Map();
|
||||
|
||||
const clone = source.clone();
|
||||
|
||||
parallelTraverse( source, clone, function ( sourceNode, clonedNode ) {
|
||||
|
||||
sourceLookup.set( clonedNode, sourceNode );
|
||||
cloneLookup.set( sourceNode, clonedNode );
|
||||
|
||||
} );
|
||||
|
||||
clone.traverse( function ( node ) {
|
||||
|
||||
if ( ! node.isSkinnedMesh ) return;
|
||||
|
||||
const clonedMesh = node;
|
||||
const sourceMesh = sourceLookup.get( node );
|
||||
const sourceBones = sourceMesh.skeleton.bones;
|
||||
|
||||
clonedMesh.skeleton = sourceMesh.skeleton.clone();
|
||||
clonedMesh.bindMatrix.copy( sourceMesh.bindMatrix );
|
||||
|
||||
clonedMesh.skeleton.bones = sourceBones.map( function ( bone ) {
|
||||
|
||||
return cloneLookup.get( bone );
|
||||
|
||||
} );
|
||||
|
||||
clonedMesh.bind( clonedMesh.skeleton, clonedMesh.bindMatrix );
|
||||
|
||||
} );
|
||||
|
||||
return clone;
|
||||
|
||||
}
|
||||
|
||||
// internal helper
|
||||
|
||||
function getBoneByName( name, skeleton ) {
|
||||
|
||||
for ( let i = 0, bones = getBones( skeleton ); i < bones.length; i ++ ) {
|
||||
|
||||
if ( name === bones[ i ].name )
|
||||
|
||||
return bones[ i ];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getBones( skeleton ) {
|
||||
|
||||
return Array.isArray( skeleton ) ? skeleton : skeleton.bones;
|
||||
|
||||
}
|
||||
|
||||
|
||||
function getHelperFromSkeleton( skeleton ) {
|
||||
|
||||
const source = new SkeletonHelper( skeleton.bones[ 0 ] );
|
||||
source.skeleton = skeleton;
|
||||
|
||||
return source;
|
||||
|
||||
}
|
||||
|
||||
function parallelTraverse( a, b, callback ) {
|
||||
|
||||
callback( a, b );
|
||||
|
||||
for ( let i = 0; i < a.children.length; i ++ ) {
|
||||
|
||||
parallelTraverse( a.children[ i ], b.children[ i ], callback );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retarget options of `SkeletonUtils`.
|
||||
*
|
||||
* @typedef {Object} module:SkeletonUtils~RetargetOptions
|
||||
* @property {boolean} [useFirstFramePosition=false] - Whether to use the position of the first frame or not.
|
||||
* @property {number} [fps] - The FPS of the clip.
|
||||
* @property {Object<string,string>} [names] - A dictionary for mapping target to source bone names.
|
||||
* @property {function(string):string} [getBoneName] - A function for mapping bone names. Alternative to `names`.
|
||||
* @property {Array<number>} [trim] - Whether to trim the clip or not. If set the array should hold two values for the start and end.
|
||||
* @property {boolean} [preserveBoneMatrix=true] - Whether to preserve bone matrices or not.
|
||||
* @property {boolean} [preserveBonePositions=true] - Whether to preserve bone positions or not.
|
||||
* @property {boolean} [useTargetMatrix=false] - Whether to use the target matrix or not.
|
||||
* @property {string} [hip='hip'] - The name of the source's hip bone.
|
||||
* @property {Vector3} [hipInfluence=(1,1,1)] - The hip influence.
|
||||
* @property {number} [scale=1] - The scale.
|
||||
**/
|
||||
|
||||
export {
|
||||
retarget,
|
||||
retargetClip,
|
||||
clone,
|
||||
};
|
||||
Reference in New Issue
Block a user