diff options
Diffstat (limited to 'libs/assimp/code/Pbrt')
-rw-r--r-- | libs/assimp/code/Pbrt/PbrtExporter.cpp | 947 | ||||
-rw-r--r-- | libs/assimp/code/Pbrt/PbrtExporter.h | 135 |
2 files changed, 1082 insertions, 0 deletions
diff --git a/libs/assimp/code/Pbrt/PbrtExporter.cpp b/libs/assimp/code/Pbrt/PbrtExporter.cpp new file mode 100644 index 0000000..25061f5 --- /dev/null +++ b/libs/assimp/code/Pbrt/PbrtExporter.cpp @@ -0,0 +1,947 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/* TODO: + +Material improvements: +- don't export embedded textures that we're not going to use +- diffuse roughness +- what is with the uv mapping, uv transform not coming through?? +- metal? glass? mirror? detect these better? + - eta/k from RGB? +- emissive textures: warn at least + +Other: +- use aiProcess_GenUVCoords if needed to handle spherical/planar uv mapping? +- don't build up a big string in memory but write directly to a file +- aiProcess_Triangulate meshes to get triangles only? +- animation (allow specifying a time) + + */ + +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_PBRT_EXPORTER + +#include "PbrtExporter.h" + +#include <assimp/version.h> +#include <assimp/DefaultIOSystem.h> +#include <assimp/IOSystem.hpp> +#include <assimp/Exporter.hpp> +#include <assimp/DefaultLogger.hpp> +#include <assimp/StreamWriter.h> +#include <assimp/Exceptional.h> +#include <assimp/material.h> +#include <assimp/scene.h> +#include <assimp/mesh.h> + +#include <algorithm> +#include <cctype> +#include <cmath> +#include <fstream> +#include <functional> +#include <iostream> +#include <memory> +#include <sstream> +#include <string> + +#include "stb/stb_image.h" + +using namespace Assimp; + +namespace Assimp { + +void ExportScenePbrt ( + const char* pFile, + IOSystem* pIOSystem, + const aiScene* pScene, + const ExportProperties* /*pProperties*/ +){ + std::string path = DefaultIOSystem::absolutePath(std::string(pFile)); + std::string file = DefaultIOSystem::completeBaseName(std::string(pFile)); + + // initialize the exporter + PbrtExporter exporter(pScene, pIOSystem, path, file); +} + +} // end of namespace Assimp + +// Constructor +PbrtExporter::PbrtExporter( + const aiScene *pScene, IOSystem *pIOSystem, + const std::string &path, const std::string &file) : + mScene(pScene), + mIOSystem(pIOSystem), + mPath(path), + mFile(file) { + // Export embedded textures. + if (mScene->mNumTextures > 0) + if (!mIOSystem->CreateDirectory("textures")) + throw DeadlyExportError("Could not create textures/ directory."); + for (unsigned int i = 0; i < mScene->mNumTextures; ++i) { + aiTexture* tex = mScene->mTextures[i]; + std::string fn = CleanTextureFilename(tex->mFilename, false); + std::cerr << "Writing embedded texture: " << tex->mFilename.C_Str() << " -> " + << fn << "\n"; + + std::unique_ptr<IOStream> outfile(mIOSystem->Open(fn, "wb")); + if (!outfile) { + throw DeadlyExportError("could not open output texture file: " + fn); + } + if (tex->mHeight == 0) { + // It's binary data + outfile->Write(tex->pcData, tex->mWidth, 1); + } else { + std::cerr << fn << ": TODO handle uncompressed embedded textures.\n"; + } + } + +#if 0 + // Debugging: print the full node hierarchy + std::function<void(aiNode*, int)> visitNode; + visitNode = [&](aiNode* node, int depth) { + for (int i = 0; i < depth; ++i) std::cerr << " "; + std::cerr << node->mName.C_Str() << "\n"; + for (int i = 0; i < node->mNumChildren; ++i) + visitNode(node->mChildren[i], depth + 1); + }; + visitNode(mScene->mRootNode, 0); +#endif + + mOutput.precision(ASSIMP_AI_REAL_TEXT_PRECISION); + + // Write everything out + WriteMetaData(); + WriteCameras(); + WriteWorldDefinition(); + + // And write the file to disk... + std::unique_ptr<IOStream> outfile(mIOSystem->Open(mPath,"wt")); + if (!outfile) { + throw DeadlyExportError("could not open output .pbrt file: " + std::string(mFile)); + } + outfile->Write(mOutput.str().c_str(), mOutput.str().length(), 1); +} + +// Destructor +PbrtExporter::~PbrtExporter() { + // Empty +} + +void PbrtExporter::WriteMetaData() { + mOutput << "#############################\n"; + mOutput << "# Scene metadata:\n"; + + aiMetadata* pMetaData = mScene->mMetaData; + for (unsigned int i = 0; i < pMetaData->mNumProperties; i++) { + mOutput << "# - "; + mOutput << pMetaData->mKeys[i].C_Str() << " :"; + switch(pMetaData->mValues[i].mType) { + case AI_BOOL : { + mOutput << " "; + if (*static_cast<bool*>(pMetaData->mValues[i].mData)) + mOutput << "TRUE\n"; + else + mOutput << "FALSE\n"; + break; + } + case AI_INT32 : { + mOutput << " " << + *static_cast<int32_t*>(pMetaData->mValues[i].mData) << + std::endl; + break; + } + case AI_UINT64 : + mOutput << " " << + *static_cast<uint64_t*>(pMetaData->mValues[i].mData) << + std::endl; + break; + case AI_FLOAT : + mOutput << " " << + *static_cast<float*>(pMetaData->mValues[i].mData) << + std::endl; + break; + case AI_DOUBLE : + mOutput << " " << + *static_cast<double*>(pMetaData->mValues[i].mData) << + std::endl; + break; + case AI_AISTRING : { + aiString* value = + static_cast<aiString*>(pMetaData->mValues[i].mData); + std::string svalue = value->C_Str(); + std::size_t found = svalue.find_first_of('\n'); + mOutput << "\n"; + while (found != std::string::npos) { + mOutput << "# " << svalue.substr(0, found) << "\n"; + svalue = svalue.substr(found + 1); + found = svalue.find_first_of('\n'); + } + mOutput << "# " << svalue << "\n"; + break; + } + case AI_AIVECTOR3D : + // TODO + mOutput << " Vector3D (unable to print)\n"; + break; + default: + // AI_META_MAX and FORCE_32BIT + mOutput << " META_MAX or FORCE_32Bit (unable to print)\n"; + break; + } + } +} + +void PbrtExporter::WriteCameras() { + mOutput << "\n"; + mOutput << "###############################\n"; + mOutput << "# Cameras (" << mScene->mNumCameras << ") total\n\n"; + + if (mScene->mNumCameras == 0) { + std::cerr << "Warning: No cameras found in scene file.\n"; + return; + } + + if (mScene->mNumCameras > 1) { + std::cerr << "Multiple cameras found in scene file; defaulting to first one specified.\n"; + } + + for (unsigned int i = 0; i < mScene->mNumCameras; i++) { + WriteCamera(i); + } +} + +aiMatrix4x4 PbrtExporter::GetNodeTransform(const aiString &name) const { + aiMatrix4x4 m; + auto node = mScene->mRootNode->FindNode(name); + if (!node) { + std::cerr << '"' << name.C_Str() << "\": node not found in scene tree.\n"; + throw DeadlyExportError("Could not find node"); + } + else { + while (node) { + m = node->mTransformation * m; + node = node->mParent; + } + } + return m; +} + +std::string PbrtExporter::TransformAsString(const aiMatrix4x4 &m) { + // Transpose on the way out to match pbrt's expected layout (sanity + // check: the translation component should be the last 3 entries + // before a '1' as the final entry in the matrix, assuming it's + // non-projective.) + std::stringstream s; + s << m.a1 << " " << m.b1 << " " << m.c1 << " " << m.d1 << " " + << m.a2 << " " << m.b2 << " " << m.c2 << " " << m.d2 << " " + << m.a3 << " " << m.b3 << " " << m.c3 << " " << m.d3 << " " + << m.a4 << " " << m.b4 << " " << m.c4 << " " << m.d4; + return s.str(); +} + +void PbrtExporter::WriteCamera(int i) { + auto camera = mScene->mCameras[i]; + bool cameraActive = i == 0; + + mOutput << "# - Camera " << i+1 << ": " + << camera->mName.C_Str() << "\n"; + + // Get camera aspect ratio + float aspect = camera->mAspect; + if (aspect == 0) { + aspect = 4.f/3.f; + mOutput << "# - Aspect ratio : 1.33333 (no aspect found, defaulting to 4/3)\n"; + } else { + mOutput << "# - Aspect ratio : " << aspect << "\n"; + } + + // Get Film xres and yres + int xres = 1920; + int yres = (int)round(xres/aspect); + + // Print Film for this camera + if (!cameraActive) + mOutput << "# "; + mOutput << "Film \"rgb\" \"string filename\" \"" << mFile << ".exr\"\n"; + if (!cameraActive) + mOutput << "# "; + mOutput << " \"integer xresolution\" [" << xres << "]\n"; + if (!cameraActive) + mOutput << "# "; + mOutput << " \"integer yresolution\" [" << yres << "]\n"; + + // Get camera fov + float hfov = AI_RAD_TO_DEG(camera->mHorizontalFOV); + float fov = (aspect >= 1.0) ? hfov : (hfov * aspect); + if (fov < 5) { + std::cerr << fov << ": suspiciously low field of view specified by camera. Setting to 45 degrees.\n"; + fov = 45; + } + + // Get camera transform + aiMatrix4x4 worldFromCamera = GetNodeTransform(camera->mName); + + // Print Camera LookAt + auto position = worldFromCamera * camera->mPosition; + auto lookAt = worldFromCamera * (camera->mPosition + camera->mLookAt); + aiMatrix3x3 worldFromCamera3(worldFromCamera); + auto up = worldFromCamera3 * camera->mUp; + up.Normalize(); + + if (!cameraActive) + mOutput << "# "; + mOutput << "Scale -1 1 1\n"; // right handed -> left handed + if (!cameraActive) + mOutput << "# "; + mOutput << "LookAt " + << position.x << " " << position.y << " " << position.z << "\n"; + if (!cameraActive) + mOutput << "# "; + mOutput << " " + << lookAt.x << " " << lookAt.y << " " << lookAt.z << "\n"; + if (!cameraActive) + mOutput << "# "; + mOutput << " " + << up.x << " " << up.y << " " << up.z << "\n"; + + // Print camera descriptor + if (!cameraActive) + mOutput << "# "; + mOutput << "Camera \"perspective\" \"float fov\" " << "[" << fov << "]\n\n"; +} + +void PbrtExporter::WriteWorldDefinition() { + // Figure out which meshes are referenced multiple times; those will be + // emitted as object instances and the rest will be emitted directly. + std::map<int, int> meshUses; + std::function<void(aiNode*)> visitNode; + visitNode = [&](aiNode* node) { + for (unsigned int i = 0; i < node->mNumMeshes; ++i) + ++meshUses[node->mMeshes[i]]; + for (unsigned int i = 0; i < node->mNumChildren; ++i) + visitNode(node->mChildren[i]); + }; + visitNode(mScene->mRootNode); + int nInstanced = 0, nUnused = 0; + for (const auto &u : meshUses) { + if (u.second == 0) ++nUnused; + else if (u.second > 1) ++nInstanced; + } + std::cerr << nInstanced << " / " << mScene->mNumMeshes << " meshes instanced.\n"; + if (nUnused) + std::cerr << nUnused << " meshes defined but not used in scene.\n"; + + mOutput << "WorldBegin\n"; + + WriteLights(); + WriteTextures(); + WriteMaterials(); + + // Object instance definitions + mOutput << "# Object instance definitions\n\n"; + for (const auto &mu : meshUses) { + if (mu.second > 1) { + WriteInstanceDefinition(mu.first); + } + } + + mOutput << "# Geometry\n\n"; + aiMatrix4x4 worldFromObject; + WriteGeometricObjects(mScene->mRootNode, worldFromObject, meshUses); +} + +void PbrtExporter::WriteTextures() { + mOutput << "###################\n"; + mOutput << "# Textures\n\n"; + + C_STRUCT aiString path; + aiTextureMapping mapping; + unsigned int uvIndex; + ai_real blend; + aiTextureOp op; + aiTextureMapMode mapMode[3]; + + // For every material in the scene, + for (unsigned int m = 0 ; m < mScene->mNumMaterials; m++) { + auto material = mScene->mMaterials[m]; + // Parse through all texture types, + for (int tt = 1; tt <= aiTextureType_UNKNOWN; tt++) { + int ttCount = material->GetTextureCount(aiTextureType(tt)); + // ... and get every texture + for (int t = 0; t < ttCount; t++) { + // TODO write out texture specifics + // TODO UV transforms may be material specific + // so those may need to be baked into unique tex name + if (material->GetTexture(aiTextureType(tt), t, &path, &mapping, + &uvIndex, &blend, &op, mapMode) != AI_SUCCESS) { + std::cerr << "Error getting texture! " << m << " " << tt << " " << t << "\n"; + continue; + } + + std::string filename = CleanTextureFilename(path); + + if (uvIndex != 0) + std::cerr << "Warning: texture \"" << filename << "\" uses uv set #" << + uvIndex << " but the pbrt converter only exports uv set 0.\n"; +#if 0 + if (op != aiTextureOp_Multiply) + std::cerr << "Warning: unexpected texture op " << (int)op << + " encountered for texture \"" << + filename << "\". The resulting scene may have issues...\n"; + if (blend != 1) + std::cerr << "Blend value of " << blend << " found for texture \"" << filename + << "\" but not handled in converter.\n"; +#endif + + std::string mappingString; +#if 0 + if (mapMode[0] != mapMode[1]) + std::cerr << "Different texture boundary mode for u and v for texture \"" << + filename << "\". Using u for both.\n"; + switch (mapMode[0]) { + case aiTextureMapMode_Wrap: + // pbrt's default + break; + case aiTextureMapMode_Clamp: + mappingString = "\"string wrap\" \"clamp\""; + break; + case aiTextureMapMode_Decal: + std::cerr << "Decal texture boundary mode not supported by pbrt for texture \"" << + filename << "\"\n"; + break; + case aiTextureMapMode_Mirror: + std::cerr << "Mirror texture boundary mode not supported by pbrt for texture \"" << + filename << "\"\n"; + break; + default: + std::cerr << "Unexpected map mode " << (int)mapMode[0] << " for texture \"" << + filename << "\"\n"; + //throw DeadlyExportError("Unexpected aiTextureMapMode"); + } +#endif + +#if 0 + aiUVTransform uvTransform; + if (material->Get(AI_MATKEY_TEXTURE(tt, t), uvTransform) == AI_SUCCESS) { + mOutput << "# UV transform " << uvTransform.mTranslation.x << " " + << uvTransform.mTranslation.y << " " << uvTransform.mScaling.x << " " + << uvTransform.mScaling.y << " " << uvTransform.mRotation << "\n"; + } +#endif + + std::string texName, texType, texOptions; + if (aiTextureType(tt) == aiTextureType_SHININESS || + aiTextureType(tt) == aiTextureType_OPACITY || + aiTextureType(tt) == aiTextureType_HEIGHT || + aiTextureType(tt) == aiTextureType_DISPLACEMENT || + aiTextureType(tt) == aiTextureType_METALNESS || + aiTextureType(tt) == aiTextureType_DIFFUSE_ROUGHNESS) { + texType = "float"; + texName = std::string("float:") + RemoveSuffix(filename); + + if (aiTextureType(tt) == aiTextureType_SHININESS) { + texOptions = " \"bool invert\" true\n"; + texName += "_Roughness"; + } + } else if (aiTextureType(tt) == aiTextureType_DIFFUSE || + aiTextureType(tt) == aiTextureType_BASE_COLOR) { + texType = "spectrum"; + texName = std::string("rgb:") + RemoveSuffix(filename); + } + + // Don't export textures we're not actually going to use... + if (texName.empty()) + continue; + + if (mTextureSet.find(texName) == mTextureSet.end()) { + mOutput << "Texture \"" << texName << "\" \"" << texType << "\" \"imagemap\"\n" + << texOptions + << " \"string filename\" \"" << filename << "\" " << mappingString << '\n'; + mTextureSet.insert(texName); + } + + // Also emit a float version for use with alpha testing... + if ((aiTextureType(tt) == aiTextureType_DIFFUSE || + aiTextureType(tt) == aiTextureType_BASE_COLOR) && + TextureHasAlphaMask(filename)) { + texType = "float"; + texName = std::string("alpha:") + filename; + if (mTextureSet.find(texName) == mTextureSet.end()) { + mOutput << "Texture \"" << texName << "\" \"" << texType << "\" \"imagemap\"\n" + << " \"string filename\" \"" << filename << "\" " << mappingString << '\n'; + mTextureSet.insert(texName); + } + } + } + } + } +} + +bool PbrtExporter::TextureHasAlphaMask(const std::string &filename) { + // TODO: STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); + // quick return if it's 3 + + int xSize, ySize, nComponents; + unsigned char *data = stbi_load(filename.c_str(), &xSize, &ySize, &nComponents, 0); + if (!data) { + std::cerr << filename << ": unable to load texture and check for alpha mask in texture. " + "Geometry will not be alpha masked with this texture.\n"; + return false; + } + + bool hasMask = false; + switch (nComponents) { + case 1: + for (int i = 0; i < xSize * ySize; ++i) + if (data[i] != 255) { + hasMask = true; + break; + } + break; + case 2: + for (int y = 0; y < ySize; ++y) + for (int x = 0; x < xSize; ++x) + if (data[2 * (x + y * xSize) + 1] != 255) { + hasMask = true; + break; + } + break; + case 3: + break; + case 4: + for (int y = 0; y < ySize; ++y) + for (int x = 0; x < xSize; ++x) + if (data[4 * (x + y * xSize) + 3] != 255) { + hasMask = true; + break; + } + break; + default: + std::cerr << filename << ": unexpected number of image channels, " << + nComponents << ".\n"; + } + + stbi_image_free(data); + return hasMask; +} + +void PbrtExporter::WriteMaterials() { + mOutput << "\n"; + mOutput << "####################\n"; + mOutput << "# Materials (" << mScene->mNumMaterials << ") total\n\n"; + + for (unsigned int i = 0; i < mScene->mNumMaterials; i++) { + WriteMaterial(i); + } + mOutput << "\n\n"; +} + +void PbrtExporter::WriteMaterial(int m) { + aiMaterial* material = mScene->mMaterials[m]; + + // get material name + auto materialName = material->GetName(); + mOutput << std::endl << "# - Material " << m+1 << ": " << materialName.C_Str() << "\n"; + + // Print out number of properties + mOutput << "# - Number of Material Properties: " << material->mNumProperties << "\n"; + + // Print out texture type counts + mOutput << "# - Non-Zero Texture Type Counts: "; + for (int i = 1; i <= aiTextureType_UNKNOWN; i++) { + int count = material->GetTextureCount(aiTextureType(i)); + if (count > 0) + mOutput << TextureTypeToString(aiTextureType(i)) << ": " << count << " "; + } + mOutput << "\n"; + + auto White = [](const aiColor3D &c) { return c.r == 1 && c.g == 1 && c.b == 1; }; + auto Black = [](const aiColor3D &c) { return c.r == 0 && c.g == 0 && c.b == 0; }; + + aiColor3D diffuse, specular, transparency; + bool constantDiffuse = (material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse) == AI_SUCCESS && + !White(diffuse)); + bool constantSpecular = (material->Get(AI_MATKEY_COLOR_SPECULAR, specular) == AI_SUCCESS && + !White(specular)); + bool constantTransparency = (material->Get(AI_MATKEY_COLOR_TRANSPARENT, transparency) == AI_SUCCESS && + !Black(transparency)); + + float opacity, shininess, shininessStrength, eta; + bool constantOpacity = (material->Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS && + opacity != 0); + bool constantShininess = material->Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS; + bool constantShininessStrength = material->Get(AI_MATKEY_SHININESS_STRENGTH, shininessStrength) == AI_SUCCESS; + bool constantEta = (material->Get(AI_MATKEY_REFRACTI, eta) == AI_SUCCESS && + eta != 1); + + mOutput << "# - Constants: diffuse " << constantDiffuse << " specular " << constantSpecular << + " transparency " << constantTransparency << " opacity " << constantOpacity << + " shininess " << constantShininess << " shininess strength " << constantShininessStrength << + " eta " << constantEta << "\n"; + + aiString roughnessMap; + if (material->Get(AI_MATKEY_TEXTURE_SHININESS(0), roughnessMap) == AI_SUCCESS) { + std::string roughnessTexture = std::string("float:") + + RemoveSuffix(CleanTextureFilename(roughnessMap)) + "_Roughness"; + mOutput << "MakeNamedMaterial \"" << materialName.C_Str() << "\"" + << " \"string type\" \"coateddiffuse\"\n" + << " \"texture roughness\" \"" << roughnessTexture << "\"\n"; + } else if (constantShininess) { + // Assume plastic for now at least + float roughness = std::max(0.f, 1.f - shininess); + mOutput << "MakeNamedMaterial \"" << materialName.C_Str() << "\"" + << " \"string type\" \"coateddiffuse\"\n" + << " \"float roughness\" " << roughness << "\n"; + } else + // Diffuse + mOutput << "MakeNamedMaterial \"" << materialName.C_Str() << "\"" + << " \"string type\" \"diffuse\"\n"; + + aiString diffuseTexture; + if (material->Get(AI_MATKEY_TEXTURE_DIFFUSE(0), diffuseTexture) == AI_SUCCESS) + mOutput << " \"texture reflectance\" \"rgb:" << RemoveSuffix(CleanTextureFilename(diffuseTexture)) << "\"\n"; + else + mOutput << " \"rgb reflectance\" [ " << diffuse.r << " " << diffuse.g << + " " << diffuse.b << " ]\n"; + + aiString displacementTexture, normalMap; + if (material->Get(AI_MATKEY_TEXTURE_NORMALS(0), displacementTexture) == AI_SUCCESS) + mOutput << " \"string normalmap\" \"" << CleanTextureFilename(displacementTexture) << "\"\n"; + else if (material->Get(AI_MATKEY_TEXTURE_HEIGHT(0), displacementTexture) == AI_SUCCESS) + mOutput << " \"texture displacement\" \"float:" << + RemoveSuffix(CleanTextureFilename(displacementTexture)) << "\"\n"; + else if (material->Get(AI_MATKEY_TEXTURE_DISPLACEMENT(0), displacementTexture) == AI_SUCCESS) + mOutput << " \"texture displacement\" \"float:" << + RemoveSuffix(CleanTextureFilename(displacementTexture)) << "\"\n"; +} + +std::string PbrtExporter::CleanTextureFilename(const aiString &f, bool rewriteExtension) const { + std::string fn = f.C_Str(); + // Remove directory name + size_t offset = fn.find_last_of("/\\"); + if (offset != std::string::npos) { + fn.erase(0, offset + 1); + } + + // Expect all textures in textures + fn = std::string("textures") + mIOSystem->getOsSeparator() + fn; + + // Rewrite extension for unsupported file formats. + if (rewriteExtension) { + offset = fn.rfind('.'); + if (offset != std::string::npos) { + std::string extension = fn; + extension.erase(0, offset + 1); + std::transform(extension.begin(), extension.end(), extension.begin(), + [](unsigned char c) { return (char)std::tolower(c); }); + + if (extension != "tga" && extension != "exr" && extension != "png" && + extension != "pfm" && extension != "hdr") { + std::string orig = fn; + fn.erase(offset + 1); + fn += "png"; + + // Does it already exist? Warn if not. + std::ifstream filestream(fn); + if (!filestream.good()) + std::cerr << orig << ": must convert this texture to PNG.\n"; + } + } + } + + return fn; +} + +std::string PbrtExporter::RemoveSuffix(std::string filename) { + size_t offset = filename.rfind('.'); + if (offset != std::string::npos) + filename.erase(offset); + return filename; +} + +void PbrtExporter::WriteLights() { + mOutput << "\n"; + mOutput << "#################\n"; + mOutput << "# Lights\n\n"; + if (mScene->mNumLights == 0) { + // Skip the default light if no cameras and this is flat up geometry + if (mScene->mNumCameras > 0) { + std::cerr << "No lights specified. Using default infinite light.\n"; + + mOutput << "AttributeBegin\n"; + mOutput << " # default light\n"; + mOutput << " LightSource \"infinite\" \"blackbody L\" [6000 1]\n"; + + mOutput << "AttributeEnd\n\n"; + } + } else { + for (unsigned int i = 0; i < mScene->mNumLights; ++i) { + const aiLight *light = mScene->mLights[i]; + + mOutput << "# Light " << light->mName.C_Str() << "\n"; + mOutput << "AttributeBegin\n"; + + aiMatrix4x4 worldFromLight = GetNodeTransform(light->mName); + mOutput << " Transform [ " << TransformAsString(worldFromLight) << " ]\n"; + + aiColor3D color = light->mColorDiffuse + light->mColorSpecular; + if (light->mAttenuationConstant != 0) + color = color * (ai_real)(1. / light->mAttenuationConstant); + + switch (light->mType) { + case aiLightSource_DIRECTIONAL: { + mOutput << " LightSource \"distant\"\n"; + mOutput << " \"point3 from\" [ " << light->mPosition.x << " " << + light->mPosition.y << " " << light->mPosition.z << " ]\n"; + aiVector3D to = light->mPosition + light->mDirection; + mOutput << " \"point3 to\" [ " << to.x << " " << to.y << " " << to.z << " ]\n"; + mOutput << " \"rgb L\" [ " << color.r << " " << color.g << " " << color.b << " ]\n"; + break; + } case aiLightSource_POINT: + mOutput << " LightSource \"distant\"\n"; + mOutput << " \"point3 from\" [ " << light->mPosition.x << " " << + light->mPosition.y << " " << light->mPosition.z << " ]\n"; + mOutput << " \"rgb L\" [ " << color.r << " " << color.g << " " << color.b << " ]\n"; + break; + case aiLightSource_SPOT: { + mOutput << " LightSource \"spot\"\n"; + mOutput << " \"point3 from\" [ " << light->mPosition.x << " " << + light->mPosition.y << " " << light->mPosition.z << " ]\n"; + aiVector3D to = light->mPosition + light->mDirection; + mOutput << " \"point3 to\" [ " << to.x << " " << to.y << " " << to.z << " ]\n"; + mOutput << " \"rgb L\" [ " << color.r << " " << color.g << " " << color.b << " ]\n"; + mOutput << " \"float coneangle\" [ " << AI_RAD_TO_DEG(light->mAngleOuterCone) << " ]\n"; + mOutput << " \"float conedeltaangle\" [ " << AI_RAD_TO_DEG(light->mAngleOuterCone - + light->mAngleInnerCone) << " ]\n"; + break; + } case aiLightSource_AMBIENT: + mOutput << "# ignored ambient light source\n"; + break; + case aiLightSource_AREA: { + aiVector3D left = light->mDirection ^ light->mUp; + // rectangle. center at position, direction is normal vector + ai_real dLeft = light->mSize.x / 2, dUp = light->mSize.y / 2; + aiVector3D vertices[4] = { + light->mPosition - dLeft * left - dUp * light->mUp, + light->mPosition + dLeft * left - dUp * light->mUp, + light->mPosition - dLeft * left + dUp * light->mUp, + light->mPosition + dLeft * left + dUp * light->mUp }; + mOutput << " AreaLightSource \"diffuse\"\n"; + mOutput << " \"rgb L\" [ " << color.r << " " << color.g << " " << color.b << " ]\n"; + mOutput << " Shape \"bilinearmesh\"\n"; + mOutput << " \"point3 p\" [ "; + for (int j = 0; j < 4; ++j) + mOutput << vertices[j].x << " " << vertices[j].y << " " << vertices[j].z; + mOutput << " ]\n"; + mOutput << " \"integer indices\" [ 0 1 2 3 ]\n"; + break; + } default: + mOutput << "# ignored undefined light source type\n"; + break; + } + mOutput << "AttributeEnd\n\n"; + } + } +} + +void PbrtExporter::WriteMesh(aiMesh* mesh) { + mOutput << "# - Mesh: "; + if (mesh->mName == aiString("")) + mOutput << "<No Name>\n"; + else + mOutput << mesh->mName.C_Str() << "\n"; + + mOutput << "AttributeBegin\n"; + aiMaterial* material = mScene->mMaterials[mesh->mMaterialIndex]; + mOutput << " NamedMaterial \"" << material->GetName().C_Str() << "\"\n"; + + // Handle area lights + aiColor3D emission; + if (material->Get(AI_MATKEY_COLOR_EMISSIVE, emission) == AI_SUCCESS && + (emission.r > 0 || emission.g > 0 || emission.b > 0)) + mOutput << " AreaLightSource \"diffuse\" \"rgb L\" [ " << emission.r << + " " << emission.g << " " << emission.b << " ]\n"; + + // Check if any types other than tri + if ( (mesh->mPrimitiveTypes & aiPrimitiveType_POINT) + || (mesh->mPrimitiveTypes & aiPrimitiveType_LINE) + || (mesh->mPrimitiveTypes & aiPrimitiveType_POLYGON)) { + std::cerr << "Error: ignoring point / line / polygon mesh " << mesh->mName.C_Str() << ".\n"; + return; + } + + // Alpha mask + std::string alpha; + aiString opacityTexture; + if (material->Get(AI_MATKEY_TEXTURE_OPACITY(0), opacityTexture) == AI_SUCCESS || + material->Get(AI_MATKEY_TEXTURE_DIFFUSE(0), opacityTexture) == AI_SUCCESS) { + // material->Get(AI_MATKEY_TEXTURE_BASE_COLOR(0), opacityTexture) == AI_SUCCESS) + std::string texName = std::string("alpha:") + CleanTextureFilename(opacityTexture); + if (mTextureSet.find(texName) != mTextureSet.end()) + alpha = std::string(" \"texture alpha\" \"") + texName + "\"\n"; + } else { + float opacity = 1; + if (material->Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS && opacity < 1) + alpha = std::string(" \"float alpha\" [ ") + std::to_string(opacity) + " ]\n"; + } + + // Output the shape specification + mOutput << "Shape \"trianglemesh\"\n" << + alpha << + " \"integer indices\" ["; + + // Start with faces (which hold indices) + for (unsigned int i = 0; i < mesh->mNumFaces; i++) { + auto face = mesh->mFaces[i]; + if (face.mNumIndices != 3) throw DeadlyExportError("oh no not a tri!"); + + for (unsigned int j = 0; j < face.mNumIndices; j++) { + mOutput << face.mIndices[j] << " "; + } + if ((i % 7) == 6) mOutput << "\n "; + } + mOutput << "]\n"; + + // Then go to vertices + mOutput << " \"point3 P\" ["; + for (unsigned int i = 0; i < mesh->mNumVertices; i++) { + auto vector = mesh->mVertices[i]; + mOutput << vector.x << " " << vector.y << " " << vector.z << " "; + if ((i % 4) == 3) mOutput << "\n "; + } + mOutput << "]\n"; + + // Normals (if present) + if (mesh->mNormals) { + mOutput << " \"normal N\" ["; + for (unsigned int i = 0; i < mesh->mNumVertices; i++) { + auto normal = mesh->mNormals[i]; + mOutput << normal.x << " " << normal.y << " " << normal.z << " "; + if ((i % 4) == 3) mOutput << "\n "; + } + mOutput << "]\n"; + } + + // Tangents (if present) + if (mesh->mTangents) { + mOutput << " \"vector3 S\" ["; + for (unsigned int i = 0; i < mesh->mNumVertices; i++) { + auto tangent = mesh->mTangents[i]; + mOutput << tangent.x << " " << tangent.y << " " << tangent.z << " "; + if ((i % 4) == 3) mOutput << "\n "; + } + mOutput << "]\n"; + } + + // Texture Coords (if present) + // Find the first set of 2D texture coordinates.. + for (int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + if (mesh->mNumUVComponents[i] == 2) { + // assert(mesh->mTextureCoords[i] != nullptr); + aiVector3D* uv = mesh->mTextureCoords[i]; + mOutput << " \"point2 uv\" ["; + for (unsigned int j = 0; j < mesh->mNumVertices; ++j) { + mOutput << uv[j].x << " " << uv[j].y << " "; + if ((j % 6) == 5) mOutput << "\n "; + } + mOutput << "]\n"; + break; + } + } + // TODO: issue warning if there are additional UV sets? + + mOutput << "AttributeEnd\n"; +} + +void PbrtExporter::WriteInstanceDefinition(int i) { + aiMesh* mesh = mScene->mMeshes[i]; + + mOutput << "ObjectBegin \""; + if (mesh->mName == aiString("")) + mOutput << "mesh_" << i+1 << "\"\n"; + else + mOutput << mesh->mName.C_Str() << "_" << i+1 << "\"\n"; + + WriteMesh(mesh); + + mOutput << "ObjectEnd\n"; +} + +void PbrtExporter::WriteGeometricObjects(aiNode* node, aiMatrix4x4 worldFromObject, + std::map<int, int> &meshUses) { + // Sometimes interior nodes have degenerate matrices?? + if (node->mTransformation.Determinant() != 0) + worldFromObject = worldFromObject * node->mTransformation; + + if (node->mNumMeshes > 0) { + mOutput << "AttributeBegin\n"; + + mOutput << " Transform [ " << TransformAsString(worldFromObject) << "]\n"; + + for (unsigned int i = 0; i < node->mNumMeshes; i++) { + aiMesh* mesh = mScene->mMeshes[node->mMeshes[i]]; + if (meshUses[node->mMeshes[i]] == 1) { + // If it's only used once in the scene, emit it directly as + // a triangle mesh. + mOutput << " # " << mesh->mName.C_Str(); + WriteMesh(mesh); + } else { + // If it's used multiple times, there will be an object + // instance for it, so emit a reference to that. + mOutput << " ObjectInstance \""; + if (mesh->mName == aiString("")) + mOutput << "mesh_" << node->mMeshes[i] + 1 << "\"\n"; + else + mOutput << mesh->mName.C_Str() << "_" << node->mMeshes[i] + 1 << "\"\n"; + } + } + mOutput << "AttributeEnd\n\n"; + } + + // Recurse through children + for (unsigned int i = 0; i < node->mNumChildren; i++) { + WriteGeometricObjects(node->mChildren[i], worldFromObject, meshUses); + } +} + +#endif // ASSIMP_BUILD_NO_PBRT_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT diff --git a/libs/assimp/code/Pbrt/PbrtExporter.h b/libs/assimp/code/Pbrt/PbrtExporter.h new file mode 100644 index 0000000..4f4e162 --- /dev/null +++ b/libs/assimp/code/Pbrt/PbrtExporter.h @@ -0,0 +1,135 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file PbrtExporter.h +* Declares the exporter class to write a scene to a pbrt file +*/ +#ifndef AI_PBRTEXPORTER_H_INC +#define AI_PBRTEXPORTER_H_INC + +#ifndef ASSIMP_BUILD_NO_PBRT_EXPORTER + +#include <assimp/types.h> +#include <assimp/StreamWriter.h> +#include <assimp/Exceptional.h> + +#include <map> +#include <set> +#include <string> +#include <sstream> + +struct aiScene; +struct aiNode; +struct aiMaterial; +struct aiMesh; + +namespace Assimp { + +class IOSystem; +class IOStream; +class ExportProperties; + +// --------------------------------------------------------------------- +/** Helper class to export a given scene to a Pbrt file. */ +// --------------------------------------------------------------------- +class PbrtExporter +{ +public: + /// Constructor for a specific scene to export + PbrtExporter(const aiScene *pScene, IOSystem *pIOSystem, + const std::string &path, const std::string &file); + + /// Destructor + virtual ~PbrtExporter(); + +private: + // the scene to export + const aiScene* mScene; + + /// Stringstream to write all output into + std::stringstream mOutput; + + /// The IOSystem for output + IOSystem* mIOSystem; + + /// Path of the directory where the scene will be exported + const std::string mPath; + + /// Name of the file (without extension) where the scene will be exported + const std::string mFile; + +private: + // A private set to keep track of which textures have been declared + std::set<std::string> mTextureSet; + + aiMatrix4x4 GetNodeTransform(const aiString& name) const; + static std::string TransformAsString(const aiMatrix4x4& m); + + static std::string RemoveSuffix(std::string filename); + std::string CleanTextureFilename(const aiString &f, bool rewriteExtension = true) const; + + void WriteMetaData(); + + void WriteWorldDefinition(); + + void WriteCameras(); + void WriteCamera(int i); + + void WriteLights(); + + void WriteTextures(); + static bool TextureHasAlphaMask(const std::string &filename); + + void WriteMaterials(); + void WriteMaterial(int i); + + void WriteMesh(aiMesh* mesh); + + void WriteInstanceDefinition(int i); + void WriteGeometricObjects(aiNode* node, aiMatrix4x4 parentTransform, + std::map<int, int> &meshUses); +}; + +} // namespace Assimp + +#endif // ASSIMP_BUILD_NO_PBRT_EXPORTER + +#endif // AI_PBRTEXPORTER_H_INC |