* Rewritten M2 Animation loading code. Much nicer now.
Also: Lumirions updates for renderflags vertexcolor and materials to m2 loader and disabled model lighting in viewer since its set in the loader now (cherry picked from commit a5d909a8a5cf003615500169095700c2f525050b)
This commit is contained in:
parent
b77202fd1c
commit
57e8d3e844
@ -1,4 +1,4 @@
|
|||||||
// #define _DEBUG 1
|
#define _DEBUG 1
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include "MemoryDataHolder.h"
|
#include "MemoryDataHolder.h"
|
||||||
#include "MemoryInterface.h"
|
#include "MemoryInterface.h"
|
||||||
@ -47,18 +47,19 @@ IAnimatedMesh* CM2MeshFileLoader::createMesh(io::IReadFile* file)
|
|||||||
MeshFile = file;
|
MeshFile = file;
|
||||||
AnimatedMesh = new scene::CM2Mesh();
|
AnimatedMesh = new scene::CM2Mesh();
|
||||||
|
|
||||||
if ( load() )
|
if ( load() )
|
||||||
{
|
{
|
||||||
AnimatedMesh->finalize();
|
AnimatedMesh->finalize();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AnimatedMesh->drop();
|
AnimatedMesh->drop();
|
||||||
AnimatedMesh = 0;
|
AnimatedMesh = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return AnimatedMesh;
|
return AnimatedMesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CM2MeshFileLoader::ReadVertices()
|
void CM2MeshFileLoader::ReadVertices()
|
||||||
{
|
{
|
||||||
//Vertices. Global data
|
//Vertices. Global data
|
||||||
@ -137,264 +138,177 @@ void CM2MeshFileLoader::ReadViewData(io::IReadFile* file)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CM2MeshFileLoader::ReadABlock(AnimBlock &ABlock, u8 datatype, u8 datanum)
|
||||||
|
{
|
||||||
|
// datatypes: 0 = float, 1 = short
|
||||||
|
// datatype & datanum: datatype 0, datanum 3 --> read 3 floats
|
||||||
|
MeshFile->read(&ABlock.header,4);
|
||||||
|
if(header.version < 0x108)//Read/Skip Interpolation Ranges for preWOTLK
|
||||||
|
MeshFile->read(&ABlock.header.InterpolationRanges,sizeof(numofs));
|
||||||
|
MeshFile->read(&ABlock.header.TimeStamp,sizeof(numofs));
|
||||||
|
MeshFile->read(&ABlock.header.Values,sizeof(numofs));
|
||||||
|
u32 offset_next_entry = MeshFile->getPos();
|
||||||
|
// logdebug("%u %u %u %u",datatype, datanum, ABlock.header.TimeStamp.num,ABlock.header.Values.num);
|
||||||
|
|
||||||
|
core::array<numofs> data_offsets;
|
||||||
|
numofs tempNumOfs;
|
||||||
|
io::IReadFile* AnimFile;
|
||||||
|
|
||||||
|
//Read Timestamps
|
||||||
|
if(header.version < 0x108)
|
||||||
|
data_offsets.push_back(ABlock.header.TimeStamp);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MeshFile->seek(ABlock.header.TimeStamp.ofs);
|
||||||
|
for(u32 i =0;i < ABlock.header.TimeStamp.num; i++)
|
||||||
|
{
|
||||||
|
MeshFile->read(&tempNumOfs,sizeof(numofs));
|
||||||
|
data_offsets.push_back(tempNumOfs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(u32 i = 0; i < data_offsets.size(); i++)
|
||||||
|
{
|
||||||
|
tempNumOfs = data_offsets[i];
|
||||||
|
|
||||||
|
if((M2MAnimations[i].flags & 0x20) == 0 && (M2MAnimations[i].flags & 0x40))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
u32 offset = 0;
|
||||||
|
if(header.version >= 0x108)
|
||||||
|
offset = M2MAnimations[i].start;//HACK: Squashing WoTLK multitimelines into one single timeline
|
||||||
|
|
||||||
|
if(header.version >= 0x108 && M2MAnimfiles[i] != 0)
|
||||||
|
AnimFile = M2MAnimfiles[i];
|
||||||
|
else
|
||||||
|
AnimFile = MeshFile;
|
||||||
|
AnimFile->seek(tempNumOfs.ofs);
|
||||||
|
u32 tempTS;
|
||||||
|
for(u32 j = 0; j < tempNumOfs.num; j++)
|
||||||
|
{
|
||||||
|
AnimFile->read(&tempTS,sizeof(u32));
|
||||||
|
ABlock.timestamps.push_back(tempTS+offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data_offsets.clear();
|
||||||
|
//Read Values
|
||||||
|
if(header.version < 0x108)
|
||||||
|
data_offsets.push_back(ABlock.header.Values);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MeshFile->seek(ABlock.header.Values.ofs);
|
||||||
|
for(u32 i =0;i < ABlock.header.Values.num; i++)
|
||||||
|
{
|
||||||
|
MeshFile->read(&tempNumOfs,sizeof(numofs));
|
||||||
|
data_offsets.push_back(tempNumOfs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(u32 i = 0; i < data_offsets.size(); i++)
|
||||||
|
{
|
||||||
|
tempNumOfs = data_offsets[i];
|
||||||
|
if((M2MAnimations[i].flags & 0x20) == 0 && (M2MAnimations[i].flags & 0x40))
|
||||||
|
continue;
|
||||||
|
if(header.version >= 0x108 && M2MAnimfiles[i] != 0)
|
||||||
|
AnimFile = M2MAnimfiles[i];
|
||||||
|
else
|
||||||
|
AnimFile = MeshFile;
|
||||||
|
AnimFile->seek(tempNumOfs.ofs);
|
||||||
|
s16 tempShort;
|
||||||
|
s32 tempInt;
|
||||||
|
f32 tempFloat;
|
||||||
|
for(u32 j = 0; j < tempNumOfs.num * datanum; j++)
|
||||||
|
{
|
||||||
|
switch(datatype)
|
||||||
|
{
|
||||||
|
case ABDT_FLOAT:
|
||||||
|
AnimFile->read(&tempFloat,sizeof(f32));
|
||||||
|
break;
|
||||||
|
case ABDT_SHORT:
|
||||||
|
MeshFile->read(&tempShort, sizeof(s16));
|
||||||
|
tempFloat=(tempShort>0?tempShort-32767:tempShort+32767)/32767.0f;
|
||||||
|
break;
|
||||||
|
case ABDT_INT:
|
||||||
|
AnimFile->read(&tempInt,sizeof(s32));
|
||||||
|
tempFloat=tempInt * 1.0f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ABlock.values.push_back(tempFloat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MeshFile->seek(offset_next_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void CM2MeshFileLoader::ReadBones()
|
void CM2MeshFileLoader::ReadBones()
|
||||||
{
|
{
|
||||||
|
//Bones. This is global data
|
||||||
|
if(!M2MBones.empty())
|
||||||
|
{
|
||||||
|
M2MBones.clear();
|
||||||
|
}
|
||||||
|
MeshFile->seek(header.Bones.ofs);
|
||||||
|
for(u32 i=0;i<header.Bones.num;i++)
|
||||||
|
{
|
||||||
|
Bone tempBone;
|
||||||
|
MeshFile->read(&tempBone,12+(header.version==0x100?0:4));
|
||||||
|
|
||||||
|
ReadABlock(tempBone.translation,ABDT_FLOAT,3);
|
||||||
|
ReadABlock(tempBone.rotation,(header.version>=0x104?ABDT_SHORT:ABDT_FLOAT),4);
|
||||||
|
ReadABlock(tempBone.scaling,ABDT_FLOAT,3);
|
||||||
|
|
||||||
|
MeshFile->read(&tempBone.PivotPoint,sizeof(core::vector3df));
|
||||||
|
tempBone.PivotPoint=fixCoordSystem(tempBone.PivotPoint);
|
||||||
|
M2MBones.push_back(tempBone);
|
||||||
|
DEBUG(logdebug("Bone %u Parent %u PP %f %f %f Flags %X",i,tempBone.parentBone,tempBone.PivotPoint.X,tempBone.PivotPoint.Y,tempBone.PivotPoint.Z, tempBone.flags));
|
||||||
|
}
|
||||||
|
DEBUG(logdebug("Read %u Bones",M2MBones.size()));
|
||||||
|
|
||||||
//Bones. This is global data
|
|
||||||
Bone tempBone;
|
|
||||||
if(!M2MBones.empty())
|
|
||||||
{
|
|
||||||
M2MBones.clear();
|
|
||||||
}
|
}
|
||||||
MeshFile->seek(header.Bones.ofs);
|
|
||||||
for(u32 i=0;i<header.Bones.num;i++)
|
|
||||||
|
void CM2MeshFileLoader::ReadColors()
|
||||||
{
|
{
|
||||||
MeshFile->read(&tempBone,12+(header.version==0x100?0:4));
|
if (!M2MVertexColor.empty())
|
||||||
|
|
||||||
MeshFile->read(&tempBone.translation.header,sizeof(AnimBlockHead));
|
|
||||||
MeshFile->read(&tempBone.rotation.header,sizeof(AnimBlockHead));
|
|
||||||
MeshFile->read(&tempBone.scaling.header,sizeof(AnimBlockHead));
|
|
||||||
|
|
||||||
MeshFile->read(&tempBone.PivotPoint,sizeof(core::vector3df));
|
|
||||||
tempBone.PivotPoint=fixCoordSystem(tempBone.PivotPoint);
|
|
||||||
M2MBones.push_back(tempBone);
|
|
||||||
DEBUG(logdebug("Bone %u Parent %u PP %f %f %f Flags %X",i,tempBone.parentBone,tempBone.PivotPoint.X,tempBone.PivotPoint.Y,tempBone.PivotPoint.Z, tempBone.flags));
|
|
||||||
}
|
|
||||||
//Fill in values referenced in Bones. local to each bone
|
|
||||||
//Interpolation Ranges are not used
|
|
||||||
u32 tempBoneTS;
|
|
||||||
numofs tempBoneNumOfs;
|
|
||||||
float tempBoneValue;
|
|
||||||
for(u32 i=0; i<M2MBones.size(); i++)
|
|
||||||
{
|
|
||||||
|
|
||||||
if(M2MBones[i].translation.header.TimeStamp.num>0)
|
|
||||||
{
|
{
|
||||||
MeshFile->seek(M2MBones[i].translation.header.TimeStamp.ofs);
|
M2MVertexColor.clear();
|
||||||
for(u32 j=0; j<M2MBones[i].translation.header.TimeStamp.num;j++)
|
|
||||||
{
|
|
||||||
MeshFile->read(&tempBoneTS, sizeof(u32));
|
|
||||||
M2MBones[i].translation.timestamps.push_back(tempBoneTS);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if(M2MBones[i].rotation.header.TimeStamp.num>0)
|
//read vertex color and alpha offsets
|
||||||
|
logdebug("Colors");
|
||||||
|
MeshFile->seek(header.Colors.ofs);
|
||||||
|
for(u32 i=0;i<header.Colors.num;i++)
|
||||||
{
|
{
|
||||||
MeshFile->seek(M2MBones[i].rotation.header.TimeStamp.ofs);
|
VertexColor tempVC;
|
||||||
for(u32 j=0; j<M2MBones[i].rotation.header.TimeStamp.num;j++)
|
logdebug("Color %u",i);
|
||||||
{
|
ReadABlock(tempVC.Colors,ABDT_FLOAT,3);
|
||||||
MeshFile->read(&tempBoneTS, sizeof(u32));
|
ReadABlock(tempVC.Alpha,ABDT_SHORT,1);
|
||||||
M2MBones[i].rotation.timestamps.push_back(tempBoneTS);
|
M2MVertexColor.push_back(tempVC);
|
||||||
}
|
|
||||||
}
|
|
||||||
if(M2MBones[i].scaling.header.TimeStamp.num>0)
|
|
||||||
{
|
|
||||||
MeshFile->seek(M2MBones[i].scaling.header.TimeStamp.ofs);
|
|
||||||
for(u32 j=0; j<M2MBones[i].scaling.header.TimeStamp.num;j++)
|
|
||||||
{
|
|
||||||
MeshFile->read(&tempBoneTS, sizeof(u32));
|
|
||||||
M2MBones[i].scaling.timestamps.push_back(tempBoneTS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(M2MBones[i].translation.header.Values.num>0)
|
|
||||||
{
|
|
||||||
MeshFile->seek(M2MBones[i].translation.header.Values.ofs);
|
|
||||||
for(u32 j=0; j<M2MBones[i].translation.header.Values.num*3;j++)
|
|
||||||
{
|
|
||||||
MeshFile->read(&tempBoneValue, sizeof(float));
|
|
||||||
M2MBones[i].translation.values.push_back(tempBoneValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(M2MBones[i].rotation.header.Values.num>0)
|
|
||||||
{
|
|
||||||
MeshFile->seek(M2MBones[i].rotation.header.Values.ofs);
|
|
||||||
for(u32 j=0; j<M2MBones[i].rotation.header.Values.num*4;j++)
|
|
||||||
{
|
|
||||||
if(header.version>=0x104)
|
|
||||||
{
|
|
||||||
s16 tempBoneShort;
|
|
||||||
MeshFile->read(&tempBoneShort, sizeof(s16));
|
|
||||||
tempBoneValue=(tempBoneShort>0?tempBoneShort-32767:tempBoneShort+32767)/32767.0f;
|
|
||||||
M2MBones[i].rotation.values.push_back(tempBoneValue);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MeshFile->read(&tempBoneValue, sizeof(f32));
|
|
||||||
M2MBones[i].rotation.values.push_back(tempBoneValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(M2MBones[i].scaling.header.Values.num>0)
|
|
||||||
{
|
|
||||||
MeshFile->seek(M2MBones[i].scaling.header.Values.ofs);
|
|
||||||
for(u32 j=0; j<M2MBones[i].scaling.header.Values.num*3;j++)
|
|
||||||
{
|
|
||||||
MeshFile->read(&tempBoneValue, sizeof(float));
|
|
||||||
M2MBones[i].scaling.values.push_back(tempBoneValue);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DEBUG(logdebug("Read %u Bones",M2MBones.size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void CM2MeshFileLoader::ReadBonesWOTLK()
|
void CM2MeshFileLoader::ReadLights()
|
||||||
{
|
{
|
||||||
//Bones. This is global data
|
if (!M2MLights.empty())
|
||||||
Bone tempBone;
|
{
|
||||||
if(!M2MBones.empty())
|
M2MLights.clear();
|
||||||
{
|
}
|
||||||
M2MBones.clear();
|
//read Lights
|
||||||
}
|
MeshFile->seek(header.Lights.ofs);
|
||||||
MeshFile->seek(header.Bones.ofs);
|
|
||||||
for(u32 i=0;i<header.Bones.num;i++)
|
|
||||||
{
|
|
||||||
MeshFile->read(&tempBone,16);
|
|
||||||
//Skip Interpolation Ranges
|
|
||||||
MeshFile->read(&tempBone.translation.header,4);
|
|
||||||
MeshFile->read(&tempBone.translation.header.TimeStamp,sizeof(numofs));
|
|
||||||
MeshFile->read(&tempBone.translation.header.Values,sizeof(numofs));
|
|
||||||
MeshFile->read(&tempBone.rotation.header,4);
|
|
||||||
MeshFile->read(&tempBone.rotation.header.TimeStamp,sizeof(numofs));
|
|
||||||
MeshFile->read(&tempBone.rotation.header.Values,sizeof(numofs));
|
|
||||||
MeshFile->read(&tempBone.scaling.header,4);
|
|
||||||
MeshFile->read(&tempBone.scaling.header.TimeStamp,sizeof(numofs));
|
|
||||||
MeshFile->read(&tempBone.scaling.header.Values,sizeof(numofs));
|
|
||||||
|
|
||||||
MeshFile->read(&tempBone.PivotPoint,sizeof(core::vector3df));
|
for(u32 i=0;i<header.Lights.num;i++)
|
||||||
tempBone.PivotPoint=fixCoordSystem(tempBone.PivotPoint);
|
|
||||||
M2MBones.push_back(tempBone);
|
|
||||||
DEBUG(logdebug("Bone %u Parent %u PP %f %f %f Flags %X",i,tempBone.parentBone,tempBone.PivotPoint.X,tempBone.PivotPoint.Y,tempBone.PivotPoint.Z, tempBone.flags));
|
|
||||||
}
|
|
||||||
//Fill in values referenced in Bones. local to each bone
|
|
||||||
//Interpolation Ranges are not used
|
|
||||||
u32 tempBoneTS;
|
|
||||||
numofs tempBoneNumOfs;
|
|
||||||
core::array<numofs> tempNumOfsList;
|
|
||||||
float tempBoneValue;
|
|
||||||
s16 tempBoneShort;
|
|
||||||
//Animations can now be stored in anim files. To read quickly we need to swap the order of things.
|
|
||||||
//We are not loading stuff by bone but by animation - every bone has data for all the animations (I hope...)
|
|
||||||
//Animations MUST BE LOADED FIRST!!!!!
|
|
||||||
bool use_animfile;
|
|
||||||
io::IReadFile* AnimFile;
|
|
||||||
for(u32 i=0; i<M2MAnimations.size(); i++)
|
|
||||||
{
|
|
||||||
use_animfile = (M2MAnimations[i].flags & 0x20) == 0;
|
|
||||||
if(use_animfile)
|
|
||||||
{
|
|
||||||
std::string AnimName = MeshFile->getFileName().c_str();
|
|
||||||
c8 ext[13];
|
|
||||||
sprintf(ext,"%04d-%02d.anim",M2MAnimations[i].animationID,M2MAnimations[i].subanimationID);
|
|
||||||
AnimName = AnimName.substr(0, AnimName.length()-3) + ext;
|
|
||||||
AnimFile = io::IrrCreateIReadFileBasic(Device, AnimName.c_str());
|
|
||||||
if (!AnimFile)
|
|
||||||
{
|
{
|
||||||
logerror("Error! Anim file not found: %s", AnimName.c_str());
|
Light tempLight;
|
||||||
if(M2MAnimations[i].flags & 0x40)
|
MeshFile->read(&tempLight,16);//2xshort + vector3df
|
||||||
{
|
ReadABlock(tempLight.AmbientColor,ABDT_FLOAT,3);
|
||||||
logerror("We actually expected this error - Where is the data for this animation???");
|
ReadABlock(tempLight.AmbientIntensity,ABDT_FLOAT,1);
|
||||||
}
|
ReadABlock(tempLight.DiffuseColor,ABDT_FLOAT,3);
|
||||||
continue;
|
ReadABlock(tempLight.DiffuseIntensity,ABDT_FLOAT,1);
|
||||||
|
ReadABlock(tempLight.AttenuationStart,ABDT_FLOAT,1);
|
||||||
|
ReadABlock(tempLight.AttenuationEnd,ABDT_FLOAT,1);
|
||||||
|
ReadABlock(tempLight.Unknown,ABDT_INT,1);
|
||||||
|
M2MLights.push_back(tempLight);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AnimFile = MeshFile;
|
|
||||||
}
|
|
||||||
for(u32 j=0; j<M2MBones.size(); j++)
|
|
||||||
{
|
|
||||||
if(M2MBones[j].translation.header.TimeStamp.num>i && M2MBones[j].translation.header.Values.num>i)
|
|
||||||
{
|
|
||||||
//Timestamps
|
|
||||||
MeshFile->seek(M2MBones[j].translation.header.TimeStamp.ofs+8*i);
|
|
||||||
MeshFile->read(&tempBoneNumOfs, sizeof(numofs));
|
|
||||||
if(tempBoneNumOfs.num > 0)
|
|
||||||
{
|
|
||||||
AnimFile->seek(tempBoneNumOfs.ofs);
|
|
||||||
for(u32 k=0; k<tempBoneNumOfs.num;k++)
|
|
||||||
{
|
|
||||||
AnimFile->read(&tempBoneTS, sizeof(u32));
|
|
||||||
M2MBones[j].translation.timestamps.push_back(tempBoneTS+M2MAnimations[i].start);//Bit of a HACK squash Multitimeline into single timeline.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Values
|
|
||||||
MeshFile->seek(M2MBones[j].translation.header.Values.ofs+8*i);
|
|
||||||
MeshFile->read(&tempBoneNumOfs, sizeof(numofs));
|
|
||||||
if(tempBoneNumOfs.num > 0)
|
|
||||||
{
|
|
||||||
AnimFile->seek(tempBoneNumOfs.ofs);
|
|
||||||
for(u32 k=0; k<tempBoneNumOfs.num*3;k++)
|
|
||||||
{
|
|
||||||
AnimFile->read(&tempBoneValue, sizeof(float));
|
|
||||||
M2MBones[j].translation.values.push_back(tempBoneValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(M2MBones[j].rotation.header.TimeStamp.num>i && M2MBones[j].rotation.header.Values.num>i)
|
|
||||||
{
|
|
||||||
//Timestamps
|
|
||||||
MeshFile->seek(M2MBones[j].rotation.header.TimeStamp.ofs+8*i);
|
|
||||||
MeshFile->read(&tempBoneNumOfs, sizeof(numofs));
|
|
||||||
if(tempBoneNumOfs.num > 0)
|
|
||||||
{
|
|
||||||
AnimFile->seek(tempBoneNumOfs.ofs);
|
|
||||||
for(u32 k=0; k<tempBoneNumOfs.num;k++)
|
|
||||||
{
|
|
||||||
AnimFile->read(&tempBoneTS, sizeof(u32));
|
|
||||||
M2MBones[j].rotation.timestamps.push_back(tempBoneTS+M2MAnimations[i].start);//Bit of a HACK squash Multitimeline into single timeline.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Values
|
|
||||||
MeshFile->seek(M2MBones[j].rotation.header.Values.ofs+8*i);
|
|
||||||
MeshFile->read(&tempBoneNumOfs, sizeof(numofs));
|
|
||||||
if(tempBoneNumOfs.num > 0)
|
|
||||||
{
|
|
||||||
AnimFile->seek(tempBoneNumOfs.ofs);
|
|
||||||
for(u32 k=0; k<tempBoneNumOfs.num*4;k++)
|
|
||||||
{
|
|
||||||
AnimFile->read(&tempBoneShort, sizeof(s16));
|
|
||||||
tempBoneValue=(tempBoneShort>0?tempBoneShort-32767:tempBoneShort+32767)/32767.0f;
|
|
||||||
M2MBones[j].rotation.values.push_back(tempBoneValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(M2MBones[j].scaling.header.TimeStamp.num>i && M2MBones[j].scaling.header.Values.num>i)
|
|
||||||
{
|
|
||||||
//Timestamps
|
|
||||||
MeshFile->seek(M2MBones[j].scaling.header.TimeStamp.ofs+8*i);
|
|
||||||
MeshFile->read(&tempBoneNumOfs, sizeof(numofs));
|
|
||||||
if(tempBoneNumOfs.num > 0)
|
|
||||||
{
|
|
||||||
AnimFile->seek(tempBoneNumOfs.ofs);
|
|
||||||
for(u32 k=0; k<tempBoneNumOfs.num;k++)
|
|
||||||
{
|
|
||||||
AnimFile->read(&tempBoneTS, sizeof(u32));
|
|
||||||
M2MBones[j].scaling.timestamps.push_back(tempBoneTS+M2MAnimations[i].start);//Bit of a HACK squash Multitimeline into single timeline.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Values
|
|
||||||
MeshFile->seek(M2MBones[j].scaling.header.Values.ofs+8*i);
|
|
||||||
MeshFile->read(&tempBoneNumOfs, sizeof(numofs));
|
|
||||||
if(tempBoneNumOfs.num > 0)
|
|
||||||
{
|
|
||||||
AnimFile->seek(tempBoneNumOfs.ofs);
|
|
||||||
for(u32 k=0; k<tempBoneNumOfs.num*3;k++)
|
|
||||||
{
|
|
||||||
AnimFile->read(&tempBoneValue, sizeof(float));
|
|
||||||
M2MBones[j].scaling.values.push_back(tempBoneValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(use_animfile)
|
|
||||||
{
|
|
||||||
AnimFile->drop();
|
|
||||||
}
|
|
||||||
AnimFile=0;
|
|
||||||
}
|
|
||||||
DEBUG(logdebug("Read %u Bones",M2MBones.size()));
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -476,6 +390,26 @@ void CM2MeshFileLoader::ReadAnimationData()
|
|||||||
tempAnimation.start = laststart;
|
tempAnimation.start = laststart;
|
||||||
tempAnimation.end = laststart + tempRaw.length;
|
tempAnimation.end = laststart + tempRaw.length;
|
||||||
laststart += tempRaw.length + 1000;
|
laststart += tempRaw.length + 1000;
|
||||||
|
if((tempAnimation.flags & 0x20) == 0)
|
||||||
|
{
|
||||||
|
std::string AnimName = MeshFile->getFileName().c_str();
|
||||||
|
c8 ext[13];
|
||||||
|
sprintf(ext,"%04d-%02d.anim",tempAnimation.animationID,tempAnimation.subanimationID);
|
||||||
|
AnimName = AnimName.substr(0, AnimName.length()-3) + ext;
|
||||||
|
io::IReadFile* AnimFile = io::IrrCreateIReadFileBasic(Device, AnimName.c_str());
|
||||||
|
if (!AnimFile)
|
||||||
|
{
|
||||||
|
logerror("Error! Anim file not found: %s", AnimName.c_str());
|
||||||
|
if(tempAnimation.flags & 0x40)
|
||||||
|
{
|
||||||
|
logerror("We actually expected this error - Where is the data for this animation???");
|
||||||
|
}
|
||||||
|
AnimFile=0;
|
||||||
|
}
|
||||||
|
M2MAnimfiles.push_back(AnimFile);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
M2MAnimfiles.push_back(0);
|
||||||
}
|
}
|
||||||
M2MAnimations.push_back(tempAnimation);
|
M2MAnimations.push_back(tempAnimation);
|
||||||
DEBUG(logdebug("Animation %u Id %u Start %u End %u",i,tempAnimation.animationID,tempAnimation.start,tempAnimation.end));
|
DEBUG(logdebug("Animation %u Id %u Start %u End %u",i,tempAnimation.animationID,tempAnimation.start,tempAnimation.end));
|
||||||
@ -563,66 +497,62 @@ void CM2MeshFileLoader::ReadTextureDefinitions()
|
|||||||
|
|
||||||
bool CM2MeshFileLoader::load()
|
bool CM2MeshFileLoader::load()
|
||||||
{
|
{
|
||||||
DEBUG(logdebug("Trying to open file %s",MeshFile->getFileName()));
|
DEBUG(logdebug("Trying to open file %s",MeshFile->getFileName().c_str()));
|
||||||
|
|
||||||
MeshFile->read(&header,20);
|
MeshFile->read(&header,20);
|
||||||
DEBUG(logdebug("M2 Version %X",header.version));
|
DEBUG(logdebug("M2 Version %X",header.version));
|
||||||
|
|
||||||
switch(header.version)
|
switch(header.version)
|
||||||
{
|
|
||||||
case 0x100:
|
|
||||||
case 0x104://NEED CHECK
|
|
||||||
case 0x105://NEED CHECK
|
|
||||||
case 0x106://NEED CHECK
|
|
||||||
case 0x107://NEED CHECK
|
|
||||||
{
|
{
|
||||||
MeshFile->read((u8*)&header+20,sizeof(ModelHeader)-20);
|
case 0x100:
|
||||||
MeshFile->seek(header.Views.ofs);
|
case 0x104://NEED CHECK
|
||||||
MeshFile->read(¤tView,sizeof(ModelView));
|
case 0x105://NEED CHECK
|
||||||
ReadViewData(MeshFile);
|
case 0x106://NEED CHECK
|
||||||
ReadVertices();
|
case 0x107://NEED CHECK
|
||||||
|
|
||||||
ReadTextureDefinitions();
|
|
||||||
ReadAnimationData();
|
|
||||||
|
|
||||||
ReadBones();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 0x108:
|
|
||||||
{
|
|
||||||
//This is pretty ugly... Any suggestions how to make this nicer?
|
|
||||||
MeshFile->read((u8*)&header+0x14,24);//nGlobalSequences - ofsAnimationLookup
|
|
||||||
MeshFile->read((u8*)&header+0x34,28);//nBones - nViews
|
|
||||||
MeshFile->read((u8*)&header+0x54,24);//nColors - nTransparency
|
|
||||||
MeshFile->read((u8*)&header+0x74,sizeof(ModelHeader)-0x74);//nTexAnims - END
|
|
||||||
|
|
||||||
std::string SkinName = MeshFile->getFileName().c_str();
|
|
||||||
SkinName = SkinName.substr(0, SkinName.length()-3) + "00.skin"; // FIX ME if we need more skins
|
|
||||||
io::IReadFile* SkinFile = io::IrrCreateIReadFileBasic(Device, SkinName.c_str());
|
|
||||||
if (!SkinFile)
|
|
||||||
{
|
{
|
||||||
logerror("Error! Skin file not found: %s", SkinName.c_str());
|
MeshFile->read((u8*)&header+20,sizeof(ModelHeader)-20);
|
||||||
return 0;
|
MeshFile->seek(header.Views.ofs);
|
||||||
|
MeshFile->read(¤tView,sizeof(ModelView));
|
||||||
|
ReadViewData(MeshFile);
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
SkinFile->seek(4); // Header of Skin Files is always SKIN
|
case 0x108:
|
||||||
SkinFile->read(¤tView,sizeof(ModelView));
|
{
|
||||||
ReadViewData(SkinFile);
|
//This is pretty ugly... Any suggestions how to make this nicer?
|
||||||
SkinFile->drop();
|
MeshFile->read((u8*)&header+0x14,24);//nGlobalSequences - ofsAnimationLookup
|
||||||
|
MeshFile->read((u8*)&header+0x34,28);//nBones - nViews
|
||||||
|
MeshFile->read((u8*)&header+0x54,24);//nColors - nTransparency
|
||||||
|
MeshFile->read((u8*)&header+0x74,sizeof(ModelHeader)-0x74);//nTexAnims - END
|
||||||
|
|
||||||
ReadVertices();
|
std::string SkinName = MeshFile->getFileName().c_str();
|
||||||
|
SkinName = SkinName.substr(0, SkinName.length()-3) + "00.skin"; // FIX ME if we need more skins
|
||||||
|
io::IReadFile* SkinFile = io::IrrCreateIReadFileBasic(Device, SkinName.c_str());
|
||||||
|
if (!SkinFile)
|
||||||
|
{
|
||||||
|
logerror("Error! Skin file not found: %s", SkinName.c_str());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
SkinFile->seek(4); // Header of Skin Files is always SKIN
|
||||||
|
SkinFile->read(¤tView,sizeof(ModelView));
|
||||||
|
ReadViewData(SkinFile);
|
||||||
|
SkinFile->drop();
|
||||||
|
|
||||||
ReadTextureDefinitions();
|
break;
|
||||||
ReadAnimationData();
|
}
|
||||||
|
default:
|
||||||
ReadBonesWOTLK();
|
{
|
||||||
break;
|
logerror("M2: [%s] Wrong header %0X! File version doesn't match or file is not a M2 file.",MeshFile->getFileName().c_str(),header.version);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
default:
|
ReadVertices();
|
||||||
{
|
|
||||||
logerror("M2: [%s] Wrong header %0X! File version doesn't match or file is not a M2 file.",MeshFile->getFileName().c_str(),header.version);
|
ReadTextureDefinitions();
|
||||||
return 0;
|
ReadAnimationData();
|
||||||
}
|
|
||||||
}
|
ReadBones();
|
||||||
|
ReadColors();
|
||||||
|
|
||||||
///////////////////////////
|
///////////////////////////
|
||||||
// EVERYTHING IS READ
|
// EVERYTHING IS READ
|
||||||
@ -677,9 +607,9 @@ for(u32 i=0;i<M2MBones.size();i++)
|
|||||||
{
|
{
|
||||||
for(u32 j=0;j<M2MBones[i].scaling.timestamps.size();j++)
|
for(u32 j=0;j<M2MBones[i].scaling.timestamps.size();j++)
|
||||||
{
|
{
|
||||||
scene::CM2Mesh::SScaleKey* scale=AnimatedMesh->addScaleKey(Joint);
|
scene::CM2Mesh::SScaleKey* scale=AnimatedMesh->addScaleKey(Joint);
|
||||||
scale->frame=M2MBones[i].scaling.timestamps[j];
|
scale->frame=M2MBones[i].scaling.timestamps[j];
|
||||||
scale->scale=core::vector3df(M2MBones[i].scaling.values[j*3],M2MBones[i].scaling.values[j*3+1],M2MBones[i].scaling.values[j*3+2]);
|
scale->scale=core::vector3df(M2MBones[i].scaling.values[j*3],M2MBones[i].scaling.values[j*3+1],M2MBones[i].scaling.values[j*3+2]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -750,8 +680,20 @@ for(u32 i=0; i < currentView.Submesh.num;i++)//
|
|||||||
{
|
{
|
||||||
if(M2MTextureUnit[j].submeshIndex1==i && !M2MTextureFiles[M2MTextureLookup[M2MTextureUnit[j].textureIndex]].empty())//if a texture unit belongs to this submesh
|
if(M2MTextureUnit[j].submeshIndex1==i && !M2MTextureFiles[M2MTextureLookup[M2MTextureUnit[j].textureIndex]].empty())//if a texture unit belongs to this submesh
|
||||||
{
|
{
|
||||||
|
//set vertex colors from colorblock
|
||||||
|
//FIXME: Only the first color is used, no animation!!
|
||||||
|
s16 vColIndex = M2MTextureUnit[j].colorIndex;
|
||||||
|
if(vColIndex != -1)
|
||||||
|
{
|
||||||
|
DEBUG(logdebug("Applying color %f %f %f %f",M2MVertexColor[vColIndex].Colors.values[0],M2MVertexColor[vColIndex].Colors.values[1],M2MVertexColor[vColIndex].Colors.values[2],M2MVertexColor[vColIndex].Alpha.values[0]));
|
||||||
|
video::SColor color = video::SColorf(M2MVertexColor[vColIndex].Colors.values[0],M2MVertexColor[vColIndex].Colors.values[1],M2MVertexColor[vColIndex].Colors.values[2],M2MVertexColor[vColIndex].Alpha.values[0]).toSColor();
|
||||||
|
// Device->getSceneManager()->getMeshManipulator()->apply(scene::SVertexColorSetManipulator(color), MeshBuffer);//
|
||||||
|
//MeshBuffer->getMaterial().DiffuseColor = color; // if we want to set diffuse instead of vertex color
|
||||||
|
}
|
||||||
|
// get and set texture
|
||||||
char buf[1000];
|
char buf[1000];
|
||||||
MemoryDataHolder::MakeTextureFilename(buf,M2MTextureFiles[M2MTextureLookup[M2MTextureUnit[j].textureIndex]].c_str());
|
MemoryDataHolder::MakeTextureFilename(buf,M2MTextureFiles[M2MTextureLookup[M2MTextureUnit[j].textureIndex]].c_str());
|
||||||
|
|
||||||
video::ITexture* tex = Device->getVideoDriver()->findTexture(buf);
|
video::ITexture* tex = Device->getVideoDriver()->findTexture(buf);
|
||||||
if(!tex)
|
if(!tex)
|
||||||
{
|
{
|
||||||
@ -764,20 +706,45 @@ for(u32 i=0; i < currentView.Submesh.num;i++)//
|
|||||||
tex = Device->getVideoDriver()->getTexture(TexFile);
|
tex = Device->getVideoDriver()->getTexture(TexFile);
|
||||||
TexFile->drop();
|
TexFile->drop();
|
||||||
}
|
}
|
||||||
MeshBuffer->getMaterial().setTexture(M2MTextureUnit[j].TextureUnitNumber,tex);
|
MeshBuffer->getMaterial().setTexture(M2MTextureUnit[j].TextureUnitNumber,tex); // set a condition here to handle animated textures if they are used irrlicht.sourceforge.net/docu/example008.html
|
||||||
|
|
||||||
DEBUG(logdebug("Render Flags: %u %u",M2MRenderFlags[M2MTextureUnit[j].renderFlagsIndex].flags,M2MRenderFlags[M2MTextureUnit[j].renderFlagsIndex].blending));
|
DEBUG(logdebug("Render Flags: %u %u",M2MRenderFlags[M2MTextureUnit[j].renderFlagsIndex].flags,M2MRenderFlags[M2MTextureUnit[j].renderFlagsIndex].blending));
|
||||||
MeshBuffer->getMaterial().BackfaceCulling=(M2MRenderFlags[M2MTextureUnit[j].renderFlagsIndex].flags & 0x04)?false:true;
|
u16 renderflags = M2MRenderFlags[M2MTextureUnit[j].renderFlagsIndex].flags;
|
||||||
|
|
||||||
|
MeshBuffer->getMaterial().Lighting=(renderflags & 0x01)?false:true;
|
||||||
|
MeshBuffer->getMaterial().FogEnable=(renderflags & 0x02)?false:true;
|
||||||
|
MeshBuffer->getMaterial().BackfaceCulling=(renderflags & 0x04)?false:true;
|
||||||
|
//We have a problem here
|
||||||
|
// MeshBuffer->getMaterial().ZBuffer=(renderflags & 0x10)?video::ECFN_LESS:video::ECFN_LESSEQUAL;
|
||||||
|
|
||||||
switch(M2MRenderFlags[M2MTextureUnit[j].renderFlagsIndex].blending)
|
switch(M2MRenderFlags[M2MTextureUnit[j].renderFlagsIndex].blending)
|
||||||
{
|
{
|
||||||
case 1:
|
case 0: //opaque
|
||||||
MeshBuffer->getMaterial().MaterialType=video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF ;
|
MeshBuffer->getMaterial().MaterialType=video::EMT_SOLID;
|
||||||
|
break;
|
||||||
|
case 1: //alpha ref
|
||||||
|
MeshBuffer->getMaterial().MaterialType=video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; // default REF = 127 maybe set a lower REF?
|
||||||
|
break;
|
||||||
|
case 2: //alpha blend
|
||||||
|
MeshBuffer->getMaterial().MaterialType=video::EMT_ONETEXTURE_BLEND;
|
||||||
|
MeshBuffer->getMaterial().MaterialTypeParam = pack_texureBlendFunc(video::EBF_SRC_ALPHA, video::EBF_ONE_MINUS_SRC_ALPHA, video::EMFN_MODULATE_1X, video::EAS_TEXTURE|video::EAS_VERTEX_COLOR);
|
||||||
|
break;
|
||||||
|
case 3: //additive
|
||||||
|
MeshBuffer->getMaterial().MaterialType=video::EMT_ONETEXTURE_BLEND;
|
||||||
|
MeshBuffer->getMaterial().MaterialTypeParam = pack_texureBlendFunc(video::EBF_SRC_COLOR, video::EBF_DST_COLOR, video::EMFN_MODULATE_1X, video::EAS_TEXTURE|video::EAS_VERTEX_COLOR); // | video::EAS_VERTEX_COLOR);
|
||||||
|
break;
|
||||||
|
case 4: //additive alpha
|
||||||
|
MeshBuffer->getMaterial().MaterialType=video::EMT_ONETEXTURE_BLEND;
|
||||||
|
MeshBuffer->getMaterial().MaterialTypeParam = pack_texureBlendFunc(video::EBF_SRC_ALPHA, video::EBF_ONE, video::EMFN_MODULATE_2X, video::EAS_TEXTURE|video::EAS_VERTEX_COLOR);
|
||||||
|
break;
|
||||||
|
case 5: //modulate blend
|
||||||
|
MeshBuffer->getMaterial().MaterialType=video::EMT_ONETEXTURE_BLEND;
|
||||||
|
MeshBuffer->getMaterial().MaterialTypeParam = pack_texureBlendFunc(video::EBF_SRC_COLOR, video::EBF_DST_COLOR, video::EMFN_MODULATE_1X, video::EAS_TEXTURE|video::EAS_VERTEX_COLOR);
|
||||||
|
break;
|
||||||
|
case 6: //Lumirion: not sure exatly so I'm using modulate2x blend like wowmodelviewer or could use EMT_TRANSPARENT_ADD_COLOR
|
||||||
|
MeshBuffer->getMaterial().MaterialType=video::EMT_ONETEXTURE_BLEND;
|
||||||
|
MeshBuffer->getMaterial().MaterialTypeParam = pack_texureBlendFunc(video::EBF_DST_COLOR, video::EBF_SRC_COLOR, video::EMFN_MODULATE_2X, video::EAS_TEXTURE|video::EAS_VERTEX_COLOR);
|
||||||
break;
|
break;
|
||||||
case 2:
|
|
||||||
case 4:
|
|
||||||
MeshBuffer->getMaterial().MaterialType=video::EMT_TRANSPARENT_ALPHA_CHANNEL;
|
|
||||||
DEBUG(logdebug("Alpha Channel Transparency on"));
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -792,6 +759,14 @@ for(u32 i=0; i < currentView.Submesh.num;i++)//
|
|||||||
}
|
}
|
||||||
Device->getSceneManager()->getMeshManipulator()->flipSurfaces(AnimatedMesh);
|
Device->getSceneManager()->getMeshManipulator()->flipSurfaces(AnimatedMesh);
|
||||||
|
|
||||||
|
for(u32 i=0; i< M2MAnimfiles.size();i++)
|
||||||
|
{
|
||||||
|
if(M2MAnimfiles[i]!=0)
|
||||||
|
M2MAnimfiles[i]->drop();
|
||||||
|
}
|
||||||
|
|
||||||
|
M2MAnimations.clear();
|
||||||
|
M2MAnimfiles.clear();
|
||||||
M2MTriangles.clear();
|
M2MTriangles.clear();
|
||||||
M2Vertices.clear();
|
M2Vertices.clear();
|
||||||
M2Indices.clear();
|
M2Indices.clear();
|
||||||
@ -803,6 +778,8 @@ M2MTextureDef.clear();
|
|||||||
M2MSubmeshes.clear();
|
M2MSubmeshes.clear();
|
||||||
M2MTextureFiles.clear();
|
M2MTextureFiles.clear();
|
||||||
M2MTextureLookup.clear();
|
M2MTextureLookup.clear();
|
||||||
|
M2MVertexColor.clear();
|
||||||
|
M2MLights.clear();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,12 @@ namespace irr
|
|||||||
namespace scene
|
namespace scene
|
||||||
{
|
{
|
||||||
|
|
||||||
|
enum ABlockDatatype{
|
||||||
|
ABDT_FLOAT,
|
||||||
|
ABDT_SHORT,
|
||||||
|
ABDT_INT
|
||||||
|
};
|
||||||
|
|
||||||
struct numofs {
|
struct numofs {
|
||||||
u32 num;
|
u32 num;
|
||||||
u32 ofs;
|
u32 ofs;
|
||||||
@ -182,6 +188,24 @@ struct Bone{
|
|||||||
core::vector3df PivotPoint;
|
core::vector3df PivotPoint;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct VertexColor{
|
||||||
|
AnimBlock Colors;
|
||||||
|
AnimBlock Alpha;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct Light{
|
||||||
|
u16 Type;
|
||||||
|
s16 Bone;
|
||||||
|
core::vector3df Position;
|
||||||
|
AnimBlock AmbientColor;
|
||||||
|
AnimBlock AmbientIntensity;
|
||||||
|
AnimBlock DiffuseColor;
|
||||||
|
AnimBlock DiffuseIntensity;
|
||||||
|
AnimBlock AttenuationStart;
|
||||||
|
AnimBlock AttenuationEnd;
|
||||||
|
AnimBlock Unknown;
|
||||||
|
};
|
||||||
|
|
||||||
class CM2MeshFileLoader : public IMeshLoader
|
class CM2MeshFileLoader : public IMeshLoader
|
||||||
{
|
{
|
||||||
@ -206,11 +230,13 @@ private:
|
|||||||
|
|
||||||
bool load();
|
bool load();
|
||||||
void ReadBones();
|
void ReadBones();
|
||||||
void ReadBonesWOTLK();
|
void ReadColors();
|
||||||
|
void ReadLights();
|
||||||
void ReadVertices();
|
void ReadVertices();
|
||||||
void ReadTextureDefinitions();
|
void ReadTextureDefinitions();
|
||||||
void ReadAnimationData();
|
void ReadAnimationData();
|
||||||
void ReadViewData(io::IReadFile* file);
|
void ReadViewData(io::IReadFile* file);
|
||||||
|
void ReadABlock(AnimBlock &ABlock, u8 datatype, u8 datanum);
|
||||||
|
|
||||||
IrrlichtDevice *Device;
|
IrrlichtDevice *Device;
|
||||||
core::stringc Texdir;
|
core::stringc Texdir;
|
||||||
@ -227,6 +253,8 @@ private:
|
|||||||
SMesh* Mesh;
|
SMesh* Mesh;
|
||||||
//SSkinMeshBuffer* MeshBuffer;
|
//SSkinMeshBuffer* MeshBuffer;
|
||||||
//Taken from the Model file, thus m2M*
|
//Taken from the Model file, thus m2M*
|
||||||
|
core::array<Light> M2MLights;
|
||||||
|
core::array<VertexColor> M2MVertexColor;
|
||||||
core::array<ModelVertex> M2MVertices;
|
core::array<ModelVertex> M2MVertices;
|
||||||
core::array<u16> M2MIndices;
|
core::array<u16> M2MIndices;
|
||||||
core::array<u16> M2MTriangles;
|
core::array<u16> M2MTriangles;
|
||||||
@ -238,6 +266,8 @@ private:
|
|||||||
core::array<RenderFlags> M2MRenderFlags;
|
core::array<RenderFlags> M2MRenderFlags;
|
||||||
core::array<u32> M2MGlobalSequences;
|
core::array<u32> M2MGlobalSequences;
|
||||||
core::array<Animation> M2MAnimations;
|
core::array<Animation> M2MAnimations;
|
||||||
|
core::array<io::IReadFile*> M2MAnimfiles;//Stores pointers to *.anim files in WOTLK
|
||||||
|
|
||||||
core::array<s16> M2MAnimationLookup;
|
core::array<s16> M2MAnimationLookup;
|
||||||
core::array<Bone> M2MBones;
|
core::array<Bone> M2MBones;
|
||||||
core::array<u16> M2MBoneLookupTable;
|
core::array<u16> M2MBoneLookupTable;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user