irrlicht/source/Irrlicht/CAnimatedMeshB3d.cpp

1958 lines
50 KiB
C++

// Copyright (C) 2002-2007 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
// B3D file loader by Luke Hoschke, File format by Mark Sibly
#include "CAnimatedMeshB3d.h"
#include "os.h"
#include "IVideoDriver.h"
#include "ISceneNode.h"
namespace irr
{
namespace scene
{
struct B3dChunkHeader
{
c8 name[4];
s32 size;
};
//! constructor
CAnimatedMeshB3d::CAnimatedMeshB3d(video::IVideoDriver* driver)
: Driver(driver)
{
if (Driver)
Driver->grab();
}
//! destructor
CAnimatedMeshB3d::~CAnimatedMeshB3d()
{
if (Driver)
Driver->drop();
s32 n;
for (n=BaseVertices.size()-1; n>=0; --n)
{
delete BaseVertices[n];
BaseVertices.erase(n);
}
for (n=Buffers.size()-1; n>=0; --n)
{
delete Buffers[n];
Buffers.erase(n);
}
for (n=Materials.size()-1; n>=0; --n)
{
if (Materials[n].Material)
delete Materials[n].Material;
Materials.erase(n);
}
for (n=Nodes.size()-1; n>=0; --n)
{
delete Nodes[n];
Nodes.erase(n);
}
}
core::stringc CAnimatedMeshB3d::readString(io::IReadFile* file)
{
core::stringc newstring;
while (file->getPos() <= file->getSize() )
{
c8 character;
file->read(&character, sizeof(character));
if (character==0)
return newstring;
newstring.append(character);
}
return newstring;
}
core::stringc CAnimatedMeshB3d::stripPathString(core::string<c8> oldstring, bool keepPath)
{
s32 lastA=oldstring.findLast('/'); // forward slash
s32 lastB=oldstring.findLast('\\'); // back slash
if (!keepPath)
{
if (lastA==-1 && lastB==-1)
return oldstring;
}
else
{
if (lastA==-1 && lastB==-1)
return core::stringc();
}
if (lastA > lastB)
{
if (!keepPath)
return oldstring.subString(lastA+1, oldstring.size() - (lastA+1));
else
return oldstring.subString(0, lastA + 1);
}
else
{
if (!keepPath)
return oldstring.subString(lastB+1, oldstring.size() - (lastB+1));
else
return oldstring.subString(0, lastB + 1);
}
}
void CAnimatedMeshB3d::readFloats(io::IReadFile* file, f32* vec, u32 count)
{
file->read(vec, count*sizeof(f32));
#ifdef __BIG_ENDIAN__
for (u32 n=0; n<count; ++n)
vec[n] = os::Byteswap::byteswap(vec[n]);
#endif
}
bool CAnimatedMeshB3d::ReadChunkTEXS(io::IReadFile* file)
{
bool Previous32BitTextureFlag = Driver->getTextureCreationFlag(video::ETCF_ALWAYS_32_BIT);
Driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true);
while(B3dStack.getLast().startposition + B3dStack.getLast().length>file->getPos()) //this chunk repeats
{
core::stringc TextureName=readString(file);
TextureName=stripPathString(file->getFileName(),true) + stripPathString(TextureName,false);
SB3dTexture B3dTexture;
B3dTexture.Texture=Driver->getTexture ( TextureName.c_str() );
file->read(&B3dTexture.Flags, sizeof(s32));
file->read(&B3dTexture.Blend, sizeof(s32));
readFloats(file, &B3dTexture.Xpos, 1);
readFloats(file, &B3dTexture.Ypos, 1);
readFloats(file, &B3dTexture.Xscale, 1);
readFloats(file, &B3dTexture.Yscale, 1);
readFloats(file, &B3dTexture.Angle, 1);
#ifdef __BIG_ENDIAN__
B3dTexture.Flags = os::Byteswap::byteswap(B3dTexture.Flags);
B3dTexture.Blend = os::Byteswap::byteswap(B3dTexture.Blend);
#endif
Textures.push_back(B3dTexture);
/*
Flags:
1: Color (default)
2: Alpha
4: Masked
8: Mipmapped
16: Clamp U
32: Clamp V
64: Spherical environment map
128: Cubic environment map
256: Store texture in vram
512: Force the use of high color textures
65536: texture uses secondary UV values
Blend:
0: Do not blend
1: No blend, or Alpha (alpha when texture loaded with alpha flag - not recommended for multitexturing - see below)
2: Multiply (default)
3: Add
4: Dot3
5: Multiply 2
*/
}
B3dStack.erase(B3dStack.size()-1);
Driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, Previous32BitTextureFlag);
return true;
}
bool CAnimatedMeshB3d::ReadChunkBRUS(io::IReadFile* file)
{
s32 n_texs;
file->read(&n_texs, sizeof(s32));
#ifdef __BIG_ENDIAN__
n_texs = os::Byteswap::byteswap(n_texs);
#endif
while(B3dStack.getLast().startposition + B3dStack.getLast().length>file->getPos()) //this chunk repeats
{
// This is what blitz basic calls a brush, like a Irrlicht Material
core::stringc MaterialName=readString(file); //Not used but we still need the read it
SB3dMaterial B3dMaterial;
B3dMaterial.Material = new irr::video::SMaterial();
B3dMaterial.Textures[0]=0;
B3dMaterial.Textures[1]=0;
s32 texture_id[8];
texture_id[0]=-1;
texture_id[1]=-1;
file->read(&B3dMaterial.red, sizeof(B3dMaterial.red));
file->read(&B3dMaterial.green, sizeof(B3dMaterial.green));
file->read(&B3dMaterial.blue, sizeof(B3dMaterial.blue));
file->read(&B3dMaterial.alpha, sizeof(B3dMaterial.alpha));
file->read(&B3dMaterial.shininess, sizeof(B3dMaterial.shininess));
file->read(&B3dMaterial.blend, sizeof(B3dMaterial.blend));
file->read(&B3dMaterial.fx, sizeof(B3dMaterial.fx));
for (s32 n=0; n < n_texs; ++n)
{
file->read(&texture_id[n], sizeof(s32)); //I'm not sure of getting the sizeof an array
//cout << "Material is using texture id:"<< texture_id[n] <<endl;//for debuging
}
#ifdef __BIG_ENDIAN__
B3dMaterial.red = os::Byteswap::byteswap(B3dMaterial.red);
B3dMaterial.green = os::Byteswap::byteswap(B3dMaterial.green);
B3dMaterial.blue = os::Byteswap::byteswap(B3dMaterial.blue);
B3dMaterial.alpha = os::Byteswap::byteswap(B3dMaterial.alpha);
B3dMaterial.shininess = os::Byteswap::byteswap(B3dMaterial.shininess);
B3dMaterial.blend = os::Byteswap::byteswap(B3dMaterial.blend);
B3dMaterial.fx = os::Byteswap::byteswap(B3dMaterial.fx);
for (s32 n=0; n < n_texs; ++n)
texture_id[n] = os::Byteswap::byteswap(texture_id[n]);
#endif
if (texture_id[0] != -1)
B3dMaterial.Textures[0]=&Textures[texture_id[0]];
if (texture_id[1] != -1)
B3dMaterial.Textures[1]=&Textures[texture_id[1]];
//Hack, Fixes problems when the lightmap is on the first texture
if (texture_id[0] != -1)
if (Textures[texture_id[0]].Flags & 65536) // 65536 = secondary UV
{
SB3dTexture *TmpTexture;
TmpTexture = B3dMaterial.Textures[1];
B3dMaterial.Textures[1] = B3dMaterial.Textures[0];
B3dMaterial.Textures[0] = TmpTexture;
}
if (B3dMaterial.Textures[0] != 0)
B3dMaterial.Material->Textures[0] = B3dMaterial.Textures[0]->Texture;
if (B3dMaterial.Textures[1] != 0)
B3dMaterial.Material->Textures[1] = B3dMaterial.Textures[1]->Texture;
//the other textures are skipped, irrlicht I think can only have 2 Textures per Material
if (B3dMaterial.Textures[1] != 0 && B3dMaterial.Textures[0] == 0) //It could happen
{
B3dMaterial.Textures[0] = B3dMaterial.Textures[1];
B3dMaterial.Textures[1] = 0;
}
//Hacky code to convert blitz fx to irrlicht...
if (B3dMaterial.Textures[1]) //Two textures
{
if (B3dMaterial.alpha==1)
{
if (B3dMaterial.Textures[1]->Blend & 5)
{
//B3dMaterial.Material->MaterialType = video::EMT_LIGHTMAP;
B3dMaterial.Material->MaterialType = video::EMT_LIGHTMAP_M2 ;
}
else
{
B3dMaterial.Material->MaterialType = video::EMT_LIGHTMAP;
}
}
else
{
//B3dMaterial.Material->MaterialType = video::EMT_LIGHTMAP;
//B3dMaterial.Material->MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
//B3dMaterial.Material->MaterialTypeParam=B3dMaterial.alpha;
B3dMaterial.Material->MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
}
}
else if (B3dMaterial.Textures[0]) //one texture
{
if (B3dMaterial.Textures[0]->Flags & 2) // Alpha mapped
{
B3dMaterial.Material->MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
}
else if (B3dMaterial.Textures[0]->Flags & 4) // Masked
{
// Not working like blitz basic, because Irrlicht is using alpha to mask, not colour like blitz basic
B3dMaterial.Material->MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
}
else if (B3dMaterial.alpha == 1)
{
B3dMaterial.Material->MaterialType = video::EMT_SOLID;
}
else
{
//B3dMaterial.Material->MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
//B3dMaterial.Material->MaterialTypeParam=B3dMaterial.alpha;
B3dMaterial.Material->MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
}
}
else //No texture
{
if (B3dMaterial.alpha == 1)
{
B3dMaterial.Material->MaterialType = video::EMT_SOLID;
}
else
{
B3dMaterial.Material->MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
}
}
if (B3dMaterial.fx & 32) //force vertex alpha-blending
{
B3dMaterial.Material->MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
//B3dMaterial.Material->Lighting=false;
}
// Material fx...
if (B3dMaterial.fx & 1) //full-bright
{
//B3dMaterial.Material->AmbientColor = video::SColor(1, 1, 1, 1).toSColor ();
B3dMaterial.Material->AmbientColor = video::SColor(255, 255, 255, 255);
//B3dMaterial.Material->EmissiveColor = video::SColorf(1, 1, 1, 0).toSColor ();//Would be too bright and brighter than blitz basic
B3dMaterial.Material->Lighting = false;
}
else
{
B3dMaterial.Material->AmbientColor = video::SColorf(B3dMaterial.red, B3dMaterial.green, B3dMaterial.blue, B3dMaterial.alpha).toSColor ();
}
if (B3dMaterial.fx & 4) //flatshaded
B3dMaterial.Material->GouraudShading = false;
if (B3dMaterial.fx & 16) //disable backface culling
B3dMaterial.Material->BackfaceCulling = false;
B3dMaterial.Material->DiffuseColor = video::SColorf(B3dMaterial.red, B3dMaterial.green, B3dMaterial.blue, B3dMaterial.alpha).toSColor ();
B3dMaterial.Material->EmissiveColor = video::SColorf(0.5, 0.5, 0.5, 0).toSColor (); //Thoughts, I'm no sure what I should set it to?
//B3dMaterial.Material->SpecularColor = video::SColorf(0, 0, 0, 0).toSColor (); //?
B3dMaterial.Material->Shininess = B3dMaterial.shininess;
Materials.push_back(B3dMaterial);
}
B3dStack.erase(B3dStack.size()-1);
return true;
}
bool CAnimatedMeshB3d::ReadChunkMESH(io::IReadFile* file, SB3dNode *InNode)
{
s32 Vertices_Start=BaseVertices.size(); //B3Ds have Vertex ID's local within the mesh I don't want this
s32 brush_id;
file->read(&brush_id, sizeof(brush_id));
#ifdef __BIG_ENDIAN__
brush_id = os::Byteswap::byteswap(brush_id);
#endif
NormalsInFile=false;
while(B3dStack.getLast().startposition + B3dStack.getLast().length>file->getPos()) //this chunk repeats
{
B3dStack.push_back(B3dChunk());
B3dChunkHeader header;
file->read(&header, sizeof(header));
#ifdef __BIG_ENDIAN__
header.size = os::Byteswap::byteswap(header.size);
#endif
B3dStack.getLast().name[0]=header.name[0];
B3dStack.getLast().name[1]=header.name[1]; //Not sure of an easier way
B3dStack.getLast().name[2]=header.name[2];
B3dStack.getLast().name[3]=header.name[3];
B3dStack.getLast().length = header.size + 8;
B3dStack.getLast().startposition = file->getPos() - 8;
bool read=false;
if ( strncmp( B3dStack.getLast().name, "VRTS", 4 ) == 0 )
{
read=true;
if (!ReadChunkVRTS(file,InNode ,0, Vertices_Start))
return false;
}
else if ( strncmp( B3dStack.getLast().name, "TRIS", 4 ) == 0 )
{
read=true;
SB3DMeshBuffer *MeshBuffer = new SB3DMeshBuffer();
MeshBuffer->VertexType=video::EVT_STANDARD;
if (brush_id!=-1)
MeshBuffer->Material=(*Materials[brush_id].Material);
if(ReadChunkTRIS(file,InNode ,MeshBuffer, Vertices_Start)==false)
return false;
MeshBuffer->recalculateBoundingBox();
if (!NormalsInFile && MeshBuffer->Material.Lighting) // No point wasting time of lightmapped levels
{
s32 i;
for ( i=0; i<(s32)MeshBuffer->Indices.size(); i+=3)
{
core::plane3d<f32> p( MeshBuffer->getVertex(MeshBuffer->Indices[i+0])->Pos,
MeshBuffer->getVertex(MeshBuffer->Indices[i+1])->Pos,
MeshBuffer->getVertex(MeshBuffer->Indices[i+2])->Pos);
MeshBuffer->getVertex(MeshBuffer->Indices[i+0])->Normal += p.Normal;
MeshBuffer->getVertex(MeshBuffer->Indices[i+1])->Normal += p.Normal;
MeshBuffer->getVertex(MeshBuffer->Indices[i+2])->Normal += p.Normal;
}
for ( i = 0; i<(s32)MeshBuffer->getVertexCount(); ++i )
{
MeshBuffer->getVertex(i)->Normal.normalize ();
BaseVertices[Vertices_Start+i]->Normal=MeshBuffer->getVertex(i)->Normal;
}
}
Buffers.push_back(MeshBuffer);
}
if (!read)
{
os::Printer::log("Unknown chunk found in mesh - skipping");
file->seek( (B3dStack.getLast().startposition + B3dStack.getLast().length) , false);
B3dStack.erase(B3dStack.size()-1);
}
}
B3dStack.erase(B3dStack.size()-1);
return true;
}
/*
VRTS:
int flags ;1=normal values present, 2=rgba values present
int tex_coord_sets ;texture coords per vertex (eg: 1 for simple U/V) max=8
int tex_coord_set_size ;components per set (eg: 2 for simple U/V) max=4
{
float x,y,z ;always present
float nx,ny,nz ;vertex normal: present if (flags&1)
float red,green,blue,alpha ;vertex color: present if (flags&2)
float tex_coords[tex_coord_sets][tex_coord_set_size] ;tex coords
}
*/
bool CAnimatedMeshB3d::ReadChunkVRTS(io::IReadFile* file, SB3dNode *InNode, SB3DMeshBuffer *MeshBuffer,s32 Vertices_Start)
{
s32 flags, tex_coord_sets, tex_coord_set_size;
file->read(&flags, sizeof(flags));
file->read(&tex_coord_sets, sizeof(tex_coord_sets));
file->read(&tex_coord_set_size, sizeof(tex_coord_set_size));
#ifdef __BIG_ENDIAN__
flags = os::Byteswap::byteswap(flags);
tex_coord_sets = os::Byteswap::byteswap(tex_coord_sets);
tex_coord_set_size = os::Byteswap::byteswap(tex_coord_set_size);
#endif
if (tex_coord_sets >= 3 || tex_coord_set_size >= 4) // Something is wrong
{
os::Printer::log("tex_coord_sets or tex_coord_set_size too big", file->getFileName(), ELL_ERROR);
return false;
}
//------ Allocate Memory, for speed -----------//
s32 MemoryNeeded = B3dStack.getLast().length / sizeof(f32);
s32 NumberOfReads = 3;
if (flags & 1) NumberOfReads += 3;
if (flags & 2) NumberOfReads += 4;
for (s32 i=0; i<tex_coord_sets; ++i)
for (s32 j=0; j<tex_coord_set_size; ++j)
NumberOfReads += 1;
MemoryNeeded /= NumberOfReads;
//Vertices_GlobalPosition.reallocate(MemoryNeeded+Vertices_GlobalPosition.size()+1);
//Normal_GlobalRotation.reallocate(MemoryNeeded+Normal_GlobalRotation.size()+1);
BaseVertices.reallocate(MemoryNeeded + BaseVertices.size() + 1);
Vertices_Moved.reallocate(MemoryNeeded + Vertices_Moved.size() + 1);
AnimatedVertices_VertexID.reallocate(MemoryNeeded + AnimatedVertices_VertexID.size() + 1);
AnimatedVertices_MeshBuffer.reallocate(MemoryNeeded + AnimatedVertices_MeshBuffer.size() + 1);
Vertices_Alpha.reallocate(MemoryNeeded + Vertices_Alpha.size() + 1);
while(B3dStack.getLast().startposition + B3dStack.getLast().length > file->getPos()) // this chunk repeats
{
f32 x=0.0f, y=0.0f, z=0.0f;
f32 nx=0.0f, ny=0.0f, nz=0.0f;
f32 red=1.0f, green=1.0f, blue=1.0f, alpha=1.0f;
f32 tex_coords[3][4];
file->read(&x, sizeof(x));
file->read(&y, sizeof(y));
file->read(&z, sizeof(z));
if (flags & 1)
{
NormalsInFile = true;
file->read(&nx, sizeof(nx));
file->read(&ny, sizeof(ny));
file->read(&nz, sizeof(nz));
}
if (flags & 2)
{
file->read(&red, sizeof(red));
file->read(&green, sizeof(green));
file->read(&blue, sizeof(blue));
file->read(&alpha, sizeof(alpha));
}
for (s32 i=0; i<tex_coord_sets; ++i)
for (s32 j=0; j<tex_coord_set_size; ++j)
file->read(&tex_coords[i][j], sizeof(f32));
#ifdef __BIG_ENDIAN__
x = os::Byteswap::byteswap(x);
y = os::Byteswap::byteswap(y);
z = os::Byteswap::byteswap(z);
if (flags&1)
{
nx = os::Byteswap::byteswap(nx);
ny = os::Byteswap::byteswap(ny);
nz = os::Byteswap::byteswap(nz);
}
if (flags & 2)
{
red = os::Byteswap::byteswap(red);
green = os::Byteswap::byteswap(green);
blue = os::Byteswap::byteswap(blue);
alpha = os::Byteswap::byteswap(alpha);
}
for (s32 i=0; i<=tex_coord_sets-1; i++)
for (s32 j=0; j<=tex_coord_set_size-1; j++)
tex_coords[i][j] = os::Byteswap::byteswap(tex_coords[i][j]);
#endif
f32 tu=0.0f, tv=0.0f;
if (tex_coord_sets >= 1 && tex_coord_set_size >= 2)
{
tu=tex_coords[0][0];
tv=tex_coords[0][1];
}
f32 tu2=0.0f, tv2=0.0f;
if (tex_coord_sets>=2 && tex_coord_set_size>=2)
{
tu2=tex_coords[1][0];
tv2=tex_coords[1][1];
}
// Create Vertex...
video::S3DVertex2TCoords *Vertex=new video::S3DVertex2TCoords
(x, y, z, video::SColorf(red, green, blue, alpha).toSColor(), tu, tv, tu2, tv2);
//video::S3DVertex *Vertex=new video::S3DVertex
// (x, y, z, nx, ny, nz, video::SColorf(red, green, blue, alpha).toSColor(), tu, tv);
Vertex->Normal = core::vector3df(nx, ny, nz); // should this be effected by the Node's Global Matrix (eg rotation)?
// Transform the Vertex position by nested node...
core::matrix4 VertexMatrix;
VertexMatrix.setTranslation(Vertex->Pos);
//Vertices_GlobalPosition.push_back(Vertex->Pos);
//Normal_GlobalRotation.push_back(Vertex->Normal);
VertexMatrix=InNode->GlobalMatrix*VertexMatrix;
Vertex->Pos=VertexMatrix.getTranslation();
//Add it...
BaseVertices.push_back(Vertex);
Vertices_Moved.push_back(false);
AnimatedVertices_VertexID.push_back(-1);
AnimatedVertices_MeshBuffer.push_back(0);
Vertices_Alpha.push_back(alpha);
}
B3dStack.erase(B3dStack.size()-1);
return true;
}
bool CAnimatedMeshB3d::ReadChunkTRIS(io::IReadFile* file, SB3dNode *InNode, SB3DMeshBuffer *MeshBuffer,s32 Vertices_Start)
{
s32 triangle_brush_id; // Note: Irrlicht can't have different brushes for each triangle (I'm using a workaround)
file->read(&triangle_brush_id, sizeof(triangle_brush_id));
#ifdef __BIG_ENDIAN__
triangle_brush_id = os::Byteswap::byteswap(triangle_brush_id);
#endif
SB3dMaterial *B3dMaterial;
if (triangle_brush_id != -1)
B3dMaterial = &Materials[triangle_brush_id];
else
B3dMaterial = 0;
if (B3dMaterial)
MeshBuffer->Material = (*B3dMaterial->Material);
s32 MemoryNeeded = B3dStack.getLast().length / sizeof(s32);
MeshBuffer->Indices.reallocate(MemoryNeeded + MeshBuffer->Indices.size() + 1);
while(B3dStack.getLast().startposition + B3dStack.getLast().length > file->getPos()) // this chunk repeats
{
s32 vertex_id[3];
file->read(&vertex_id[0], sizeof(s32));
file->read(&vertex_id[1], sizeof(s32));
file->read(&vertex_id[2], sizeof(s32));
#ifdef __BIG_ENDIAN__
vertex_id[0] = os::Byteswap::byteswap(vertex_id[0]);
vertex_id[1] = os::Byteswap::byteswap(vertex_id[1]);
vertex_id[2] = os::Byteswap::byteswap(vertex_id[2]);
#endif
vertex_id[0] += Vertices_Start;
vertex_id[1] += Vertices_Start;
vertex_id[2] += Vertices_Start;
for(s32 i=0; i<3; ++i)
{
if (AnimatedVertices_VertexID[ vertex_id[i] ] == -1)
{
if (BaseVertices[ vertex_id[i] ]->TCoords2 != core::vector2d<f32>(0,0))
{
MeshBuffer->MoveTo_2TCoords();
}
if (MeshBuffer->VertexType == video::EVT_STANDARD)
MeshBuffer->Vertices_Standard.push_back( *((video::S3DVertex*)BaseVertices[ vertex_id[i] ] ) );
else
MeshBuffer->Vertices_2TCoords.push_back(*BaseVertices[ vertex_id[i] ] );
AnimatedVertices_VertexID[ vertex_id[i] ] = MeshBuffer->getVertexCount()-1;
AnimatedVertices_MeshBuffer[ vertex_id[i] ] = MeshBuffer;
// Apply Material...
irr::video::S3DVertex *Vertex=MeshBuffer->getVertex(MeshBuffer->getVertexCount()-1);
if (Vertices_Alpha[ vertex_id[i] ] != 1.0f)
Vertex->Color.setAlpha( (s32)(Vertices_Alpha[ vertex_id[i] ] * 255.0f) );
else if (B3dMaterial)
Vertex->Color.setAlpha( (s32)(B3dMaterial->alpha * 255.0f) );
if (B3dMaterial)
{
// A bit of a hack, there
if (B3dMaterial->Textures[0])
{
Vertex->TCoords.X *=B3dMaterial->Textures[0]->Xscale;
Vertex->TCoords.Y *=B3dMaterial->Textures[0]->Yscale;
}
/*
if (B3dMaterial->Textures[1])
{
Vertex->TCoords2.X *=B3dMaterial->Textures[1]->Xscale;
Vertex->TCoords2.Y *=B3dMaterial->Textures[1]->Yscale;
}
*/
}
}
}
MeshBuffer->Indices.push_back( AnimatedVertices_VertexID[ vertex_id[0] ] );
MeshBuffer->Indices.push_back( AnimatedVertices_VertexID[ vertex_id[1] ] );
MeshBuffer->Indices.push_back( AnimatedVertices_VertexID[ vertex_id[2] ] );
}
B3dStack.erase(B3dStack.size()-1);
return true;
}
bool CAnimatedMeshB3d::ReadChunkNODE(io::IReadFile* file, SB3dNode *InNode)
{
core::stringc NodeName = readString(file);
f32 position[3];
f32 scale[3];
f32 rotation[4];
s32 n;
for (n=0; n<=2; n++)
file->read(&position[n], sizeof(f32));
for (n=0; n<=2; n++)
file->read(&scale[n], sizeof(f32));
for (n=0; n<=3; n++)
file->read(&rotation[n], sizeof(f32));
#ifdef __BIG_ENDIAN__
for (n=0; n<=2; n++)
position[n] = os::Byteswap::byteswap(position[n]);
for (n=0; n<=2; n++)
scale[n] = os::Byteswap::byteswap(scale[n]);
for (n=0; n<=3; n++)
rotation[n] = os::Byteswap::byteswap(rotation[n]);
#endif
SB3dNode *Node = new SB3dNode();
Node->Name = NodeName;
Node->Animate = true;
Node->AnimatingPositionKeys = false;
Node->AnimatingScaleKeys = false;
Node->AnimatingRotationKeys = false;
Node->HasScaleAnimation = false;
Node->position = core::vector3df(position[0],position[1],position[2]);
Node->scale = core::vector3df(scale[0],scale[1],scale[2]);
Node->rotation = core::quaternion(rotation[1], rotation[2], rotation[3], rotation[0]); // meant to be in this order
Node->Animatedposition = Node->position;
Node->Animatedscale = Node->scale;
Node->Animatedrotation = Node->rotation;
irr::core::matrix4 positionMatrix;
positionMatrix.setTranslation(Node->position);
irr::core::matrix4 scaleMatrix;
scaleMatrix.setScale(Node->scale);
irr::core::matrix4 rotationMatrix = Node->rotation.getMatrix();
Node->LocalMatrix = positionMatrix * rotationMatrix * scaleMatrix;
Node->LocalAnimatedMatrix = Node->LocalMatrix; //For non-animated meshes
if (!InNode)
{
Node->GlobalMatrix = Node->LocalMatrix;
RootNodes.push_back(Node);
}
else
{
Node->GlobalMatrix = InNode->GlobalMatrix * Node->LocalMatrix;
}
Node->GlobalInversedMatrix = Node->GlobalMatrix;
Node->GlobalInversedMatrix.makeInverse(); // slow
Nodes.push_back(Node);
if (InNode != 0)
InNode->Nodes.push_back(Node);
while(B3dStack.getLast().startposition + B3dStack.getLast().length > file->getPos()) // this chunk repeats
{
B3dStack.push_back(B3dChunk());
B3dChunkHeader header;
file->read(&header, sizeof(header));
#ifdef __BIG_ENDIAN__
header.size = os::Byteswap::byteswap(header.size);
#endif
B3dStack.getLast().name[0] = header.name[0];
B3dStack.getLast().name[1] = header.name[1]; //Not sure of an easier way
B3dStack.getLast().name[2] = header.name[2];
B3dStack.getLast().name[3] = header.name[3];
B3dStack.getLast().length = header.size+8;
B3dStack.getLast().startposition = file->getPos() - 8;
bool read = false;
if ( strncmp( B3dStack.getLast().name, "NODE", 4 ) == 0 )
{
read = true;
if (!ReadChunkNODE(file, Node))
return false;
}
else if ( strncmp( B3dStack.getLast().name, "MESH", 4 ) == 0 )
{
read = true;
if (!ReadChunkMESH(file,Node))
return false;
}
else if ( strncmp( B3dStack.getLast().name, "ANIM", 4 ) == 0 )
{
read = true;
if (!ReadChunkANIM(file,Node))
return false;
}
else if ( strncmp( B3dStack.getLast().name, "BONE", 4 ) == 0 )
{
read = true;
if (!ReadChunkBONE(file,Node))
return false;
}
else if ( strncmp( B3dStack.getLast().name, "KEYS", 4 ) == 0 )
{
read = true;
if(!ReadChunkKEYS(file,Node))
return false;
}
if (!read)
{
os::Printer::log("Unknown chunk found in node - skipping");
file->seek( (B3dStack.getLast().startposition + B3dStack.getLast().length), false);
B3dStack.erase(B3dStack.size()-1);
}
}
B3dStack.erase(B3dStack.size()-1);
return true;
}
bool CAnimatedMeshB3d::ReadChunkBONE(io::IReadFile* file, SB3dNode *InNode)
{
if (B3dStack.getLast().length > 8)
{
while(B3dStack.getLast().startposition + B3dStack.getLast().length>file->getPos()) // this chunk repeats
{
SB3dBone Bone;
file->read(&Bone.vertex_id, sizeof(Bone.vertex_id));
file->read(&Bone.weight, sizeof(Bone.weight));
#ifdef __BIG_ENDIAN__
Bone.vertex_id = os::Byteswap::byteswap(Bone.vertex_id);
Bone.weight = os::Byteswap::byteswap(Bone.weight);
#endif
if (Bone.weight != 0 || Bone.weight < 0)
{
HasBones = true;
InNode->Bones.push_back(Bone);
}
}
}
B3dStack.erase(B3dStack.size()-1);
return true;
}
bool CAnimatedMeshB3d::ReadChunkKEYS(io::IReadFile* file, SB3dNode *InNode)
{
s32 flags;
file->read(&flags, sizeof(flags));
#ifdef __BIG_ENDIAN__
flags = os::Byteswap::byteswap(flags);
#endif
while(B3dStack.getLast().startposition + B3dStack.getLast().length>file->getPos()) //this chunk repeats
{
if (flags & 2) //scale
InNode->HasScaleAnimation = true;
s32 frame;
f32 positionData[3];
f32 scaleData[3];
f32 rotationData[4];
file->read(&frame, sizeof(frame));
if (flags&1)
readFloats(file, positionData, 3);
if (flags&2)
readFloats(file, scaleData, 3);
if (flags&4)
readFloats(file, rotationData, 4);
#ifdef __BIG_ENDIAN__
frame = os::Byteswap::byteswap(frame);
#endif
frame *= 100; // Scale the animation frames up
core::vector3df position = core::vector3df(positionData[0], positionData[1], positionData[2]);
core::vector3df scale = core::vector3df(scaleData[0], scaleData[1], scaleData[2]);
core::quaternion rotation = core::quaternion(rotationData[1], rotationData[2], rotationData[3], rotationData[0]); // meant to be in this order
// Workout what types of keys need to animate
if (InNode->PositionKeys.size() > 0)
if (flags&1 && InNode->Animatedposition != position)
InNode->AnimatingPositionKeys = true;
if (InNode->ScaleKeys.size() > 0)
if (flags&2 && InNode->Animatedscale != scale)
InNode->AnimatingScaleKeys = true;
if (InNode->RotationKeys.size() > 0)
{
if (flags & 4)
{
if (InNode->Animatedrotation.W != rotation.W
|| InNode->Animatedrotation.X != rotation.X
|| InNode->Animatedrotation.Y != rotation.Y
|| InNode->Animatedrotation.Z != rotation.Z)
{
InNode->AnimatingRotationKeys = true;
}
}
}
if (flags & 1)
InNode->Animatedposition = position;
if (flags & 2)
InNode->Animatedscale = scale;
if (flags & 4)
InNode->Animatedrotation = rotation;
// Add key frame
if (flags & 1)
{
SB3dPositionKey Key;
Key.frame = frame;
Key.position = position;
InNode->PositionKeys.push_back(Key);
}
if (flags & 2)
{
SB3dScaleKey Key;
Key.frame = frame;
Key.scale=scale;
InNode->ScaleKeys.push_back(Key);
}
if (flags & 4)
{
SB3dRotationKey Key;
Key.frame = frame;
Key.rotation = rotation;
InNode->RotationKeys.push_back(Key);
}
}
B3dStack.erase(B3dStack.size()-1);
return true;
}
bool CAnimatedMeshB3d::ReadChunkANIM(io::IReadFile* file, SB3dNode *InNode)
{
file->read(&AnimFlags, sizeof(s32));
file->read(&AnimFrames, sizeof(s32));
readFloats(file, &AnimFPS, 1);
#ifdef __BIG_ENDIAN__
AnimFlags = os::Byteswap::byteswap(AnimFlags);
AnimFrames = os::Byteswap::byteswap(AnimFrames);
#endif
AnimFrames*=100;
totalTime=(f32)AnimFrames;
HasAnimation = 1;
lastCalculatedFrame = -1;
B3dStack.erase(B3dStack.size()-1);
return true;
}
bool CAnimatedMeshB3d::loadFile(io::IReadFile* file)
{
if (!file)
return false;
totalTime = 0;
HasAnimation = 0;
HasBones = 0;
lastCalculatedFrame = -1;
lastAnimateMode = -1;
AnimFlags = 0; // Unused for now
AnimFrames = 1; // how many frames in anim
AnimFPS = 0.0f;
AnimateNormals = false;
InterpolationMode = 1; // Set linear interpolation animation
AnimateMode = 3; // Update both the nodes and the skin in animation
B3dChunkHeader header;
file->read(&header, sizeof(header));
#ifdef __BIG_ENDIAN__
header.size = os::Byteswap::byteswap(header.size);
#endif
if ( strncmp( header.name, "BB3D", 4 ) != 0 )
{
os::Printer::log("File is not a b3d file. Loading failed", file->getFileName(), ELL_ERROR);
return false;
}
// header Chunk size here
B3dStack.clear();
B3dStack.push_back(B3dChunk());
B3dStack.getLast().name[0] = header.name[0];
B3dStack.getLast().name[1] = header.name[1];
B3dStack.getLast().name[2] = header.name[2];
B3dStack.getLast().name[3] = header.name[3];
B3dStack.getLast().startposition = file->getPos()-8;
B3dStack.getLast().length = header.size+8;
//Get file version...
u32 FileVersion;
file->read(&FileVersion, sizeof(FileVersion));
#ifdef __BIG_ENDIAN__
FileVersion = os::Byteswap::byteswap(FileVersion);
#endif
while (B3dStack.getLast().startposition + B3dStack.getLast().length > file->getPos())
{
B3dStack.push_back(B3dChunk());
file->read(&header, sizeof(header));
#ifdef __BIG_ENDIAN__
header.size = os::Byteswap::byteswap(header.size);
#endif
B3dStack.getLast().name[0] = header.name[0];
B3dStack.getLast().name[1] = header.name[1];
B3dStack.getLast().name[2] = header.name[2];
B3dStack.getLast().name[3] = header.name[3];
B3dStack.getLast().startposition = file->getPos() - 8;
B3dStack.getLast().length = header.size + 8;
bool read = false;
if ( strncmp( B3dStack.getLast().name, "TEXS", 4 ) == 0 )
{
read = true;
if (!ReadChunkTEXS(file))
return false;
}
else if ( strncmp( B3dStack.getLast().name, "BRUS", 4 ) == 0 )
{
read = true;
if (!ReadChunkBRUS(file))
return false;
}
else if ( strncmp( B3dStack.getLast().name, "NODE", 4 ) == 0 )
{
read = true;
if (!ReadChunkNODE(file, (SB3dNode*)0) )
return false;
}
if (!read)
{
os::Printer::log("Unknown chunk found in mesh base - skipping");
file->seek( (B3dStack.getLast().startposition + B3dStack.getLast().length) , false);
B3dStack.erase(B3dStack.size()-1);
}
}
B3dStack.clear();
if (HasBones)
normalizeWeights();
// Get BoundingBox...
if (Buffers.empty())
BoundingBox.reset(0,0,0);
else
{
BoundingBox.reset(Buffers[0]->BoundingBox.MaxEdge);
for (s32 i=0; i<(s32)Buffers.size(); ++i)
{
BoundingBox.addInternalBox(Buffers[i]->BoundingBox);
}
}
// For skinning
for (s32 i=0; i<(s32)Nodes.size(); ++i)
{
SB3dNode *Node = Nodes[i];
for (s32 j=0; j<(s32)Node->Bones.size(); ++j)
{
Node->Bones[j].pos = BaseVertices[Node->Bones[j].vertex_id]->Pos;
Node->Bones[j].normal = BaseVertices[Node->Bones[j].vertex_id]->Normal;
Node->Bones[j].vertex =
AnimatedVertices_MeshBuffer[ Node->Bones[j].vertex_id ]->getVertex(
AnimatedVertices_VertexID[ Node->Bones[j].vertex_id ] );
}
}
return true;
}
void CAnimatedMeshB3d::normalizeWeights()
{
// Normalise the weights on bones
s32 i;
core::array<f32> Vertices_TotalWeight;
Vertices_TotalWeight.set_used(BaseVertices.size());
for (i=0; i<(s32)Vertices_TotalWeight.size(); ++i)
Vertices_TotalWeight[i] = 0;
for (i=0; i<(s32)Nodes.size(); ++i)
{
SB3dNode *Node=Nodes[i];
for (s32 j=0; j<(s32)Node->Bones.size(); ++j)
Vertices_TotalWeight[ Node->Bones[j].vertex_id ] += Node->Bones[j].weight;
}
for (i=0; i<(s32)Nodes.size(); ++i)
{
SB3dNode *Node=Nodes[i];
for (s32 j=0; j<(s32)Node->Bones.size(); ++j)
{
f32 total = Vertices_TotalWeight[ Node->Bones[j].vertex_id ];
if (total != 0 && total != 1)
Node->Bones[j].weight /= total;
}
}
}
//! returns the amount of frames in milliseconds. If the amount is 1, it is a static (=non animated) mesh.
s32 CAnimatedMeshB3d::getFrameCount()
{
return AnimFrames;
}
//! returns the animated mesh based on a detail level. 0 is the lowest, 255 the highest detail. Note, that some Meshes will ignore the detail level.
IMesh* CAnimatedMeshB3d::getMesh(s32 frame, s32 detailLevel, s32 startFrameLoop, s32 endFrameLoop)
{
animate(frame,startFrameLoop, endFrameLoop);
return this;
}
//! Returns a pointer to a transformation matrix of a part of the
//! mesh based on a frame time.
core::matrix4* CAnimatedMeshB3d::getMatrixOfJoint(s32 jointNumber, s32 frame)
{
if (!HasAnimation || jointNumber < 0 || jointNumber >= (s32)Nodes.size())
return 0;
return &Nodes[jointNumber]->GlobalAnimatedMatrix;
}
core::matrix4* CAnimatedMeshB3d::getLocalMatrixOfJoint(s32 jointNumber)
{
if (!HasAnimation || jointNumber < 0 || jointNumber >= (s32)Nodes.size())
return 0;
return &Nodes[jointNumber]->LocalMatrix;
}
core::matrix4* CAnimatedMeshB3d::getMatrixOfJointUnanimated(s32 jointNumber)
{
if (!HasAnimation || jointNumber < 0 || jointNumber >= (s32)Nodes.size())
return 0;
return &Nodes[jointNumber]->GlobalMatrix;
}
//! Gets joint count.
s32 CAnimatedMeshB3d::getJointCount() const
{
return Nodes.size();
}
//! Gets the name of a joint.
void CAnimatedMeshB3d::setJointAnimation(s32 jointNumber, bool On)
{
if (jointNumber < 0 || jointNumber >= (s32)Nodes.size())
return;
Nodes[jointNumber]->Animate = On;
}
//! Gets the name of a joint.
const c8* CAnimatedMeshB3d::getJointName(s32 number) const
{
if (number < 0 || number >= (s32)Nodes.size())
return 0;
return Nodes[number]->Name.c_str();
}
//! Gets a joint number from its name
s32 CAnimatedMeshB3d::getJointNumber(const c8* name) const
{
for (s32 i=0; i<(s32)Nodes.size(); ++i)
if (Nodes[i]->Name == name)
return i;
return -1;
}
void CAnimatedMeshB3d::CalculateGlobalMatrixes(SB3dNode *Node,SB3dNode *ParentNode)
{
if (!Node && ParentNode) // bit of protection from endless loops
return;
if (!Node)
{
for (s32 i=0; i<(s32)RootNodes.size(); ++i)
CalculateGlobalMatrixes(RootNodes[i],0);
return;
}
if (!ParentNode)
Node->GlobalAnimatedMatrix = Node->LocalAnimatedMatrix;
else
Node->GlobalAnimatedMatrix = ParentNode->GlobalAnimatedMatrix * Node->LocalAnimatedMatrix;
for (s32 j=0; j<(s32)Node->Nodes.size(); ++j)
CalculateGlobalMatrixes(Node->Nodes[j],Node);
}
void CAnimatedMeshB3d::animateSkin(f32 frame,f32 startFrame, f32 endFrame,SB3dNode *Node,SB3dNode *ParentNode)
{
// Get animated matrix...
if (!ParentNode)
Node->GlobalAnimatedMatrix = Node->LocalAnimatedMatrix;
else
Node->GlobalAnimatedMatrix = ParentNode->GlobalAnimatedMatrix * Node->LocalAnimatedMatrix;
if (Node->Bones.size())
{
core::matrix4 VerticesMatrixMove(core::matrix4::EM4CONST_NOTHING);
VerticesMatrixMove.setbyproduct(Node->GlobalAnimatedMatrix,Node->GlobalInversedMatrix);
core::vector3df ThisVertexMove, ThisNormalMove;
SB3dBone *Bone;
//__ Skin Vertex's Normals __//
if (AnimateNormals)
{
for (u32 i=0; i<Node->Bones.size(); ++i)
{
Bone=&Node->Bones[i];
//Transform normal...
//Normal_Rotation=&Base_Vertex->Normal;
ThisNormalMove.X = Bone->normal.X*VerticesMatrixMove[0] + Bone->normal.Y*VerticesMatrixMove[4] + Bone->normal.Z*VerticesMatrixMove[8];
ThisNormalMove.Y = Bone->normal.X*VerticesMatrixMove[1] + Bone->normal.Y*VerticesMatrixMove[5] + Bone->normal.Z*VerticesMatrixMove[9];
ThisNormalMove.Z = Bone->normal.X*VerticesMatrixMove[2] + Bone->normal.Y*VerticesMatrixMove[6] + Bone->normal.Z*VerticesMatrixMove[10];
if (!Vertices_Moved[Bone->vertex_id])
Bone->vertex->Normal = ThisNormalMove*Bone->weight;
else
Bone->vertex->Normal = Bone->vertex->Normal + (ThisNormalMove * Bone->weight);
}
}
//__ Skin Vertex's Position __//
for (u32 i=0; i<Node->Bones.size(); ++i)
{
Bone=&Node->Bones[i];
// Transform vector...
ThisVertexMove.X = VerticesMatrixMove[0]*Bone->pos.X + VerticesMatrixMove[4]*Bone->pos.Y + VerticesMatrixMove[8]*Bone->pos.Z + VerticesMatrixMove[12];
ThisVertexMove.Y = VerticesMatrixMove[1]*Bone->pos.X + VerticesMatrixMove[5]*Bone->pos.Y + VerticesMatrixMove[9]*Bone->pos.Z + VerticesMatrixMove[13];
ThisVertexMove.Z = VerticesMatrixMove[2]*Bone->pos.X + VerticesMatrixMove[6]*Bone->pos.Y + VerticesMatrixMove[10]*Bone->pos.Z + VerticesMatrixMove[14];
if (!Vertices_Moved[Bone->vertex_id])
{
Vertices_Moved[Bone->vertex_id] = true;
Bone->vertex->Pos = ThisVertexMove * Bone->weight;
}
else
{
Bone->vertex->Pos = Bone->vertex->Pos + (ThisVertexMove*Bone->weight);
}
}
}
for (s32 j=0; j<(s32)Node->Nodes.size(); ++j)
animateSkin(frame, startFrame, endFrame, Node->Nodes[j], Node);
}
void CAnimatedMeshB3d::getNodeAnimation(f32 frame,SB3dNode *Node,core::vector3df &position, core::vector3df &scale, core::quaternion &rotation)
{
bool foundPosition = false;
bool foundScale = false;
bool foundRotation = false;
s32 LastPosition = -1;
s32 LastScale = -1;
s32 LastRotation = -1;
s32 j, k;
if (Node->AnimatingPositionKeys)
{
j=0;
for (k=0; k < (s32)Node->PositionKeys.size(); k += 40)
{
if (Node->PositionKeys[k].frame < frame)
j=k;
else
break;
}
for (; j<(s32)Node->PositionKeys.size(); j++)
{
SB3dPositionKey *Key=&Node->PositionKeys[j];
if (Key->frame >= frame)
{
if (InterpolationMode == 0)
{
// Constant interpolate...
if (LastPosition != -1)
position = Node->PositionKeys[LastPosition].position;
else
position = Key->position;
}
else if (InterpolationMode == 1)
{
//Linear interpolate...
if (LastPosition == -1)
position = Key->position;
else
{
SB3dPositionKey *LastKey = &Node->PositionKeys[LastPosition];
if (Key->position == LastKey->position)
position = Key->position;
else
{
f32 fd1 = frame-LastKey->frame;
f32 fd2 = Key->frame-frame;
position = ((Key->position-LastKey->position)/(fd1+fd2))*fd1 + LastKey->position;
}
}
}
foundPosition = true;
break;
}
LastPosition = j;
}
if (!foundPosition && LastPosition != -1)
position = Node->PositionKeys[LastPosition].position;
}
if (Node->AnimatingRotationKeys)
{
j=0;
for (k=0; k<(s32)Node->RotationKeys.size(); k += 40)
{
if (Node->RotationKeys[k].frame < frame)
j=k;
else
break;
}
for (; j<(s32)Node->RotationKeys.size(); ++j)
{
SB3dRotationKey *Key = &Node->RotationKeys[j];
if (Key->frame >= frame)
{
if (InterpolationMode == 0)
{
// Constant interpolate...
if (LastRotation != -1)
rotation = Node->RotationKeys[LastRotation].rotation;
else
rotation = Key->rotation;
}
else if (InterpolationMode == 1)
{
// Linear interpolate...
if (LastRotation == -1)
rotation = Key->rotation;
else
{
SB3dRotationKey *LastKey=&Node->RotationKeys[LastRotation];
if (Key->rotation == LastKey->rotation)
rotation = Key->rotation;
else
{
f32 fd1 = frame-LastKey->frame;
f32 fd2 = Key->frame - frame;
f32 t = (1.0f/(fd1+fd2))*fd1;
rotation.slerp(LastKey->rotation, Key->rotation, t);
}
}
}
foundRotation = true;
break;
}
LastRotation = j;
}
if (!foundRotation && LastRotation != -1)
rotation = Node->RotationKeys[LastRotation].rotation;
}
if (Node->AnimatingScaleKeys)
{
j=0;
for (k=0; k<(s32)Node->ScaleKeys.size(); k+=40)
{
if (Node->ScaleKeys[k].frame < frame)
j = k;
else
break;
}
for (; j<(s32)Node->ScaleKeys.size(); j++)
{
SB3dScaleKey *Key = &Node->ScaleKeys[j];
if (Key->frame >= frame)
{
if (InterpolationMode == 0)
{
// Constant interpolate...
if (LastScale != -1)
scale = Node->ScaleKeys[LastScale].scale;
else
scale = Key->scale;
}
else if (InterpolationMode == 1)
{
// Linear interpolate...
if (LastScale == -1)
scale = Key->scale;
else
{
SB3dScaleKey *LastKey = &Node->ScaleKeys[LastScale];
if (Key->scale == LastKey->scale)
scale = Key->scale;
else
{
f32 fd1 = frame-LastKey->frame;
f32 fd2 = Key->frame - frame;
scale = (((Key->scale-LastKey->scale)/(fd1+fd2))*fd1) + LastKey->scale;
}
}
}
foundScale = true;
break;
}
LastScale = j;
}
if (!foundScale && LastScale != -1)
scale = Node->ScaleKeys[LastScale].scale;
}
}
void CAnimatedMeshB3d::animateNodes(f32 frame,f32 startFrame, f32 endFrame)
{
for (s32 i=0; i<(s32)Nodes.size(); ++i)
{
SB3dNode *Node = Nodes[i];
if (Node->Animate)
{
//Get keyframe...
core::vector3df position = Node->position;
core::vector3df scale = Node->scale;
core::quaternion rotation = Node->rotation;
getNodeAnimation(frame, Node, position, scale, rotation);
Node->Animatedposition = position;
Node->Animatedscale = scale;
Node->Animatedrotation = rotation;
core::matrix4 positionMatrix;
positionMatrix.setTranslation(Node->Animatedposition);
core::matrix4 rotationMatrix;
rotationMatrix = Node->Animatedrotation.getMatrix();
if (!Node->HasScaleAnimation)
{
Node->LocalAnimatedMatrix = positionMatrix * rotationMatrix;
}
else
{
core::matrix4 scaleMatrix;
scaleMatrix.setScale(Node->Animatedscale);
Node->LocalAnimatedMatrix = positionMatrix * rotationMatrix * scaleMatrix;
}
}
}
}
void CAnimatedMeshB3d::animate(s32 intframe,s32 startFrameLoop, s32 endFrameLoop)// Why cannot this be "animate(f32 frame)", it would make so much nicer animations
{
if ( !HasAnimation || (lastCalculatedFrame == intframe && lastAnimateMode == AnimateMode) )
return;
lastCalculatedFrame = intframe;
lastAnimateMode = AnimateMode;
f32 frame = (f32)intframe;
f32 startFrame = (f32)startFrameLoop;
f32 endFrame = (f32)endFrameLoop;
if (AnimateMode & 1) // Update Nodes
animateNodes(frame, startFrame, endFrame);
if (AnimateMode & 2) // Update skin
{
// Reset skin
s32 i;
for (i=0; i<(s32)Vertices_Moved.size(); ++i)
Vertices_Moved[i]=false;
for (i=0; i<(s32)RootNodes.size(); ++i)
{
animateSkin(frame, startFrame, endFrame, RootNodes[i], 0);
}
/*
if (AnimateNormals)
{
for (i=0; i<(s32)Nodes.size(); ++i)
{
SB3dNode *Node = Nodes[i];
for (s32 j=0; j<(s32)Node->Bones.size(); ++j)
{
u16 VertexID = AnimatedVertices_VertexID[ Node->Bones[j].vertex_id ];
SB3DMeshBuffer *MeshBuffer = AnimatedVertices_MeshBuffer[ Node->Bones[j].vertex_id ];
video::S3DVertex2TCoords *Vertex = &MeshBuffer->Vertices[VertexID];
//video::S3DVertex *Vertex = &MeshBuffer->Vertices[VertexID];
//Vertex->Normal.normalize();
}
}
}
*/
}
}
//! returns amount of mesh buffers.
u32 CAnimatedMeshB3d::getMeshBufferCount() const
{
return Buffers.size();
}
//! returns pointer to a mesh buffer
IMeshBuffer* CAnimatedMeshB3d::getMeshBuffer(u32 nr) const
{
if (nr < Buffers.size())
return Buffers[nr];
else
return 0;
}
//! Returns pointer to a mesh buffer which fits a material
IMeshBuffer* CAnimatedMeshB3d::getMeshBuffer( const video::SMaterial &material) const
{
for (u32 i=0; i < Buffers.size(); ++i)
{
if (Buffers[i]->getMaterial() == material)
return Buffers[i];
}
return 0;
}
//! returns an axis aligned bounding box
const core::aabbox3d<f32>& CAnimatedMeshB3d::getBoundingBox() const
{
return BoundingBox;
}
//! set user axis aligned bounding box
void CAnimatedMeshB3d::setBoundingBox( const core::aabbox3df& box)
{
BoundingBox = box;
}
//! sets a flag of all contained materials to a new value
void CAnimatedMeshB3d::setMaterialFlag(video::E_MATERIAL_FLAG flag, bool newvalue)
{
for (s32 i=0; i < (s32)Buffers.size(); ++i)
Buffers[i]->Material.setFlag(flag, newvalue);
}
//! Returns the type of the animated mesh.
E_ANIMATED_MESH_TYPE CAnimatedMeshB3d::getMeshType() const
{
return EAMT_B3D;
}
//!Update Normals when Animating
//!False= Don't (default)
//!True= Update normals, slower
void CAnimatedMeshB3d::updateNormalsWhenAnimating(bool on)
{
AnimateNormals = on;
}
//!Sets Interpolation Mode
//!0- Constant
//!1- Linear (default)
void CAnimatedMeshB3d::setInterpolationMode(s32 mode)
{
InterpolationMode = mode;
}
//!Want should happen on when animating
//!0-Nothing
//!1-Update nodes only
//!2-Update skin only
//!3-Update both nodes and skin (default)
void CAnimatedMeshB3d::setAnimateMode(s32 mode)
{
AnimateMode = mode;
}
void CAnimatedMeshB3d::recoverJointsFromMesh(core::array<ISceneNode*> &JointChildSceneNodes)
{
// Note: This function works because of the way the b3d fomat nests nodes, other mesh loaders may need a different function
for (s32 i=0; i<(s32)Nodes.size(); ++i)
{
ISceneNode* node = JointChildSceneNodes[i];
SB3dNode *B3dNode = Nodes[i];
node->setPosition( B3dNode->LocalAnimatedMatrix.getTranslation() );
node->setRotation( B3dNode->LocalAnimatedMatrix.getRotationDegrees() );
//node->setScale( B3dNode->LocalAnimatedMatrix.getScale() );
node->updateAbsolutePosition(); // works because of nests nodes
}
}
void CAnimatedMeshB3d::tranferJointsToMesh(core::array<ISceneNode*> &JointChildSceneNodes)
{
for (s32 i=0;i<(s32)Nodes.size();++i)
{
ISceneNode* node = JointChildSceneNodes[i];
SB3dNode *B3dNode = Nodes[i];
B3dNode->LocalAnimatedMatrix.setTranslation( node->getPosition() );
B3dNode->LocalAnimatedMatrix.setRotationDegrees( node->getRotation() );
//B3dNode->LocalAnimatedMatrix.setScale( node->getScale() );
}
lastCalculatedFrame = -1;
}
void CAnimatedMeshB3d::createJoints(core::array<ISceneNode*> &JointChildSceneNodes, ISceneNode* AnimatedMeshSceneNode, ISceneManager* SceneManager)
{
// Note: This function works because of the way the b3d format nests nodes, other mesh loaders may need a different function
createSkelton_Helper(SceneManager, JointChildSceneNodes, AnimatedMeshSceneNode, 0, 0, 0);
}
void CAnimatedMeshB3d::createSkelton_Helper(ISceneManager* SceneManager, core::array<ISceneNode*> &JointChildSceneNodes, ISceneNode* AnimatedMeshSceneNode, ISceneNode* ParentNode, SB3dNode *ParentB3dNode,SB3dNode *B3dNode)
{
// Note: This function works because of the way the b3d fomat nests nodes, other mesh loaders may need a different function
if (!ParentNode)
{
for (s32 i=0; i<(s32)RootNodes.size(); ++i)
{
B3dNode = RootNodes[i];
ISceneNode* node = SceneManager->addEmptySceneNode(AnimatedMeshSceneNode);
JointChildSceneNodes.push_back(node);
for (s32 j=0; j<(s32)B3dNode->Nodes.size(); ++j)
createSkelton_Helper(SceneManager, JointChildSceneNodes, AnimatedMeshSceneNode, node, B3dNode, B3dNode->Nodes[j]);
}
}
else
{
ISceneNode* node = SceneManager->addEmptySceneNode(ParentNode);
JointChildSceneNodes.push_back(node);
for (s32 j=0; j<(s32)B3dNode->Nodes.size(); ++j)
createSkelton_Helper(SceneManager, JointChildSceneNodes, AnimatedMeshSceneNode, node, B3dNode, B3dNode->Nodes[j]);
}
}
void CAnimatedMeshB3d::convertToTangents()
{
// now calculate tangents
for (u32 b=0; b < Buffers.size(); ++b)
{
if (Buffers[b])
{
Buffers[b]->MoveTo_Tangents();
s32 idxCnt = Buffers[b]->getIndexCount();
u16* idx = Buffers[b]->getIndices();
video::S3DVertexTangents* v =
(video::S3DVertexTangents*)Buffers[b]->getVertices();
for (s32 i=0; i<idxCnt; i+=3)
{
calculateTangents(
v[idx[i+0]].Normal,
v[idx[i+0]].Tangent,
v[idx[i+0]].Binormal,
v[idx[i+0]].Pos,
v[idx[i+1]].Pos,
v[idx[i+2]].Pos,
v[idx[i+0]].TCoords,
v[idx[i+1]].TCoords,
v[idx[i+2]].TCoords);
calculateTangents(
v[idx[i+1]].Normal,
v[idx[i+1]].Tangent,
v[idx[i+1]].Binormal,
v[idx[i+1]].Pos,
v[idx[i+2]].Pos,
v[idx[i+0]].Pos,
v[idx[i+1]].TCoords,
v[idx[i+2]].TCoords,
v[idx[i+0]].TCoords);
calculateTangents(
v[idx[i+2]].Normal,
v[idx[i+2]].Tangent,
v[idx[i+2]].Binormal,
v[idx[i+2]].Pos,
v[idx[i+0]].Pos,
v[idx[i+1]].Pos,
v[idx[i+2]].TCoords,
v[idx[i+0]].TCoords,
v[idx[i+1]].TCoords);
}
}
}
// For skinning
for (s32 i=0; i < (s32)Nodes.size(); ++i)
{
SB3dNode *Node=Nodes[i];
for (s32 j=0; j<(s32)Node->Bones.size(); ++j)
{
Node->Bones[j].pos = BaseVertices[Node->Bones[j].vertex_id]->Pos;
Node->Bones[j].normal = BaseVertices[Node->Bones[j].vertex_id]->Normal;
Node->Bones[j].vertex =
AnimatedVertices_MeshBuffer[ Node->Bones[j].vertex_id ]->getVertex(
AnimatedVertices_VertexID[ Node->Bones[j].vertex_id ] );
}
}
}
void CAnimatedMeshB3d::calculateTangents(
core::vector3df& normal,
core::vector3df& tangent,
core::vector3df& binormal,
core::vector3df& vt1, core::vector3df& vt2, core::vector3df& vt3, // vertices
core::vector2df& tc1, core::vector2df& tc2, core::vector2df& tc3) // texture coords
{
core::vector3df v1 = vt1 - vt2;
core::vector3df v2 = vt3 - vt1;
normal = v2.crossProduct(v1);
normal.normalize();
// binormal
f32 deltaX1 = tc1.X - tc2.X;
f32 deltaX2 = tc3.X - tc1.X;
binormal = (v1 * deltaX2) - (v2 * deltaX1);
binormal.normalize();
// tangent
f32 deltaY1 = tc1.Y - tc2.Y;
f32 deltaY2 = tc3.Y - tc1.Y;
tangent = (v1 * deltaY2) - (v2 * deltaY1);
tangent.normalize();
// adjust
core::vector3df txb = tangent.crossProduct(binormal);
if (txb.dotProduct(normal) < 0.0f)
{
tangent *= -1.0f;
binormal *= -1.0f;
}
}
} // end namespace scene
} // end namespace irr