summaryrefslogtreecommitdiffhomepage
path: root/third-party/l3ds/l3ds.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'third-party/l3ds/l3ds.cpp')
-rw-r--r--third-party/l3ds/l3ds.cpp1790
1 files changed, 1790 insertions, 0 deletions
diff --git a/third-party/l3ds/l3ds.cpp b/third-party/l3ds/l3ds.cpp
new file mode 100644
index 0000000..50b27b8
--- /dev/null
+++ b/third-party/l3ds/l3ds.cpp
@@ -0,0 +1,1790 @@
+// copyright (c) 2001 Lev Povalahev
+
+#include "l3ds.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+//using namespace std;
+
+//-------------------------------------------------------
+// generic stuff
+//-------------------------------------------------------
+
+typedef unsigned long ulong;
+
+#define SEEK_START 1900
+#define SEEK_CURSOR 1901
+
+// common chunks
+// colors
+#define COLOR_F 0x0010
+#define COLOR_24 0x0011
+#define LIN_COLOR_24 0x0012
+#define LIN_COLOR_F 0x0013
+// percentage
+#define INT_PERCENTAGE 0x0030
+#define FLOAT_PERCENTAGE 0x0031
+// ambient light
+#define AMBIENT_LIGHT 0x2100
+
+
+#define MAIN3DS 0x4D4D
+#define EDIT3DS 0x3D3D // this is the start of the editor config
+
+// keyframer chunk ids
+#define KFDATA 0xB000 // the keyframer section
+#define KFHDR 0xB00A
+#define OBJECT_NODE_TAG 0xB002
+#define NODE_HDR 0xB010
+#define PIVOT 0xB013
+#define POS_TRACK_TAG 0xB020
+#define ROT_TRACK_TAG 0xB021
+#define SCL_TRACK_TAG 0xB022
+
+// material entries
+#define MAT_ENTRY 0xAFFF
+#define MAT_NAME 0xA000
+#define MAT_AMBIENT 0xA010
+#define MAT_DIFFUSE 0xA020
+#define MAT_SPECULAR 0xA030
+#define MAT_SHININESS 0xA040
+#define MAT_SHIN2PCT 0xA041
+#define MAT_TRANSPARENCY 0xA050
+#define MAT_SHADING 0xA100
+#define MAT_TWO_SIDE 0xA081
+#define MAT_ADDITIVE 0xA083
+#define MAT_WIRE 0xA085
+#define MAT_FACEMAP 0xA088
+#define MAT_WIRESIZE 0xA087
+#define MAT_DECAL 0xA082
+#define MAT_TEXMAP 0xA200
+#define MAT_MAPNAME 0xA300
+#define MAT_MAP_TILING 0xA351
+#define MAT_MAP_USCALE 0xA354
+#define MAT_MAP_VSCALE 0xA356
+#define MAT_MAP_UOFFSET 0xA358
+#define MAT_MAP_VOFFSET 0xA35A
+#define MAT_MAP_ANG 0xA35C
+#define MAT_TEX2MAP 0xA33A
+#define MAT_OPACMAP 0xA210
+#define MAT_BUMPMAP 0xA230
+#define MAT_SPECMAP 0xA204
+#define MAT_SHINMAP 0xA33C
+#define MAT_REFLMAP 0xA220
+#define MAT_ACUBIC 0xA310
+
+#define EDIT_OBJECT 0x4000
+#define OBJ_TRIMESH 0x4100
+#define OBJ_LIGHT 0x4600
+#define OBJ_CAMERA 0x4700
+#define LIT_OFF 0x4620
+#define LIT_SPOT 0x4610
+#define TRI_VERTEXLIST 0x4110
+#define TRI_VERTEXOPTIONS 0x4111
+
+#define TRI_FACELIST 0x4120
+ #define TRI_MAT_GROUP 0x4130
+ #define TRI_SMOOTH_GROUP 0x4150
+
+#define TRI_FACEMAPPING 0x4140
+#define TRI_MATRIX 0x4160
+
+#define SPOTLIGHT 0x4610
+
+//----------------------------------
+
+#define MAX_SHARED_TRIS 100
+
+// the error reporting routine
+
+void ErrorMsg(const char *msg)
+{
+
+}
+
+struct LChunk
+{
+ unsigned short id;
+ uint start;
+ uint end;
+};
+
+struct LTri
+{
+ unsigned short a;
+ unsigned short b;
+ unsigned short c;
+ ulong smoothingGroups;
+ LVector3 normal;
+ LVector3 tangent;
+ LVector3 binormal;
+ uint materialId;
+};
+
+// globals
+
+LColor3 black = {0, 0, 0};
+
+LVector3 zero3 = {0, 0, 0};
+
+LVector4 zero4 = {0, 0, 0, 0};
+
+LMap emptyMap = {0, "", 1, 1, 0, 0, 0};
+
+LVector3 _4to3(const LVector4 &vec)
+{
+ LVector3 t;
+ t.x = vec.x;
+ t.y = vec.y;
+ t.z = vec.z;
+ return t;
+}
+
+LVector3 AddVectors(const LVector3 &a, const LVector3 &b)
+{
+ LVector3 t;
+ t.x = a.x+b.x;
+ t.y = a.y+b.y;
+ t.z = a.z+b.z;
+ return t;
+}
+
+LVector3 SubtractVectors(const LVector3 &a, const LVector3 &b)
+{
+ LVector3 t;
+ t.x = a.x-b.x;
+ t.y = a.y-b.y;
+ t.z = a.z-b.z;
+ return t;
+}
+
+float VectorLength(const LVector3 &vec)
+{
+ return (float)sqrt(vec.x*vec.x + vec.y*vec.y+vec.z*vec.z);
+}
+
+LVector3 NormalizeVector(const LVector3 &vec)
+{
+ float a = VectorLength(vec);
+ if (a == 0)
+ return vec;
+ float b = 1/a;
+ LVector3 v;
+ v.x = vec.x*b;
+ v.y = vec.y*b;
+ v.z = vec.z*b;
+ return v;
+}
+
+LVector3 CrossProduct(const LVector3 &a, const LVector3 &b)
+{
+ LVector3 v;
+ v.x = a.y*b.z - a.z*b.y;
+ v.y = a.z*b.x - a.x*b.z;
+ v.z = a.x*b.y - a.y*b.x;
+ return v;
+}
+
+void LoadIdentityMatrix(LMatrix4 &m)
+{
+ for (int i = 0; i < 4; i++)
+ for (int j = 0; j < 4; j++)
+ m.m[i][j] = (i==j) ? 1.0f : 0.0f;
+}
+
+LVector4 VectorByMatrix(const LMatrix4 &m, const LVector4 &vec)
+{
+ LVector4 res;
+
+ res.x = m.m[0][0]*vec.x + m.m[1][0]*vec.y + m.m[2][0]*vec.z + m.m[3][0]*vec.w;
+ res.y = m.m[0][1]*vec.x + m.m[1][1]*vec.y + m.m[2][1]*vec.z + m.m[3][1]*vec.w;
+ res.z = m.m[0][2]*vec.x + m.m[1][2]*vec.y + m.m[2][2]*vec.z + m.m[3][2]*vec.w;
+ res.w = m.m[0][3]*vec.x + m.m[1][3]*vec.y + m.m[2][3]*vec.z + m.m[3][3]*vec.w;
+
+ if (res.w != 0) {
+ float b = 1/res.w;
+ res.x *= b;
+ res.y *= b;
+ res.z *= b;
+ res.w = 1;
+ }
+ else {
+ res.w = 1;
+ }
+
+ return res;
+}
+
+void QuatToMatrix(const LVector4 &quat, LMatrix4 &m)
+{
+
+}
+
+//-------------------------------------------------------
+// LObject implementation
+//-------------------------------------------------------
+
+LObject::LObject()
+{
+ m_name = "";//.clear();
+}
+
+LObject::~LObject()
+{
+ // nothing here
+}
+
+void LObject::SetName(const std::string& value)
+{
+ m_name = value;
+}
+
+const std::string& LObject::GetName()
+{
+ return m_name;
+}
+
+bool LObject::IsObject(const std::string &name)
+{
+ return (m_name == name);
+}
+
+
+//-------------------------------------------------------
+// LMaterial implementation
+//-------------------------------------------------------
+
+LMaterial::LMaterial()
+: LObject()
+{
+ m_id = 0;
+ m_texMap1 = emptyMap;
+ m_texMap2 = emptyMap;
+ m_opacMap = emptyMap;
+ m_bumpMap = emptyMap;
+ m_reflMap = emptyMap;
+ m_specMap = emptyMap;
+ m_ambient = black;
+ m_diffuse = black;
+ m_specular = black;
+ m_shading = sGouraud;
+ m_shininess = 0;
+ m_transparency = 0;
+}
+
+LMaterial::~LMaterial()
+{
+
+}
+
+uint LMaterial::GetID()
+{
+ return m_id;
+}
+
+LMap& LMaterial::GetTextureMap1()
+{
+ return m_texMap1;
+}
+
+LMap& LMaterial::GetTextureMap2()
+{
+ return m_texMap2;
+}
+
+LMap& LMaterial::GetOpacityMap()
+{
+ return m_opacMap;
+}
+
+LMap& LMaterial::GetSpecularMap()
+{
+ return m_specMap;
+}
+
+LMap& LMaterial::GetBumpMap()
+{
+ return m_bumpMap;
+}
+
+LMap& LMaterial::GetReflectionMap()
+{
+ return m_reflMap;
+}
+
+LColor3 LMaterial::GetAmbientColor()
+{
+ return m_ambient;
+}
+
+LColor3 LMaterial::GetDiffuseColor()
+{
+ return m_diffuse;
+}
+
+LColor3 LMaterial::GetSpecularColor()
+{
+ return m_specular;
+}
+
+float LMaterial::GetShininess()
+{
+ return m_shininess;
+}
+
+float LMaterial::GetTransparency()
+{
+ return m_transparency;
+}
+
+LShading LMaterial::GetShadingType()
+{
+ return m_shading;
+}
+
+void LMaterial::SetID(uint value)
+{
+ m_id = value;
+}
+
+void LMaterial::SetAmbientColor(const LColor3 &color)
+{
+ m_ambient = color;
+}
+
+void LMaterial::SetDiffuseColor(const LColor3 &color)
+{
+ m_diffuse = color;
+}
+
+void LMaterial::SetSpecularColor(const LColor3 &color)
+{
+ m_specular = color;
+}
+
+void LMaterial::SetShininess(float value)
+{
+ m_shininess = value;
+ if (m_shininess < 0)
+ m_shininess = 0;
+ if (m_shininess > 1)
+ m_shininess = 1;
+}
+
+void LMaterial::SetTransparency(float value)
+{
+ m_transparency = value;
+ if (m_transparency < 0)
+ m_transparency = 0;
+ if (m_transparency > 1)
+ m_transparency = 1;
+}
+
+void LMaterial::SetShadingType(LShading shading)
+{
+ m_shading = shading;
+}
+
+//-------------------------------------------------------
+// LMesh implementation
+//-------------------------------------------------------
+
+LMesh::LMesh()
+: LObject()
+{
+ Clear();
+}
+
+LMesh::~LMesh()
+{
+ Clear();
+}
+
+void LMesh::Clear()
+{
+ m_vertices.clear();
+ m_normals.clear();
+ m_uv.clear();
+ m_tangents.clear();
+ m_binormals.clear();
+ m_triangles.clear();
+ m_tris.clear();
+ m_materials.clear();
+ LoadIdentityMatrix(m_matrix);
+}
+
+uint LMesh::GetVertexCount()
+{
+ return m_vertices.size();
+}
+
+void LMesh::SetVertexArraySize(uint value)
+{
+ m_vertices.resize(value);
+ m_normals.resize(value);
+ m_uv.resize(value);
+ m_tangents.resize(value);
+ m_binormals.resize(value);
+}
+
+uint LMesh::GetTriangleCount()
+{
+ return m_triangles.size();
+}
+
+void LMesh::SetTriangleArraySize(uint value)
+{
+ m_triangles.resize(value);
+ m_tris.resize(value);
+}
+
+const LVector4& LMesh::GetVertex(uint index)
+{
+ return m_vertices[index];
+}
+
+const LVector3& LMesh::GetNormal(uint index)
+{
+ return m_normals[index];
+}
+
+const LVector2& LMesh::GetUV(uint index)
+{
+ return m_uv[index];
+}
+
+const LVector3& LMesh::GetTangent(uint index)
+{
+ return m_tangents[index];
+}
+
+const LVector3& LMesh::GetBinormal(uint index)
+{
+ return m_binormals[index];
+}
+
+void LMesh::SetVertex(const LVector4 &vec, uint index)
+{
+ if (index >= m_vertices.size())
+ return;
+ m_vertices[index] = vec;
+}
+
+void LMesh::SetNormal(const LVector3 &vec, uint index)
+{
+ if (index >= m_vertices.size())
+ return;
+ m_normals[index] = vec;
+}
+
+void LMesh::SetUV(const LVector2 &vec, uint index)
+{
+ if (index >= m_vertices.size())
+ return;
+ m_uv[index] = vec;
+}
+
+void LMesh::SetTangent(const LVector3 &vec, uint index)
+{
+ if (index >= m_vertices.size())
+ return;
+ m_tangents[index] = vec;
+}
+
+void LMesh::SetBinormal(const LVector3 &vec, uint index)
+{
+ if (index >= m_vertices.size())
+ return;
+ m_binormals[index] = vec;
+}
+
+const LTriangle& LMesh::GetTriangle(uint index)
+{
+ return m_triangles[index];
+}
+
+LTriangle2 LMesh::GetTriangle2(uint index)
+{
+ LTriangle2 f;
+ LTriangle t = GetTriangle(index);
+ f.vertices[0] = GetVertex(t.a);
+ f.vertices[1] = GetVertex(t.b);
+ f.vertices[2] = GetVertex(t.c);
+
+ f.vertexNormals[0] = GetNormal(t.a);
+ f.vertexNormals[1] = GetNormal(t.b);
+ f.vertexNormals[2] = GetNormal(t.c);
+
+ f.textureCoords[0] = GetUV(t.a);
+ f.textureCoords[1] = GetUV(t.b);
+ f.textureCoords[2] = GetUV(t.c);
+
+ LVector3 a, b;
+
+ a = SubtractVectors(_4to3(f.vertices[1]), _4to3(f.vertices[0]));
+ b = SubtractVectors(_4to3(f.vertices[1]), _4to3(f.vertices[2]));
+
+ f.faceNormal = CrossProduct(b, a);
+
+ f.faceNormal = NormalizeVector(f.faceNormal);
+
+ f.materialId = m_tris[index].materialId;
+
+ return f;
+}
+
+LMatrix4 LMesh::GetMatrix()
+{
+ return m_matrix;
+}
+
+void LMesh::SetMatrix(LMatrix4 m)
+{
+ m_matrix = m;
+}
+
+void LMesh::TransformVertices()
+{
+ for (uint i=0; i<m_vertices.size(); i++)
+ m_vertices[i] = VectorByMatrix(m_matrix, m_vertices[i]);
+
+ LoadIdentityMatrix(m_matrix);
+}
+
+void LMesh::CalcNormals(bool useSmoothingGroups)
+{
+ uint i;
+ // first calculate the face normals
+ for (i=0; i<m_triangles.size(); i++)
+ {
+ LVector3 a, b;
+ a = SubtractVectors(_4to3(m_vertices[m_tris[i].b]), _4to3(m_vertices[m_tris[i].a]));
+ b = SubtractVectors(_4to3(m_vertices[m_tris[i].b]), _4to3(m_vertices[m_tris[i].c]));
+ m_tris[i].normal = NormalizeVector(CrossProduct(b, a));
+ }
+
+ std::vector< std::vector<int> > array;
+ array.resize(m_vertices.size());
+ for (i=0; i<m_triangles.size(); i++)
+ {
+ uint k = m_tris[i].a;
+ array[k].push_back(i);
+
+ k = m_tris[i].b;
+ array[k].push_back(i);
+
+ k = m_tris[i].c;
+ array[k].push_back(i);
+ }
+
+ LVector3 temp;
+
+ if (!useSmoothingGroups)
+ {
+ // now calculate the normals without using smoothing groups
+ for (i=0; i<m_vertices.size(); i++)
+ {
+ temp = zero3;
+ int t = array[i].size();
+
+ for (int k=0; k<t; k++)
+ {
+ temp.x += m_tris[array[i][k]].normal.x;
+ temp.y += m_tris[array[i][k]].normal.y;
+ temp.z += m_tris[array[i][k]].normal.z;
+ }
+ m_normals[i] = NormalizeVector(temp);
+ }
+ }
+ else
+ {
+ // now calculate the normals _USING_ smoothing groups
+ // I'm assuming a triangle can only belong to one smoothing group at a time!
+ std::vector<ulong> smGroups;
+ std::vector< std::vector <uint> > smList;
+
+ uint loop_size = m_vertices.size();
+
+ for (i=0; i<loop_size; i++)
+ {
+ int t = array[i].size();
+
+ if (t == 0)
+ continue;
+
+ smGroups.clear();
+ smList.clear();
+ smGroups.push_back(m_tris[array[i][0]].smoothingGroups);
+ smList.resize(smGroups.size());
+ smList[smGroups.size()-1].push_back(array[i][0]);
+
+ // first build a list of smoothing groups for the vertex
+ for (int k=0; k<t; k++)
+ {
+ bool found = false;
+ for (uint j=0; j<smGroups.size(); j++)
+ {
+ if (m_tris[array[i][k]].smoothingGroups == smGroups[j])
+ {
+ smList[j].push_back(array[i][k]);
+ found = true;
+ }
+ }
+ if (!found)
+ {
+ smGroups.push_back(m_tris[array[i][k]].smoothingGroups);
+ smList.resize(smGroups.size());
+ smList[smGroups.size()-1].push_back(array[i][k]);
+ }
+ }
+ // now we have the list of faces for the vertex sorted by smoothing groups
+
+
+ // now duplicate the vertices so that there's only one smoothing group "per vertex"
+ if (smGroups.size() > 1)
+ for (uint j=1; j< smGroups.size(); j++)
+ {
+ m_vertices.push_back(m_vertices[i]);
+ m_normals.push_back(m_normals[i]);
+ m_uv.push_back(m_uv[i]);
+ m_tangents.push_back(m_tangents[i]);
+ m_binormals.push_back(m_binormals[i]);
+
+ uint t = m_vertices.size()-1;
+ for (uint h=0; h<smList[j].size(); h++)
+ {
+ if (m_tris[smList[j][h]].a == i)
+ m_tris[smList[j][h]].a = t;
+ if (m_tris[smList[j][h]].b == i)
+ m_tris[smList[j][h]].b = t;
+ if (m_tris[smList[j][h]].c == i)
+ m_tris[smList[j][h]].c = t;
+ }
+ }
+ }
+
+ // now rebuild a face list for each vertex, since the old one is invalidated
+ for (i=0; i<array.size(); i++)
+ array[i].clear();
+ array.clear();
+ array.resize(m_vertices.size());
+ for (i=0; i<m_triangles.size(); i++)
+ {
+ uint k = m_tris[i].a;
+ array[k].push_back(i);
+
+ k = m_tris[i].b;
+ array[k].push_back(i);
+
+ k = m_tris[i].c;
+ array[k].push_back(i);
+ }
+
+ // now compute the normals
+ for (i=0; i<m_vertices.size(); i++)
+ {
+ temp = zero3;
+ int t = array[i].size();
+
+ for (int k=0; k<t; k++)
+ {
+ temp.x += m_tris[array[i][k]].normal.x;
+ temp.y += m_tris[array[i][k]].normal.y;
+ temp.z += m_tris[array[i][k]].normal.z;
+ }
+ m_normals[i] = NormalizeVector(temp);
+ }
+
+ }
+
+ // copy m_tris to m_triangles
+ for (i=0; i<m_triangles.size(); i++)
+ {
+ m_triangles[i].a = m_tris[i].a;
+ m_triangles[i].b = m_tris[i].b;
+ m_triangles[i].c = m_tris[i].c;
+ }
+
+}
+
+void LMesh::CalcTextureSpace()
+{
+ // a understandable description of how to do that can be found here:
+ // http://members.rogers.com/deseric/tangentspace.htm
+ // first calculate the tangent for each triangle
+ LVector3 x_vec,
+ y_vec,
+ z_vec;
+ LVector3 v1, v2;
+ for (uint i=0; i<m_triangles.size(); i++)
+ {
+ v1.x = m_vertices[m_tris[i].b].x - m_vertices[m_tris[i].a].x;
+ v1.y = m_uv[m_tris[i].b].x - m_uv[m_tris[i].a].x;
+ v1.z = m_uv[m_tris[i].b].y - m_uv[m_tris[i].a].y;
+
+ v2.x = m_vertices[m_tris[i].c].x - m_vertices[m_tris[i].a].x;
+ v2.y = m_uv[m_tris[i].c].x - m_uv[m_tris[i].a].x;
+ v2.z = m_uv[m_tris[i].c].y - m_uv[m_tris[i].a].y;
+
+ x_vec = CrossProduct(v1, v2);
+
+ v1.x = m_vertices[m_tris[i].b].y - m_vertices[m_tris[i].a].y;
+ v1.y = m_uv[m_tris[i].b].x - m_uv[m_tris[i].a].x;
+ v1.z = m_uv[m_tris[i].b].y - m_uv[m_tris[i].a].y;
+
+ v2.x = m_vertices[m_tris[i].c].y - m_vertices[m_tris[i].a].y;
+ v2.y = m_uv[m_tris[i].c].x - m_uv[m_tris[i].a].x;
+ v2.z = m_uv[m_tris[i].c].y - m_uv[m_tris[i].a].y;
+
+ y_vec = CrossProduct(v1, v2);
+
+ v1.x = m_vertices[m_tris[i].b].z - m_vertices[m_tris[i].a].z;
+ v1.y = m_uv[m_tris[i].b].x - m_uv[m_tris[i].a].x;
+ v1.z = m_uv[m_tris[i].b].y - m_uv[m_tris[i].a].y;
+
+ v2.x = m_vertices[m_tris[i].c].z - m_vertices[m_tris[i].a].z;
+ v2.y = m_uv[m_tris[i].c].x - m_uv[m_tris[i].a].x;
+ v2.z = m_uv[m_tris[i].c].y - m_uv[m_tris[i].a].y;
+
+ z_vec = CrossProduct(v1, v2);
+
+ m_tris[i].tangent.x = -(x_vec.y/x_vec.x);
+ m_tris[i].tangent.y = -(y_vec.y/y_vec.x);
+ m_tris[i].tangent.z = -(z_vec.y/z_vec.x);
+
+ m_tris[i].binormal.x = -(x_vec.z/x_vec.x);
+ m_tris[i].binormal.y = -(y_vec.z/y_vec.x);
+ m_tris[i].binormal.z = -(z_vec.z/z_vec.x);
+
+ }
+
+ // now for each vertex build a list of face that share this vertex
+ std::vector< std::vector<int> > array;
+ array.resize(m_vertices.size());
+ for (int i=0; i<(int)m_triangles.size(); i++)
+ {
+ uint k = m_tris[i].a;
+ array[k].push_back(i);
+
+ k = m_tris[i].b;
+ array[k].push_back(i);
+
+ k = m_tris[i].c;
+ array[k].push_back(i);
+ }
+
+ // now average the tangents and compute the binormals as (tangent X normal)
+ for (int i=0; i<(int)m_vertices.size(); i++)
+ {
+ v1 = zero3;
+ v2 = zero3;
+ int t = array[i].size();
+
+ for (int k=0; k<t; k++)
+ {
+ v1.x += m_tris[array[i][k]].tangent.x;
+ v1.y += m_tris[array[i][k]].tangent.y;
+ v1.z += m_tris[array[i][k]].tangent.z;
+
+ v2.x += m_tris[array[i][k]].binormal.x;
+ v2.y += m_tris[array[i][k]].binormal.y;
+ v2.z += m_tris[array[i][k]].binormal.z;
+ }
+ m_tangents[i] = NormalizeVector(v1);
+ //m_binormals[i] = NormalizeVector(v2);
+
+ m_binormals[i] = NormalizeVector(CrossProduct(m_tangents[i], m_normals[i]));
+ }
+}
+
+void LMesh::Optimize(LOptimizationLevel value)
+{
+ switch (value)
+ {
+ case oNone:
+ TransformVertices();
+ break;
+ case oSimple:
+ TransformVertices();
+ CalcNormals(false);
+ break;
+ case oFull:
+ TransformVertices();
+ CalcNormals(true);
+ CalcTextureSpace();
+ break;
+ }
+}
+
+void LMesh::SetTri(const LTri &tri, uint index)
+{
+ if (index >= m_triangles.size())
+ return;
+ m_tris[index] = tri;
+}
+
+LTri& LMesh::GetTri(uint index)
+{
+ return m_tris[index];
+}
+
+uint LMesh::GetMaterial(uint index)
+{
+ return m_materials[index];
+}
+
+uint LMesh::AddMaterial(uint id)
+{
+ m_materials.push_back(id);
+ return m_materials.size()-1;
+}
+
+uint LMesh::GetMaterialCount()
+{
+ return m_materials.size();
+}
+
+//-------------------------------------------------------
+// LLight implementation
+//-------------------------------------------------------
+
+LLight::LLight()
+: LObject()
+{
+ Clear();
+}
+
+LLight::~LLight()
+{
+
+}
+
+void LLight::Clear()
+{
+ m_pos.x = m_pos.y = m_pos.z = 0.0f;
+ m_color.r = m_color.g = m_color.b = 0.0f;
+ m_spotlight = false;
+}
+
+void LLight::SetPosition(LVector3 vec)
+{
+ m_pos = vec;
+}
+
+LVector3 LLight::GetPosition()
+{
+ return m_pos;
+}
+
+void LLight::SetColor(LColor3 color)
+{
+ m_color = color;
+}
+
+LColor3 LLight::GetColor()
+{
+ return m_color;
+}
+
+void LLight::SetSpotlight(bool value)
+{
+ m_spotlight = value;
+}
+
+bool LLight::GetSpotlight()
+{
+ return m_spotlight;
+}
+
+void LLight::SetTarget(LVector3 target)
+{
+ m_target = target;
+}
+
+LVector3 LLight::GetTarget()
+{
+ return m_target;
+}
+
+void LLight::SetHotspot(float value)
+{
+ m_hotspot = value;
+}
+
+float LLight::GetHotspot()
+{
+ return m_hotspot;
+}
+
+void LLight::SetFalloff(float value)
+{
+ m_falloff = value;
+}
+
+float LLight::GetFalloff()
+{
+ return m_falloff;
+}
+
+//-------------------------------------------------------
+// LImporter implementation
+//-------------------------------------------------------
+
+LImporter::LImporter()
+{
+ Clear();
+}
+
+LImporter::~LImporter()
+{
+ Clear();
+}
+
+uint LImporter::GetMeshCount()
+{
+ return m_meshes.size();
+}
+
+uint LImporter::GetLightCount()
+{
+ return m_lights.size();
+}
+
+uint LImporter::GetMaterialCount()
+{
+ return m_materials.size();
+}
+
+LMesh& LImporter::GetMesh(uint index)
+{
+ return m_meshes[index];
+}
+
+LLight& LImporter::GetLight(uint index)
+{
+ return m_lights[index];
+}
+
+LMaterial& LImporter::GetMaterial(uint index)
+{
+ return m_materials[index];
+}
+
+LMaterial* LImporter::FindMaterial(const std::string& name)
+{
+ for (uint i=0; i<m_materials.size(); i++)
+ if (m_materials[i].IsObject(name))
+ return &m_materials[i];
+ return 0;
+}
+
+LMesh* LImporter::FindMesh(const std::string& name)
+{
+ for (uint i=0; i<m_meshes.size(); i++)
+ if (m_meshes[i].IsObject(name))
+ return &m_meshes[i];
+ return 0;
+}
+
+LLight* LImporter::FindLight(const std::string& name)
+{
+ for (uint i=0; i<m_lights.size(); i++)
+ if (m_lights[i].IsObject(name))
+ return &m_lights[i];
+ return 0;
+}
+
+void LImporter::Clear()
+{
+ m_meshes.clear();
+ m_lights.clear();
+ m_materials.clear();
+ m_optLevel = oFull;
+}
+
+void LImporter::SetOptimizationLevel(LOptimizationLevel value)
+{
+ m_optLevel = value;
+}
+
+LOptimizationLevel LImporter::GetOptimizationLevel()
+{
+ return m_optLevel;
+}
+
+//-------------------------------------------------------
+// L3DS implementation
+//-------------------------------------------------------
+
+L3DS::L3DS()
+: LImporter()
+{
+ m_buffer = 0;
+ m_bufferSize = 0;
+ m_pos = 0;
+ m_eof = false;
+}
+
+L3DS::L3DS(const char *filename)
+: LImporter()
+{
+ m_buffer = 0;
+ m_bufferSize = 0;
+ m_pos = 0;
+ m_eof = false;
+ LoadFile(filename);
+}
+
+L3DS::~L3DS()
+{
+ if (m_bufferSize > 0)
+ free(m_buffer);
+}
+
+bool L3DS::LoadFile(const char *filename)
+{
+ FILE *f;
+ f = fopen(filename, "rb");
+ if (f == 0)
+ {
+ ErrorMsg("L3DS::LoadFile - cannot open file");
+ return false;
+ }
+ fseek(f, 0, SEEK_END);
+ m_bufferSize = ftell(f);
+ fseek(f, 0, SEEK_SET);
+ m_buffer = (unsigned char*) calloc(m_bufferSize, 1);
+ if (m_buffer == 0)
+ {
+ ErrorMsg("L3DS::LoadFile - not enough memory (malloc failed)");
+ return false;
+ }
+ if (fread(m_buffer, m_bufferSize, 1, f) != 1)
+ {
+ fclose(f);
+ free(m_buffer);
+ m_bufferSize = 0;
+ ErrorMsg("L3DS::LoadFile - error reading from file");
+ return false;
+ }
+ fclose(f);
+ Clear();
+ bool res = Read3DS();
+ free(m_buffer);
+ m_buffer = 0;
+ m_bufferSize = 0;
+ return res;
+}
+
+short L3DS::ReadShort()
+{
+ if ((m_buffer!=0) && (m_bufferSize != 0) && ((m_pos+2)<m_bufferSize))
+ {
+ short *w = (short*)(m_buffer+m_pos);
+ short s = *w;//(short)*(m_buffer+m_pos);
+ m_pos += 2;
+ return s;
+ }
+ m_eof = true;
+ return 0;
+}
+
+int L3DS::ReadInt()
+{
+ if ((m_buffer!=0) && (m_bufferSize != 0) && ((m_pos+4)<m_bufferSize))
+ {
+ int *w = (int*)(m_buffer+m_pos);
+ int s = *w;//(int)*(m_buffer+m_pos);
+ m_pos += 4;
+ return s;
+ }
+ m_eof = true;
+ return 0;
+}
+
+char L3DS::ReadChar()
+{
+ if ((m_buffer!=0) && (m_bufferSize != 0) && ((m_pos+1)<m_bufferSize))
+ {
+ char s = (char)*(m_buffer+m_pos);
+ m_pos += 1;
+ return s;
+ }
+ m_eof = true;
+ return 0;
+}
+
+float L3DS::ReadFloat()
+{
+ if ((m_buffer!=0) && (m_bufferSize != 0) && ((m_pos+4)<m_bufferSize))
+ {
+ float *w = (float*)(m_buffer+m_pos);
+ float s = *w;//(float)*(m_buffer+m_pos);
+ m_pos += 4;
+ return s;
+ }
+ m_eof = true;
+ return 0.0;
+}
+
+byte L3DS::ReadByte()
+{
+ if ((m_buffer!=0) && (m_bufferSize != 0) && ((m_pos+1)<m_bufferSize))
+ {
+ byte s = (byte)*(m_buffer+m_pos);
+ m_pos += 1;
+ return s;
+ }
+ m_eof = true;
+ return 0;
+}
+
+int L3DS::ReadASCIIZ(char *buf, int max_count)
+{
+ int count;
+ if ((m_buffer==0) || (m_bufferSize == 0) || (m_pos>=m_bufferSize))
+ {
+ count = 0;
+ m_eof = true;
+ return count;
+ }
+ count = 0;
+ char c = ReadChar();
+ while ((c!=0) && (count<max_count-1))
+ {
+ buf[count] = c;
+ count ++;
+ c = ReadChar();
+ }
+ buf[count] = 0;
+ return count;
+}
+
+void L3DS::Seek(int offset, int origin)
+{
+ if (origin == SEEK_START)
+ m_pos = offset;
+ if (origin == SEEK_CURSOR)
+ m_pos += offset;
+ if (m_pos >= m_bufferSize)
+ m_pos = m_bufferSize-1;
+ m_eof = false;
+}
+
+uint L3DS::Pos()
+{
+ return m_pos;
+}
+
+LChunk L3DS::ReadChunk()
+{
+ LChunk chunk;
+ chunk.id = ReadShort();
+ int a = ReadInt();
+ chunk.start = Pos();
+ chunk.end = chunk.start+a-6;
+ return chunk;
+}
+
+bool L3DS::FindChunk(LChunk &target, const LChunk &parent)
+{
+ if (Pos() >= parent.end)
+ return false;
+ LChunk chunk;
+ chunk = ReadChunk();
+ while (( chunk.id != target.id) && (chunk.end <= parent.end))
+ {
+ SkipChunk(chunk);
+ if (chunk.end >= parent.end)
+ break;
+ chunk = ReadChunk();
+ }
+ if (chunk.id == target.id)
+ {
+ target.start = chunk.start;
+ target.end = chunk.end;
+ return true;
+ }
+ return false;
+}
+
+void L3DS::SkipChunk(const LChunk &chunk)
+{
+ Seek(chunk.end, SEEK_START);
+}
+
+void L3DS::GotoChunk(const LChunk &chunk)
+{
+ Seek(chunk.start, SEEK_START);
+}
+
+LColor3 L3DS::ReadColor(const LChunk &chunk)
+{
+ LColor3 col = black;
+ GotoChunk(chunk);
+ switch (chunk.id)
+ {
+ case COLOR_F:
+ col.r = ReadFloat();
+ col.g = ReadFloat();
+ col.b = ReadFloat();
+ break;
+ case COLOR_24:
+ col.r = ReadByte()/255.0f;
+ col.g = ReadByte()/255.0f;
+ col.b = ReadByte()/255.0f;
+ break;
+ case LIN_COLOR_F:
+ col.r = ReadFloat();
+ col.g = ReadFloat();
+ col.b = ReadFloat();
+ break;
+ case LIN_COLOR_24:
+ col.r = ReadByte()/255.0f;
+ col.g = ReadByte()/255.0f;
+ col.b = ReadByte()/255.0f;
+ break;
+ default:
+ ErrorMsg("L3DS::ReadColor - error this is not a color chunk");
+ }
+ return col;
+}
+
+float L3DS::ReadPercentage(const LChunk &chunk)
+{
+ GotoChunk(chunk);
+ switch (chunk.id)
+ {
+ case INT_PERCENTAGE:
+ return (ReadShort()/100.0f);
+ case FLOAT_PERCENTAGE:
+ return ReadFloat();
+ }
+ ErrorMsg("L3DS::ReadPercentage - error, the chunk is not a percentage chunk");
+ return 0;
+}
+
+bool L3DS::Read3DS()
+{
+ LChunk mainchunk;
+ LChunk edit;
+ edit.id = EDIT3DS;
+ mainchunk = ReadChunk();
+ if (mainchunk.id != MAIN3DS)
+ {
+ ErrorMsg("L3DS::Read3DS - wrong file format");
+ return false;
+ }
+ if (!FindChunk(edit, mainchunk))
+ return false;
+ LChunk obj;
+ LChunk ml;
+
+ GotoChunk(edit);
+ obj.id = MAT_ENTRY;
+ while (FindChunk(obj, edit))
+ {
+ ReadMaterial(obj);
+ SkipChunk(obj);
+ }
+ GotoChunk(edit);
+
+ obj.id = EDIT_OBJECT;
+ {
+ while (FindChunk(obj, edit))
+ {
+ ReadASCIIZ(m_objName, 99);
+ ml = ReadChunk();
+ if (ml.id == OBJ_TRIMESH)
+ ReadMesh(ml);
+ if (ml.id == OBJ_LIGHT)
+ ReadLight(ml);
+ SkipChunk(obj);
+ }
+ }
+
+ // read the keyframer data here to find out correct object orientation
+
+ LChunk keyframer;
+ keyframer.id = KFDATA;
+
+ LChunk objtrack;
+ objtrack.id = OBJECT_NODE_TAG;
+
+ GotoChunk(mainchunk);
+ if (FindChunk(keyframer, mainchunk))
+ { // keyframer chunk is present
+ GotoChunk(keyframer);
+ while (FindChunk(objtrack, keyframer))
+ {
+ ReadKeyframeData(objtrack);
+ SkipChunk(objtrack);
+ }
+ }
+
+ for (uint i=0; i<m_meshes.size(); i++) {
+ m_meshes[i].Optimize(m_optLevel);
+ }
+
+ m_pos = 0;
+ strcpy(m_objName, "");
+ return true;
+}
+
+void L3DS::ReadLight(const LChunk &parent)
+{
+ float t;
+ LVector3 v;
+ LLight light;
+ light.SetName(m_objName);
+ GotoChunk(parent);
+ LChunk chunk = ReadChunk();
+ if (chunk.id == OBJ_LIGHT)
+ {
+ v.x = ReadFloat();
+ v.y = ReadFloat();
+ v.z = ReadFloat();
+ if (chunk.end < parent.end)
+ chunk = ReadChunk();
+ while (chunk.end <= parent.end)
+ {
+ switch (chunk.id)
+ {
+ case COLOR_24:
+ case COLOR_F:
+ case LIN_COLOR_F:
+ case LIN_COLOR_24:
+ light.SetColor(ReadColor(chunk));
+ break;
+ case SPOTLIGHT:
+ v.x = ReadFloat();
+ v.y = ReadFloat();
+ v.z = ReadFloat();
+ light.SetTarget(v);
+ t = ReadFloat();
+ light.SetHotspot(t);
+ t = ReadFloat();
+ light.SetFalloff(t);
+ break;
+ default:
+ break;
+ }
+ SkipChunk(chunk);
+ if (chunk.end >= parent.end)
+ break;
+ chunk = ReadChunk();
+
+ }
+ }
+ m_lights.push_back(light);
+}
+
+void L3DS::ReadMesh(const LChunk &parent)
+{
+ unsigned short count, i;
+ LVector4 p;
+ LMatrix4 m;
+ LVector2 t;
+ p.w = 1.0f;
+ LMesh mesh;
+ mesh.SetName(m_objName);
+ GotoChunk(parent);
+ LChunk chunk = ReadChunk();
+ while (chunk.end <= parent.end)
+ {
+ switch (chunk.id)
+ {
+ case TRI_VERTEXLIST:
+ count = ReadShort();
+ mesh.SetVertexArraySize(count);
+ for (i=0; i < count; i++)
+ {
+ p.x = ReadFloat();
+ p.y = ReadFloat();
+ p.z = ReadFloat();
+ mesh.SetVertex(p, i);
+ }
+ break;
+ case TRI_FACEMAPPING:
+ count = ReadShort();
+ if (mesh.GetVertexCount() == 0)
+ mesh.SetVertexArraySize(count);
+ for (i=0; i < count; i++)
+ {
+ t.x = ReadFloat();
+ t.y = ReadFloat();
+ mesh.SetUV(t, i);
+ }
+ break;
+ case TRI_FACELIST:
+ ReadFaceList(chunk, mesh);
+ break;
+ case TRI_MATRIX:
+ {
+ for (int i = 0; i < 4; i++)
+ for (int j = 0; j < 3; j++)
+ m.m[i][j] = ReadFloat();
+
+ m.m[0][3] = 0.0f;
+ m.m[1][3] = 0.0f;
+ m.m[2][3] = 0.0f;
+ m.m[3][3] = 1.0f;
+
+ mesh.SetMatrix(m);
+ }
+ break;
+ default:
+ break;
+ }
+ SkipChunk(chunk);
+ if (chunk.end >= parent.end)
+ break;
+ chunk = ReadChunk();
+ }
+ m_meshes.push_back(mesh);
+}
+
+void L3DS::ReadFaceList(const LChunk &chunk, LMesh &mesh)
+{
+ // variables
+ unsigned short count, t;
+ uint i;
+ LTri tri;
+ LChunk ch;
+ char str[20];
+ //uint mat;
+
+ // consistency checks
+ if (chunk.id != TRI_FACELIST)
+ {
+ ErrorMsg("L3DS::ReadFaceList - internal error: wrong chunk passed as parameter");
+ return;
+ }
+ GotoChunk(chunk);
+ tri.smoothingGroups = 1;
+ // read the number of faces
+ count = ReadShort();
+ mesh.SetTriangleArraySize(count);
+ for (i=0; i<count; i++)
+ {
+ tri.a = ReadShort();
+ tri.b = ReadShort();
+ tri.c = ReadShort();
+ ReadShort();
+ mesh.SetTri(tri, i);
+ }
+ // now read the optional chunks
+ ch = ReadChunk();
+ while (ch.end <= chunk.end)
+ {
+ switch (ch.id)
+ {
+ case TRI_MAT_GROUP: {
+ ReadASCIIZ(str, 20);
+ int mat_id = FindMaterial(str)->GetID();
+ mesh.AddMaterial(mat_id);
+ count = ReadShort();
+ for (i=0; i<count; i++) {
+ t = ReadShort();
+ mesh.GetTri(t).materialId = mat_id;
+ }
+ }
+ break;
+
+ case TRI_SMOOTH_GROUP:
+ for (i=0; i<mesh.GetTriangleCount(); i++)
+ mesh.GetTri(i).smoothingGroups = (ulong) ReadInt();
+ break;
+ }
+ SkipChunk(ch);
+ ch = ReadChunk();
+ }
+}
+
+void L3DS::ReadMaterial(const LChunk &parent)
+{
+ // variables
+ LChunk chunk;
+ LChunk child;
+ char str[30];
+ LMaterial mat;
+ short sh;
+
+ GotoChunk(parent);
+
+ chunk = ReadChunk();
+ while (chunk.end <= parent.end)
+ {
+ switch (chunk.id)
+ {
+ case MAT_NAME:
+ ReadASCIIZ(str, 30);
+ mat.SetName(str);
+ break;
+ case MAT_AMBIENT:
+ child = ReadChunk();
+ mat.SetAmbientColor(ReadColor(child));
+ break;
+ case MAT_DIFFUSE:
+ child = ReadChunk();
+ mat.SetDiffuseColor(ReadColor(child));
+ break;
+ case MAT_SPECULAR:
+ child = ReadChunk();
+ mat.SetSpecularColor(ReadColor(child));
+ break;
+ case MAT_SHININESS:
+ child = ReadChunk();
+ mat.SetShininess(ReadPercentage(child));
+ break;
+ case MAT_TRANSPARENCY:
+ child = ReadChunk();
+ mat.SetTransparency(ReadPercentage(child));
+ break;
+ case MAT_SHADING:
+ sh = ReadShort();
+ switch (sh)
+ {
+ case 0:
+ mat.SetShadingType(sWireframe);
+ break;
+ case 1:
+ mat.SetShadingType(sFlat);
+ break;
+ case 2:
+ mat.SetShadingType(sGouraud);
+ break;
+ case 3:
+ mat.SetShadingType(sPhong);
+ break;
+ case 4:
+ mat.SetShadingType(sMetal);
+ break;
+ }
+ break;
+ case MAT_WIRE:
+ mat.SetShadingType(sWireframe);
+ break;
+ case MAT_TEXMAP:
+ ReadMap(chunk, mat.GetTextureMap1());
+ break;
+ case MAT_TEX2MAP:
+ ReadMap(chunk, mat.GetTextureMap2());
+ break;
+ case MAT_OPACMAP:
+ ReadMap(chunk, mat.GetOpacityMap());
+ break;
+ case MAT_BUMPMAP:
+ ReadMap(chunk, mat.GetBumpMap());
+ break;
+ case MAT_SPECMAP:
+ ReadMap(chunk, mat.GetSpecularMap());
+ break;
+ case MAT_REFLMAP:
+ child = ReadChunk();
+ mat.GetReflectionMap().strength = ReadPercentage(child);
+ SkipChunk(child);
+ child = ReadChunk();
+ if (child.id != MAT_MAPNAME)
+ {
+ ErrorMsg("L3DS::ReadMaterial - error, expected chunk not found");
+ return;
+ }
+ ReadASCIIZ(str, 30);
+ if (strcmp(str, "") == 0)
+ strcpy(mat.GetReflectionMap().mapName, "auto");
+ break;
+ }
+
+ SkipChunk(chunk);
+ chunk = ReadChunk();
+ }
+ m_materials.push_back(mat);
+ m_materials[m_materials.size()-1].SetID(m_materials.size()-1);
+}
+
+void L3DS::ReadMap(const LChunk &chunk, LMap& map)
+{
+ LChunk child;
+ char str[20];
+ GotoChunk(chunk);
+ child = ReadChunk();
+ while (child.end <= chunk.end)
+ {
+ switch (child.id)
+ {
+ case INT_PERCENTAGE:
+ map.strength = ReadPercentage(child);
+ break;
+ case MAT_MAPNAME:
+ ReadASCIIZ(str, 20);
+ strcpy(map.mapName, str);
+ break;
+ case MAT_MAP_USCALE:
+ map.uScale = ReadFloat();
+ break;
+ case MAT_MAP_VSCALE:
+ map.vScale = ReadFloat();
+ break;
+ case MAT_MAP_UOFFSET:
+ map.uOffset = ReadFloat();
+ break;
+ case MAT_MAP_VOFFSET:
+ map.vOffset = ReadFloat();
+ break;
+ case MAT_MAP_ANG:
+ map.angle = ReadFloat();
+ break;
+ }
+ SkipChunk(child);
+ child = ReadChunk();
+ }
+}
+
+void L3DS::ReadKeyframeData(const LChunk &parent)
+{
+ uint frames = 0;
+
+ LChunk node_hdr;
+ node_hdr.id = NODE_HDR;
+
+ char str[20];
+ LMesh *mesh;
+
+ GotoChunk(parent);
+ if (!FindChunk(node_hdr, parent))
+ return;
+ GotoChunk(node_hdr);
+ ReadASCIIZ(str, 19);
+ mesh = FindMesh(str);
+ if (mesh == 0)
+ return;
+ GotoChunk(parent);
+
+ // read the pivot
+ LVector3 pivot = zero3;
+
+ LChunk pivotchunk;
+ pivotchunk.id = PIVOT;
+ if (FindChunk(pivotchunk, parent))
+ {
+ GotoChunk(pivotchunk);
+ pivot.x = ReadFloat();
+ pivot.y = ReadFloat();
+ pivot.z = ReadFloat();
+ }
+ GotoChunk(parent);
+
+ // read frame 0 from the position track
+ LVector3 pos = zero3;
+
+ frames = 0;
+
+ LChunk poschunk;
+ poschunk.id = POS_TRACK_TAG;
+ if (FindChunk(poschunk, parent))
+ {
+ GotoChunk(poschunk);
+ // read the trackheader structure
+ ReadShort();
+ ReadInt();
+ ReadInt();
+ frames = ReadInt();
+ if (frames > 0)
+ {
+ ReadKeyheader();
+ pos.x = ReadFloat();
+ pos.y = ReadFloat();
+ pos.z = ReadFloat();
+ }
+ }
+ GotoChunk(parent);
+
+ // now read the rotation track
+ LVector4 rot = zero4;
+
+ LChunk rotchunk;
+ rotchunk.id = ROT_TRACK_TAG;
+
+ frames = 0;
+ if (FindChunk(rotchunk, parent))
+ {
+ GotoChunk(rotchunk);
+ // read the trackheader structure
+ ReadShort();
+ ReadInt();
+ ReadInt();
+ frames = ReadInt();
+ if (frames > 0)
+ {
+ ReadKeyheader();
+ rot.x = ReadFloat();
+ rot.y = ReadFloat();
+ rot.z = ReadFloat();
+ rot.w = ReadFloat();
+ }
+ }
+ GotoChunk(parent);
+
+ // now read the scaling chunk
+ LVector3 scale;
+ scale.x = 1;
+ scale.y = 1;
+ scale.z = 1;
+
+ LChunk scalechunk;
+ scalechunk.id = SCL_TRACK_TAG;
+
+ frames = 0;
+
+ if (FindChunk(scalechunk, parent))
+ {
+ GotoChunk(scalechunk);
+ // read the trackheader structure
+ ReadShort();
+ ReadInt();
+ ReadInt();
+ frames = ReadInt();
+ if (frames > 0)
+ {
+ ReadKeyheader();
+ scale.x = ReadFloat();
+ scale.y = ReadFloat();
+ scale.z = ReadFloat();
+ }
+ }
+ GotoChunk(parent);
+
+
+}
+
+long L3DS::ReadKeyheader()
+{
+ long frame;
+ frame = ReadInt();
+ short opts = ReadShort();
+ if (opts & 32768) // 32768 is 1000000000000000 binary
+ { // tension is present
+ ReadFloat();
+ }
+ if (opts & 16384) // 16384 is 0100000000000000 binary
+ { // continuity is present
+ ReadFloat();
+ }
+ if (opts & 8192)
+ { // bias info present
+ ReadFloat();
+ }
+ if (opts & 4096)
+ { // "ease to" present
+ ReadFloat();
+ }
+ if (opts & 2048)
+ { // "ease from" present
+ ReadFloat();
+ }
+ return frame;
+}