
/*           -- FEXW 3D vector engine --
 *        Copyright (c) Mats Byggmastar 1996
 *
 *      3D Studio object & keyframer reader
 */

#define FILELOAD    1
#define MEMLOAD     1

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#include <malloc.h>
#include "3dsread.h"


static void (* dread)(void * dest, int len);
static void (* dsetpos)(uint pos);
static uint (* dgetpos)(void);

static char * errmsg;
static jmp_buf EnvState;


static void * xmalloc(int bytes)
{
    return malloc(bytes);    
}

static void xfree(void * mem)
{
    free(mem);
}

static void * xrealloc(void * mem, int bytes)
{
    return realloc(mem, bytes);
}


#ifdef FILELOAD

static FILE * InFile=0;

static void FileRead(void * dest, int len)
{
	if(fread(dest, len, 1, InFile) != 1) {
        strcpy(errmsg, "Error reading file.");
		longjmp(EnvState, 1);
	}
}

static void FileSetpos(uint pos)
{
	if(fseek(InFile, (long)pos, SEEK_SET) != 0) {
        strcpy(errmsg, "Error moving file pointer.");
		longjmp(EnvState, 1);
	}
}

static uint FileGetpos(void)
{
	long pos;
	if((pos=ftell(InFile)) == -1L) {
        strcpy(errmsg, "Error reading file position.");
		longjmp(EnvState, 1);
	}
    return (uint) pos;
}
#endif


#ifdef MEMLOAD
static uchar * MemBuffer=0;
static uint MemBufferLen=0, MemBufferIndex=0;

static void MemRead(void * dest, int len)
{
	// Never allow any reads past the membuffer.
	if(MemBufferIndex+len > MemBufferLen) {
        strcpy(errmsg, "Trying to read past memory buffer.");
		longjmp(EnvState, 1);
	}
	memcpy(dest, &MemBuffer[MemBufferIndex], len);
    MemBufferIndex += len;
}

static void MemSetpos(uint pos)
{
	// It should be legal to move the position one byte
	// past the membuffer. The mem must not be read though.
	if(pos > MemBufferLen) {
        strcpy(errmsg, "Trying to set pointer past memory buffer.");
		longjmp(EnvState, 1);
	}
	MemBufferIndex=pos;
}

static uint MemGetpos(void)
{
	return MemBufferIndex;
}
#endif



static F3dsScene * Scene;

static F3dsObject * GetObject(void)
{
	void * mem;
    uint size = sizeof(F3dsObject)*(Scene->objects+1);
    if((mem=xrealloc(Scene->objlist, size)) == 0) {
        sprintf(errmsg, "Error reallocating memory for object list "
                        "(%u bytes).", size);
		longjmp(EnvState, 1);
	}
    Scene->objlist=(F3dsObject *) mem;
    F3dsObject * obj=&Scene->objlist[Scene->objects++];
    memset(obj, 0, sizeof(F3dsObject));
	return obj;
}

static F3dsMaterial * GetMaterial(void)
{
	void * mem;
    uint size = sizeof(F3dsMaterial)*(Scene->materials+1);
    if((mem=xrealloc(Scene->matlist, size)) == 0) {
        sprintf(errmsg, "Error reallocating memory for material list "
                        "(%u bytes).", size);
		longjmp(EnvState, 1);
	}
    Scene->matlist=(F3dsMaterial *) mem;
    F3dsMaterial * mat=&Scene->matlist[Scene->materials++];
    memset(mat, 0, sizeof(F3dsMaterial));
    return mat;
}

static F3dsCamera * GetCamera(void)
{
	void * mem;
    uint size = sizeof(F3dsCamera)*(Scene->cameras+1);
    if((mem=xrealloc(Scene->camlist, size)) == 0) {
        sprintf(errmsg, "Error reallocating memory for camera list "
                        "(%u bytes).", size);
		longjmp(EnvState, 1);
	}
    Scene->camlist=(F3dsCamera *) mem;
    F3dsCamera * cam=&Scene->camlist[Scene->cameras++];
    memset(cam, 0, sizeof(F3dsCamera));
    return cam;
}

static F3dsLight * GetLight(void)
{
	void * mem;
    uint size = sizeof(F3dsLight)*(Scene->lights+1);
    if((mem=xrealloc(Scene->liglist, size)) == 0) {
        sprintf(errmsg, "Error reallocating memory for light list ",
                        "(%u bytes).", size);
		longjmp(EnvState, 1);
	}
    Scene->liglist=(F3dsLight *) mem;
    F3dsLight * lig=&Scene->liglist[Scene->lights++];
    memset(lig, 0, sizeof(F3dsLight));
    return lig;
}


static void * getmem(int size)
{
	void * mem;
    if((mem=xmalloc(size))==0) {
        sprintf(errmsg, "Failed to allocate memory (%u bytes).", size);
		longjmp(EnvState, 1);
	}
    memset(mem, 0, size);
    return mem;
}

/*
 *  Each 3DS data-chunk starts with a 6 byte header. The first item in the
 *  header is a 2 byte (ushort) id-number. After that follows a uint which
 *  gives the size of the data-chunk including the header. The size can be
 *  used as an relative offset to the next chunk.
 */

// tab 4
enum {
    CHUNK_RGBF      = 0x0010,
    CHUNK_RGBB      = 0x0011,
    //CHUNK_RBGB2     = 0x0012,       // ?? NOT HLS.
    CHUNK_PRJ       = 0xC23D,
    CHUNK_MLI       = 0x3DAA,
    CHUNK_MAIN      = 0x4D4D,
        CHUNK_OBJMESH   = 0x3D3D,
            CHUNK_BKGCOLOR  = 0x1200,
            CHUNK_AMBCOLOR  = 0x2100,
            CHUNK_OBJBLOCK  = 0x4000,
                CHUNK_TRIMESH   = 0x4100,
                    CHUNK_VERTLIST  = 0x4110,
                    CHUNK_FACELIST  = 0x4120,
                    CHUNK_FACEMAT   = 0x4130,
                    CHUNK_MAPLIST   = 0x4140,
                    CHUNK_SMOOLIST  = 0x4150,
                    CHUNK_TRMATRIX  = 0x4160,
                    CHUNK_MESHCOLOR = 0x4165,
                    CHUNK_TXTINFO   = 0x4170,
                CHUNK_LIGHT     = 0x4600,
                    CHUNK_SPOTLIGHT = 0x4610,
                CHUNK_CAMERA    = 0x4700,
                CHUNK_HIERARCHY = 0x4F00,
            CHUNK_VIEWPORT  = 0x7001,
            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_AMBIENTKEY    = 0xB001,
            CHUNK_TRACKINFO = 0xB002,
                CHUNK_TRACKOBJNAME  = 0xB010,
                CHUNK_TRACKPIVOT    = 0xB013,
                CHUNK_TRACKPOS      = 0xB020,
                CHUNK_TRACKROTATE   = 0xB021,
                CHUNK_TRACKSCALE    = 0xB022,
                CHUNK_TRACKMORPH    = 0xB026,
                CHUNK_TRACKHIDE     = 0xB029,
                CHUNK_OBJNUMBER     = 0xB030,
            CHUNK_TRACKCAMERA = 0xB003,
                CHUNK_TRACKFOV  = 0xB023,
                CHUNK_TRACKROLL = 0xB024,
            CHUNK_TRACKCAMTGT = 0xB004,
            CHUNK_TRACKLIGHT  = 0xB005,
            CHUNK_TRACKLIGTGT = 0xB006,
            CHUNK_TRACKSPOTL  = 0xB007,
            CHUNK_FRAMES    = 0xB008,
};

static void ReadName(char * name, uint maxlen)
{
	uint n=0;
	do {
		dread(&name[n++], 1);
	} while(name[n-1]!='\0' && n<maxlen);
	name[n-1]='\0';
}

static ushort ReadObjectNumber(uint p)
{
	ushort w;
	dread(&w, sizeof(w));
	return w;
}

static void ReadVertList(uint, F3dsObject * obj)
{
    float c[3];
    ushort nv;
	dread(&nv, sizeof(nv));
    obj->vertices=nv;
	int k=nv;
    obj->vtxlist=(F3dsVertex *) getmem(sizeof(F3dsVertex)*k);
	for(int n=0; n<k; n++) {
        dread(c, sizeof(c));
        obj->vtxlist[n].x=c[0];
        obj->vtxlist[n].y=c[1];
        obj->vtxlist[n].z=c[2];
	}
}

static void ReadFaceList(uint, F3dsObject * obj)
{
    ushort c[4];
    ushort nv;
	dread(&nv, sizeof(nv));
    obj->faces=nv;
	int k=nv;
    obj->faclist=(F3dsFace *) getmem(sizeof(F3dsFace)*k);
	for(int n=0; n<k; n++) {
        dread(c, sizeof(c));
        obj->faclist[n].p0=c[0];
        obj->faclist[n].p1=c[1];
        obj->faclist[n].p2=c[2];
        obj->faclist[n].flags=c[3];
	}
}

static void ReadMapList(uint, F3dsObject * obj)
{
    float c[2];
    ushort nv;
	dread(&nv, sizeof(nv));
    if(nv != obj->vertices) {
        sprintf(errmsg, "Bad number of map coordinates [%s].", obj->name);
		longjmp(EnvState, 1);
    }            
    int k=nv;
	for(int n=0; n<k; n++) {
        dread(c, sizeof(c));
        // Set unset U,V coordinates to 0
        if(c[0] < -100.0 || c[0] > 100.0) c[0] = 0.0;
        if(c[1] < -100.0 || c[1] > 100.0) c[1] = 0.0;
        obj->vtxlist[n].u=c[0];
        obj->vtxlist[n].v=c[1];
	}
}

static void ReadSmoothList(uint p, F3dsObject * obj)
{
    uint c;
    int n=0;
    while(dgetpos() < p && n < obj->faces) {
        dread(&c, sizeof(c));
        obj->faclist[n].smooth = c;
        n++;
	}
}

static void ReadTraMatrix(uint, F3dsObject * obj)
{
    float c[3];
    // Read 3x3 rotation matrix (world -> object space)
    for(int n=0; n<3; n++) {
        dread(c, sizeof(c));
        obj->aff[n][0]=c[0];
        obj->aff[n][1]=c[1];
        obj->aff[n][2]=c[2];
    }
    // Read 3x1 translation matrix (object -> world space)
    // Invert it and concatenate it with the rotation matrix to make
    // a complete affine transformation matrix. 
    dread(c, sizeof(c));

    // Store the 3x1 translation matrix as object pivot also
    obj->objpivx = c[0];
    obj->objpivy = c[1];
    obj->objpivz = c[2];

    float x=-c[0];
    float y=-c[1];
    float z=-c[2];
    obj->aff[3][0]=obj->aff[0][0]*x+obj->aff[0][1]*y+obj->aff[0][2]*z;
    obj->aff[3][1]=obj->aff[1][0]*x+obj->aff[1][1]*y+obj->aff[1][2]*z;
    obj->aff[3][2]=obj->aff[2][0]*x+obj->aff[2][1]*y+obj->aff[2][2]*z;

    // This 4x3 matrix now is a ready to use affine transformation matrix
    // for converting from world -> object space. The object vertices
    // are in world space in the 3DS file.
}

static void ReadFaceMat(uint, F3dsObject * obj)
{
    ushort nv;
    char name[20];
    // Material name is the first item
    ReadName(name, sizeof(name));
    // Find the material number in the material list
    int matnum=0;
    for(int n=0; n<Scene->materials; n++) 
        if(!strcmp(Scene->matlist[n].name, name)) {
            matnum=n;
            break;
        }

	dread(&nv, sizeof(nv));
    int k=nv;
    for(n=0; n<k; n++) {
        dread(&nv, sizeof(nv));
        if(nv > obj->faces)
            nv = 0;
        obj->faclist[nv].material = matnum;
    }
}

static void ReadTxtInfoBlock(uint, F3dsObject * obj)
{
    float c[2];
    dsetpos(dgetpos()+sizeof(ushort));      // skip map_type
    dread(c, sizeof(c));
    obj->utile = c[0];
    obj->vtile = c[1];
    dsetpos(dgetpos()+19*sizeof(float));    // skip rest of chunk
}

static void ReadTriMeshBlocks(uint p, char * name)
{
    ushort id;
    uint len, pc;
    uchar c;
    F3dsObject * obj=GetObject();
    strcpy(obj->name, name);
	while((pc=dgetpos()) < p)
	{
		dread(&id, sizeof(id));
		dread(&len, sizeof(len));
		switch((int)id)
		{
            // Note! The FACELIST chunk include FACEMAT and SMOOLIST chunks.
            case CHUNK_FACELIST:  ReadFaceList  (pc+len, obj); continue;
            case CHUNK_FACEMAT:   ReadFaceMat   (pc+len, obj); break;
            case CHUNK_SMOOLIST:  ReadSmoothList(pc+len, obj); break;
            case CHUNK_VERTLIST:  ReadVertList  (pc+len, obj); break;
            case CHUNK_MAPLIST:   ReadMapList   (pc+len, obj); break;
            case CHUNK_TRMATRIX:  ReadTraMatrix (pc+len, obj); break;
            case CHUNK_TXTINFO:   ReadTxtInfoBlock(pc+len, obj); break;
            case CHUNK_MESHCOLOR: dread(&c, sizeof(c)); obj->color=c; break; 
		}
        dsetpos(pc+len);
    }
}

static void ReadCameraBlock(uint p, char * name)
{
    float c[8];
    F3dsCamera * cam=GetCamera();
    strcpy(cam->name, name);
    dread(&c, sizeof(c));
    cam->px=c[0];
    cam->py=c[1];
    cam->pz=c[2];  
    cam->tx=c[3];
    cam->ty=c[4];
    cam->tz=c[5];
    cam->roll=c[6];
    cam->lens=c[7];
}

static void ReadLightRGB(uint, F3dsLight * lig)
{
    float c[3];
    dread(&c, sizeof(c));
    lig->r=c[0];
    lig->g=c[1];
    lig->b=c[2];
}

static void ReadLightBlock(uint p, char * name)
{
    ushort id;
    uint len, pc;
    float c[3];

    F3dsLight * lig=GetLight();
    strcpy(lig->name, name);
    dread(&c, sizeof(c));
    lig->x=c[0];
    lig->y=c[1];
    lig->z=c[2];

	while((pc=dgetpos()) < p)
	{
		dread(&id, sizeof(id));
		dread(&len, sizeof(len));
		switch((int)id)
		{
            case CHUNK_RGBF: ReadLightRGB(pc+len, lig); break;
            //case CHUNK_SPOTLIGHT:
		}
        dsetpos(pc+len);
	}
}

static void ReadObjectBlocks(uint p)
{
    ushort id;
    uint len, pc;
	char name[16];

    // Object name is the first item
    ReadName(name, sizeof(name));

	while((pc=dgetpos()) < p)
	{
		dread(&id, sizeof(id));
		dread(&len, sizeof(len));
		switch((int)id)
		{
			case CHUNK_TRIMESH:	ReadTriMeshBlocks(pc+len, name); break;
            case CHUNK_CAMERA:  ReadCameraBlock(pc+len, name);   break;
            case CHUNK_LIGHT:   ReadLightBlock(pc+len, name);    break;
		}
        dsetpos(pc+len);
	}
}

static void ReadMaterialBlocks(uint p)
{
    ushort id;
    uint len, pc;

    F3dsMaterial * mat=GetMaterial();
	while((pc=dgetpos()) < p)
	{
		dread(&id, sizeof(id));
		dread(&len, sizeof(len));
		switch((int)id)
		{
            case CHUNK_MATNAME: ReadName(mat->name, sizeof(mat->name)); break;
            //case CHUNK_AMBIENT: 
            //case CHUNK_DIFFUSE:
            //case CHUNK_SPECULAR:
            //case CHUNK_TEXTURE:
            //case CHUNK_BUMPMAP:
            //case CHUNK_MAPFILE:
		}
        dsetpos(pc+len);
	}
}

static void ReadObjMeshBlocks(uint p)
{
    ushort id;
    uint len, pc;

	while((pc=dgetpos()) < p)
	{
		dread(&id, sizeof(id));
		dread(&len, sizeof(len));
		switch((int)id)
		{
            case CHUNK_OBJBLOCK: ReadObjectBlocks(pc+len);   break;
            case CHUNK_MATERIAL: ReadMaterialBlocks(pc+len); break;
            //case CHUNK_VIEWPORT
			//case CHUNK_AMBCOLOR:
			//case CHUNK_BKGCOLOR:
		}
        dsetpos(pc+len);
	}
}

static void ReadFramesBlock(uint)
{
    uint c[2];
    dread(c, sizeof(c));
    Scene->startframe = c[0];
    Scene->endframe = c[1];
}

static void ReadTrackPivot(uint, F3dsObject * obj)
{
    float c[3];
    dread(c, sizeof(c));
    obj->trackpivx = c[0];
    obj->trackpivy = c[1];
    obj->trackpivz = c[2];
}

static void ReadSplineFlags(float * data, ushort flags)
{
    for(int i=0; i<16; i++) 
        if(flags & (1 << i)) 
            dread(data+i, sizeof(float));
}

static F3dsPosKey * ReadPosKeys(uint p, ushort * keys, ushort * kflags)
{
    ushort n, unknown, frame, flags;
    float pos[3], data[16];
    F3dsPosKey * key;

    dread(kflags, 2);
    dsetpos(dgetpos()+8);   // unknown data
    dread(&n, sizeof(n));
    *keys = n;
    dsetpos(dgetpos()+2);   // unknown data
    key = (F3dsPosKey *) getmem((int)n*sizeof(F3dsPosKey));

    for(int t=0; t<n; t++) {
        data[0] = 0.0;      // default tension
        data[1] = 0.0;      // default continuity
        data[2] = 0.0;      // default bias
        data[3] = 0.0;      // default ease to
        data[4] = 0.0;      // default ease from
        dread(&frame, sizeof(frame));
        dread(&unknown, sizeof(unknown));
        dread(&flags, sizeof(flags));
        ReadSplineFlags(data, flags);
        dread(pos, sizeof(pos));
        key[t].tens = data[0];
        key[t].cont = data[1];
        key[t].bias = data[2];
        key[t].easeTo = data[3];
        key[t].easeFrom = data[4];
        key[t].frame = frame;
        key[t].flags = flags;
        key[t].x = pos[0];
        key[t].y = pos[1];
        key[t].z = pos[2];
    }
    return key;
}

static F3dsRotKey * ReadRotKeys(uint p, ushort * keys, ushort * kflags)
{
    ushort n, unknown, frame, flags;
    float rot[4], data[16];
    F3dsRotKey * key;

    dread(kflags, 2);
    dsetpos(dgetpos()+8);   // unknown data
    dread(&n, sizeof(n));
    *keys = n;
    dsetpos(dgetpos()+2);   // unknown data
    key = (F3dsRotKey *) getmem((int)n*sizeof(F3dsRotKey));

    for(int t=0; t<n; t++) {
        data[0] = 0.0;      // default tension
        data[1] = 0.0;      // default continuity
        data[2] = 0.0;      // default bias
        data[3] = 0.0;      // default ease to
        data[4] = 0.0;      // default ease from
        dread(&frame, sizeof(frame));
        dread(&unknown, sizeof(unknown));
        dread(&flags, sizeof(flags));
        ReadSplineFlags(data, flags);
        dread(rot, sizeof(rot));
        key[t].tens = data[0];
        key[t].cont = data[1];
        key[t].bias = data[2];
        key[t].easeTo = data[3];
        key[t].easeFrom = data[4];
        key[t].frame = frame;
        key[t].flags = flags;
        key[t].a = rot[0];
        key[t].x = rot[1];
        key[t].y = rot[2];
        key[t].z = rot[3];
    }
    return key;
}

static F3dsValKey * ReadValKeys(uint p, ushort * keys, ushort * kflags)
{
    ushort n, unknown, frame, flags;
    float val, data[16];
    F3dsValKey * key;

    dread(kflags, 2);
    dsetpos(dgetpos()+8);   // unknown data
    dread(&n, sizeof(n));
    *keys = n;
    dsetpos(dgetpos()+2);   // unknown data
    key = (F3dsValKey *) getmem((int)n*sizeof(F3dsValKey));

    for(int t=0; t<n; t++) {
        data[0] = 0.0;      // default tension
        data[1] = 0.0;      // default continuity
        data[2] = 0.0;      // default bias
        data[3] = 0.0;      // default ease to
        data[4] = 0.0;      // default ease from
        dread(&frame, sizeof(frame));
        dread(&unknown, sizeof(unknown));
        dread(&flags, sizeof(flags));
        ReadSplineFlags(data, flags);
        dread(&val, sizeof(val));
        key[t].tens = data[0];
        key[t].cont = data[1];
        key[t].bias = data[2];
        key[t].easeTo = data[3];
        key[t].easeFrom = data[4];
        key[t].frame = frame;
        key[t].flags = flags;
        key[t].v = val;
    }
    return key;
}

static F3dsMorKey * ReadMorKeys(uint p, ushort * keys, ushort * kflags)
{
    ushort n, unknown, frame, flags;
    float data[16];
    F3dsMorKey * key;

    dread(kflags, 2);
    dsetpos(dgetpos()+8);   // unknown data
    dread(&n, sizeof(n));
    *keys = n;
    dsetpos(dgetpos()+2);   // unknown data
    key = (F3dsMorKey *) getmem((int)n*sizeof(F3dsMorKey));

    for(int t=0; t<n; t++) {
        data[0] = 0.0;      // default tension
        data[1] = 0.0;      // default continuity
        data[2] = 0.0;      // default bias
        data[3] = 0.0;      // default ease to
        data[4] = 0.0;      // default ease from
        dread(&frame, sizeof(frame));
        dread(&unknown, sizeof(unknown));
        dread(&flags, sizeof(flags));
        ReadSplineFlags(data, flags);
        ReadName(key[t].name, sizeof(key[t].name));
        key[t].tens = data[0];
        key[t].cont = data[1];
        key[t].bias = data[2];
        key[t].easeTo = data[3];
        key[t].easeFrom = data[4];
        key[t].frame = frame;
        key[t].flags = flags;
    }
    return key;
}

static F3dsHidKey * ReadHidKeys(uint p, ushort * keys, ushort * kflags)
{
    ushort n, unknown, frame;
    F3dsHidKey * key;

    dread(kflags, 2);
    dsetpos(dgetpos()+8);   // unknown data
    dread(&n, sizeof(n));
    *keys = n;
    dsetpos(dgetpos()+2);   // unknown data
    key = (F3dsHidKey *) getmem((int)n*sizeof(F3dsHidKey));

    for(int t=0; t<n; t++) {
        dread(&frame, sizeof(frame));
        dread(&unknown, sizeof(unknown));
        dread(&unknown, sizeof(unknown));
        key[t].frame = frame;
    }
    return key;
}


static void ReadTrackInfoBlocks(uint p)
{
    ushort id;
    uint len, pc;
    ushort number=0;
    char name[16]={0};
    ushort data[3];
    F3dsObject * obj = Scene->objlist;   // Just in case...

	while((pc=dgetpos()) < p)
	{
		dread(&id, sizeof(id));
		dread(&len, sizeof(len));
		switch((int)id)
		{
            case CHUNK_OBJNUMBER:
                    number = ReadObjectNumber(pc+len);
                    break;
            case CHUNK_TRACKOBJNAME:
                    {
                        ReadName(name, sizeof(name));
                        // Find object with this name
                        dread(data, sizeof(data));
                        for(int n=0; n<Scene->objects; n++)
                            if(!strcmp(name, Scene->objlist[n].name)) {
                                obj = Scene->objlist+n;
                                obj->number = number;
                                break;
                            }
                        obj->flags  = data[0];
                        obj->parent = data[2];
                    }
                    break;
            case CHUNK_TRACKPIVOT:
                    ReadTrackPivot(pc+len, obj);
                    break;
            case CHUNK_TRACKPOS:
                    obj->poslist = ReadPosKeys(pc+len, &obj->poskeys, &obj->posflags);
                    break;
            case CHUNK_TRACKROTATE:
                    obj->rotlist = ReadRotKeys(pc+len, &obj->rotkeys, &obj->rotflags);
                    break;
            case CHUNK_TRACKMORPH:
                    obj->morlist = ReadMorKeys(pc+len, &obj->morkeys, &obj->morflags);
                    break;
            case CHUNK_TRACKHIDE:
                    obj->hidlist = ReadHidKeys(pc+len, &obj->hidkeys, &obj->hidflags);
                    break;
            case CHUNK_TRACKSCALE:
                    obj->scalist = ReadPosKeys(pc+len, &obj->scakeys, &obj->scaflags);
                    break;
		}
        dsetpos(pc+len);
	}
}

static void ReadCameraTrackBlocks(uint p)
{
    ushort id;
    uint len, pc;
    ushort posnumber=0;
    char name[16]={0};
    ushort data[3];
    F3dsCamera * cam = Scene->camlist;   // Just in case...

	while((pc=dgetpos()) < p)
	{
		dread(&id, sizeof(id));
		dread(&len, sizeof(len));
		switch((int)id)
		{
            case CHUNK_OBJNUMBER:
                    posnumber = ReadObjectNumber(pc+len);
                    break;
            case CHUNK_TRACKOBJNAME:
                    {
                        ReadName(name, sizeof(name));
                        dread(data, sizeof(data));
                        // Find camera with this name
                        for(int n=0; n<Scene->cameras; n++)
                            if(!strcmp(name, Scene->camlist[n].name)) {
                                cam = Scene->camlist+n;
                                cam->posnumber = posnumber;
                                break;
                            }
                        cam->flags = data[0];
                        cam->posparent = data[2];
                    }
                    break;
            case CHUNK_TRACKPOS:
                    cam->poslist = ReadPosKeys(pc+len, &cam->poskeys, &cam->posflags);
                    break;
            case CHUNK_TRACKFOV:
                    cam->fovlist = ReadValKeys(pc+len, &cam->fovkeys, &cam->fovflags);
                    break;
            case CHUNK_TRACKROLL:
                    cam->rollist = ReadValKeys(pc+len, &cam->rolkeys, &cam->rolflags);
                    break;
		}
        dsetpos(pc+len);
	}
}

static void ReadCameraTgtTrackBlocks(uint p)
{
    ushort id;
    uint len, pc;
    ushort tarnumber=0;
    char name[16]={0};
    ushort data[3];
    F3dsCamera * cam = Scene->camlist;   // Just in case...

	while((pc=dgetpos()) < p)
	{
		dread(&id, sizeof(id));
		dread(&len, sizeof(len));
		switch((int)id)
		{
            case CHUNK_OBJNUMBER:
                    tarnumber = ReadObjectNumber(pc+len);
                    break;
            case CHUNK_TRACKOBJNAME:
                    {
                        ReadName(name, sizeof(name));
                        dread(data, sizeof(data));
                        // Find camera with this name
                        for(int n=0; n<Scene->cameras; n++)
                            if(!strcmp(name, Scene->camlist[n].name)) {
                                cam = Scene->camlist+n;
                                cam->tarnumber = tarnumber;
                                break;
                            }
                        cam->flags = data[0];
                        cam->tarparent = data[2];
                    }
                    break;
            case CHUNK_TRACKPOS:
                    cam->tarlist = ReadPosKeys(pc+len, &cam->tarkeys, &cam->tarflags);
                    break;
		}
        dsetpos(pc+len);
	}
}

static void ReadLightTrackBlocks(uint p)
{
    ushort id;
    uint len, pc;
    ushort number=0;
    char name[16]={0};
    ushort data[3];
    F3dsLight * lig = Scene->liglist;   // Just in case...

	while((pc=dgetpos()) < p)
	{
		dread(&id, sizeof(id));
		dread(&len, sizeof(len));
		switch((int)id)
		{
            case CHUNK_OBJNUMBER:
                    number = ReadObjectNumber(pc+len);
                    break;
            case CHUNK_TRACKOBJNAME:
                    {
                        ReadName(name, sizeof(name));
                        dread(data, sizeof(data));
                        // Find light with this name
                        for(int n=0; n<Scene->lights; n++)
                            if(!strcmp(name, Scene->liglist[n].name)) {
                                lig = Scene->liglist+n;
                                lig->number = number;
                                break;
                            }
                        lig->flags  = data[0];
                        lig->parent = data[2];
                    }
                    break;
            case CHUNK_TRACKPOS:
                    lig->poslist = ReadPosKeys(pc+len, &lig->poskeys, &lig->posflags);
                    break;
		}
        dsetpos(pc+len);
	}
}

static void ReadKeyframerBlocks(uint p)
{
    ushort id;
    uint len, pc;

	while((pc=dgetpos()) < p)
	{
		dread(&id, sizeof(id));
		dread(&len, sizeof(len));
		switch((int)id)
		{
            case CHUNK_FRAMES:      ReadFramesBlock(pc+len); break;
            case CHUNK_TRACKINFO:   ReadTrackInfoBlocks(pc+len); break;
            case CHUNK_TRACKCAMERA: ReadCameraTrackBlocks(pc+len); break;
            case CHUNK_TRACKCAMTGT: ReadCameraTgtTrackBlocks(pc+len); break;
            case CHUNK_TRACKLIGHT:  ReadLightTrackBlocks(pc+len); break;
            //case CHUNK_TRACKLIGTGT:
            //case CHUNK_TRACKSPOTL:
            //case CHUNK_AMBIENTKEY:
		}
        dsetpos(pc+len);
	}
}

static void ReadMainBlocks(uint p)
{
    ushort id;
    uint len, pc;

	while((pc=dgetpos()) < p)
	{
		dread(&id, sizeof(id));
		dread(&len, sizeof(len));
		switch((int)id)
		{
            case CHUNK_OBJMESH:   ReadObjMeshBlocks(pc+len);   break;
            case CHUNK_KEYFRAMER: ReadKeyframerBlocks(pc+len); break;
		}
        dsetpos(pc+len);
	}
}

void F3dsFreeScene(F3dsScene * scene)
{
    if(scene) {
        if(scene->liglist) {
            for(int n=0; n<scene->lights; n++) {
                if(scene->liglist[n].poslist)
                    xfree(scene->liglist[n].poslist);
            }
            xfree(scene->liglist);
        }
        if(scene->camlist) {
            for(int n=0; n<scene->cameras; n++) {
                if(scene->camlist[n].poslist)
                    xfree(scene->camlist[n].poslist);
                if(scene->camlist[n].tarlist)
                    xfree(scene->camlist[n].tarlist);
                if(scene->camlist[n].fovlist)
                    xfree(scene->camlist[n].fovlist);
                if(scene->camlist[n].rollist)
                    xfree(scene->camlist[n].rollist);
            }
            xfree(scene->camlist);
        }
        if(scene->matlist)
            xfree(scene->matlist);
        if(scene->objlist) {
            for(int n=0; n<scene->objects; n++) {
                if(scene->objlist[n].faclist)
                    xfree(scene->objlist[n].faclist);
                if(scene->objlist[n].vtxlist)
                    xfree(scene->objlist[n].vtxlist);
                if(scene->objlist[n].poslist)
                    xfree(scene->objlist[n].poslist);
                if(scene->objlist[n].rotlist)
                    xfree(scene->objlist[n].rotlist);
                if(scene->objlist[n].scalist)
                    xfree(scene->objlist[n].scalist);
                if(scene->objlist[n].morlist)
                    xfree(scene->objlist[n].morlist);
                if(scene->objlist[n].hidlist)
                    xfree(scene->objlist[n].hidlist);
            }
            xfree(scene->objlist);
        }
        xfree(scene);
    }
}

F3dsScene * F3dsReadScene(char * msg, void * ptr, int what, uint size)
{
	if(ptr==0)
		return 0;
    errmsg=msg;

  #ifdef FILELOAD
	if(what == FILE_3DS) {
		// Load from file
		InFile=(FILE *) ptr;
		dread=FileRead;
		dsetpos=FileSetpos;
		dgetpos=FileGetpos;
	}
	else
  #endif
  #ifdef MEMLOAD
	if(what == MEMORY_3DS) {
		// Read from mem
		MemBufferIndex=0;
		MemBufferLen=size;
        MemBuffer=(uchar *) ptr;
		dread=MemRead;
		dsetpos=MemSetpos;
		dgetpos=MemGetpos;
	}
	else
  #endif
	{
        sprintf(errmsg, "Invalid reading method selected (%d).", what);
		return 0;
	}

    if((Scene=(F3dsScene *) xmalloc(sizeof(F3dsScene)))==0)
		return 0;
    memset(Scene, 0, sizeof(F3dsScene));

	int retval = setjmp(EnvState);

	if(retval==0) {
		// Return address set, start loading 3DS data.
        ushort id;
        uint len, pc;
		pc=dgetpos();
		dread(&id, sizeof(id));
		dread(&len, sizeof(len));
		if((int)id!=CHUNK_MAIN) {
            F3dsFreeScene(Scene);
            strcpy(errmsg, "This is not 3ds data.");
			return 0;
		}
		ReadMainBlocks(pc+len);
	}
	else {
        // There was an error, free the allocated mem and return NULL.
        F3dsFreeScene(Scene);
		return 0;
	}

    // Calc the new world -> pivot space matrix
    for(int n=0; n<Scene->objects; n++)
        F3dsMakeWorldToPivotAff(Scene->objlist[n].wpaff, Scene->objlist + n);

    // Check the objects to see if they have a subtree
    for(int i=0; i<Scene->objects; i++) {
        Scene->objlist[i].haschild = 0;
        ushort number = Scene->objlist[i].number;
        for(int o=0; o<Scene->objects; o++) {
            if(number == Scene->objlist[o].parent) {
                Scene->objlist[i].haschild = 1;
                break;
            }
        }
        for(int k=0; k<Scene->cameras; k++) {
            if(number == Scene->camlist[k].posparent ||
               number == Scene->camlist[k].tarparent) {
                Scene->objlist[i].haschild = 1;
                break;
            }
        }
        for(int l=0; l<Scene->lights; l++) {
            if(number == Scene->liglist[l].parent) {
                Scene->objlist[i].haschild = 1;
                break;
            }
        }
    }

	return Scene;
}


static void FMulAff(float dst[4][3], float a[4][3], float b[4][3])
{
    for(int i=0; i<3; i++)
        for(int j=0; j<3; j++) {
            float ab = 0.0;
            for(int k=0; k<3; k++)
                ab += a[i][k] * b[k][j];
            dst[i][j] = ab;
        }
    dst[3][0] = a[0][0]*b[3][0] + a[0][1]*b[3][1] + a[0][2]*b[3][2] + a[3][0];
    dst[3][1] = a[1][0]*b[3][0] + a[1][1]*b[3][1] + a[1][2]*b[3][2] + a[3][1];
    dst[3][2] = a[2][0]*b[3][0] + a[2][1]*b[3][1] + a[2][2]*b[3][2] + a[3][2];
}

void F3dsMakeWorldToPivotAff(float aff[4][3], F3dsObject * obj3ds)
{
    static float piv[4][3] = {
                    { 1.0, 0.0, 0.0 },
                    { 0.0, 1.0, 0.0 },
                    { 0.0, 0.0, 1.0 },
                    { 0.0, 0.0, 0.0 }};

    piv[3][0] = -obj3ds->trackpivx;
    piv[3][1] = -obj3ds->trackpivy;
    piv[3][2] = -obj3ds->trackpivz;

    if(obj3ds->aff[0][0] == -1.0 && obj3ds->aff[0][1] == 0.0 && obj3ds->aff[0][2] == 0.0)
    {
        // Ignore the object roation matrix for now
        piv[3][0] -= obj3ds->objpivx;
        piv[3][1] -= obj3ds->objpivy;
        piv[3][2] -= obj3ds->objpivz;
        memcpy(aff, piv, sizeof(piv));
    }    
    else
        FMulAff(aff, piv, obj3ds->aff);
}


inline void swap(float * x, float * y)
{
    float tmp = *x;
    *x = *y;
    *y = tmp;
}

void F3dsSwapYZ(F3dsScene * src)
{
    for(int i=0; i<src->objects; i++) {
        F3dsObject * obj = src->objlist+i;
        int v;
        for(v=0; v<obj->vertices; v++)
            swap(&obj->vtxlist[v].y, &obj->vtxlist[v].z);
        for(v=0; v<obj->poskeys; v++)
            swap(&obj->poslist[v].y, &obj->poslist[v].z);
        for(v=0; v<obj->scakeys; v++)
            swap(&obj->scalist[v].y, &obj->scalist[v].z);
        for(v=0; v<obj->rotkeys; v++)
            swap(&obj->rotlist[v].y, &obj->rotlist[v].z);
        swap(&obj->objpivy, &obj->objpivz);
        swap(&obj->trackpivy, &obj->trackpivz);

        swap(&obj->aff[3][1], &obj->aff[3][2]);

        swap(&obj->aff[0][1], &obj->aff[0][2]);  
        swap(&obj->aff[1][0], &obj->aff[2][0]);
        swap(&obj->aff[1][1], &obj->aff[2][2]);
        swap(&obj->aff[1][2], &obj->aff[2][1]);

        swap(&obj->wpaff[3][1], &obj->wpaff[3][2]);

        swap(&obj->wpaff[0][1], &obj->wpaff[0][2]);  
        swap(&obj->wpaff[1][0], &obj->wpaff[2][0]);
        swap(&obj->wpaff[1][1], &obj->wpaff[2][2]);
        swap(&obj->wpaff[1][2], &obj->wpaff[2][1]);

    }
    for(int c=0; c<src->cameras; c++) {
        F3dsCamera * cam = src->camlist+c;
        swap(&cam->py, &cam->pz);
        swap(&cam->ty, &cam->tz);
        int v;
        for(v=0; v<cam->tarkeys; v++)
            swap(&cam->tarlist[v].y, &cam->tarlist[v].z);
        for(v=0; v<cam->poskeys; v++)
            swap(&cam->poslist[v].y, &cam->poslist[v].z);
    }
    for(int l=0; l<src->lights; l++) {
        F3dsLight * lig = src->liglist+l;
        swap(&lig->y, &lig->z);
        int v;
        for(v=0; v<lig->poskeys; v++)
            swap(&lig->poslist[v].y, &lig->poslist[v].z);
    }
}



