1 // meshmanager.cpp
2 //
3 // Copyright (C) 2001-2006 Chris Laurel <claurel@shatters.net>
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9
10 // Experimental particle system support
11 #define PARTICLE_SYSTEM 0
12
13 #include <iostream>
14 #include <fstream>
15 #include <cassert>
16
17 #include "celestia.h"
18 #include <celutil/debug.h>
19 #include <celutil/filetype.h>
20 #include <celutil/util.h>
21 #include <celmath/mathlib.h>
22 #include <celmath/perlin.h>
23 #include <cel3ds/3dsread.h>
24
25 #include "modelfile.h"
26 #if PARTICLE_SYSTEM
27 #include "particlesystem.h"
28 #include "particlesystemfile.h"
29 #endif
30 #include "vertexlist.h"
31 #include "parser.h"
32 #include "spheremesh.h"
33 #include "texmanager.h"
34 #include "meshmanager.h"
35
36 using namespace std;
37
38
39 static Model* LoadCelestiaMesh(const string& filename);
40 static Model* Convert3DSModel(const M3DScene& scene, const string& texPath);
41
42 static GeometryManager* geometryManager = NULL;
43
44 static const char UniqueSuffixChar = '!';
45
46
GetGeometryManager()47 GeometryManager* GetGeometryManager()
48 {
49 if (geometryManager == NULL)
50 geometryManager = new GeometryManager("models");
51 return geometryManager;
52 }
53
54
resolve(const string & baseDir)55 string GeometryInfo::resolve(const string& baseDir)
56 {
57 // Ensure that models with different centers get resolved to different objects by
58 // adding a 'uniquifying' suffix to the filename that encodes the center value.
59 // This suffix is stripped before the file is actually loaded.
60 char uniquifyingSuffix[128];
61 sprintf(uniquifyingSuffix, "%c%f,%f,%f,%f,%d", UniqueSuffixChar, center.x, center.y, center.z, scale, (int) isNormalized);
62
63 if (!path.empty())
64 {
65 string filename = path + "/models/" + source;
66 ifstream in(filename.c_str());
67 if (in.good())
68 {
69 resolvedToPath = true;
70 return filename + uniquifyingSuffix;
71 }
72 }
73
74 return baseDir + "/" + source + uniquifyingSuffix;
75 }
76
77
load(const string & resolvedFilename)78 Geometry* GeometryInfo::load(const string& resolvedFilename)
79 {
80 // Strip off the uniquifying suffix
81 string::size_type uniquifyingSuffixStart = resolvedFilename.rfind(UniqueSuffixChar);
82 string filename(resolvedFilename, 0, uniquifyingSuffixStart);
83
84 clog << _("Loading model: ") << filename << '\n';
85 Model* model = NULL;
86 ContentType fileType = DetermineFileType(filename);
87
88 if (fileType == Content_3DStudio)
89 {
90 M3DScene* scene = Read3DSFile(filename);
91 if (scene != NULL)
92 {
93 if (resolvedToPath)
94 model = Convert3DSModel(*scene, path);
95 else
96 model = Convert3DSModel(*scene, "");
97
98 if (isNormalized)
99 model->normalize(center);
100 else
101 model->transform(center, scale);
102
103 delete scene;
104 }
105 }
106 else if (fileType == Content_CelestiaModel)
107 {
108 ifstream in(filename.c_str(), ios::binary);
109 if (in.good())
110 {
111 model = LoadModel(in, path);
112 if (model != NULL)
113 {
114 if (isNormalized)
115 model->normalize(center);
116 else
117 model->transform(center, scale);
118 }
119 }
120 }
121 else if (fileType == Content_CelestiaMesh)
122 {
123 model = LoadCelestiaMesh(filename);
124 if (model != NULL)
125 {
126 if (isNormalized)
127 model->normalize(center);
128 else
129 model->transform(center, scale);
130 }
131 }
132 #if PARTICLE_SYSTEM
133 else if (fileType == Content_CelestiaParticleSystem)
134 {
135 ifstream in(filename.c_str());
136 if (in.good())
137 {
138 return LoadParticleSystem(in, path);
139 }
140 }
141 #endif
142
143 // Condition the model for optimal rendering
144 if (model != NULL)
145 {
146 // Many models tend to have a lot of duplicate materials; eliminate
147 // them, since unnecessarily setting material parameters can adversely
148 // impact rendering performance. Ideally uniquification of materials
149 // would be performed just once when the model was created, but
150 // that's not the case.
151 uint32 originalMaterialCount = model->getMaterialCount();
152 model->uniquifyMaterials();
153
154 // Sort the submeshes roughly by opacity. This will eliminate a
155 // good number of the errors caused when translucent triangles are
156 // rendered before geometry that they cover.
157 model->sortMeshes(Model::OpacityComparator());
158
159 model->determineOpacity();
160
161 // Display some statics for the model
162 clog << _(" Model statistics: ")
163 << model->getVertexCount() << _(" vertices, ")
164 << model->getPrimitiveCount() << _(" primitives, ")
165 << originalMaterialCount << _(" materials ")
166 << "(" << model->getMaterialCount() << _(" unique)\n");
167 }
168 else
169 {
170 cerr << _("Error loading model '") << filename << "'\n";
171 }
172
173 return model;
174 }
175
176
177 struct NoiseMeshParameters
178 {
179 Vec3f size;
180 Vec3f offset;
181 float featureHeight;
182 float octaves;
183 float slices;
184 float rings;
185 };
186
187
NoiseDisplacementFunc(float u,float v,void * info)188 static float NoiseDisplacementFunc(float u, float v, void* info)
189 {
190 float theta = u * (float) PI * 2;
191 float phi = (v - 0.5f) * (float) PI;
192 float x = (float) (cos(phi) * cos(theta));
193 float y = (float) sin(phi);
194 float z = (float) (cos(phi) * sin(theta));
195
196 // assert(info != NULL);
197 NoiseMeshParameters* params = (NoiseMeshParameters*) info;
198
199 return fractalsum(Point3f(x, y, z) + params->offset,
200 params->octaves) * params->featureHeight;
201 }
202
203
204 // TODO: The Celestia mesh format is deprecated
LoadCelestiaMesh(const string & filename)205 Model* LoadCelestiaMesh(const string& filename)
206 {
207 ifstream meshFile(filename.c_str(), ios::in);
208 if (!meshFile.good())
209 {
210 DPRINTF(0, "Error opening mesh file: %s\n", filename.c_str());
211 return NULL;
212 }
213
214 Tokenizer tokenizer(&meshFile);
215 Parser parser(&tokenizer);
216
217 if (tokenizer.nextToken() != Tokenizer::TokenName)
218 {
219 DPRINTF(0, "Mesh file %s is invalid.\n", filename.c_str());
220 return NULL;
221 }
222
223 if (tokenizer.getStringValue() != "SphereDisplacementMesh")
224 {
225 DPRINTF(0, "%s: Unrecognized mesh type %s.\n",
226 filename.c_str(),
227 tokenizer.getStringValue().c_str());
228 return NULL;
229 }
230
231 Value* meshDefValue = parser.readValue();
232 if (meshDefValue == NULL)
233 {
234 DPRINTF(0, "%s: Bad mesh file.\n", filename.c_str());
235 return NULL;
236 }
237
238 if (meshDefValue->getType() != Value::HashType)
239 {
240 DPRINTF(0, "%s: Bad mesh file.\n", filename.c_str());
241 delete meshDefValue;
242 return NULL;
243 }
244
245 Hash* meshDef = meshDefValue->getHash();
246
247 NoiseMeshParameters params;
248
249 params.size = Vec3f(1, 1, 1);
250 params.offset = Vec3f(10, 10, 10);
251 params.featureHeight = 0.0f;
252 params.octaves = 1;
253 params.slices = 20;
254 params.rings = 20;
255
256 meshDef->getVector("Size", params.size);
257 meshDef->getVector("NoiseOffset", params.offset);
258 meshDef->getNumber("FeatureHeight", params.featureHeight);
259 meshDef->getNumber("Octaves", params.octaves);
260 meshDef->getNumber("Slices", params.slices);
261 meshDef->getNumber("Rings", params.rings);
262
263 delete meshDefValue;
264
265 Model* model = new Model();
266 SphereMesh* sphereMesh = new SphereMesh(params.size,
267 (int) params.rings, (int) params.slices,
268 NoiseDisplacementFunc,
269 (void*) ¶ms);
270 if (sphereMesh != NULL)
271 {
272 Mesh* mesh = sphereMesh->convertToMesh();
273 model->addMesh(mesh);
274 delete sphereMesh;
275 }
276
277 return model;
278 }
279
280
ConvertToVertexList(M3DTriangleMesh & mesh,const M3DScene & scene,const string & texturePath)281 static VertexList* ConvertToVertexList(M3DTriangleMesh& mesh,
282 const M3DScene& scene,
283 const string& texturePath)
284 {
285 int nFaces = mesh.getFaceCount();
286 int nVertices = mesh.getVertexCount();
287 int nTexCoords = mesh.getTexCoordCount();
288 bool smooth = (mesh.getSmoothingGroupCount() == nFaces);
289 int i;
290
291 uint32 parts = VertexList::VertexNormal;
292 if (nTexCoords >= nVertices)
293 parts |= VertexList::TexCoord0;
294 VertexList* vl = new VertexList(parts);
295
296 Vec3f* faceNormals = new Vec3f[nFaces];
297 Vec3f* vertexNormals = new Vec3f[nFaces * 3];
298 int* faceCounts = new int[nVertices];
299 int** vertexFaces = new int*[nVertices];
300
301 for (i = 0; i < nVertices; i++)
302 {
303 faceCounts[i] = 0;
304 vertexFaces[i] = NULL;
305 }
306
307 // generate face normals
308 for (i = 0; i < nFaces; i++)
309 {
310 uint16 v0, v1, v2;
311 mesh.getFace(i, v0, v1, v2);
312
313 faceCounts[v0]++;
314 faceCounts[v1]++;
315 faceCounts[v2]++;
316
317 Point3f p0 = mesh.getVertex(v0);
318 Point3f p1 = mesh.getVertex(v1);
319 Point3f p2 = mesh.getVertex(v2);
320 faceNormals[i] = cross(p1 - p0, p2 - p1);
321 faceNormals[i].normalize();
322 }
323
324 if (!smooth && 0)
325 {
326 for (i = 0; i < nFaces; i++)
327 {
328 vertexNormals[i * 3] = faceNormals[i];
329 vertexNormals[i * 3 + 1] = faceNormals[i];
330 vertexNormals[i * 3 + 2] = faceNormals[i];
331 }
332 }
333 else
334 {
335 // allocate space for vertex face indices
336 for (i = 0; i < nVertices; i++)
337 {
338 vertexFaces[i] = new int[faceCounts[i] + 1];
339 vertexFaces[i][0] = faceCounts[i];
340 }
341
342 for (i = 0; i < nFaces; i++)
343 {
344 uint16 v0, v1, v2;
345 mesh.getFace(i, v0, v1, v2);
346 vertexFaces[v0][faceCounts[v0]--] = i;
347 vertexFaces[v1][faceCounts[v1]--] = i;
348 vertexFaces[v2][faceCounts[v2]--] = i;
349 }
350
351 // average face normals to compute the vertex normals
352 for (i = 0; i < nFaces; i++)
353 {
354 uint16 v0, v1, v2;
355 mesh.getFace(i, v0, v1, v2);
356 // uint32 smoothingGroups = mesh.getSmoothingGroups(i);
357
358 int j;
359 Vec3f v = Vec3f(0, 0, 0);
360 for (j = 1; j <= vertexFaces[v0][0]; j++)
361 {
362 int k = vertexFaces[v0][j];
363 // if (k == i || (smoothingGroups & mesh.getSmoothingGroups(k)) != 0)
364 if (faceNormals[i] * faceNormals[k] > 0.5f)
365 v += faceNormals[k];
366 }
367 v.normalize();
368 vertexNormals[i * 3] = v;
369
370 v = Vec3f(0, 0, 0);
371 for (j = 1; j <= vertexFaces[v1][0]; j++)
372 {
373 int k = vertexFaces[v1][j];
374 // if (k == i || (smoothingGroups & mesh.getSmoothingGroups(k)) != 0)
375 if (faceNormals[i] * faceNormals[k] > 0.5f)
376 v += faceNormals[k];
377 }
378 v.normalize();
379 vertexNormals[i * 3 + 1] = v;
380
381 v = Vec3f(0, 0, 0);
382 for (j = 1; j <= vertexFaces[v2][0]; j++)
383 {
384 int k = vertexFaces[v2][j];
385 // if (k == i || (smoothingGroups & mesh.getSmoothingGroups(k)) != 0)
386 if (faceNormals[i] * faceNormals[k] > 0.5f)
387 v += faceNormals[k];
388 }
389 v.normalize();
390 vertexNormals[i * 3 + 2] = v;
391 }
392 }
393
394 // build the triangle list
395 for (i = 0; i < nFaces; i++)
396 {
397 uint16 triVert[3];
398 mesh.getFace(i, triVert[0], triVert[1], triVert[2]);
399
400 for (int j = 0; j < 3; j++)
401 {
402 VertexList::Vertex v;
403 v.point = mesh.getVertex(triVert[j]);
404 v.normal = vertexNormals[i * 3 + j];
405 if ((parts & VertexList::TexCoord0) != 0)
406 v.texCoords[0] = mesh.getTexCoord(triVert[j]);
407 vl->addVertex(v);
408 }
409 }
410
411 // Set the material properties
412 {
413 string materialName = mesh.getMaterialName();
414 if (materialName.length() > 0)
415 {
416 int nMaterials = scene.getMaterialCount();
417 for (i = 0; i < nMaterials; i++)
418 {
419 M3DMaterial* material = scene.getMaterial(i);
420 if (materialName == material->getName())
421 {
422 M3DColor diffuse = material->getDiffuseColor();
423 vl->setDiffuseColor(Color(diffuse.red, diffuse.green, diffuse.blue, material->getOpacity()));
424 M3DColor specular = material->getSpecularColor();
425 vl->setSpecularColor(Color(specular.red, specular.green, specular.blue));
426 float shininess = material->getShininess();
427
428 // Map the 3DS file's shininess from percentage (0-100) to
429 // range that OpenGL uses for the specular exponent. The
430 // current equation is just a guess at the mapping that
431 // 3DS actually uses.
432 shininess = (float) pow(2.0, 1.0 + 0.1 * shininess);
433 //shininess = 2.0f + shininess;
434 //clog << materialName << ": shininess=" << shininess << ", color=" << specular.red << "," << specular.green << "," << specular.blue << '\n';
435 if (shininess > 128.0f)
436 shininess = 128.0f;
437 vl->setShininess(shininess);
438
439 if (material->getTextureMap() != "")
440 {
441 ResourceHandle tex = GetTextureManager()->getHandle(TextureInfo(material->getTextureMap(), texturePath, TextureInfo::WrapTexture));
442 vl->setTexture(tex);
443 }
444
445 break;
446 }
447 }
448 }
449 }
450
451 // clean up
452 if (faceNormals != NULL)
453 delete[] faceNormals;
454 if (vertexNormals != NULL)
455 delete[] vertexNormals;
456 if (faceCounts != NULL)
457 delete[] faceCounts;
458 if (vertexFaces != NULL)
459 {
460 for (i = 0; i < nVertices; i++)
461 {
462 if (vertexFaces[i] != NULL)
463 delete[] vertexFaces[i];
464 }
465 delete[] vertexFaces;
466 }
467
468 return vl;
469 }
470
471
472 static Mesh*
ConvertVertexListToMesh(VertexList * vlist,const string &,uint32 material)473 ConvertVertexListToMesh(VertexList* vlist,
474 const string& /*texPath*/, //TODO: remove parameter??
475 uint32 material)
476 {
477 Mesh::VertexAttribute attributes[8];
478 uint32 nAttributes = 0;
479 uint32 offset = 0;
480
481 uint32 parts = vlist->getVertexParts();
482
483 // Position attribute is always present in a vertex list
484 attributes[nAttributes] = Mesh::VertexAttribute(Mesh::Position, Mesh::Float3, 0);
485 nAttributes++;
486 offset += 12;
487
488 if ((parts & VertexList::VertexNormal) != 0)
489 {
490 attributes[nAttributes] = Mesh::VertexAttribute(Mesh::Normal, Mesh::Float3, offset);
491 nAttributes++;
492 offset += 12;
493 }
494
495 if ((parts & VertexList::VertexColor0) != 0)
496 {
497 attributes[nAttributes] = Mesh::VertexAttribute(Mesh::Color0, Mesh::UByte4, offset);
498 nAttributes++;
499 offset += 4;
500 }
501
502 if ((parts & VertexList::TexCoord0) != 0)
503 {
504 attributes[nAttributes] = Mesh::VertexAttribute(Mesh::Texture0, Mesh::Float2, offset);
505 nAttributes++;
506 offset += 8;
507 }
508
509 if ((parts & VertexList::TexCoord1) != 0)
510 {
511 attributes[nAttributes] = Mesh::VertexAttribute(Mesh::Texture1, Mesh::Float2, offset);
512 nAttributes++;
513 offset += 8;
514 }
515
516 uint32 nVertices = vlist->getVertexCount();
517
518 Mesh* mesh = new Mesh();
519 mesh->setVertexDescription(Mesh::VertexDescription(offset, nAttributes, attributes));
520 mesh->setVertices(nVertices, vlist->getVertexData());
521
522 // Vertex lists are not indexed, so the conversion to an indexed format is
523 // trivial (although much space is wasted storing unnecessary indices.)
524 uint32* indices = new uint32[nVertices];
525 for (uint32 i = 0; i < nVertices; i++)
526 indices[i] = i;
527
528 mesh->addGroup(Mesh::TriList, material, nVertices, indices);
529
530 return mesh;
531 }
532
533
534 static Model*
Convert3DSModel(const M3DScene & scene,const string & texPath)535 Convert3DSModel(const M3DScene& scene, const string& texPath)
536 {
537 Model* model = new Model();
538 uint32 materialIndex = 0;
539
540 for (unsigned int i = 0; i < scene.getModelCount(); i++)
541 {
542 M3DModel* model3ds = scene.getModel(i);
543 if (model3ds != NULL)
544 {
545 for (unsigned int j = 0; j < model3ds->getTriMeshCount(); j++)
546 {
547 M3DTriangleMesh* mesh = model3ds->getTriMesh(j);
548 if (mesh != NULL)
549 {
550 // The vertex list is just an intermediate stage in conversion
551 // to a Celestia model structure. Eventually, we should handle
552 // the conversion in a single step.
553 VertexList* vlist = ConvertToVertexList(*mesh, scene, texPath);
554 Mesh* mesh = ConvertVertexListToMesh(vlist, texPath, materialIndex);
555
556 // Convert the vertex list material
557 Mesh::Material* material = new Mesh::Material();
558 material->diffuse = vlist->getDiffuseColor();
559 material->specular = vlist->getSpecularColor();
560 material->specularPower = vlist->getShininess();
561 material->opacity = vlist->getDiffuseColor().alpha();
562 material->maps[Mesh::DiffuseMap] = vlist->getTexture();
563 model->addMaterial(material);
564 materialIndex++;
565
566 model->addMesh(mesh);
567
568 delete vlist;
569 }
570 }
571 }
572 }
573
574 return model;
575 #if 0
576 // Sort the vertex lists to make sure that the transparent ones are
577 // rendered after the opaque ones and material state changes are minimized.
578 sort(vertexLists.begin(), vertexLists.end(), compareVertexLists);
579 #endif
580 }
581