569 lines
12 KiB
C++
569 lines
12 KiB
C++
|
|
#include "loader_3ds.hpp"
|
|
#include "animation.hpp"
|
|
#include "meshmodel.hpp"
|
|
#include "std.hpp"
|
|
|
|
extern gxRuntime* gx_runtime;
|
|
|
|
#ifdef BETA
|
|
#define _log(X) gx_runtime->debugLog((string(X)).c_str());
|
|
#else
|
|
#define _log(X)
|
|
#endif
|
|
|
|
class Box;
|
|
|
|
static filebuf in;
|
|
static int chunk_end;
|
|
static vector<int> parent_end;
|
|
static unsigned short anim_len;
|
|
|
|
static bool conv, flip_tris;
|
|
static Transform conv_tform;
|
|
static bool collapse, animonly;
|
|
|
|
struct Face3DS {
|
|
int verts[3];
|
|
Brush brush;
|
|
};
|
|
|
|
static vector<Face3DS> faces;
|
|
//static vector<Surface::Vertex> vertices;
|
|
|
|
static map<string, Brush> materials_map;
|
|
static map<string, MeshModel*> name_map;
|
|
static map<int, MeshModel*> id_map;
|
|
|
|
static int nextChunk()
|
|
{
|
|
in.pubseekoff(chunk_end, ios_base::beg);
|
|
if (chunk_end == parent_end.back())
|
|
return 0;
|
|
unsigned short id;
|
|
int len;
|
|
in.sgetn((char*)&id, 2);
|
|
in.sgetn((char*)&len, 4);
|
|
chunk_end = (int)in.pubseekoff(0, ios_base::cur) + len - 6;
|
|
return id;
|
|
}
|
|
|
|
static void enterChunk()
|
|
{
|
|
parent_end.push_back(chunk_end);
|
|
chunk_end = (int)in.pubseekoff(0, ios_base::cur);
|
|
}
|
|
|
|
static void leaveChunk()
|
|
{
|
|
chunk_end = parent_end.back();
|
|
parent_end.pop_back();
|
|
}
|
|
|
|
static string parseString()
|
|
{
|
|
string t;
|
|
while (int c = in.sbumpc())
|
|
t += char(c);
|
|
return t;
|
|
}
|
|
|
|
enum {
|
|
CHUNK_RGBF = 0x0010,
|
|
CHUNK_RGBB = 0x0011,
|
|
// CHUNK_RBGB2 = 0x0012, // ?? NOT HLS.
|
|
CHUNK_MAIN = 0x4D4D,
|
|
CHUNK_SCENE = 0x3D3D,
|
|
CHUNK_BKGCOLOR = 0x1200,
|
|
CHUNK_AMBCOLOR = 0x2100,
|
|
CHUNK_OBJECT = 0x4000,
|
|
CHUNK_TRIMESH = 0x4100,
|
|
CHUNK_VERTLIST = 0x4110,
|
|
CHUNK_FACELIST = 0x4120,
|
|
CHUNK_FACEMAT = 0x4130,
|
|
CHUNK_MAPLIST = 0x4140,
|
|
CHUNK_SMOOLIST = 0x4150,
|
|
CHUNK_TRMATRIX = 0x4160,
|
|
CHUNK_LIGHT = 0x4600,
|
|
CHUNK_SPOTLIGHT = 0x4610,
|
|
CHUNK_CAMERA = 0x4700,
|
|
CHUNK_MATERIAL = 0xAFFF,
|
|
CHUNK_MATNAME = 0xA000,
|
|
CHUNK_AMBIENT = 0xA010,
|
|
CHUNK_DIFFUSE = 0xA020,
|
|
CHUNK_SPECULAR = 0xA030,
|
|
CHUNK_TEXTURE = 0xA200,
|
|
CHUNK_BUMPMAP = 0xA230,
|
|
CHUNK_MAPFILE = 0xA300,
|
|
CHUNK_KEYFRAMER = 0xB000,
|
|
CHUNK_MESHINFO = 0xB002,
|
|
CHUNK_HIERPOS = 0xB030,
|
|
CHUNK_HIERINFO = 0xB010,
|
|
CHUNK_FRAMES = 0xB008
|
|
};
|
|
|
|
static Vector parseColor()
|
|
{
|
|
Vector v;
|
|
unsigned char rgb[3];
|
|
enterChunk();
|
|
while (int id = nextChunk()) {
|
|
switch (id) {
|
|
case CHUNK_RGBF:
|
|
in.sgetn((char*)&v, 12);
|
|
break;
|
|
case CHUNK_RGBB:
|
|
in.sgetn((char*)rgb, 3);
|
|
v = Vector(rgb[0] / 255.0f, rgb[1] / 255.0f, rgb[2] / 255.0f);
|
|
}
|
|
}
|
|
leaveChunk();
|
|
return v;
|
|
}
|
|
|
|
static void parseVertList()
|
|
{
|
|
unsigned short cnt;
|
|
in.sgetn((char*)&cnt, 2);
|
|
_log("VertList cnt=" + itoa(cnt));
|
|
while (cnt--) {
|
|
Surface::Vertex v;
|
|
in.sgetn((char*)&v.coords, 12);
|
|
if (conv)
|
|
v.coords = conv_tform * v.coords;
|
|
MeshLoader::addVertex(v);
|
|
}
|
|
}
|
|
|
|
static void parseFaceMat()
|
|
{
|
|
string name = parseString();
|
|
_log("FaceMat: " + name);
|
|
Brush mat = materials_map[name];
|
|
unsigned short cnt;
|
|
in.sgetn((char*)&cnt, 2);
|
|
while (cnt--) {
|
|
unsigned short face;
|
|
in.sgetn((char*)&face, 2);
|
|
faces[face].brush = mat;
|
|
}
|
|
}
|
|
|
|
static void parseFaceList()
|
|
{
|
|
unsigned short cnt;
|
|
in.sgetn((char*)&cnt, 2);
|
|
_log("FaceList cnt=" + itoa(cnt));
|
|
while (cnt--) {
|
|
unsigned short v[4];
|
|
in.sgetn((char*)v, 8);
|
|
Face3DS face;
|
|
face.verts[0] = v[0];
|
|
face.verts[1] = v[1];
|
|
face.verts[2] = v[2];
|
|
if (flip_tris)
|
|
std::swap(face.verts[1], face.verts[2]);
|
|
faces.push_back(face);
|
|
}
|
|
enterChunk();
|
|
while (int id = nextChunk()) {
|
|
switch (id) {
|
|
case CHUNK_FACEMAT:
|
|
parseFaceMat();
|
|
break;
|
|
}
|
|
}
|
|
leaveChunk();
|
|
}
|
|
|
|
static void parseMapList()
|
|
{
|
|
_log("MapList");
|
|
unsigned short cnt;
|
|
in.sgetn((char*)&cnt, 2);
|
|
for (int k = 0; k < cnt; ++k) {
|
|
float uv[2];
|
|
in.sgetn((char*)uv, 8);
|
|
Surface::Vertex& v = MeshLoader::refVertex(k);
|
|
v.tex_coords[0][0] = v.tex_coords[1][0] = uv[0];
|
|
v.tex_coords[0][1] = v.tex_coords[1][1] = 1 - uv[1];
|
|
// v->tex_coords[0]=v->tex_coords[1]=Vector( uv[0],1-uv[1],1 );
|
|
}
|
|
}
|
|
|
|
static void parseTriMesh(MeshModel* mesh)
|
|
{
|
|
_log("TriMesh");
|
|
enterChunk();
|
|
Transform tform;
|
|
|
|
faces.clear();
|
|
|
|
MeshLoader::beginMesh();
|
|
|
|
while (int id = nextChunk()) {
|
|
switch (id) {
|
|
case CHUNK_VERTLIST:
|
|
if (!animonly)
|
|
parseVertList();
|
|
break;
|
|
case CHUNK_MAPLIST:
|
|
if (!animonly)
|
|
parseMapList();
|
|
break;
|
|
case CHUNK_FACELIST:
|
|
if (!animonly)
|
|
parseFaceList();
|
|
break;
|
|
case CHUNK_TRMATRIX:
|
|
in.sgetn((char*)&tform, 48);
|
|
if (conv)
|
|
tform = conv_tform * tform * -conv_tform;
|
|
break;
|
|
}
|
|
}
|
|
leaveChunk();
|
|
|
|
//should really do something here...
|
|
// bool neg_x=tform.m.j.cross(tform.m.k).dot(tform.m.i)<0;
|
|
|
|
int k;
|
|
|
|
mesh->SetWorldTransform(tform);
|
|
|
|
if (animonly) {
|
|
MeshLoader::endMesh(0);
|
|
return;
|
|
}
|
|
|
|
Transform inv_tform = -tform;
|
|
for (k = 0; k < MeshLoader::numVertices(); ++k) {
|
|
Surface::Vertex& v = MeshLoader::refVertex(k);
|
|
v.coords = inv_tform * v.coords;
|
|
}
|
|
|
|
for (k = 0; k < faces.size(); ++k) {
|
|
const Face3DS& f = faces[k];
|
|
MeshLoader::addTriangle(f.verts, f.brush);
|
|
}
|
|
|
|
MeshLoader::endMesh(mesh);
|
|
mesh->updateNormals();
|
|
|
|
faces.clear();
|
|
}
|
|
|
|
static void parseObject(MeshModel* root)
|
|
{
|
|
//skip name
|
|
string name = parseString();
|
|
_log("Object:" + name);
|
|
MeshModel* mesh = 0;
|
|
|
|
enterChunk();
|
|
while (int id = nextChunk()) {
|
|
switch (id) {
|
|
case CHUNK_TRIMESH:
|
|
mesh = new MeshModel();
|
|
mesh->SetName(name);
|
|
mesh->SetParent(root);
|
|
name_map[name] = mesh;
|
|
parseTriMesh(mesh);
|
|
break;
|
|
}
|
|
}
|
|
leaveChunk();
|
|
}
|
|
|
|
static void parseMaterial()
|
|
{
|
|
_log("Material");
|
|
Brush mat;
|
|
string name, tex_name;
|
|
enterChunk();
|
|
while (int id = nextChunk()) {
|
|
switch (id) {
|
|
case CHUNK_MATNAME:
|
|
name = parseString();
|
|
break;
|
|
case CHUNK_DIFFUSE:
|
|
mat.setColor(parseColor());
|
|
break;
|
|
case CHUNK_AMBIENT:
|
|
break;
|
|
case CHUNK_SPECULAR:
|
|
break;
|
|
case CHUNK_TEXTURE:
|
|
enterChunk();
|
|
while (int id = nextChunk()) {
|
|
switch (id) {
|
|
case CHUNK_MAPFILE:
|
|
tex_name = parseString();
|
|
break;
|
|
}
|
|
}
|
|
leaveChunk();
|
|
break;
|
|
}
|
|
}
|
|
if (tex_name.size()) {
|
|
mat.setTexture(0, Texture(tex_name, 0), 0);
|
|
mat.setColor(Vector(1, 1, 1));
|
|
}
|
|
if (name.size()) {
|
|
materials_map[name] = mat;
|
|
}
|
|
leaveChunk();
|
|
}
|
|
|
|
static void parseScene(MeshModel* root)
|
|
{
|
|
_log("Scene");
|
|
enterChunk();
|
|
while (int id = nextChunk()) {
|
|
switch (id) {
|
|
case CHUNK_OBJECT:
|
|
parseObject(root);
|
|
break;
|
|
case CHUNK_MATERIAL:
|
|
if (!animonly)
|
|
parseMaterial();
|
|
break;
|
|
}
|
|
}
|
|
leaveChunk();
|
|
}
|
|
|
|
static void parseAnimKeys(Animation* anim, int type)
|
|
{
|
|
int cnt = 0;
|
|
short t_flags;
|
|
in.sgetn((char*)&t_flags, 2);
|
|
in.pubseekoff(8, ios_base::cur);
|
|
in.sgetn((char*)&cnt, 2);
|
|
in.pubseekoff(2, ios_base::cur);
|
|
_log("ANIM_TRACK: frames=" + itoa(cnt));
|
|
Vector pos, axis, scale;
|
|
float angle;
|
|
Quat quat;
|
|
for (int k = 0; k < cnt; ++k) {
|
|
int time;
|
|
short flags;
|
|
in.sgetn((char*)&time, 4);
|
|
in.sgetn((char*)&flags, 2);
|
|
float tens = 0, cont = 0, bias = 0, ease_to = 0, ease_from = 0;
|
|
if (flags & 1)
|
|
in.sgetn((char*)&tens, 4);
|
|
if (flags & 2)
|
|
in.sgetn((char*)&cont, 4);
|
|
if (flags & 4)
|
|
in.sgetn((char*)&bias, 4);
|
|
if (flags & 8)
|
|
in.sgetn((char*)&ease_to, 4);
|
|
if (flags & 16)
|
|
in.sgetn((char*)&ease_from, 4);
|
|
switch (type) {
|
|
case 0xb020: //POS_TRACK_TAG
|
|
in.sgetn((char*)&pos, 12);
|
|
if (conv)
|
|
pos = conv_tform * pos;
|
|
// _log( "POS_KEY: time="+itoa(time)+" pos="+ftoa( pos.x )+","+ftoa( pos.y )+","+ftoa( pos.z ) );
|
|
if (time <= anim_len)
|
|
anim->setPositionKey(time, pos);
|
|
break;
|
|
case 0xb021: //ROT_TRACK_TAG
|
|
in.sgetn((char*)&angle, 4);
|
|
in.sgetn((char*)&axis, 12);
|
|
// _log( "ROT_KEY: time="+itoa(time)+" angle="+ftoa(angle)+" axis="+ftoa(axis.x)+","+ftoa(axis.y)+","+ftoa(axis.z) );
|
|
if (axis.length() > FLT_EPSILON) {
|
|
if (flip_tris)
|
|
angle = -angle;
|
|
if (conv)
|
|
axis = conv_tform.m * axis;
|
|
quat = Quat(cosf(angle / 2), axis.normalized() * sinf(angle / 2)) * quat;
|
|
quat.normalize();
|
|
}
|
|
if (time <= anim_len)
|
|
anim->setRotationKey(time, quat);
|
|
break;
|
|
case 0xb022: //SCL_TRACK_TAG
|
|
in.sgetn((char*)&scale, 12);
|
|
if (conv)
|
|
scale = conv_tform.m * scale;
|
|
// scale.x=fabs(scale.x);scale.y=fabs(scale.y);scale.z=fabs(scale.z);
|
|
_log("SCL_KEY: time=" + itoa(time) + " scale=" + ftoa(scale.x) + "," + ftoa(scale.y) + "," + ftoa(scale.z));
|
|
if (time <= anim_len)
|
|
anim->setScaleKey(time, scale);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void parseMeshInfo(MeshModel* root, float curr_time)
|
|
{
|
|
_log("OBJECT_NODE_TAG");
|
|
enterChunk();
|
|
string name, inst;
|
|
Vector pivot;
|
|
Animation anim;
|
|
unsigned short id = 65535, parent = 65535, flags1, flags2;
|
|
Box box = Box(Vector(), Vector());
|
|
Vector box_centre;
|
|
while (int chunk_id = nextChunk()) {
|
|
switch (chunk_id) {
|
|
case 0xb030: //NODE_ID
|
|
in.sgetn((char*)&id, 2);
|
|
_log("NODE_ID: " + itoa(id));
|
|
break;
|
|
case 0xb010: //NODE_HDR
|
|
name = parseString();
|
|
in.sgetn((char*)&flags1, 2);
|
|
in.sgetn((char*)&flags2, 2);
|
|
in.sgetn((char*)&parent, 2);
|
|
_log("NODE_HDR: name=" + name + " parent=" + itoa(parent));
|
|
break;
|
|
case 0xb011: //INSTANCE NAME
|
|
inst = parseString();
|
|
_log("INSTANCE_NAME: " + inst);
|
|
break;
|
|
case 0xb013: //PIVOT
|
|
in.sgetn((char*)&pivot, 12);
|
|
if (conv)
|
|
pivot = conv_tform * pivot;
|
|
_log("PIVOT: " + ftoa(pivot.x) + "," + ftoa(pivot.y) + "," + ftoa(pivot.z));
|
|
break;
|
|
case 0xb014: //BOUNDBOX
|
|
in.sgetn((char*)&(box.a), 12);
|
|
in.sgetn((char*)&(box.b), 12);
|
|
box_centre = box.centre();
|
|
if (conv)
|
|
box_centre = conv_tform * box_centre;
|
|
_log("BOUNDBOX: min=" + ftoa(box.a.x) + "," + ftoa(box.a.y) + "," + ftoa(box.a.z) + " max=" + ftoa(box.b.x)
|
|
+ "," + ftoa(box.b.y) + "," + ftoa(box.b.z));
|
|
break;
|
|
case 0xb020: //POS_TRACK_TAG
|
|
case 0xb021: //ROT_TRACK_TAG
|
|
case 0xb022: //SCALE_TRACK_TAG
|
|
if (!collapse)
|
|
parseAnimKeys(&anim, chunk_id);
|
|
break;
|
|
}
|
|
}
|
|
leaveChunk();
|
|
|
|
MeshModel* p = root;
|
|
if (parent != 65535) {
|
|
map<int, MeshModel*>::const_iterator it = id_map.find(parent);
|
|
if (it == id_map.end())
|
|
return;
|
|
p = it->second;
|
|
}
|
|
MeshModel* mesh = 0;
|
|
if (name == "$$$DUMMY") {
|
|
mesh = new MeshModel();
|
|
mesh->SetName(inst);
|
|
mesh->SetParent(p);
|
|
} else {
|
|
map<string, MeshModel*>::const_iterator it = name_map.find(name);
|
|
if (it == name_map.end())
|
|
return;
|
|
mesh = it->second;
|
|
name_map.erase(name);
|
|
if (pivot != Vector()) {
|
|
mesh->transform(-pivot);
|
|
}
|
|
Transform t = mesh->GetWorldTransform();
|
|
mesh->SetParent(p);
|
|
mesh->SetWorldTransform(t);
|
|
}
|
|
|
|
mesh->setAnimation(anim);
|
|
|
|
if (id != 65535)
|
|
id_map[id] = mesh;
|
|
}
|
|
|
|
static void parseKeyFramer(MeshModel* root)
|
|
{
|
|
_log("KeyFramer");
|
|
enterChunk();
|
|
string file_3ds;
|
|
unsigned short rev, curr_time = 0;
|
|
while (int id = nextChunk()) {
|
|
switch (id) {
|
|
case 0xb009: //CURR_TIME
|
|
in.sgetn((char*)&curr_time, 2);
|
|
_log("CURR_TIME: " + itoa(curr_time));
|
|
break;
|
|
case 0xb00a: //KFHDR
|
|
in.sgetn((char*)&rev, 2);
|
|
file_3ds = parseString();
|
|
in.sgetn((char*)&anim_len, 2);
|
|
_log("KFHDR: revision=" + itoa(rev) + " 3dsfile=" + file_3ds + " anim_len=" + itoa(anim_len));
|
|
break;
|
|
case 0xb002: //object keyframer data...
|
|
parseMeshInfo(root, curr_time);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!collapse) {
|
|
root->setAnimator(new Animator(root, anim_len));
|
|
}
|
|
|
|
leaveChunk();
|
|
}
|
|
|
|
static MeshModel* parseFile()
|
|
{
|
|
unsigned short id;
|
|
int len;
|
|
in.sgetn((char*)&id, 2);
|
|
in.sgetn((char*)&len, 4);
|
|
if (id != CHUNK_MAIN)
|
|
return 0;
|
|
chunk_end = (int)in.pubseekoff(0, ios_base::cur) + len - 6;
|
|
|
|
enterChunk();
|
|
MeshModel* root = new MeshModel();
|
|
while (int id = nextChunk()) {
|
|
switch (id) {
|
|
case CHUNK_SCENE:
|
|
parseScene(root);
|
|
break;
|
|
case CHUNK_KEYFRAMER:
|
|
parseKeyFramer(root);
|
|
break;
|
|
}
|
|
}
|
|
leaveChunk();
|
|
return root;
|
|
}
|
|
|
|
MeshModel* Loader_3DS::load(const string& filename, const Transform& t, int hint)
|
|
{
|
|
conv_tform = t;
|
|
conv = flip_tris = false;
|
|
if (conv_tform != Transform()) {
|
|
conv = true;
|
|
if (conv_tform.m.i.cross(conv_tform.m.j).dot(conv_tform.m.k) < 0)
|
|
flip_tris = true;
|
|
}
|
|
|
|
collapse = !!(hint & MeshLoader::HINT_COLLAPSE);
|
|
animonly = !!(hint & MeshLoader::HINT_ANIMONLY);
|
|
|
|
if (!in.open(filename.c_str(), ios_base::in | ios_base::binary)) {
|
|
return 0;
|
|
}
|
|
|
|
MeshModel* root = parseFile();
|
|
in.close();
|
|
|
|
materials_map.clear();
|
|
name_map.clear();
|
|
id_map.clear();
|
|
|
|
return root;
|
|
}
|