Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

three-m2loader

Package Overview
Dependencies
Maintainers
1
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

three-m2loader - npm Package Compare versions

Comparing version 2.0.1 to 2.1.0

704

M2Loader.js

@@ -7,2 +7,3 @@ import {

CompressedTexture,
DataTexture,
DoubleSide,

@@ -13,2 +14,7 @@ FileLoader,

Group,
InterpolateLinear,
InterpolateSmooth,
InterpolateDiscrete,
LinearFilter,
LinearMipmapLinearFilter,
Loader,

@@ -21,2 +27,3 @@ LoaderUtils,

Quaternion,
QuaternionKeyframeTrack,
RepeatWrapping,

@@ -28,9 +35,9 @@ RGBA_S3TC_DXT1_Format,

Skeleton,
SkinnedMesh,
SRGBColorSpace,
Uint8BufferAttribute,
Vector2,
Vector3,
VectorKeyframeTrack,
DataTexture,
LinearFilter,
LinearMipmapLinearFilter,
SRGBColorSpace
Vector4,
VectorKeyframeTrack
} from 'three';

@@ -74,8 +81,4 @@

this.parse( buffer, url, function ( object ) {
this.parse( buffer, url ).then( onLoad );
onLoad( object );
} );
} catch ( e ) {

@@ -109,3 +112,3 @@

*/
parse( buffer, url, onLoad, onError ) {
async parse( buffer, url ) {

@@ -160,3 +163,4 @@ const parser = new BinaryParser( buffer );

const textureWeightDefinitions = this._readTextureWeightDefinitions( parser, header );
// const boneDefinitions = this._readBoneDefinitions( parser, header );
const globalSequences = this._readGlobalSequences( parser, header ); // eslint-disable-line no-unused-vars
const boneDefinitions = this._readBoneDefinitions( parser, header );

@@ -183,62 +187,21 @@ // lookup tables

// textures
// load textures and skin data asynchronously
const texturePromises = this._buildTextures( textureDefinitions, textureLoader, name, chunks );
const textures = await Promise.all( this._loadTextures( textureDefinitions, textureLoader, name, chunks ) );
const skinData = await this._loadSkin( header, parser, skinLoader, name, chunks );
// skins
// build scene
Promise.all( texturePromises ).then( ( textures ) => {
const geometries = this._buildGeometries( skinData, vertices );
const skeleton = this._buildSkeleton( boneDefinitions, globalSequences );
const materials = this._buildMaterials( materialDefinitions );
const textureTransforms = this._buildTextureTransforms( textureTransformDefinitions, globalSequences );
const textureWeights = this._buildTextureWeights( textureWeightDefinitions, globalSequences );
const group = this._buildObjects( name, geometries, skeleton, materials, textures, textureTransforms, textureWeights, skinData, lookupTables );
// skins
return group;
let promise;
if ( header.version <= M2_VERSION_THE_BURNING_CRUSADE ) {
promise = Promise.resolve( this._readEmbeddedSkinData( parser, header ) );
} else {
let filename = ( name + '00.skin' ).toLowerCase(); // default skin name based on .m2 file
const skinFileDataIDs = chunks.get( 'SFID' );
if ( skinFileDataIDs !== undefined ) {
filename = skinFileDataIDs[ 0 ] + '.skin';
}
promise = new Promise( ( resolve, reject ) => {
skinLoader.load( filename, resolve, undefined, () => {
reject( new Error( 'THREE.M2Loader: Failed to load skin file: ' + filename ) );
} );
} );
}
// build
promise.then( skinData => {
const geometries = this._buildGeometries( skinData, vertices );
// const skeleton = this._buildSkeleton( boneDefinitions );
const materials = this._buildMaterials( materialDefinitions );
const textureTransforms = this._buildTextureTransforms( textureTransformDefinitions );
const textureWeights = this._buildTextureWeights( textureWeightDefinitions );
const group = this._buildObjects( name, geometries, materials, textures, textureTransforms, textureWeights, skinData, lookupTables );
onLoad( group );
} ).catch( onError );
} ).catch( onError );
}
_buildObjects( name, geometries, materials, textures, textureTransforms, textureWeights, skinData, lookupTables ) {
_buildObjects( name, geometries, skeleton, materials, textures, textureTransforms, textureWeights, skinData, lookupTables ) {

@@ -259,7 +222,7 @@ const group = new Group();

const geometry = geometries[ batch.skinSectionIndex ];
const material = materials[ batch.materialIndex ];
const material = materials[ batch.materialIndex ].clone(); // cloning is required since the same material might be animated differently
if ( animationMap.has( material ) === false ) {
animationMap.set( material, new Set() ); // store animations targeting this material
animationMap.set( material, [] ); // store animations targeting this material

@@ -274,7 +237,7 @@ }

material.map = textures[ textureIndex ].clone(); // cloning is required since the same texture might be transformed/animated differently
material.map = textures[ textureIndex ].clone(); // cloning is required since the same texture might be animated differently
if ( animationMap.has( material.map ) === false ) {
animationMap.set( material.map, new Set() ); // store animations targeting this texture
animationMap.set( material.map, [] ); // store animations targeting this texture

@@ -296,3 +259,3 @@ }

const textureAnimations = animationMap.get( material.map );
textureAnimations.add( ...textureTransform );
textureAnimations.push( ...textureTransform );

@@ -314,3 +277,3 @@ }

const materialAnimations = animationMap.get( material );
materialAnimations.add( ...textureWeight );
materialAnimations.push( ...textureWeight );

@@ -323,3 +286,23 @@ }

const mesh = new Mesh( geometry, material );
let mesh;
if ( skeleton !== null ) {
mesh = new SkinnedMesh( geometry, material );
mesh.bind( skeleton );
for ( let bone of skeleton.bones ) {
if ( bone.parent === null ) mesh.add( bone );
}
animationMap.set( group, skeleton.userData.animations );
} else {
mesh = new Mesh( geometry, material );
}
group.add( mesh );

@@ -348,2 +331,4 @@

const uv = [];
const skinIndex = [];
const skinWeight = [];

@@ -360,2 +345,4 @@ for ( let i = 0; i < localVertexList.length; i ++ ) {

uv.push( vertex.texCoords[ 0 ].x, vertex.texCoords[ 0 ].y );
skinIndex.push( vertex.boneIndices.x, vertex.boneIndices.y, vertex.boneIndices.z, vertex.boneIndices.w );
skinWeight.push( vertex.boneWeights.x, vertex.boneWeights.y, vertex.boneWeights.z, vertex.boneWeights.w );

@@ -367,2 +354,4 @@ }

const uvAttribute = new Float32BufferAttribute( uv, 2 );
const skinIndexAttribute = new Uint8BufferAttribute( skinIndex, 4 );
const skinWeightAttribute = new Uint8BufferAttribute( skinWeight, 4, true );

@@ -383,2 +372,4 @@ // geometries

geometry.setAttribute( 'uv', uvAttribute );
geometry.setAttribute( 'skinIndex', skinIndexAttribute );
geometry.setAttribute( 'skinWeight', skinWeightAttribute );
geometry.setIndex( index );

@@ -452,3 +443,3 @@

_buildSkeleton( boneDefinitions ) {
_buildSkeleton( boneDefinitions, globalSequences ) {

@@ -461,2 +452,5 @@ // TODO: Find out a better way for detecting static models

const bones = [];
const animations = [];
const keyframes = [];
const globalKeyframes = [];

@@ -466,51 +460,152 @@ for ( let i = 0; i < boneDefinitions.length; i ++ ) {

const boneDefinition = boneDefinitions[ i ];
const bone = new Bone();
bone.pivot = boneDefinition.pivot; // TODO: three.js does not support pivot points so this hack is required as well as overwriting Object3D.updateMatrix()
bones.push( bone );
// build hierarchy
const parentIndex = boneDefinition.parentBone;
if ( parentIndex !== - 1 ) bones[ parentIndex ].add( bone );
}
// animations
return new Skeleton( bones );
const translationData = boneDefinition.translation;
const rotationData = boneDefinition.rotation;
const scaleData = boneDefinition.scale;
}
for ( let j = 0; j < translationData.timestamps.length; j ++ ) {
_buildTextures( textureDefinitions, loader, name, chunks ) {
let maxTimeStamp = null;
const promises = [];
if ( translationData.globalSequence >= 0 ) {
for ( let i = 0; i < textureDefinitions.length; i ++ ) {
maxTimeStamp = globalSequences[ translationData.globalSequence ] / 1000;
const textureDefinition = textureDefinitions[ i ];
let filename = textureDefinition.filename;
}
// if the filename is empty, use the FileDataID field from the TXID chunk
const ti = translationData.timestamps[ j ];
const vi = translationData.values[ j ];
if ( filename === '' ) {
const times = [];
const values = [];
const textureFileDataIds = chunks.get( 'TXID' );
// ignore empty tracks
if ( textureFileDataIds !== undefined ) {
if ( ti.length <= 1 ) continue;
filename = textureFileDataIds[ i ] + '.blp';
// times
for ( let k = 0; k < ti.length; k ++ ) {
times.push( ti[ k ] / 1000 );
}
if ( maxTimeStamp !== null ) {
times[ times.length - 1 ] = maxTimeStamp;
}
// values
for ( let k = 0; k < vi.length; k += 3 ) {
values.push( vi[ k ] );
values.push( vi[ k + 1 ] );
values.push( vi[ k + 2 ] );
}
// interpolation type
const interpolation = this._getInterpolation( translationData.interpolationType );
// keyframe track
if ( maxTimeStamp !== null ) {
if ( globalKeyframes[ j ] === undefined ) globalKeyframes[ j ] = [];
globalKeyframes[ j ].push( new VectorKeyframeTrack( bone.uuid + '.position', times, values, interpolation ) );
} else {
if ( keyframes[ j ] === undefined ) keyframes[ j ] = [];
keyframes[ j ].push( new VectorKeyframeTrack( bone.uuid + '.position', times, values, interpolation ) );
}
}
// fallback: if the first texture has an empty name, use .m2 name
for ( let j = 0; j < rotationData.timestamps.length; j ++ ) {
if ( filename === '' ) {
let maxTimeStamp = null;
if ( i === 0 ) {
if ( rotationData.globalSequence >= 0 ) {
filename = ( name + '.blp' ).toLowerCase();
maxTimeStamp = globalSequences[ rotationData.globalSequence ] / 1000;
}
const ti = rotationData.timestamps[ j ];
const vi = rotationData.values[ j ];
const times = [];
const values = [];
// ignore empty tracks
if ( ti.length <= 1 ) continue;
// times
for ( let k = 0; k < ti.length; k ++ ) {
times.push( ti[ k ] / 1000 );
}
if ( maxTimeStamp !== null ) {
times[ times.length - 1 ] = maxTimeStamp;
}
// values
for ( let k = 0; k < vi.length; k += 4 ) {
values.push( vi[ k ] );
values.push( vi[ k + 1 ] );
values.push( vi[ k + 2 ] );
values.push( vi[ k + 3 ] );
}
// interpolation type
const interpolation = this._getInterpolation( rotationData.interpolationType );
// keyframe track
if ( maxTimeStamp !== null ) {
if ( globalKeyframes[ j ] === undefined ) globalKeyframes[ j ] = [];
globalKeyframes[ j ].push( new QuaternionKeyframeTrack( bone.uuid + '.quaternion', times, values, interpolation ) );
} else {
continue;
if ( keyframes[ j ] === undefined ) keyframes[ j ] = [];
keyframes[ j ].push( new QuaternionKeyframeTrack( bone.uuid + '.quaternion', times, values, interpolation ) );
}

@@ -520,28 +615,99 @@

//
for ( let j = 0; j < scaleData.timestamps.length; j ++ ) {
const promise = new Promise( ( resolve, reject ) => {
let maxTimeStamp = null;
const config = {
url: filename,
flags: textureDefinition.flags
};
if ( scaleData.globalSequence >= 0 ) {
loader.load( config, resolve, undefined, () => {
maxTimeStamp = globalSequences[ scaleData.globalSequence ] / 1000;
reject( new Error( 'THREE.M2Loader: Failed to load texture: ' + config.url ) );
}
} );
const ti = scaleData.timestamps[ j ];
const vi = scaleData.values[ j ];
} );
const times = [];
const values = [];
promises.push( promise );
// ignore empty tracks
if ( ti.length <= 1 ) continue;
// times
for ( let k = 0; k < ti.length; k ++ ) {
times.push( ti[ k ] / 1000 );
}
if ( maxTimeStamp !== null ) {
times[ times.length - 1 ] = maxTimeStamp;
}
// values
for ( let k = 0; k < vi.length; k += 3 ) {
values.push( vi[ k ] );
values.push( vi[ k + 1 ] );
values.push( vi[ k + 2 ] );
}
// interpolation type
const interpolation = this._getInterpolation( translationData.interpolationType );
// keyframe track
if ( maxTimeStamp !== null ) {
if ( globalKeyframes[ j ] === undefined ) globalKeyframes[ j ] = [];
globalKeyframes[ j ].push( new VectorKeyframeTrack( bone.uuid + '.scale', times, values, interpolation ) );
} else {
if ( keyframes[ j ] === undefined ) keyframes[ j ] = [];
keyframes[ j ].push( new VectorKeyframeTrack( bone.uuid + '.scale', times, values, interpolation ) );
}
}
}
return promises;
for ( let j = 0; j < keyframes.length; j ++ ) {
if ( keyframes[ j ] === undefined ) continue;
const clip = new AnimationClip( 'SkeletonAnimation_' + j, - 1, [ ... keyframes[ j ] ] );
animations.push( clip );
}
for ( let j = 0; j < globalKeyframes.length; j ++ ) {
if ( globalKeyframes[ j ] === undefined ) continue;
const clip = new AnimationClip( 'GlobalSkeletonAnimation_' + j, - 1, [ ... globalKeyframes[ j ] ] );
animations.push( clip );
}
const skeleton = new Skeleton( bones );
skeleton.userData = {};
skeleton.userData.animations = animations;
return skeleton;
}
_buildTextureTransforms( textureTransformDefinitions ) {
_buildTextureTransforms( textureTransformDefinitions, globalSequences ) {

@@ -558,10 +724,22 @@ const textureTransforms = [];

const keyframes = [];
const globalKeyframes = [];
const translationData = textureTransformDefinition.translation;
const rotationData = textureTransformDefinition.rotation;
// translation
for ( let j = 0; j < textureTransformDefinition.translation.timestamps.length; j ++ ) {
for ( let j = 0; j < translationData.timestamps.length; j ++ ) {
const ti = textureTransformDefinition.translation.timestamps[ j ];
const vi = textureTransformDefinition.translation.values[ j ];
let maxTimeStamp = null;
if ( translationData.globalSequence >= 0 ) {
maxTimeStamp = globalSequences[ translationData.globalSequence ] / 1000;
}
const ti = translationData.timestamps[ j ];
const vi = translationData.values[ j ];
const times = [];

@@ -582,2 +760,8 @@ const values = [];

if ( maxTimeStamp !== null ) {
times[ times.length - 1 ] = maxTimeStamp;
}
// values

@@ -594,6 +778,23 @@

if ( keyframes[ j ] === undefined ) keyframes[ j ] = [];
// interpolation type
keyframes[ j ].push( new VectorKeyframeTrack( '.offset', times, values ) );
const interpolation = this._getInterpolation( translationData.interpolationType );
// keyframe track
if ( maxTimeStamp !== null ) {
if ( globalKeyframes[ j ] === undefined ) globalKeyframes[ j ] = [];
globalKeyframes[ j ].push( new VectorKeyframeTrack( '.offset', times, values, interpolation ) );
} else {
if ( keyframes[ j ] === undefined ) keyframes[ j ] = [];
keyframes[ j ].push( new VectorKeyframeTrack( '.offset', times, values, interpolation ) );
}
}

@@ -609,7 +810,15 @@

for ( let j = 0; j < textureTransformDefinition.rotation.timestamps.length; j ++ ) {
for ( let j = 0; j < rotationData.timestamps.length; j ++ ) {
const ti = textureTransformDefinition.rotation.timestamps[ j ];
const vi = textureTransformDefinition.rotation.values[ j ];
let maxTimeStamp = null;
if ( translationData.globalSequence >= 0 ) {
maxTimeStamp = globalSequences[ translationData.globalSequence ];
}
const ti = rotationData.timestamps[ j ];
const vi = rotationData.values[ j ];
const times = [];

@@ -630,2 +839,8 @@ const values = [];

if ( maxTimeStamp !== null ) {
times[ times.length - 1 ] = maxTimeStamp;
}
// values

@@ -654,6 +869,23 @@

if ( keyframes[ j ] === undefined ) keyframes[ j ] = [];
// interpolation type
keyframes[ j ].push( new NumberKeyframeTrack( '.rotation', times, values ) );
const interpolation = this._getInterpolation( rotationData.interpolationType );
// keyframe track
if ( maxTimeStamp !== null ) {
if ( globalKeyframes[ j ] === undefined ) globalKeyframes[ j ] = [];
globalKeyframes[ j ].push( new NumberKeyframeTrack( '.offset', times, values, interpolation ) );
} else {
if ( keyframes[ j ] === undefined ) keyframes[ j ] = [];
keyframes[ j ].push( new NumberKeyframeTrack( '.rotation', times, values, interpolation ) );
}
}

@@ -668,2 +900,9 @@

for ( let j = 0; j < globalKeyframes.length; j ++ ) {
const clip = new AnimationClip( 'GlobalTextureTransform_' + j, - 1, [ ... globalKeyframes[ j ] ] );
animations.push( clip );
}
textureTransforms.push( animations );

@@ -679,3 +918,3 @@

_buildTextureWeights( textureWeightDefinitions ) {
_buildTextureWeights( textureWeightDefinitions, globalSequences ) {

@@ -692,5 +931,14 @@ const textureWeights = [];

const opacityKeyFrames = [];
const globalOpacityKeyFrames = [];
for ( let j = 0; j < textureWeightDefinition.timestamps.length; j ++ ) {
let maxTimeStamp = null;
if ( textureWeightDefinition.globalSequence >= 0 ) {
maxTimeStamp = globalSequences[ textureWeightDefinition.globalSequence ];
}
const ti = textureWeightDefinition.timestamps[ j ];

@@ -715,2 +963,8 @@ const vi = textureWeightDefinition.values[ j ];

if ( maxTimeStamp !== null ) {
times[ times.length - 1 ] = maxTimeStamp;
}
// values

@@ -724,4 +978,18 @@

opacityKeyFrames.push( new NumberKeyframeTrack( '.opacity', times, values ) );
// interpolation type
const interpolation = this._getInterpolation( textureWeightDefinition.interpolationType );
// keyframe track
if ( maxTimeStamp !== null ) {
globalOpacityKeyFrames.push( new NumberKeyframeTrack( '.opacity', times, values, interpolation ) );
} else {
opacityKeyFrames.push( new NumberKeyframeTrack( '.opacity', times, values, interpolation ) );
}
}

@@ -736,2 +1004,9 @@

for ( let j = 0; j < globalOpacityKeyFrames.length; j ++ ) {
const clip = new AnimationClip( 'GlobalOpacity_' + j, - 1, [ globalOpacityKeyFrames[ j ] ] );
animations.push( clip );
}
textureWeights.push( animations );

@@ -747,2 +1022,132 @@

_getInterpolation( type ) {
let interpolation;
switch ( type ) {
case 0:
interpolation = InterpolateDiscrete;
break;
case 1:
interpolation = InterpolateLinear;
break;
case 2:
case 3:
interpolation = InterpolateSmooth;
break;
default:
console.warn( 'THREE.M2Loader: Unsupported interpolation type.' );
interpolation = InterpolateLinear; // fallback
break;
}
return interpolation;
}
_loadSkin( header, parser, skinLoader, name, chunks ) {
let promise;
if ( header.version <= M2_VERSION_THE_BURNING_CRUSADE ) {
promise = Promise.resolve( this._readEmbeddedSkinData( parser, header ) );
} else {
let filename = ( name + '00.skin' ).toLowerCase(); // default skin name based on .m2 file
const skinFileDataIDs = chunks.get( 'SFID' );
if ( skinFileDataIDs !== undefined ) {
filename = skinFileDataIDs[ 0 ] + '.skin';
}
promise = new Promise( ( resolve, reject ) => {
skinLoader.load( filename, resolve, undefined, () => {
reject( new Error( 'THREE.M2Loader: Failed to load skin file: ' + filename ) );
} );
} );
}
return promise;
}
_loadTextures( textureDefinitions, loader, name, chunks ) {
const promises = [];
for ( let i = 0; i < textureDefinitions.length; i ++ ) {
const textureDefinition = textureDefinitions[ i ];
let filename = textureDefinition.filename;
// if the filename is empty, use the FileDataID field from the TXID chunk
if ( filename === '' ) {
const textureFileDataIds = chunks.get( 'TXID' );
if ( textureFileDataIds !== undefined ) {
filename = textureFileDataIds[ i ] + '.blp';
}
}
// fallback: if the first texture has an empty name, use .m2 name
if ( filename === '' ) {
if ( i === 0 ) {
filename = ( name + '.blp' ).toLowerCase();
} else {
continue;
}
}
//
const promise = new Promise( ( resolve, reject ) => {
const config = {
url: filename,
flags: textureDefinition.flags
};
loader.load( config, resolve, undefined, () => {
reject( new Error( 'THREE.M2Loader: Failed to load texture: ' + config.url ) );
} );
} );
promises.push( promise );
}
return promises;
}
_readHeader( parser ) {

@@ -1002,2 +1407,24 @@

_readGlobalSequences( parser, header ) {
const length = header.globalLoopsLength;
const offset = header.globalLoopsOffset;
parser.saveState();
parser.moveTo( offset );
const timestamps = [];
for ( let i = 0; i < length; i ++ ) {
timestamps.push( parser.readUInt32() );
}
parser.restoreState();
return timestamps;
}
_readMaterialDefinitions( parser, header ) {

@@ -1317,9 +1744,16 @@

values.push(
parser.readFloat32(),
parser.readFloat32(),
parser.readFloat32(),
parser.readFloat32()
);
// conversion from short to float, see https://wowdev.wiki/Quaternion_values_and_2.x
let x = parser.readInt16();
let y = parser.readInt16();
let z = parser.readInt16();
let w = parser.readInt16();
x = ( x < 0 ? x + 32768 : x - 32767 ) / 32767;
y = ( y < 0 ? y + 32768 : y - 32767 ) / 32767;
z = ( z < 0 ? z + 32768 : z - 32767 ) / 32767;
w = ( w < 0 ? w + 32768 : w - 32767 ) / 32767;
values.push( x, y, z, w );
break;

@@ -1392,15 +1826,11 @@

vertex.boneWeights.push(
parser.readUInt8(),
parser.readUInt8(),
parser.readUInt8(),
parser.readUInt8()
);
vertex.boneWeights.x = parser.readUInt8();
vertex.boneWeights.y = parser.readUInt8();
vertex.boneWeights.z = parser.readUInt8();
vertex.boneWeights.w = parser.readUInt8();
vertex.boneIndices.push(
parser.readUInt8(),
parser.readUInt8(),
parser.readUInt8(),
parser.readUInt8()
);
vertex.boneIndices.x = parser.readUInt8();
vertex.boneIndices.y = parser.readUInt8();
vertex.boneIndices.z = parser.readUInt8();
vertex.boneIndices.w = parser.readUInt8();

@@ -2158,4 +2588,4 @@ vertex.normal.x = parser.readFloat32();

this.pos = new Vector3();
this.boneWeights = [];
this.boneIndices = [];
this.boneWeights = new Vector4();
this.boneIndices = new Vector4();
this.normal = new Vector3();

@@ -2162,0 +2592,0 @@ this.texCoords = [ new Vector2(), new Vector2() ];

2

package.json
{
"name": "three-m2loader",
"version": "2.0.1",
"version": "2.1.0",
"description": "three.js loader for importing M2 assets from World of Warcraft.",

@@ -5,0 +5,0 @@ "main": "M2Loader.js",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc