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