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.
7 #include "Loader_ase.hpp"
8 #include "MaterialSystem/Material.hpp"
9 #include "Math3D/BoundingBox.hpp"
10 #include "TextParser/TextParser.hpp"
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.
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.
49 // Nested block found - skip it and continue the search.
50 TP
.SkipBlock("{", "}", true);
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.
61 // Otherwise, just continue the search.
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);
83 unsigned long ExpectedNrOfVertices
=0;
84 unsigned long ExpectedNrOfFaces
=0;
85 unsigned long ExpectedNrOfTexCoords
=0;
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!
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");
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);
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...
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!
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!
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.
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];
265 GO
.CastShadows
=false;
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.
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);
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
])
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.
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";
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
];
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.
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
]);
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());
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
);