Fix issue in Rocket.lua script.
[Cafu-Engine.git] / Libs / Models / Loader_ase.cpp
blob45a0c546927b79c6317ad3e100623f8104f9f163
1 /*
2 Cafu Engine, http://www.cafu.de/
3 Copyright (c) Carsten Fuchs and other contributors.
4 This project is licensed under the terms of the MIT license.
5 */
7 #include "Loader_ase.hpp"
8 #include "MaterialSystem/Material.hpp"
9 #include "Math3D/BoundingBox.hpp"
10 #include "TextParser/TextParser.hpp"
12 #include <assert.h>
13 #include <stdio.h>
16 void LoaderAseT::ReadMaterials(TextParserT& TP)
18 // Last seen token was "*MATERIAL_LIST".
19 TP.AssertAndSkipToken("{");
20 TP.AssertAndSkipToken("*MATERIAL_COUNT");
22 const unsigned long ExpectedNrOfMaterials=strtoul(TP.GetNextToken().c_str(), NULL, 0);
24 for (unsigned long MaterialNr=0; MaterialNr<ExpectedNrOfMaterials; MaterialNr++)
26 TP.AssertAndSkipToken("*MATERIAL");
27 if (strtoul(TP.GetNextToken().c_str(), NULL, 0)!=MaterialNr) throw TextParserT::ParseError();
28 TP.AssertAndSkipToken("{");
30 // Now do just search for "*MATERIAL_NAME" in this material block.
31 while (true)
33 std::string Token=TP.GetNextToken();
35 if (Token=="*MATERIAL_NAME")
37 // Good, found the material name.
38 std::string MatName=TP.GetNextToken();
40 // Make sure that it is unique.
41 while (m_MaterialNames.Find(MatName)>=0) MatName+=std::string("/")+char('a'+(MaterialNr % 26));
43 m_MaterialNames.PushBack(MatName); // Store the material name.
44 TP.SkipBlock("{", "}", true); // Skip the rest of this *MATERIAL block.
45 break; // Proceed with the next material.
47 else if (Token=="{")
49 // Nested block found - skip it and continue the search.
50 TP.SkipBlock("{", "}", true);
52 else if (Token=="}")
54 // Bad - end of material found, but no material name.
55 printf("Missing \"*MATERIAL_NAME\" in material %lu.\n", MaterialNr);
56 m_MaterialNames.PushBack("NoMaterial"); // Store a dummy name.
57 break; // Proceed with the next material.
59 else
61 // Otherwise, just continue the search.
62 continue;
67 TP.AssertAndSkipToken("}");
71 void LoaderAseT::GeomObjectT::ReadMesh(TextParserT& TP)
73 // Last seen token was "*MESH".
74 TP.AssertAndSkipToken("{");
76 if (Vertices.Size()>0 || TexCoords.Size()>0 || Triangles.Size()>0)
78 printf("Warning: More than one mesh per \"*GEOMOBJECT\"!? Skipping...\n");
79 TP.SkipBlock("{", "}", true);
80 return;
83 unsigned long ExpectedNrOfVertices =0;
84 unsigned long ExpectedNrOfFaces =0;
85 unsigned long ExpectedNrOfTexCoords=0;
87 while (true)
89 const std::string Token=TP.GetNextToken();
91 if (Token=="*TIMEVALUE" ) TP.GetNextToken(); // Ignore the timevalue.
92 else if (Token=="*MESH_NUMVERTEX" ) ExpectedNrOfVertices =strtoul(TP.GetNextToken().c_str(), NULL, 0);
93 else if (Token=="*MESH_VERTEX_LIST")
95 TP.AssertAndSkipToken("{");
97 for (unsigned long VertexNr=0; VertexNr<ExpectedNrOfVertices; VertexNr++)
99 TP.AssertAndSkipToken("*MESH_VERTEX");
100 if (strtoul(TP.GetNextToken().c_str(), NULL, 0)!=VertexNr) throw TextParserT::ParseError();
102 // This cannot be collapsed to VectorT(atof(...), atof(...), atof(...)), because
103 // the order of the calls to atof(...) is then not necessarily from left to right!
104 VectorT Vert;
105 Vert.x=atof(TP.GetNextToken().c_str());
106 Vert.y=atof(TP.GetNextToken().c_str());
107 Vert.z=atof(TP.GetNextToken().c_str());
108 Vertices.PushBack(Vert);
111 TP.AssertAndSkipToken("}");
113 else if (Token=="*MESH_NUMFACES" ) ExpectedNrOfFaces =strtoul(TP.GetNextToken().c_str(), NULL, 0);
114 else if (Token=="*MESH_FACE_LIST" )
116 TP.AssertAndSkipToken("{");
118 Triangles.PushBackEmpty(ExpectedNrOfFaces-Triangles.Size());
120 for (unsigned long FaceNr=0; FaceNr<ExpectedNrOfFaces; FaceNr++)
122 TP.AssertAndSkipToken("*MESH_FACE");
123 // The colon in the token (ex.: "0:") is ignored by strtoul().
124 if (strtoul(TP.GetNextToken().c_str(), NULL, 0)!=FaceNr) throw TextParserT::ParseError();
126 TP.AssertAndSkipToken("A:"); Triangles[FaceNr].IndVertices[0]=strtoul(TP.GetNextToken().c_str(), NULL, 0);
127 TP.AssertAndSkipToken("B:"); Triangles[FaceNr].IndVertices[1]=strtoul(TP.GetNextToken().c_str(), NULL, 0);
128 TP.AssertAndSkipToken("C:"); Triangles[FaceNr].IndVertices[2]=strtoul(TP.GetNextToken().c_str(), NULL, 0);
130 TP.AssertAndSkipToken("AB:"); TP.GetNextToken(); // Ignore this edge flag.
131 TP.AssertAndSkipToken("BC:"); TP.GetNextToken(); // Ignore this edge flag.
132 TP.AssertAndSkipToken("CA:"); TP.GetNextToken(); // Ignore this edge flag.
134 TP.AssertAndSkipToken("*MESH_SMOOTHING");
135 while (true)
137 std::string SmoothGroupString=TP.GetNextToken();
139 // Note 1: TP has the comma among its delimiters!
140 // Note 2: If we ever change this because some faces have the "*MESH_MTLID" keyword not specified at all,
141 // remeber to not ignore the "*MATERIAL_REF" keyword any longer, and fix all faces with no "*MESH_MTLID" after loading!
142 if (SmoothGroupString=="*MESH_MTLID") break;
144 const unsigned long SG=strtoul(SmoothGroupString.c_str(), NULL, 0);
146 if (SG > 31)
147 printf("Mesh is in smoothing group %lu, but should be in 0...31.\n", SG);
149 Triangles[FaceNr].SmoothGroups.PushBack(SG);
150 Triangles[FaceNr].SmoothGrps=uint32_t(1) << SG;
152 if (Triangles[FaceNr].SmoothGroups.Size()>32) throw TextParserT::ParseError(); // Safe-Guard...
155 // WRONG!!
156 // It seems that the number after *MESH_MTLID describes some logical group,
157 // like the six sides of a box. What WE need is the number after *MATERIAL_REF.
158 // Triangles[FaceNr].IndMaterial=strtoul(TP.GetNextToken().c_str(), NULL, 0);
159 TP.GetNextToken(); // Right: Ignore the sub-material number.
162 TP.AssertAndSkipToken("}");
164 else if (Token=="*MESH_NUMTVERTEX" ) ExpectedNrOfTexCoords=strtoul(TP.GetNextToken().c_str(), NULL, 0);
165 else if (Token=="*MESH_TVERTLIST" )
167 TP.AssertAndSkipToken("{");
169 for (unsigned long TexCoordNr=0; TexCoordNr<ExpectedNrOfTexCoords; TexCoordNr++)
171 TP.AssertAndSkipToken("*MESH_TVERT");
172 if (strtoul(TP.GetNextToken().c_str(), NULL, 0)!=TexCoordNr) throw TextParserT::ParseError();
174 // This cannot be collapsed to VectorT(atof(...), atof(...), atof(...)), because
175 // the order of the calls to atof(...) is then not necessarily from left to right!
176 VectorT TexCoord;
177 TexCoord.x= atof(TP.GetNextToken().c_str());
178 TexCoord.y=-atof(TP.GetNextToken().c_str());
179 TexCoord.z= atof(TP.GetNextToken().c_str());
180 TexCoords.PushBack(TexCoord);
183 TP.AssertAndSkipToken("}");
185 else if (Token=="*MESH_NUMTVFACES" )
187 unsigned long ExpectedNrOfTextureFaces=strtoul(TP.GetNextToken().c_str(), NULL, 0);
189 if (ExpectedNrOfFaces==0 || ExpectedNrOfTextureFaces!=ExpectedNrOfFaces)
190 printf("Warning: Expected number of faces: %lu, expected number of texture faces: %lu.\n", ExpectedNrOfFaces, ExpectedNrOfTextureFaces);
192 else if (Token=="*MESH_TFACELIST" )
194 TP.AssertAndSkipToken("{");
196 Triangles.PushBackEmpty(ExpectedNrOfFaces-Triangles.Size());
198 for (unsigned long FaceNr=0; FaceNr<ExpectedNrOfFaces; FaceNr++)
200 TP.AssertAndSkipToken("*MESH_TFACE");
201 if (strtoul(TP.GetNextToken().c_str(), NULL, 0)!=FaceNr) throw TextParserT::ParseError();
203 Triangles[FaceNr].IndTexCoords[0]=strtoul(TP.GetNextToken().c_str(), NULL, 0);
204 Triangles[FaceNr].IndTexCoords[1]=strtoul(TP.GetNextToken().c_str(), NULL, 0);
205 Triangles[FaceNr].IndTexCoords[2]=strtoul(TP.GetNextToken().c_str(), NULL, 0);
208 TP.AssertAndSkipToken("}");
210 else if (Token=="*MESH_MAPPINGCHANNEL")
212 // Ignore additional sets of TexCoords. I may change my mind later, though!
213 TP.GetNextToken();
214 TP.SkipBlock("{", "}", false);
216 else if (Token=="*MESH_NUMCVERTEX") TP.GetNextToken(); // Ignore the number of color vertices.
217 else if (Token=="*MESH_CVERTLIST" ) TP.SkipBlock("{", "}", false); // Ignore the per-vertex color list.
218 else if (Token=="*MESH_NUMCVFACES") TP.GetNextToken(); // Ignore the number of color faces.
219 else if (Token=="*MESH_CFACELIST" ) TP.SkipBlock("{", "}", false); // Ignore the per-face color specifications.
220 else if (Token=="*MESH_NORMALS" ) TP.SkipBlock("{", "}", false); // Ignore the normals - we compute the entire tangent space ourselves!
221 else if (Token=="}" ) break; // End of mesh.
222 else
224 // Unknown token!
225 // If the next token is a block start, skip the block.
226 // Otherwise it was something else that we just throw away.
227 printf("Unknown token \"%s\", skipping...\n", Token.c_str());
228 if (TP.GetNextToken()=="{") TP.SkipBlock("{", "}", true);
233 // Run a few sanity checks on the just loaded mesh.
234 // Doing so was motivated by models that have faces (triangles) specified, but no texture-coordinates.
235 unsigned long MaxVertexIndex =0;
236 unsigned long MaxTexCoordIndex=0;
238 for (unsigned long TriNr=0; TriNr<Triangles.Size(); TriNr++)
240 TriangleT& Tri=Triangles[TriNr];
242 for (unsigned long i=0; i<3; i++)
244 if (Tri.IndVertices [i]>MaxVertexIndex ) MaxVertexIndex =Tri.IndVertices [i];
245 if (Tri.IndTexCoords[i]>MaxTexCoordIndex) MaxTexCoordIndex=Tri.IndTexCoords[i];
249 // "Fix" the model by pushing back "dummy" array elements...
250 while (MaxVertexIndex >=Vertices .Size()) Vertices .PushBackEmpty();
251 while (MaxTexCoordIndex>=TexCoords.Size()) TexCoords.PushBackEmpty();
255 void LoaderAseT::ReadGeometry(TextParserT& TP)
257 // Last seen token was "*GEOMOBJECT".
258 TP.AssertAndSkipToken("{");
260 m_GeomObjects.PushBackEmpty();
261 GeomObjectT& GO=m_GeomObjects[m_GeomObjects.Size()-1];
263 GO.Name="Object";
264 GO.IndexMaterial=0;
265 GO.CastShadows=false;
267 while (true)
269 const std::string Token=TP.GetNextToken();
271 if (Token=="*NODE_NAME" ) GO.Name=TP.GetNextToken();
272 else if (Token=="*NODE_TM" ) TP.SkipBlock("{", "}", false); // Ignore the transformation matrix specification. May change my mind later, though!
273 else if (Token=="*MESH" ) GO.ReadMesh(TP);
274 else if (Token=="*PROP_MOTIONBLUR") TP.GetNextToken(); // Ignore the "motion blur" property.
275 else if (Token=="*PROP_CASTSHADOW") GO.CastShadows=(TP.GetNextToken()!="0");
276 else if (Token=="*PROP_RECVSHADOW") TP.GetNextToken(); // Ignore the "receive shadow" property.
277 else if (Token=="*TM_ANIMATION" ) TP.SkipBlock("{", "}", false); // Ignore the specification of the transformation matrix for animation.
278 else if (Token=="*MATERIAL_REF" ) GO.IndexMaterial=strtoul(TP.GetNextToken().c_str(), NULL, 0);
279 else if (Token=="}" ) break; // End of object.
280 else
282 // Unknown token!
283 // If the next token is a block start, skip the block.
284 // Otherwise it was something else that we just throw away.
285 printf("Unknown token \"%s\", skipping...\n", Token.c_str());
286 if (TP.GetNextToken()=="{") TP.SkipBlock("{", "}", true);
292 LoaderAseT::LoaderAseT(const std::string& FileName, int Flags)
293 : ModelLoaderT(FileName, Flags)
295 TextParserT TP(FileName.c_str(), ","); // The comma is required for SmoothGroups.
299 // A good description of the ASE file format can be found at
300 // http://www.unrealwiki.com/wiki/ASE_File_Format
301 while (!TP.IsAtEOF())
303 const std::string Token=TP.GetNextToken();
305 if (Token=="*3DSMAX_ASCIIEXPORT") TP.GetNextToken(); // Ignore the version.
306 else if (Token=="*COMMENT" ) TP.GetNextToken(); // Ignore the comment.
307 else if (Token=="*SCENE" ) TP.SkipBlock("{", "}", false); // Ignore the scene description.
308 else if (Token=="*MATERIAL_LIST" ) ReadMaterials(TP);
309 else if (Token=="*GEOMOBJECT" ) ReadGeometry(TP);
310 else if (Token=="*LIGHTOBJECT" ) TP.SkipBlock("{", "}", false);
311 else if (Token=="*CAMERAOBJECT" ) TP.SkipBlock("{", "}", false);
312 else
314 // Unknown token!
315 // If the next token is a block start, skip the block.
316 // Otherwise it was something else that we just throw away.
317 printf("Unknown token \"%s\", skipping...\n", Token.c_str());
318 if (TP.GetNextToken()=="{") TP.SkipBlock("{", "}", true);
322 if (m_MaterialNames.Size()==0) throw TextParserT::ParseError();
323 if (m_GeomObjects .Size()==0) throw TextParserT::ParseError();
325 catch (const TextParserT::ParseError&)
327 throw LoadErrorT("Could not parse the file.");
331 // TODO: Sort the triangles according to their material! Profile!
332 // Can we even do this across GeomObjects? Without introducing another layer of indirection?
333 // Probably yes, by simply loading everything into one big triangle list... hehe.
334 // The question is if this is reasonable wrt. silhouette detection... yes, I think so;
335 // one big mesh offers more opportunity for fewer silhouette edges than many small!
336 // Then get one MeshT(MeshT::Triangles) per batch of triangles with common material.
337 // Will never be more than there are materials (will be exactly as many as there are used materials).
340 // Find the SmoothGroup with the highest ID.
341 // We need this below in order to be able to obtain more unique SmoothGroup IDs.
342 unsigned long NextSmoothGroupID=0;
344 for (unsigned long GONr=0; GONr<m_GeomObjects.Size(); GONr++)
345 for (unsigned long TriangleNr=0; TriangleNr<m_GeomObjects[GONr].Triangles.Size(); TriangleNr++)
347 const GeomObjectT::TriangleT& Tri=m_GeomObjects[GONr].Triangles[TriangleNr];
349 for (unsigned long SGNr=0; SGNr<Tri.SmoothGroups.Size(); SGNr++)
350 if (Tri.SmoothGroups[SGNr]>=NextSmoothGroupID)
351 NextSmoothGroupID=Tri.SmoothGroups[SGNr]+1;
354 // Now assign each triangle that is in NO SmoothGroup an own, UNIQUE SmoothGroup.
355 // This avoids special-case treatment below, and is intentionally done ACROSS GeomObjects (rather than per GeomObject).
356 for (unsigned long GONr=0; GONr<m_GeomObjects.Size(); GONr++)
357 for (unsigned long TriangleNr=0; TriangleNr<m_GeomObjects[GONr].Triangles.Size(); TriangleNr++)
359 GeomObjectT::TriangleT& Tri=m_GeomObjects[GONr].Triangles[TriangleNr];
361 if (Tri.SmoothGroups.Size()==0) Tri.SmoothGroups.PushBack(NextSmoothGroupID++);
365 // Compute the tangent space vectors.
366 for (unsigned long GONr=0; GONr<m_GeomObjects.Size(); GONr++)
368 GeomObjectT& GO=m_GeomObjects[GONr];
370 // Precompute the tangent-space of each triangle. Needed below as sources for computing the averages (per-vertex).
371 // ArrayT<VectorT> PerTriNormals; // Stored with each triangle, as we need the info also for the stencil shadows.
372 ArrayT<VectorT> PerTriTangents;
373 ArrayT<VectorT> PerTriBiNormals;
375 for (unsigned long TriangleNr=0; TriangleNr<GO.Triangles.Size(); TriangleNr++)
377 GeomObjectT::TriangleT& Tri=GO.Triangles[TriangleNr];
379 // Understanding what's going on here is easy. The key statement is
380 // "The tangent vector is parallel to the direction of increasing S on a parametric surface."
381 // First, there is a short explanation in "The Cg Tutorial", chapter 8.
382 // Second, I have drawn a simple figure that leads to a simple 2x2 system of Gaussian equations, see my TechArchive.
383 const VectorT Edge01=GO.Vertices[Tri.IndVertices[1]]-GO.Vertices[Tri.IndVertices[0]];
384 const VectorT Edge02=GO.Vertices[Tri.IndVertices[2]]-GO.Vertices[Tri.IndVertices[0]];
385 const VectorT uv01 =GO.TexCoords[Tri.IndTexCoords[1]]-GO.TexCoords[Tri.IndTexCoords[0]];
386 const VectorT uv02 =GO.TexCoords[Tri.IndTexCoords[2]]-GO.TexCoords[Tri.IndTexCoords[0]];
387 const double f =uv01.x*uv02.y-uv01.y*uv02.x>0.0 ? 1.0 : -1.0;
389 const VectorT Normal =cross(Edge01, Edge02);
390 const VectorT Tangent =scale(Edge02, -uv01.y*f) + scale(Edge01, uv02.y*f);
391 const VectorT BiNormal=scale(Edge02, uv01.x*f) - scale(Edge01, uv02.x*f);
393 const double NormalL =length(Normal );
394 const double TangentL =length(Tangent );
395 const double BiNormalL=length(BiNormal);
397 Tri.Normal=NormalL >0.000001 ? scale(Normal , 1.0/NormalL ) : VectorT(0, 0, 1);
398 PerTriTangents .PushBack(TangentL >0.000001 ? scale(Tangent , 1.0/TangentL ) : VectorT(1, 0, 0));
399 PerTriBiNormals.PushBack(BiNormalL>0.000001 ? scale(BiNormal, 1.0/BiNormalL) : VectorT(0, 1, 0));
402 // Precompute a table that stores for each vertex which triangles refer to it.
403 // This accelerates the subsequent computations a lot.
404 ArrayT< ArrayT<unsigned long> > TrianglesForVertex;
406 TrianglesForVertex.PushBackEmpty(GO.Vertices.Size());
408 for (unsigned long TriangleNr=0; TriangleNr<GO.Triangles.Size(); TriangleNr++)
410 GeomObjectT::TriangleT& Tri=GO.Triangles[TriangleNr];
412 TrianglesForVertex[Tri.IndVertices[0]].PushBack(TriangleNr);
413 TrianglesForVertex[Tri.IndVertices[1]].PushBack(TriangleNr);
414 TrianglesForVertex[Tri.IndVertices[2]].PushBack(TriangleNr);
417 // Compute the tangent space for each vertex of each triangle.
418 for (unsigned long TriangleNr=0; TriangleNr<GO.Triangles.Size(); TriangleNr++)
420 GeomObjectT::TriangleT& Tri=GO.Triangles[TriangleNr];
422 for (unsigned long VNr=0; VNr<3; VNr++)
424 Tri.Normals [VNr]=VectorT(0, 0, 0);
425 Tri.Tangents [VNr]=VectorT(0, 0, 0);
426 Tri.BiNormals[VNr]=VectorT(0, 0, 0);
428 // Simply loop over those triangles that are known to refer to Tri.IndVertices[VNr].
429 // This speeds up this code a lot. The old loop simply was: for (unsigned long T2Nr=0; T2Nr<GO.Triangles.Size(); T2Nr++)
430 for (unsigned long RefNr=0; RefNr<TrianglesForVertex[Tri.IndVertices[VNr]].Size(); RefNr++)
432 // Note that Tri==Tri2 is well possible (a normal case), thanks to the removal of "no" smoothgroups above.
433 const unsigned long T2Nr=TrianglesForVertex[Tri.IndVertices[VNr]][RefNr];
434 const GeomObjectT::TriangleT& Tri2=GO.Triangles[T2Nr];
436 // See if Tri and Tri2 are in the same SmoothGroup (that is, if their sets of SmoothGroups intersect).
437 bool DoIntersect=false;
439 for (unsigned long SG1Nr=0; SG1Nr<Tri.SmoothGroups.Size() && !DoIntersect; SG1Nr++)
440 for (unsigned long SG2Nr=0; SG2Nr<Tri2.SmoothGroups.Size() && !DoIntersect; SG2Nr++)
441 if (Tri.SmoothGroups[SG1Nr]==Tri2.SmoothGroups[SG2Nr])
442 DoIntersect=true;
444 // Don't intersect? Then Tri and Tri2 are not in a common smoothgroup.
445 if (!DoIntersect) continue;
447 // If Tri2 doesn't share Tri.IndVertices[VNr], Tri2 does not influence the tangent space at that vertex.
448 // Update: I don't think that this condition is required any more!
449 // It made sense when we looped over *all* triangles of the GO, but as we now only loop over those that
450 // refer to Tri.IndVertices[VNr] anyway, Tri.IndVertices[VNr] is trivially shared by Tri2.
451 if (Tri.IndVertices[VNr]!=Tri2.IndVertices[0] &&
452 Tri.IndVertices[VNr]!=Tri2.IndVertices[1] &&
453 Tri.IndVertices[VNr]!=Tri2.IndVertices[2]) continue;
455 // Okay, Tri and Tri2 are in a common smoothgroup, and share the current vertex.
456 Tri.Normals [VNr]=Tri.Normals [VNr]+Tri2.Normal;
457 Tri.Tangents [VNr]=Tri.Tangents [VNr]+PerTriTangents [T2Nr];
458 Tri.BiNormals[VNr]=Tri.BiNormals[VNr]+PerTriBiNormals[T2Nr];
461 // Normalize the tangent space vectors.
462 double Len;
464 Len=length(Tri.Normals [VNr]); Tri.Normals [VNr]=(Len>0.000001) ? scale(Tri.Normals [VNr], 1.0/Len) : Tri.Normal;
465 Len=length(Tri.Tangents [VNr]); Tri.Tangents [VNr]=(Len>0.000001) ? scale(Tri.Tangents [VNr], 1.0/Len) : PerTriTangents [TriangleNr];
466 Len=length(Tri.BiNormals[VNr]); Tri.BiNormals[VNr]=(Len>0.000001) ? scale(Tri.BiNormals[VNr], 1.0/Len) : PerTriBiNormals[TriangleNr];
472 // Check if the material indices are all within the allowed range.
473 for (unsigned long GONr=0; GONr<m_GeomObjects.Size(); GONr++)
475 // Ase files can come with too few materials specified.
476 // Make sure that there are enough of them, or else we'll crash with array-index-out-of-bounds later.
477 if (m_GeomObjects[GONr].IndexMaterial>=m_MaterialNames.Size())
479 printf("ase model error (%s): GeomObject %lu refers to material index %lu, but there are only %lu materials total.\n", FileName.c_str(), GONr, m_GeomObjects[GONr].IndexMaterial, m_MaterialNames.Size());
480 throw LoadErrorT("A GeomObject refers to an unknown material.");
486 void LoaderAseT::Load(ArrayT<CafuModelT::JointT>& Joints, ArrayT<CafuModelT::MeshT>& Meshes, ArrayT<CafuModelT::AnimT>& Anims, MaterialManagerImplT& MaterialMan)
488 // Create a default "identity" joint.
489 // That single joint is used for (shared by) all weights of all meshes.
490 Joints.PushBackEmpty();
492 Joints[0].Name ="root";
493 Joints[0].Parent=-1;
494 // Joints[0].Pos =Vector3fT();
495 // Joints[0].Qtr =Vector3fT(); // Identity quaternion...
496 Joints[0].Scale =Vector3fT(1.0f, 1.0f, 1.0f);
499 for (unsigned long GONr=0; GONr<m_GeomObjects.Size(); GONr++)
501 Meshes.PushBackEmpty();
503 const GeomObjectT& GO =m_GeomObjects[GONr];
504 CafuModelT::MeshT& Mesh=Meshes[GONr];
506 Mesh.Name=GO.Name;
508 // Set the default tangent-space method for ASE models to HARD.
509 // This helps with many older and/or simpler models that are not immediately edited in the Model Editor.
510 Mesh.TSMethod=CafuModelT::MeshT::HARD;
512 for (unsigned long TriNr=0; TriNr<GO.Triangles.Size(); TriNr++)
514 Mesh.Triangles.PushBackEmpty();
516 const GeomObjectT::TriangleT& AseTri=GO.Triangles[TriNr];
517 CafuModelT::MeshT::TriangleT& CafuTri=Mesh.Triangles[TriNr];
519 for (unsigned long i=0; i<3; i++)
521 const Vector3fT AseVertexPos=GO.Vertices[AseTri.IndVertices[i]].AsVectorOfFloat();
522 unsigned long CafuVertexNr=0;
524 // Try to find a suitable vertex in Mesh.Vertices[].
525 for (CafuVertexNr=0; CafuVertexNr<Mesh.Vertices.Size(); CafuVertexNr++)
527 const CafuModelT::MeshT::VertexT& CafuVertex=Mesh.Vertices[CafuVertexNr];
529 assert(CafuVertex.NumWeights==1);
531 if (Mesh.Weights[CafuVertex.FirstWeightIdx].Pos!=AseVertexPos) continue;
532 if (CafuVertex.u!=GO.TexCoords[AseTri.IndTexCoords[i]].AsVectorOfFloat().x) continue;
533 if (CafuVertex.v!=GO.TexCoords[AseTri.IndTexCoords[i]].AsVectorOfFloat().y) continue;
535 // This vertex meets all criteria - take it.
536 break;
539 // If no suitable vertex was found in Mesh.Vertices[], insert a new one.
540 if (CafuVertexNr>=Mesh.Vertices.Size())
542 unsigned long WeightNr=0;
544 for (WeightNr=0; WeightNr<Mesh.Weights.Size(); WeightNr++)
545 if (Mesh.Weights[WeightNr].Pos==AseVertexPos) break;
547 if (WeightNr>=Mesh.Weights.Size())
549 Mesh.Weights.PushBackEmpty();
551 Mesh.Weights[WeightNr].JointIdx=0;
552 Mesh.Weights[WeightNr].Weight =1.0f;
553 Mesh.Weights[WeightNr].Pos =AseVertexPos;
556 Mesh.Vertices.PushBackEmpty();
557 CafuModelT::MeshT::VertexT& CafuVertex=Mesh.Vertices[CafuVertexNr];
559 CafuVertex.u =GO.TexCoords[AseTri.IndTexCoords[i]].AsVectorOfFloat().x;
560 CafuVertex.v =GO.TexCoords[AseTri.IndTexCoords[i]].AsVectorOfFloat().y;
561 CafuVertex.FirstWeightIdx=WeightNr;
562 CafuVertex.NumWeights =1;
565 // Triangles are ordered CW for Cafu models and CCW for ase models,
566 // so we write [2-i] in order to record the vertices in the proper (reversed) order.
567 CafuTri.VertexIdx[2-i]=CafuVertexNr;
570 CafuTri.SmoothGroups=AseTri.SmoothGrps;
574 Mesh.Material=MaterialMan.GetMaterial(m_MaterialNames[GO.IndexMaterial]);
576 if (!Mesh.Material)
578 // As we haven't parsed the *MATERIAL definitions more deeply, we cannot reasonably reconstruct materials here.
579 // Thus if there isn't an appropriately prepared .cmat file (so that MatName is found in MaterialMan),
580 // go for the wire-frame substitute straight away.
581 Mesh.Material=MaterialMan.RegisterMaterial(CreateDefaultMaterial(m_MaterialNames[GO.IndexMaterial]));
587 void LoaderAseT::Load(ArrayT<CafuModelT::GuiFixtureT>& GuiFixtures)
592 void LoaderAseT::Print() const
594 printf("\nThis is an ase model. FileName: \"%s\"\n", m_FileName.c_str());
595 printf("Materials:\n");
596 for (unsigned long MaterialNr=0; MaterialNr<m_MaterialNames.Size(); MaterialNr++)
597 printf(" %2lu %s\n", MaterialNr, m_MaterialNames[MaterialNr].c_str());
598 printf("\n");
600 printf("GeomObjects:\n");
601 for (unsigned long GONr=0; GONr<m_GeomObjects.Size(); GONr++)
603 const GeomObjectT& GO=m_GeomObjects[GONr];
605 printf(" %2lu, using Mat %lu\n", GONr, GO.IndexMaterial);
607 printf("\n");