/src/assimp/code/Pbrt/PbrtExporter.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | Open Asset Import Library (assimp) |
3 | | ---------------------------------------------------------------------- |
4 | | |
5 | | Copyright (c) 2006-2024, assimp team |
6 | | |
7 | | All rights reserved. |
8 | | |
9 | | Redistribution and use of this software in source and binary forms, |
10 | | with or without modification, are permitted provided that the |
11 | | following conditions are met: |
12 | | |
13 | | * Redistributions of source code must retain the above |
14 | | copyright notice, this list of conditions and the |
15 | | following disclaimer. |
16 | | |
17 | | * Redistributions in binary form must reproduce the above |
18 | | copyright notice, this list of conditions and the |
19 | | following disclaimer in the documentation and/or other |
20 | | materials provided with the distribution. |
21 | | |
22 | | * Neither the name of the assimp team, nor the names of its |
23 | | contributors may be used to endorse or promote products |
24 | | derived from this software without specific prior |
25 | | written permission of the assimp team. |
26 | | |
27 | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
28 | | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
29 | | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
30 | | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
31 | | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
32 | | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
33 | | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
34 | | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
35 | | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
36 | | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
37 | | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
38 | | |
39 | | ---------------------------------------------------------------------- |
40 | | */ |
41 | | |
42 | | /* TODO: |
43 | | |
44 | | Material improvements: |
45 | | - don't export embedded textures that we're not going to use |
46 | | - diffuse roughness |
47 | | - what is with the uv mapping, uv transform not coming through?? |
48 | | - metal? glass? mirror? detect these better? |
49 | | - eta/k from RGB? |
50 | | - emissive textures: warn at least |
51 | | |
52 | | Other: |
53 | | - use aiProcess_GenUVCoords if needed to handle spherical/planar uv mapping? |
54 | | - don't build up a big string in memory but write directly to a file |
55 | | - aiProcess_Triangulate meshes to get triangles only? |
56 | | - animation (allow specifying a time) |
57 | | |
58 | | */ |
59 | | |
60 | | #ifndef ASSIMP_BUILD_NO_EXPORT |
61 | | #ifndef ASSIMP_BUILD_NO_PBRT_EXPORTER |
62 | | |
63 | | #include "PbrtExporter.h" |
64 | | |
65 | | #include <assimp/version.h> |
66 | | #include <assimp/DefaultIOSystem.h> |
67 | | #include <assimp/IOSystem.hpp> |
68 | | #include <assimp/Exporter.hpp> |
69 | | #include <assimp/DefaultLogger.hpp> |
70 | | #include <assimp/StreamWriter.h> |
71 | | #include <assimp/Exceptional.h> |
72 | | #include <assimp/material.h> |
73 | | #include <assimp/scene.h> |
74 | | #include <assimp/mesh.h> |
75 | | |
76 | | #include <algorithm> |
77 | | #include <cctype> |
78 | | #include <cmath> |
79 | | #include <fstream> |
80 | | #include <functional> |
81 | | #include <iostream> |
82 | | #include <memory> |
83 | | #include <sstream> |
84 | | #include <string> |
85 | | |
86 | | #include "Common/StbCommon.h" |
87 | | |
88 | | using namespace Assimp; |
89 | | |
90 | | namespace Assimp { |
91 | | |
92 | | void ExportScenePbrt(const char *pFile, IOSystem *pIOSystem, const aiScene *pScene, |
93 | 0 | const ExportProperties *) { |
94 | 0 | std::string path = DefaultIOSystem::absolutePath(std::string(pFile)); |
95 | 0 | std::string file = DefaultIOSystem::completeBaseName(std::string(pFile)); |
96 | 0 | path = path + file + ".pbrt"; |
97 | | // initialize the exporter |
98 | 0 | PbrtExporter exporter(pScene, pIOSystem, path, file); |
99 | 0 | } |
100 | | |
101 | | } // end of namespace Assimp |
102 | | |
103 | 0 | static void create_embedded_textures_folder(const aiScene *scene, IOSystem *pIOSystem) { |
104 | 0 | if (scene->mNumTextures > 0) { |
105 | 0 | if (!pIOSystem->Exists("textures")) { |
106 | 0 | if (!pIOSystem->CreateDirectory("textures")) { |
107 | 0 | throw DeadlyExportError("Could not create textures/ directory."); |
108 | 0 | } |
109 | 0 | } |
110 | 0 | } |
111 | 0 | } |
112 | | |
113 | | PbrtExporter::PbrtExporter( |
114 | | const aiScene *pScene, IOSystem *pIOSystem, |
115 | | const std::string &path, const std::string &file) : |
116 | | mScene(pScene), |
117 | | mIOSystem(pIOSystem), |
118 | | mPath(path), |
119 | | mFile(file), |
120 | | mRootTransform( |
121 | | // rotates the (already left-handed) CRS -90 degrees around the x axis in order to |
122 | | // make +Z 'up' and +Y 'towards viewer', as in default in pbrt |
123 | | 1.f, 0.f, 0.f, 0.f, // |
124 | | 0.f, 0.f, -1.f, 0.f, // |
125 | | 0.f, 1.f, 0.f, 0.f, // |
126 | | 0.f, 0.f, 0.f, 1.f // |
127 | 0 | ) { |
128 | |
|
129 | 0 | mRootTransform = aiMatrix4x4( |
130 | 0 | -1.f, 0, 0.f, 0.f, // |
131 | 0 | 0.0f, -1.f, 0.f, 0.f, // |
132 | 0 | 0.f, 0.f, 1.f, 0.f, // |
133 | 0 | 0.f, 0.f, 0.f, 1.f // |
134 | 0 | ) * mRootTransform; |
135 | | |
136 | | // Export embedded textures. |
137 | 0 | create_embedded_textures_folder(mScene, mIOSystem); |
138 | | |
139 | 0 | for (unsigned int i = 0; i < mScene->mNumTextures; ++i) { |
140 | 0 | aiTexture* tex = mScene->mTextures[i]; |
141 | 0 | std::string fn = CleanTextureFilename(tex->mFilename, false); |
142 | 0 | std::cerr << "Writing embedded texture: " << tex->mFilename.C_Str() << " -> " |
143 | 0 | << fn << "\n"; |
144 | |
|
145 | 0 | std::unique_ptr<IOStream> outfile(mIOSystem->Open(fn, "wb")); |
146 | 0 | if (!outfile) { |
147 | 0 | throw DeadlyExportError("could not open output texture file: " + fn); |
148 | 0 | } |
149 | 0 | if (tex->mHeight == 0) { |
150 | | // It's binary data |
151 | 0 | outfile->Write(tex->pcData, tex->mWidth, 1); |
152 | 0 | } else { |
153 | 0 | std::cerr << fn << ": TODO handle uncompressed embedded textures.\n"; |
154 | 0 | } |
155 | 0 | } |
156 | | |
157 | | #if 0 |
158 | | // Debugging: print the full node hierarchy |
159 | | std::function<void(aiNode*, int)> visitNode; |
160 | | visitNode = [&](aiNode* node, int depth) { |
161 | | for (int i = 0; i < depth; ++i) std::cerr << " "; |
162 | | std::cerr << node->mName.C_Str() << "\n"; |
163 | | for (int i = 0; i < node->mNumChildren; ++i) |
164 | | visitNode(node->mChildren[i], depth + 1); |
165 | | }; |
166 | | visitNode(mScene->mRootNode, 0); |
167 | | #endif |
168 | | |
169 | 0 | mOutput.precision(ASSIMP_AI_REAL_TEXT_PRECISION); |
170 | | |
171 | | // Write everything out |
172 | 0 | WriteMetaData(); |
173 | 0 | WriteCameras(); |
174 | 0 | WriteWorldDefinition(); |
175 | | |
176 | | // And write the file to disk... |
177 | 0 | std::unique_ptr<IOStream> outfile(mIOSystem->Open(mPath,"wt")); |
178 | 0 | if (!outfile) { |
179 | 0 | throw DeadlyExportError("could not open output .pbrt file: " + std::string(mFile)); |
180 | 0 | } |
181 | 0 | outfile->Write(mOutput.str().c_str(), mOutput.str().length(), 1); |
182 | 0 | } |
183 | | |
184 | 0 | void PbrtExporter::WriteMetaData() { |
185 | 0 | mOutput << "#############################\n"; |
186 | 0 | mOutput << "# Scene metadata:\n"; |
187 | |
|
188 | 0 | aiMetadata* pMetaData = mScene->mMetaData; |
189 | 0 | for (unsigned int i = 0; i < pMetaData->mNumProperties; i++) { |
190 | 0 | mOutput << "# - "; |
191 | 0 | mOutput << pMetaData->mKeys[i].C_Str() << " :"; |
192 | 0 | switch(pMetaData->mValues[i].mType) { |
193 | 0 | case AI_BOOL : { |
194 | 0 | mOutput << " "; |
195 | 0 | if (*static_cast<bool*>(pMetaData->mValues[i].mData)) |
196 | 0 | mOutput << "TRUE\n"; |
197 | 0 | else |
198 | 0 | mOutput << "FALSE\n"; |
199 | 0 | break; |
200 | 0 | } |
201 | 0 | case AI_INT32 : { |
202 | 0 | mOutput << " " << |
203 | 0 | *static_cast<int32_t*>(pMetaData->mValues[i].mData) << |
204 | 0 | std::endl; |
205 | 0 | break; |
206 | 0 | } |
207 | 0 | case AI_UINT64 : |
208 | 0 | mOutput << " " << |
209 | 0 | *static_cast<uint64_t*>(pMetaData->mValues[i].mData) << |
210 | 0 | std::endl; |
211 | 0 | break; |
212 | 0 | case AI_FLOAT : |
213 | 0 | mOutput << " " << |
214 | 0 | *static_cast<float*>(pMetaData->mValues[i].mData) << |
215 | 0 | std::endl; |
216 | 0 | break; |
217 | 0 | case AI_DOUBLE : |
218 | 0 | mOutput << " " << |
219 | 0 | *static_cast<double*>(pMetaData->mValues[i].mData) << |
220 | 0 | std::endl; |
221 | 0 | break; |
222 | 0 | case AI_AISTRING : { |
223 | 0 | aiString* value = |
224 | 0 | static_cast<aiString*>(pMetaData->mValues[i].mData); |
225 | 0 | std::string svalue = value->C_Str(); |
226 | 0 | std::size_t found = svalue.find_first_of('\n'); |
227 | 0 | mOutput << "\n"; |
228 | 0 | while (found != std::string::npos) { |
229 | 0 | mOutput << "# " << svalue.substr(0, found) << "\n"; |
230 | 0 | svalue = svalue.substr(found + 1); |
231 | 0 | found = svalue.find_first_of('\n'); |
232 | 0 | } |
233 | 0 | mOutput << "# " << svalue << "\n"; |
234 | 0 | break; |
235 | 0 | } |
236 | 0 | case AI_AIVECTOR3D : |
237 | | // TODO |
238 | 0 | mOutput << " Vector3D (unable to print)\n"; |
239 | 0 | break; |
240 | 0 | default: |
241 | | // AI_META_MAX and FORCE_32BIT |
242 | 0 | mOutput << " META_MAX or FORCE_32Bit (unable to print)\n"; |
243 | 0 | break; |
244 | 0 | } |
245 | 0 | } |
246 | 0 | } |
247 | | |
248 | 0 | void PbrtExporter::WriteCameras() { |
249 | 0 | mOutput << "\n"; |
250 | 0 | mOutput << "###############################\n"; |
251 | 0 | mOutput << "# Cameras (" << mScene->mNumCameras << ") total\n\n"; |
252 | |
|
253 | 0 | if (mScene->mNumCameras == 0) { |
254 | 0 | std::cerr << "Warning: No cameras found in scene file.\n"; |
255 | 0 | return; |
256 | 0 | } |
257 | | |
258 | 0 | if (mScene->mNumCameras > 1) { |
259 | 0 | std::cerr << "Multiple cameras found in scene file; defaulting to first one specified.\n"; |
260 | 0 | } |
261 | |
|
262 | 0 | for (unsigned int i = 0; i < mScene->mNumCameras; i++) { |
263 | 0 | WriteCamera(i); |
264 | 0 | } |
265 | 0 | } |
266 | | |
267 | 0 | aiMatrix4x4 PbrtExporter::GetNodeTransform(const aiString &name) const { |
268 | 0 | aiMatrix4x4 m; |
269 | 0 | auto node = mScene->mRootNode->FindNode(name); |
270 | 0 | if (!node) { |
271 | 0 | std::cerr << '"' << name.C_Str() << "\": node not found in scene tree.\n"; |
272 | 0 | throw DeadlyExportError("Could not find node"); |
273 | 0 | } |
274 | 0 | else { |
275 | 0 | while (node) { |
276 | 0 | m = node->mTransformation * m; |
277 | 0 | node = node->mParent; |
278 | 0 | } |
279 | 0 | } |
280 | 0 | return mRootTransform * m; |
281 | 0 | } |
282 | | |
283 | 0 | std::string PbrtExporter::TransformAsString(const aiMatrix4x4 &m) { |
284 | | // Transpose on the way out to match pbrt's expected layout (sanity |
285 | | // check: the translation component should be the last 3 entries |
286 | | // before a '1' as the final entry in the matrix, assuming it's |
287 | | // non-projective.) |
288 | 0 | std::stringstream s; |
289 | 0 | s << m.a1 << " " << m.b1 << " " << m.c1 << " " << m.d1 << " " |
290 | 0 | << m.a2 << " " << m.b2 << " " << m.c2 << " " << m.d2 << " " |
291 | 0 | << m.a3 << " " << m.b3 << " " << m.c3 << " " << m.d3 << " " |
292 | 0 | << m.a4 << " " << m.b4 << " " << m.c4 << " " << m.d4; |
293 | 0 | return s.str(); |
294 | 0 | } |
295 | | |
296 | 0 | void PbrtExporter::WriteCamera(int i) { |
297 | 0 | auto camera = mScene->mCameras[i]; |
298 | 0 | bool cameraActive = i == 0; |
299 | |
|
300 | 0 | mOutput << "# - Camera " << i+1 << ": " |
301 | 0 | << camera->mName.C_Str() << "\n"; |
302 | | |
303 | | // Get camera aspect ratio |
304 | 0 | float aspect = camera->mAspect; |
305 | 0 | if (aspect == 0) { |
306 | 0 | aspect = 4.f/3.f; |
307 | 0 | mOutput << "# - Aspect ratio : 1.33333 (no aspect found, defaulting to 4/3)\n"; |
308 | 0 | } else { |
309 | 0 | mOutput << "# - Aspect ratio : " << aspect << "\n"; |
310 | 0 | } |
311 | | |
312 | | // Get Film xres and yres |
313 | 0 | int xres = 1920; |
314 | 0 | int yres = (int)round(xres/aspect); |
315 | | |
316 | | // Print Film for this camera |
317 | 0 | if (!cameraActive) |
318 | 0 | mOutput << "# "; |
319 | 0 | mOutput << "Film \"rgb\" \"string filename\" \"" << mFile << ".exr\"\n"; |
320 | 0 | if (!cameraActive) |
321 | 0 | mOutput << "# "; |
322 | 0 | mOutput << " \"integer xresolution\" [" << xres << "]\n"; |
323 | 0 | if (!cameraActive) |
324 | 0 | mOutput << "# "; |
325 | 0 | mOutput << " \"integer yresolution\" [" << yres << "]\n"; |
326 | | |
327 | | // Get camera fov |
328 | 0 | float hfov = AI_RAD_TO_DEG(camera->mHorizontalFOV); |
329 | 0 | float fov = (aspect >= 1.0) ? hfov : (hfov / aspect); |
330 | 0 | if (fov < 5) { |
331 | 0 | std::cerr << fov << ": suspiciously low field of view specified by camera. Setting to 45 degrees.\n"; |
332 | 0 | fov = 45; |
333 | 0 | } |
334 | | |
335 | | // Get camera transform |
336 | 0 | aiMatrix4x4 worldFromCamera = GetNodeTransform(camera->mName); |
337 | | |
338 | | // Print Camera LookAt |
339 | 0 | auto position = worldFromCamera * camera->mPosition; |
340 | 0 | auto lookAt = worldFromCamera * (camera->mPosition + camera->mLookAt); |
341 | 0 | aiMatrix3x3 worldFromCamera3(worldFromCamera); |
342 | 0 | auto up = worldFromCamera3 * camera->mUp; |
343 | 0 | up.Normalize(); |
344 | |
|
345 | 0 | if (!cameraActive) |
346 | 0 | mOutput << "# "; |
347 | 0 | mOutput << "Scale 1 1 1\n"; |
348 | 0 | if (!cameraActive) |
349 | 0 | mOutput << "# "; |
350 | 0 | mOutput << "LookAt " |
351 | 0 | << position.x << " " << position.y << " " << position.z << "\n"; |
352 | 0 | if (!cameraActive) |
353 | 0 | mOutput << "# "; |
354 | 0 | mOutput << " " |
355 | 0 | << lookAt.x << " " << lookAt.y << " " << lookAt.z << "\n"; |
356 | 0 | if (!cameraActive) |
357 | 0 | mOutput << "# "; |
358 | 0 | mOutput << " " |
359 | 0 | << up.x << " " << up.y << " " << up.z << "\n"; |
360 | | |
361 | | // Print camera descriptor |
362 | 0 | if (!cameraActive) |
363 | 0 | mOutput << "# "; |
364 | 0 | mOutput << "Camera \"perspective\" \"float fov\" " << "[" << fov << "]\n\n"; |
365 | 0 | } |
366 | | |
367 | 0 | void PbrtExporter::WriteWorldDefinition() { |
368 | | // Figure out which meshes are referenced multiple times; those will be |
369 | | // emitted as object instances and the rest will be emitted directly. |
370 | 0 | std::map<int, int> meshUses; |
371 | 0 | std::function<void(aiNode*)> visitNode; |
372 | 0 | visitNode = [&](aiNode* node) { |
373 | 0 | for (unsigned int i = 0; i < node->mNumMeshes; ++i) |
374 | 0 | ++meshUses[node->mMeshes[i]]; |
375 | 0 | for (unsigned int i = 0; i < node->mNumChildren; ++i) |
376 | 0 | visitNode(node->mChildren[i]); |
377 | 0 | }; |
378 | 0 | visitNode(mScene->mRootNode); |
379 | 0 | int nInstanced = 0, nUnused = 0; |
380 | 0 | for (const auto &u : meshUses) { |
381 | 0 | if (u.second == 0) ++nUnused; |
382 | 0 | else if (u.second > 1) ++nInstanced; |
383 | 0 | } |
384 | 0 | std::cerr << nInstanced << " / " << mScene->mNumMeshes << " meshes instanced.\n"; |
385 | 0 | if (nUnused) |
386 | 0 | std::cerr << nUnused << " meshes defined but not used in scene.\n"; |
387 | |
|
388 | 0 | mOutput << "WorldBegin\n"; |
389 | |
|
390 | 0 | WriteLights(); |
391 | 0 | WriteTextures(); |
392 | 0 | WriteMaterials(); |
393 | | |
394 | | // Object instance definitions |
395 | 0 | mOutput << "# Object instance definitions\n\n"; |
396 | 0 | for (const auto &mu : meshUses) { |
397 | 0 | if (mu.second > 1) { |
398 | 0 | WriteInstanceDefinition(mu.first); |
399 | 0 | } |
400 | 0 | } |
401 | |
|
402 | 0 | mOutput << "# Geometry\n\n"; |
403 | |
|
404 | 0 | WriteGeometricObjects(mScene->mRootNode, mRootTransform, meshUses); |
405 | 0 | } |
406 | | |
407 | 0 | void PbrtExporter::WriteTextures() { |
408 | 0 | mOutput << "###################\n"; |
409 | 0 | mOutput << "# Textures\n\n"; |
410 | |
|
411 | 0 | C_STRUCT aiString path; |
412 | 0 | aiTextureMapping mapping; |
413 | 0 | unsigned int uvIndex; |
414 | 0 | ai_real blend; |
415 | 0 | aiTextureOp op; |
416 | 0 | aiTextureMapMode mapMode[3]; |
417 | | |
418 | | // For every material in the scene, |
419 | 0 | for (unsigned int m = 0 ; m < mScene->mNumMaterials; m++) { |
420 | 0 | auto material = mScene->mMaterials[m]; |
421 | | // Parse through all texture types, |
422 | 0 | for (int tt = 1; tt <= aiTextureType_UNKNOWN; tt++) { |
423 | 0 | int ttCount = material->GetTextureCount(aiTextureType(tt)); |
424 | | // ... and get every texture |
425 | 0 | for (int t = 0; t < ttCount; t++) { |
426 | | // TODO write out texture specifics |
427 | | // TODO UV transforms may be material specific |
428 | | // so those may need to be baked into unique tex name |
429 | 0 | if (material->GetTexture(aiTextureType(tt), t, &path, &mapping, |
430 | 0 | &uvIndex, &blend, &op, mapMode) != AI_SUCCESS) { |
431 | 0 | std::cerr << "Error getting texture! " << m << " " << tt << " " << t << "\n"; |
432 | 0 | continue; |
433 | 0 | } |
434 | | |
435 | 0 | std::string filename = CleanTextureFilename(path); |
436 | |
|
437 | 0 | if (uvIndex != 0) |
438 | 0 | std::cerr << "Warning: texture \"" << filename << "\" uses uv set #" << |
439 | 0 | uvIndex << " but the pbrt converter only exports uv set 0.\n"; |
440 | | #if 0 |
441 | | if (op != aiTextureOp_Multiply) |
442 | | std::cerr << "Warning: unexpected texture op " << (int)op << |
443 | | " encountered for texture \"" << |
444 | | filename << "\". The resulting scene may have issues...\n"; |
445 | | if (blend != 1) |
446 | | std::cerr << "Blend value of " << blend << " found for texture \"" << filename |
447 | | << "\" but not handled in converter.\n"; |
448 | | #endif |
449 | |
|
450 | 0 | std::string mappingString; |
451 | | #if 0 |
452 | | if (mapMode[0] != mapMode[1]) |
453 | | std::cerr << "Different texture boundary mode for u and v for texture \"" << |
454 | | filename << "\". Using u for both.\n"; |
455 | | switch (mapMode[0]) { |
456 | | case aiTextureMapMode_Wrap: |
457 | | // pbrt's default |
458 | | break; |
459 | | case aiTextureMapMode_Clamp: |
460 | | mappingString = "\"string wrap\" \"clamp\""; |
461 | | break; |
462 | | case aiTextureMapMode_Decal: |
463 | | std::cerr << "Decal texture boundary mode not supported by pbrt for texture \"" << |
464 | | filename << "\"\n"; |
465 | | break; |
466 | | case aiTextureMapMode_Mirror: |
467 | | std::cerr << "Mirror texture boundary mode not supported by pbrt for texture \"" << |
468 | | filename << "\"\n"; |
469 | | break; |
470 | | default: |
471 | | std::cerr << "Unexpected map mode " << (int)mapMode[0] << " for texture \"" << |
472 | | filename << "\"\n"; |
473 | | //throw DeadlyExportError("Unexpected aiTextureMapMode"); |
474 | | } |
475 | | #endif |
476 | |
|
477 | | #if 0 |
478 | | aiUVTransform uvTransform; |
479 | | if (material->Get(AI_MATKEY_TEXTURE(tt, t), uvTransform) == AI_SUCCESS) { |
480 | | mOutput << "# UV transform " << uvTransform.mTranslation.x << " " |
481 | | << uvTransform.mTranslation.y << " " << uvTransform.mScaling.x << " " |
482 | | << uvTransform.mScaling.y << " " << uvTransform.mRotation << "\n"; |
483 | | } |
484 | | #endif |
485 | |
|
486 | 0 | std::string texName, texType, texOptions; |
487 | 0 | if (aiTextureType(tt) == aiTextureType_SHININESS || |
488 | 0 | aiTextureType(tt) == aiTextureType_OPACITY || |
489 | 0 | aiTextureType(tt) == aiTextureType_HEIGHT || |
490 | 0 | aiTextureType(tt) == aiTextureType_DISPLACEMENT || |
491 | 0 | aiTextureType(tt) == aiTextureType_METALNESS || |
492 | 0 | aiTextureType(tt) == aiTextureType_DIFFUSE_ROUGHNESS) { |
493 | 0 | texType = "float"; |
494 | 0 | texName = std::string("float:") + RemoveSuffix(filename); |
495 | |
|
496 | 0 | if (aiTextureType(tt) == aiTextureType_SHININESS) { |
497 | 0 | texOptions = " \"bool invert\" true\n"; |
498 | 0 | texName += "_Roughness"; |
499 | 0 | } |
500 | 0 | } else if (aiTextureType(tt) == aiTextureType_DIFFUSE || |
501 | 0 | aiTextureType(tt) == aiTextureType_BASE_COLOR) { |
502 | 0 | texType = "spectrum"; |
503 | 0 | texName = std::string("rgb:") + RemoveSuffix(filename); |
504 | 0 | } |
505 | | |
506 | | // Don't export textures we're not actually going to use... |
507 | 0 | if (texName.empty()) |
508 | 0 | continue; |
509 | | |
510 | 0 | if (mTextureSet.find(texName) == mTextureSet.end()) { |
511 | 0 | mOutput << "Texture \"" << texName << "\" \"" << texType << "\" \"imagemap\"\n" |
512 | 0 | << texOptions |
513 | 0 | << " \"string filename\" \"" << filename << "\" " << mappingString << '\n'; |
514 | 0 | mTextureSet.insert(texName); |
515 | 0 | } |
516 | | |
517 | | // Also emit a float version for use with alpha testing... |
518 | 0 | if ((aiTextureType(tt) == aiTextureType_DIFFUSE || |
519 | 0 | aiTextureType(tt) == aiTextureType_BASE_COLOR) && |
520 | 0 | TextureHasAlphaMask(filename)) { |
521 | 0 | texType = "float"; |
522 | 0 | texName = std::string("alpha:") + filename; |
523 | 0 | if (mTextureSet.find(texName) == mTextureSet.end()) { |
524 | 0 | mOutput << "Texture \"" << texName << "\" \"" << texType << "\" \"imagemap\"\n" |
525 | 0 | << " \"string filename\" \"" << filename << "\" " << mappingString << '\n'; |
526 | 0 | mTextureSet.insert(texName); |
527 | 0 | } |
528 | 0 | } |
529 | 0 | } |
530 | 0 | } |
531 | 0 | } |
532 | 0 | } |
533 | | |
534 | 0 | bool PbrtExporter::TextureHasAlphaMask(const std::string &filename) { |
535 | | // TODO: STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); |
536 | | // quick return if it's 3 |
537 | |
|
538 | 0 | int xSize, ySize, nComponents; |
539 | 0 | unsigned char *data = stbi_load(filename.c_str(), &xSize, &ySize, &nComponents, 0); |
540 | 0 | if (!data) { |
541 | 0 | std::cerr << filename << ": unable to load texture and check for alpha mask in texture. " |
542 | 0 | "Geometry will not be alpha masked with this texture.\n"; |
543 | 0 | return false; |
544 | 0 | } |
545 | | |
546 | 0 | bool hasMask = false; |
547 | 0 | switch (nComponents) { |
548 | 0 | case 1: |
549 | 0 | for (int i = 0; i < xSize * ySize; ++i) |
550 | 0 | if (data[i] != 255) { |
551 | 0 | hasMask = true; |
552 | 0 | break; |
553 | 0 | } |
554 | 0 | break; |
555 | 0 | case 2: |
556 | 0 | for (int y = 0; y < ySize; ++y) |
557 | 0 | for (int x = 0; x < xSize; ++x) |
558 | 0 | if (data[2 * (x + y * xSize) + 1] != 255) { |
559 | 0 | hasMask = true; |
560 | 0 | break; |
561 | 0 | } |
562 | 0 | break; |
563 | 0 | case 3: |
564 | 0 | break; |
565 | 0 | case 4: |
566 | 0 | for (int y = 0; y < ySize; ++y) |
567 | 0 | for (int x = 0; x < xSize; ++x) |
568 | 0 | if (data[4 * (x + y * xSize) + 3] != 255) { |
569 | 0 | hasMask = true; |
570 | 0 | break; |
571 | 0 | } |
572 | 0 | break; |
573 | 0 | default: |
574 | 0 | std::cerr << filename << ": unexpected number of image channels, " << |
575 | 0 | nComponents << ".\n"; |
576 | 0 | } |
577 | | |
578 | 0 | stbi_image_free(data); |
579 | 0 | return hasMask; |
580 | 0 | } |
581 | | |
582 | 0 | void PbrtExporter::WriteMaterials() { |
583 | 0 | mOutput << "\n"; |
584 | 0 | mOutput << "####################\n"; |
585 | 0 | mOutput << "# Materials (" << mScene->mNumMaterials << ") total\n\n"; |
586 | |
|
587 | 0 | for (unsigned int i = 0; i < mScene->mNumMaterials; i++) { |
588 | 0 | WriteMaterial(i); |
589 | 0 | } |
590 | 0 | mOutput << "\n\n"; |
591 | 0 | } |
592 | | |
593 | 0 | void PbrtExporter::WriteMaterial(int m) { |
594 | 0 | aiMaterial* material = mScene->mMaterials[m]; |
595 | | |
596 | | // get material name |
597 | 0 | auto materialName = material->GetName(); |
598 | 0 | mOutput << std::endl << "# - Material " << m+1 << ": " << materialName.C_Str() << "\n"; |
599 | | |
600 | | // Print out number of properties |
601 | 0 | mOutput << "# - Number of Material Properties: " << material->mNumProperties << "\n"; |
602 | | |
603 | | // Print out texture type counts |
604 | 0 | mOutput << "# - Non-Zero Texture Type Counts: "; |
605 | 0 | for (int i = 1; i <= aiTextureType_UNKNOWN; i++) { |
606 | 0 | int count = material->GetTextureCount(aiTextureType(i)); |
607 | 0 | if (count > 0) |
608 | 0 | mOutput << aiTextureTypeToString(aiTextureType(i)) << ": " << count << " "; |
609 | 0 | } |
610 | 0 | mOutput << "\n"; |
611 | |
|
612 | 0 | auto White = [](const aiColor3D &c) { return c.r == 1 && c.g == 1 && c.b == 1; }; |
613 | 0 | auto Black = [](const aiColor3D &c) { return c.r == 0 && c.g == 0 && c.b == 0; }; |
614 | |
|
615 | 0 | aiColor3D diffuse, specular, transparency; |
616 | 0 | bool constantDiffuse = (material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse) == AI_SUCCESS && |
617 | 0 | !White(diffuse)); |
618 | 0 | bool constantSpecular = (material->Get(AI_MATKEY_COLOR_SPECULAR, specular) == AI_SUCCESS && |
619 | 0 | !White(specular)); |
620 | 0 | bool constantTransparency = (material->Get(AI_MATKEY_COLOR_TRANSPARENT, transparency) == AI_SUCCESS && |
621 | 0 | !Black(transparency)); |
622 | |
|
623 | 0 | float opacity, shininess, shininessStrength, eta; |
624 | 0 | bool constantOpacity = (material->Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS && |
625 | 0 | opacity != 0); |
626 | 0 | bool constantShininess = material->Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS; |
627 | 0 | bool constantShininessStrength = material->Get(AI_MATKEY_SHININESS_STRENGTH, shininessStrength) == AI_SUCCESS; |
628 | 0 | bool constantEta = (material->Get(AI_MATKEY_REFRACTI, eta) == AI_SUCCESS && |
629 | 0 | eta != 1); |
630 | |
|
631 | 0 | mOutput << "# - Constants: diffuse " << constantDiffuse << " specular " << constantSpecular << |
632 | 0 | " transparency " << constantTransparency << " opacity " << constantOpacity << |
633 | 0 | " shininess " << constantShininess << " shininess strength " << constantShininessStrength << |
634 | 0 | " eta " << constantEta << "\n"; |
635 | |
|
636 | 0 | aiString roughnessMap; |
637 | 0 | if (material->Get(AI_MATKEY_TEXTURE_SHININESS(0), roughnessMap) == AI_SUCCESS) { |
638 | 0 | std::string roughnessTexture = std::string("float:") + |
639 | 0 | RemoveSuffix(CleanTextureFilename(roughnessMap)) + "_Roughness"; |
640 | 0 | mOutput << "MakeNamedMaterial \"" << materialName.C_Str() << "\"" |
641 | 0 | << " \"string type\" \"coateddiffuse\"\n" |
642 | 0 | << " \"texture roughness\" \"" << roughnessTexture << "\"\n"; |
643 | 0 | } else if (constantShininess) { |
644 | | // Assume plastic for now at least |
645 | 0 | float roughness = std::max(0.f, 1.f - shininess); |
646 | 0 | mOutput << "MakeNamedMaterial \"" << materialName.C_Str() << "\"" |
647 | 0 | << " \"string type\" \"coateddiffuse\"\n" |
648 | 0 | << " \"float roughness\" " << roughness << "\n"; |
649 | 0 | } else |
650 | | // Diffuse |
651 | 0 | mOutput << "MakeNamedMaterial \"" << materialName.C_Str() << "\"" |
652 | 0 | << " \"string type\" \"diffuse\"\n"; |
653 | |
|
654 | 0 | aiString diffuseTexture; |
655 | 0 | if (material->Get(AI_MATKEY_TEXTURE_DIFFUSE(0), diffuseTexture) == AI_SUCCESS) |
656 | 0 | mOutput << " \"texture reflectance\" \"rgb:" << RemoveSuffix(CleanTextureFilename(diffuseTexture)) << "\"\n"; |
657 | 0 | else |
658 | 0 | mOutput << " \"rgb reflectance\" [ " << diffuse.r << " " << diffuse.g << |
659 | 0 | " " << diffuse.b << " ]\n"; |
660 | |
|
661 | 0 | aiString displacementTexture, normalMap; |
662 | 0 | if (material->Get(AI_MATKEY_TEXTURE_NORMALS(0), displacementTexture) == AI_SUCCESS) |
663 | 0 | mOutput << " \"string normalmap\" \"" << CleanTextureFilename(displacementTexture) << "\"\n"; |
664 | 0 | else if (material->Get(AI_MATKEY_TEXTURE_HEIGHT(0), displacementTexture) == AI_SUCCESS) |
665 | 0 | mOutput << " \"texture displacement\" \"float:" << |
666 | 0 | RemoveSuffix(CleanTextureFilename(displacementTexture)) << "\"\n"; |
667 | 0 | else if (material->Get(AI_MATKEY_TEXTURE_DISPLACEMENT(0), displacementTexture) == AI_SUCCESS) |
668 | 0 | mOutput << " \"texture displacement\" \"float:" << |
669 | 0 | RemoveSuffix(CleanTextureFilename(displacementTexture)) << "\"\n"; |
670 | 0 | } |
671 | | |
672 | 0 | std::string PbrtExporter::CleanTextureFilename(const aiString &f, bool rewriteExtension) const { |
673 | 0 | std::string fn = f.C_Str(); |
674 | | // Remove directory name |
675 | 0 | size_t offset = fn.find_last_of("/\\"); |
676 | 0 | if (offset != std::string::npos) { |
677 | 0 | fn.erase(0, offset + 1); |
678 | 0 | } |
679 | | |
680 | | // Expect all textures in textures |
681 | 0 | fn = std::string("textures") + mIOSystem->getOsSeparator() + fn; |
682 | | |
683 | | // Rewrite extension for unsupported file formats. |
684 | 0 | if (rewriteExtension) { |
685 | 0 | offset = fn.rfind('.'); |
686 | 0 | if (offset != std::string::npos) { |
687 | 0 | std::string extension = fn; |
688 | 0 | extension.erase(0, offset + 1); |
689 | 0 | std::transform(extension.begin(), extension.end(), extension.begin(), |
690 | 0 | [](unsigned char c) { return (char)std::tolower(c); }); |
691 | |
|
692 | 0 | if (extension != "tga" && extension != "exr" && extension != "png" && |
693 | 0 | extension != "pfm" && extension != "hdr") { |
694 | 0 | std::string orig = fn; |
695 | 0 | fn.erase(offset + 1); |
696 | 0 | fn += "png"; |
697 | | |
698 | | // Does it already exist? Warn if not. |
699 | 0 | std::ifstream filestream(fn); |
700 | 0 | if (!filestream.good()) |
701 | 0 | std::cerr << orig << ": must convert this texture to PNG.\n"; |
702 | 0 | } |
703 | 0 | } |
704 | 0 | } |
705 | |
|
706 | 0 | return fn; |
707 | 0 | } |
708 | | |
709 | 0 | std::string PbrtExporter::RemoveSuffix(std::string filename) { |
710 | 0 | size_t offset = filename.rfind('.'); |
711 | 0 | if (offset != std::string::npos) |
712 | 0 | filename.erase(offset); |
713 | 0 | return filename; |
714 | 0 | } |
715 | | |
716 | 0 | void PbrtExporter::WriteLights() { |
717 | 0 | mOutput << "\n"; |
718 | 0 | mOutput << "#################\n"; |
719 | 0 | mOutput << "# Lights\n\n"; |
720 | 0 | if (mScene->mNumLights == 0) { |
721 | | // Skip the default light if no cameras and this is flat up geometry |
722 | 0 | if (mScene->mNumCameras > 0) { |
723 | 0 | std::cerr << "No lights specified. Using default infinite light.\n"; |
724 | |
|
725 | 0 | mOutput << "AttributeBegin\n"; |
726 | 0 | mOutput << " # default light\n"; |
727 | 0 | mOutput << " LightSource \"infinite\" \"blackbody L\" [6000 1]\n"; |
728 | |
|
729 | 0 | mOutput << "AttributeEnd\n\n"; |
730 | 0 | } |
731 | 0 | } else { |
732 | 0 | for (unsigned int i = 0; i < mScene->mNumLights; ++i) { |
733 | 0 | const aiLight *light = mScene->mLights[i]; |
734 | |
|
735 | 0 | mOutput << "# Light " << light->mName.C_Str() << "\n"; |
736 | 0 | mOutput << "AttributeBegin\n"; |
737 | |
|
738 | 0 | aiMatrix4x4 worldFromLight = GetNodeTransform(light->mName); |
739 | 0 | mOutput << " Transform [ " << TransformAsString(worldFromLight) << " ]\n"; |
740 | |
|
741 | 0 | aiColor3D color = light->mColorDiffuse + light->mColorSpecular; |
742 | 0 | if (light->mAttenuationConstant != 0) |
743 | 0 | color = color * (ai_real)(1. / light->mAttenuationConstant); |
744 | |
|
745 | 0 | switch (light->mType) { |
746 | 0 | case aiLightSource_DIRECTIONAL: { |
747 | 0 | mOutput << " LightSource \"distant\"\n"; |
748 | 0 | mOutput << " \"point3 from\" [ " << light->mPosition.x << " " << |
749 | 0 | light->mPosition.y << " " << light->mPosition.z << " ]\n"; |
750 | 0 | aiVector3D to = light->mPosition + light->mDirection; |
751 | 0 | mOutput << " \"point3 to\" [ " << to.x << " " << to.y << " " << to.z << " ]\n"; |
752 | 0 | mOutput << " \"rgb L\" [ " << color.r << " " << color.g << " " << color.b << " ]\n"; |
753 | 0 | break; |
754 | 0 | } case aiLightSource_POINT: |
755 | 0 | mOutput << " LightSource \"distant\"\n"; |
756 | 0 | mOutput << " \"point3 from\" [ " << light->mPosition.x << " " << |
757 | 0 | light->mPosition.y << " " << light->mPosition.z << " ]\n"; |
758 | 0 | mOutput << " \"rgb L\" [ " << color.r << " " << color.g << " " << color.b << " ]\n"; |
759 | 0 | break; |
760 | 0 | case aiLightSource_SPOT: { |
761 | 0 | mOutput << " LightSource \"spot\"\n"; |
762 | 0 | mOutput << " \"point3 from\" [ " << light->mPosition.x << " " << |
763 | 0 | light->mPosition.y << " " << light->mPosition.z << " ]\n"; |
764 | 0 | aiVector3D to = light->mPosition + light->mDirection; |
765 | 0 | mOutput << " \"point3 to\" [ " << to.x << " " << to.y << " " << to.z << " ]\n"; |
766 | 0 | mOutput << " \"rgb L\" [ " << color.r << " " << color.g << " " << color.b << " ]\n"; |
767 | 0 | mOutput << " \"float coneangle\" [ " << AI_RAD_TO_DEG(light->mAngleOuterCone) << " ]\n"; |
768 | 0 | mOutput << " \"float conedeltaangle\" [ " << AI_RAD_TO_DEG(light->mAngleOuterCone - |
769 | 0 | light->mAngleInnerCone) << " ]\n"; |
770 | 0 | break; |
771 | 0 | } case aiLightSource_AMBIENT: |
772 | 0 | mOutput << "# ignored ambient light source\n"; |
773 | 0 | break; |
774 | 0 | case aiLightSource_AREA: { |
775 | 0 | aiVector3D left = light->mDirection ^ light->mUp; |
776 | | // rectangle. center at position, direction is normal vector |
777 | 0 | ai_real dLeft = light->mSize.x / 2, dUp = light->mSize.y / 2; |
778 | 0 | aiVector3D vertices[4] = { |
779 | 0 | light->mPosition - dLeft * left - dUp * light->mUp, |
780 | 0 | light->mPosition + dLeft * left - dUp * light->mUp, |
781 | 0 | light->mPosition - dLeft * left + dUp * light->mUp, |
782 | 0 | light->mPosition + dLeft * left + dUp * light->mUp }; |
783 | 0 | mOutput << " AreaLightSource \"diffuse\"\n"; |
784 | 0 | mOutput << " \"rgb L\" [ " << color.r << " " << color.g << " " << color.b << " ]\n"; |
785 | 0 | mOutput << " Shape \"bilinearmesh\"\n"; |
786 | 0 | mOutput << " \"point3 p\" [ "; |
787 | 0 | for (int j = 0; j < 4; ++j) |
788 | 0 | mOutput << vertices[j].x << " " << vertices[j].y << " " << vertices[j].z; |
789 | 0 | mOutput << " ]\n"; |
790 | 0 | mOutput << " \"integer indices\" [ 0 1 2 3 ]\n"; |
791 | 0 | break; |
792 | 0 | } default: |
793 | 0 | mOutput << "# ignored undefined light source type\n"; |
794 | 0 | break; |
795 | 0 | } |
796 | 0 | mOutput << "AttributeEnd\n\n"; |
797 | 0 | } |
798 | 0 | } |
799 | 0 | } |
800 | | |
801 | 0 | void PbrtExporter::WriteMesh(aiMesh* mesh) { |
802 | 0 | mOutput << "# - Mesh: "; |
803 | 0 | if (mesh->mName == aiString("")) |
804 | 0 | mOutput << "<No Name>\n"; |
805 | 0 | else |
806 | 0 | mOutput << mesh->mName.C_Str() << "\n"; |
807 | |
|
808 | 0 | mOutput << "AttributeBegin\n"; |
809 | 0 | aiMaterial* material = mScene->mMaterials[mesh->mMaterialIndex]; |
810 | 0 | mOutput << " NamedMaterial \"" << material->GetName().C_Str() << "\"\n"; |
811 | | |
812 | | // Handle area lights |
813 | 0 | aiColor3D emission; |
814 | 0 | if (material->Get(AI_MATKEY_COLOR_EMISSIVE, emission) == AI_SUCCESS && |
815 | 0 | (emission.r > 0 || emission.g > 0 || emission.b > 0)) |
816 | 0 | mOutput << " AreaLightSource \"diffuse\" \"rgb L\" [ " << emission.r << |
817 | 0 | " " << emission.g << " " << emission.b << " ]\n"; |
818 | | |
819 | | // Check if any types other than tri |
820 | 0 | if ( (mesh->mPrimitiveTypes & aiPrimitiveType_POINT) |
821 | 0 | || (mesh->mPrimitiveTypes & aiPrimitiveType_LINE) |
822 | 0 | || (mesh->mPrimitiveTypes & aiPrimitiveType_POLYGON)) { |
823 | 0 | std::cerr << "Error: ignoring point / line / polygon mesh " << mesh->mName.C_Str() << ".\n"; |
824 | 0 | return; |
825 | 0 | } |
826 | | |
827 | | // Alpha mask |
828 | 0 | std::string alpha; |
829 | 0 | aiString opacityTexture; |
830 | 0 | if (material->Get(AI_MATKEY_TEXTURE_OPACITY(0), opacityTexture) == AI_SUCCESS || |
831 | 0 | material->Get(AI_MATKEY_TEXTURE_DIFFUSE(0), opacityTexture) == AI_SUCCESS) { |
832 | | // material->Get(AI_MATKEY_TEXTURE_BASE_COLOR(0), opacityTexture) == AI_SUCCESS) |
833 | 0 | std::string texName = std::string("alpha:") + CleanTextureFilename(opacityTexture); |
834 | 0 | if (mTextureSet.find(texName) != mTextureSet.end()) |
835 | 0 | alpha = std::string(" \"texture alpha\" \"") + texName + "\"\n"; |
836 | 0 | } else { |
837 | 0 | float opacity = 1; |
838 | 0 | if (material->Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS && opacity < 1) |
839 | 0 | alpha = std::string(" \"float alpha\" [ ") + std::to_string(opacity) + " ]\n"; |
840 | 0 | } |
841 | | |
842 | | // Output the shape specification |
843 | 0 | mOutput << "Shape \"trianglemesh\"\n" << |
844 | 0 | alpha << |
845 | 0 | " \"integer indices\" ["; |
846 | | |
847 | | // Start with faces (which hold indices) |
848 | 0 | for (unsigned int i = 0; i < mesh->mNumFaces; i++) { |
849 | 0 | auto face = mesh->mFaces[i]; |
850 | 0 | if (face.mNumIndices != 3) throw DeadlyExportError("oh no not a tri!"); |
851 | | |
852 | 0 | for (unsigned int j = 0; j < face.mNumIndices; j++) { |
853 | 0 | mOutput << face.mIndices[j] << " "; |
854 | 0 | } |
855 | 0 | if ((i % 7) == 6) mOutput << "\n "; |
856 | 0 | } |
857 | 0 | mOutput << "]\n"; |
858 | | |
859 | | // Then go to vertices |
860 | 0 | mOutput << " \"point3 P\" ["; |
861 | 0 | for (unsigned int i = 0; i < mesh->mNumVertices; i++) { |
862 | 0 | auto vector = mesh->mVertices[i]; |
863 | 0 | mOutput << vector.x << " " << vector.y << " " << vector.z << " "; |
864 | 0 | if ((i % 4) == 3) mOutput << "\n "; |
865 | 0 | } |
866 | 0 | mOutput << "]\n"; |
867 | | |
868 | | // Normals (if present) |
869 | 0 | if (mesh->mNormals) { |
870 | 0 | mOutput << " \"normal N\" ["; |
871 | 0 | for (unsigned int i = 0; i < mesh->mNumVertices; i++) { |
872 | 0 | auto normal = mesh->mNormals[i]; |
873 | 0 | mOutput << normal.x << " " << normal.y << " " << normal.z << " "; |
874 | 0 | if ((i % 4) == 3) mOutput << "\n "; |
875 | 0 | } |
876 | 0 | mOutput << "]\n"; |
877 | 0 | } |
878 | | |
879 | | // Tangents (if present) |
880 | 0 | if (mesh->mTangents) { |
881 | 0 | mOutput << " \"vector3 S\" ["; |
882 | 0 | for (unsigned int i = 0; i < mesh->mNumVertices; i++) { |
883 | 0 | auto tangent = mesh->mTangents[i]; |
884 | 0 | mOutput << tangent.x << " " << tangent.y << " " << tangent.z << " "; |
885 | 0 | if ((i % 4) == 3) mOutput << "\n "; |
886 | 0 | } |
887 | 0 | mOutput << "]\n"; |
888 | 0 | } |
889 | | |
890 | | // Texture Coords (if present) |
891 | | // Find the first set of 2D texture coordinates.. |
892 | 0 | for (int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { |
893 | 0 | if (mesh->mNumUVComponents[i] == 2) { |
894 | | // assert(mesh->mTextureCoords[i] != nullptr); |
895 | 0 | aiVector3D* uv = mesh->mTextureCoords[i]; |
896 | 0 | mOutput << " \"point2 uv\" ["; |
897 | 0 | for (unsigned int j = 0; j < mesh->mNumVertices; ++j) { |
898 | 0 | mOutput << uv[j].x << " " << uv[j].y << " "; |
899 | 0 | if ((j % 6) == 5) mOutput << "\n "; |
900 | 0 | } |
901 | 0 | mOutput << "]\n"; |
902 | 0 | break; |
903 | 0 | } |
904 | 0 | } |
905 | | // TODO: issue warning if there are additional UV sets? |
906 | |
|
907 | 0 | mOutput << "AttributeEnd\n"; |
908 | 0 | } |
909 | | |
910 | 0 | void PbrtExporter::WriteInstanceDefinition(int i) { |
911 | 0 | aiMesh* mesh = mScene->mMeshes[i]; |
912 | |
|
913 | 0 | mOutput << "ObjectBegin \""; |
914 | 0 | if (mesh->mName == aiString("")) |
915 | 0 | mOutput << "mesh_" << i+1 << "\"\n"; |
916 | 0 | else |
917 | 0 | mOutput << mesh->mName.C_Str() << "_" << i+1 << "\"\n"; |
918 | |
|
919 | 0 | WriteMesh(mesh); |
920 | |
|
921 | 0 | mOutput << "ObjectEnd\n"; |
922 | 0 | } |
923 | | |
924 | | void PbrtExporter::WriteGeometricObjects(aiNode* node, aiMatrix4x4 worldFromObject, |
925 | 0 | std::map<int, int> &meshUses) { |
926 | | // Sometimes interior nodes have degenerate matrices?? |
927 | 0 | if (node->mTransformation.Determinant() != 0) |
928 | 0 | worldFromObject = worldFromObject * node->mTransformation; |
929 | |
|
930 | 0 | if (node->mNumMeshes > 0) { |
931 | 0 | mOutput << "AttributeBegin\n"; |
932 | |
|
933 | 0 | mOutput << " Transform [ " << TransformAsString(worldFromObject) << "]\n"; |
934 | |
|
935 | 0 | for (unsigned int i = 0; i < node->mNumMeshes; i++) { |
936 | 0 | aiMesh* mesh = mScene->mMeshes[node->mMeshes[i]]; |
937 | 0 | if (meshUses[node->mMeshes[i]] == 1) { |
938 | | // If it's only used once in the scene, emit it directly as |
939 | | // a triangle mesh. |
940 | 0 | mOutput << " # " << mesh->mName.C_Str(); |
941 | 0 | WriteMesh(mesh); |
942 | 0 | } else { |
943 | | // If it's used multiple times, there will be an object |
944 | | // instance for it, so emit a reference to that. |
945 | 0 | mOutput << " ObjectInstance \""; |
946 | 0 | if (mesh->mName == aiString("")) |
947 | 0 | mOutput << "mesh_" << node->mMeshes[i] + 1 << "\"\n"; |
948 | 0 | else |
949 | 0 | mOutput << mesh->mName.C_Str() << "_" << node->mMeshes[i] + 1 << "\"\n"; |
950 | 0 | } |
951 | 0 | } |
952 | 0 | mOutput << "AttributeEnd\n\n"; |
953 | 0 | } |
954 | | |
955 | | // Recurse through children |
956 | 0 | for (unsigned int i = 0; i < node->mNumChildren; i++) { |
957 | 0 | WriteGeometricObjects(node->mChildren[i], worldFromObject, meshUses); |
958 | 0 | } |
959 | 0 | } |
960 | | |
961 | | #endif // ASSIMP_BUILD_NO_PBRT_EXPORTER |
962 | | #endif // ASSIMP_BUILD_NO_EXPORT |