new stuff

This commit is contained in:
Tarion 2026-07-03 03:41:25 +02:00
parent 8998bc3936
commit 64c8492e5c
No known key found for this signature in database
6907 changed files with 3401899 additions and 0 deletions

View file

@ -0,0 +1,421 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2026, 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.
----------------------------------------------------------------------
*/
#ifndef ASSIMP_BUILD_NO_EXPORT
#ifndef ASSIMP_BUILD_NO_OBJ_EXPORTER
#include "ObjExporter.h"
#include <assimp/Exceptional.h>
#include <assimp/StringComparison.h>
#include <assimp/version.h>
#include <assimp/IOSystem.hpp>
#include <assimp/Exporter.hpp>
#include <assimp/material.h>
#include <assimp/scene.h>
#include <memory>
using namespace Assimp;
namespace Assimp {
// ------------------------------------------------------------------------------------------------
// Worker function for exporting a scene to Wavefront OBJ. Prototyped and registered in Exporter.cpp
void ExportSceneObj(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* props) {
// invoke the exporter
ObjExporter exporter(pFile, pScene, false, props);
if (exporter.mOutput.fail() || exporter.mOutputMat.fail()) {
throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile));
}
// we're still here - export successfully completed. Write both the main OBJ file and the material script
{
std::unique_ptr<IOStream> outfile (pIOSystem->Open(pFile,"wt"));
if (outfile == nullptr) {
throw DeadlyExportError("could not open output .obj file: " + std::string(pFile));
}
outfile->Write( exporter.mOutput.str().c_str(), static_cast<size_t>(exporter.mOutput.tellp()),1);
}
{
std::unique_ptr<IOStream> outfile (pIOSystem->Open(exporter.GetMaterialLibFileName(),"wt"));
if (outfile == nullptr) {
throw DeadlyExportError("could not open output .mtl file: " + std::string(exporter.GetMaterialLibFileName()));
}
outfile->Write( exporter.mOutputMat.str().c_str(), static_cast<size_t>(exporter.mOutputMat.tellp()),1);
}
}
// ------------------------------------------------------------------------------------------------
// Worker function for exporting a scene to Wavefront OBJ without the material file. Prototyped and registered in Exporter.cpp
void ExportSceneObjNoMtl(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* props) {
// invoke the exporter
ObjExporter exporter(pFile, pScene, true, props);
if (exporter.mOutput.fail() || exporter.mOutputMat.fail()) {
throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile));
}
// we're still here - export successfully completed. Write both the main OBJ file and the material script
{
std::unique_ptr<IOStream> outfile (pIOSystem->Open(pFile,"wt"));
if (outfile == nullptr) {
throw DeadlyExportError("could not open output .obj file: " + std::string(pFile));
}
outfile->Write( exporter.mOutput.str().c_str(), static_cast<size_t>(exporter.mOutput.tellp()),1);
}
}
} // end of namespace Assimp
static const std::string MaterialExt = ".mtl";
// ------------------------------------------------------------------------------------------------
ObjExporter::ObjExporter(const char* _filename, const aiScene* pScene, bool noMtl, const ExportProperties* props)
: filename(_filename)
, pScene(pScene)
, vn()
, vt()
, vp()
, useVc(false)
, mVnMap()
, mVtMap()
, mVpMap()
, mMeshes()
, endl("\n") {
// make sure that all formatting happens using the standard, C locale and not the user's current locale
const std::locale& l = std::locale("C");
mOutput.imbue(l);
mOutput.precision(ASSIMP_AI_REAL_TEXT_PRECISION);
mOutputMat.imbue(l);
mOutputMat.precision(ASSIMP_AI_REAL_TEXT_PRECISION);
WriteGeometryFile(
noMtl,
props == nullptr ? true : props->GetPropertyBool("bJoinIdenticalVertices", true)
);
if ( !noMtl ) {
WriteMaterialFile();
}
}
// ------------------------------------------------------------------------------------------------
ObjExporter::~ObjExporter() = default;
// ------------------------------------------------------------------------------------------------
std::string ObjExporter::GetMaterialLibName() {
// within the Obj file, we use just the relative file name with the path stripped
const std::string& s = GetMaterialLibFileName();
std::string::size_type il = s.find_last_of("/\\");
if (il != std::string::npos) {
return s.substr(il + 1);
}
return s;
}
// ------------------------------------------------------------------------------------------------
std::string ObjExporter::GetMaterialLibFileName() {
// Remove existing .obj file extension so that the final material file name will be fileName.mtl and not fileName.obj.mtl
size_t lastdot = filename.find_last_of('.');
if ( lastdot != std::string::npos ) {
return filename.substr( 0, lastdot ) + MaterialExt;
}
return filename + MaterialExt;
}
// ------------------------------------------------------------------------------------------------
void ObjExporter::WriteHeader(std::ostringstream& out) {
out << "# File produced by Open Asset Import Library (http://www.assimp.sf.net)" << endl;
out << "# (assimp v" << aiGetVersionMajor() << '.' << aiGetVersionMinor() << '.'
<< aiGetVersionRevision() << ")" << endl << endl;
}
// ------------------------------------------------------------------------------------------------
std::string ObjExporter::GetMaterialName(unsigned int index) {
static const std::string EmptyStr;
if ( nullptr == pScene->mMaterials ) {
return EmptyStr;
}
const aiMaterial* const mat = pScene->mMaterials[index];
if ( nullptr == mat ) {
return EmptyStr;
}
aiString s;
if(AI_SUCCESS == mat->Get(AI_MATKEY_NAME,s)) {
return std::string(s.data,s.length);
}
char number[ sizeof(unsigned int) * 3 + 1 ];
ASSIMP_itoa10(number,index);
return "$Material_" + std::string(number);
}
// ------------------------------------------------------------------------------------------------
void ObjExporter::WriteMaterialFile() {
WriteHeader(mOutputMat);
for(unsigned int i = 0; i < pScene->mNumMaterials; ++i) {
const aiMaterial* const mat = pScene->mMaterials[i];
int illum = 1;
mOutputMat << "newmtl " << GetMaterialName(i) << endl;
aiColor4D c;
if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_DIFFUSE,c)) {
mOutputMat << "Kd " << c.r << " " << c.g << " " << c.b << endl;
}
if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_AMBIENT,c)) {
mOutputMat << "Ka " << c.r << " " << c.g << " " << c.b << endl;
}
if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_SPECULAR,c)) {
mOutputMat << "Ks " << c.r << " " << c.g << " " << c.b << endl;
}
if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_EMISSIVE,c)) {
mOutputMat << "Ke " << c.r << " " << c.g << " " << c.b << endl;
}
if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_TRANSPARENT,c)) {
mOutputMat << "Tf " << c.r << " " << c.g << " " << c.b << endl;
}
ai_real o;
if(AI_SUCCESS == mat->Get(AI_MATKEY_OPACITY,o)) {
mOutputMat << "d " << o << endl;
}
if(AI_SUCCESS == mat->Get(AI_MATKEY_REFRACTI,o)) {
mOutputMat << "Ni " << o << endl;
}
if(AI_SUCCESS == mat->Get(AI_MATKEY_SHININESS,o) && o) {
mOutputMat << "Ns " << o << endl;
illum = 2;
}
mOutputMat << "illum " << illum << endl;
aiString s;
if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_DIFFUSE(0),s)) {
mOutputMat << "map_Kd " << s.data << endl;
}
if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_AMBIENT(0),s)) {
mOutputMat << "map_Ka " << s.data << endl;
}
if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_SPECULAR(0),s)) {
mOutputMat << "map_Ks " << s.data << endl;
}
if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_SHININESS(0),s)) {
mOutputMat << "map_Ns " << s.data << endl;
}
if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_OPACITY(0),s)) {
mOutputMat << "map_d " << s.data << endl;
}
if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_HEIGHT(0),s) || AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_NORMALS(0),s)) {
// implementations seem to vary here, so write both variants
mOutputMat << "bump " << s.data << endl;
mOutputMat << "map_bump " << s.data << endl;
}
mOutputMat << endl;
}
}
void ObjExporter::WriteGeometryFile(bool noMtl, bool merge_identical_vertices) {
WriteHeader(mOutput);
if (!noMtl)
mOutput << "mtllib " << GetMaterialLibName() << endl << endl;
// collect mesh geometry
aiMatrix4x4 mBase;
AddNode(pScene->mRootNode, mBase, merge_identical_vertices);
// write vertex positions with colors, if any
mVpMap.getKeys( vp );
if ( !useVc ) {
mOutput << "# " << vp.size() << " vertex positions" << endl;
for ( const vertexData& v : vp ) {
mOutput << "v " << v.vp.x << " " << v.vp.y << " " << v.vp.z << endl;
}
} else {
mOutput << "# " << vp.size() << " vertex positions and colors" << endl;
for ( const vertexData& v : vp ) {
mOutput << "v " << v.vp.x << " " << v.vp.y << " " << v.vp.z << " " << v.vc.r << " " << v.vc.g << " " << v.vc.b << endl;
}
}
mOutput << endl;
// write uv coordinates
mVtMap.getKeys(vt);
mOutput << "# " << vt.size() << " UV coordinates" << endl;
for(const aiVector3D& v : vt) {
mOutput << "vt " << v.x << " " << v.y << " " << v.z << endl;
}
mOutput << endl;
// write vertex normals
mVnMap.getKeys(vn);
mOutput << "# " << vn.size() << " vertex normals" << endl;
for(const aiVector3D& v : vn) {
mOutput << "vn " << v.x << " " << v.y << " " << v.z << endl;
}
mOutput << endl;
// now write all mesh instances
for(const MeshInstance& m : mMeshes) {
mOutput << "# Mesh \'" << m.name << "\' with " << m.faces.size() << " faces" << endl;
if (!m.name.empty()) {
mOutput << "g " << m.name << endl;
}
if ( !noMtl ) {
mOutput << "usemtl " << m.matname << endl;
}
for(const Face& f : m.faces) {
mOutput << f.kind << ' ';
for(const FaceVertex& fv : f.indices) {
mOutput << ' ' << fv.vp;
if (f.kind != 'p') {
if (fv.vt || f.kind == 'f') {
mOutput << '/';
}
if (fv.vt) {
mOutput << fv.vt;
}
if (f.kind == 'f' && fv.vn) {
mOutput << '/' << fv.vn;
}
}
}
mOutput << endl;
}
mOutput << endl;
}
}
// ------------------------------------------------------------------------------------------------
void ObjExporter::AddMesh(const aiString& name, const aiMesh* m, const aiMatrix4x4& mat, bool merge_identical_vertices) {
mMeshes.emplace_back();
MeshInstance& mesh = mMeshes.back();
if ( nullptr != m->mColors[ 0 ] ) {
useVc = true;
}
mesh.name = std::string( name.data, name.length );
mesh.matname = GetMaterialName(m->mMaterialIndex);
mesh.faces.resize(m->mNumFaces);
for(unsigned int i = 0; i < m->mNumFaces; ++i) {
const aiFace& f = m->mFaces[i];
Face& face = mesh.faces[i];
switch (f.mNumIndices) {
case 1:
face.kind = 'p';
break;
case 2:
face.kind = 'l';
break;
default:
face.kind = 'f';
}
face.indices.resize(f.mNumIndices);
for(unsigned int a = 0; a < f.mNumIndices; ++a) {
const unsigned int idx = f.mIndices[a];
const unsigned int fi = merge_identical_vertices ? 0 : idx;
aiVector3D vert = mat * m->mVertices[idx];
if ( nullptr != m->mColors[ 0 ] ) {
aiColor4D col4 = m->mColors[ 0 ][ idx ];
face.indices[a].vp = mVpMap.getIndex({vert, aiColor3D(col4.r, col4.g, col4.b), fi});
} else {
face.indices[a].vp = mVpMap.getIndex({vert, aiColor3D(0,0,0), fi});
}
if (m->mNormals) {
aiVector3D norm = aiMatrix3x3(mat) * m->mNormals[idx];
face.indices[a].vn = mVnMap.getIndex(norm);
} else {
face.indices[a].vn = 0;
}
if ( m->mTextureCoords[ 0 ] ) {
face.indices[a].vt = mVtMap.getIndex(m->mTextureCoords[0][idx]);
} else {
face.indices[a].vt = 0;
}
}
}
}
// ------------------------------------------------------------------------------------------------
void ObjExporter::AddNode(const aiNode* nd, const aiMatrix4x4& mParent, bool merge_identical_vertices) {
if (nd == nullptr) {
return;
}
const aiMatrix4x4& mAbs = mParent * nd->mTransformation;
aiMesh *cm{nullptr};
for(unsigned int i = 0; i < nd->mNumMeshes; ++i) {
cm = pScene->mMeshes[nd->mMeshes[i]];
if (nullptr != cm) {
AddMesh(cm->mName, pScene->mMeshes[nd->mMeshes[i]], mAbs, merge_identical_vertices);
} else {
AddMesh(nd->mName, pScene->mMeshes[nd->mMeshes[i]], mAbs, merge_identical_vertices);
}
}
for(unsigned int i = 0; i < nd->mNumChildren; ++i) {
AddNode(nd->mChildren[i], mAbs, merge_identical_vertices);
}
}
// ------------------------------------------------------------------------------------------------
#endif // ASSIMP_BUILD_NO_OBJ_EXPORTER
#endif // ASSIMP_BUILD_NO_EXPORT

View file

@ -0,0 +1,193 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2026, 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 ObjExporter.h
* Declares the exporter class to write a scene to a Collada file
*/
#ifndef AI_OBJEXPORTER_H_INC
#define AI_OBJEXPORTER_H_INC
#include <assimp/types.h>
#include <sstream>
#include <vector>
#include <map>
#include <assimp/Exporter.hpp>
struct aiScene;
struct aiNode;
struct aiMesh;
namespace Assimp {
// ------------------------------------------------------------------------------------------------
/** Helper class to export a given scene to an OBJ file. */
// ------------------------------------------------------------------------------------------------
class ObjExporter final {
public:
/// Constructor for a specific scene to export
ObjExporter(const char* filename, const aiScene* pScene, bool noMtl=false, const ExportProperties* props = nullptr);
~ObjExporter();
std::string GetMaterialLibName();
std::string GetMaterialLibFileName();
/// public string-streams to write all output into
std::ostringstream mOutput, mOutputMat;
private:
// intermediate data structures
struct FaceVertex {
FaceVertex()
: vp()
, vn()
, vt() {
// empty
}
// one-based, 0 means: 'does not exist'
unsigned int vp, vn, vt;
};
struct Face {
char kind;
std::vector<FaceVertex> indices;
};
struct MeshInstance {
std::string name, matname;
std::vector<Face> faces;
};
void WriteHeader(std::ostringstream& out);
void WriteMaterialFile();
void WriteGeometryFile(bool noMtl=false, bool merge_identical_vertices = false);
std::string GetMaterialName(unsigned int index);
void AddMesh(const aiString& name, const aiMesh* m, const aiMatrix4x4& mat, bool merge_identical_vertices);
void AddNode(const aiNode* nd, const aiMatrix4x4& mParent, bool merge_identical_vertices);
private:
std::string filename;
const aiScene* const pScene;
struct vertexData {
aiVector3D vp;
aiColor3D vc; // OBJ does not support 4D color
uint32_t index = 0;
};
std::vector<aiVector3D> vn, vt;
std::vector<aiColor4D> vc;
std::vector<vertexData> vp;
bool useVc;
struct vertexDataCompare {
bool operator() ( const vertexData& a, const vertexData& b ) const {
// position
if (a.vp.x < b.vp.x) return true;
if (a.vp.x > b.vp.x) return false;
if (a.vp.y < b.vp.y) return true;
if (a.vp.y > b.vp.y) return false;
if (a.vp.z < b.vp.z) return true;
if (a.vp.z > b.vp.z) return false;
// color
if (a.vc.r < b.vc.r) return true;
if (a.vc.r > b.vc.r) return false;
if (a.vc.g < b.vc.g) return true;
if (a.vc.g > b.vc.g) return false;
if (a.vc.b < b.vc.b) return true;
if (a.vc.b > b.vc.b) return false;
return a.index < b.index;
}
};
struct aiVectorCompare {
bool operator() (const aiVector3D& a, const aiVector3D& b) const {
if(a.x < b.x) return true;
if(a.x > b.x) return false;
if(a.y < b.y) return true;
if(a.y > b.y) return false;
if(a.z < b.z) return true;
return false;
}
};
template <class T, class Compare = std::less<T>>
class indexMap {
int mNextIndex;
typedef std::map<T, int, Compare> dataType;
dataType vecMap;
public:
indexMap()
: mNextIndex(1) {
// empty
}
int getIndex(const T& key) {
typename dataType::iterator vertIt = vecMap.find(key);
// vertex already exists, so reference it
if(vertIt != vecMap.end()){
return vertIt->second;
}
return vecMap[key] = mNextIndex++;
};
void getKeys( std::vector<T>& keys ) {
keys.resize(vecMap.size());
for(typename dataType::iterator it = vecMap.begin(); it != vecMap.end(); ++it){
keys[it->second-1] = it->first;
}
};
};
indexMap<aiVector3D, aiVectorCompare> mVnMap, mVtMap;
indexMap<vertexData, vertexDataCompare> mVpMap;
std::vector<MeshInstance> mMeshes;
// this endl() doesn't flush() the stream
const std::string endl;
};
}
#endif

View file

@ -0,0 +1,345 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2026, 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.
----------------------------------------------------------------------
*/
#pragma once
#ifndef OBJ_FILEDATA_H_INC
#define OBJ_FILEDATA_H_INC
#include <assimp/mesh.h>
#include <assimp/types.h>
#include <map>
#include <vector>
#include "Common/Maybe.h"
namespace Assimp {
namespace ObjFile {
struct Object;
struct Face;
struct Material;
// ------------------------------------------------------------------------------------------------
//! \struct Face
//! \brief Data structure for a simple obj-face, describes discredit,l.ation and materials
// ------------------------------------------------------------------------------------------------
struct Face {
using IndexArray = std::vector<unsigned int>;
//! Primitive type
aiPrimitiveType mPrimitiveType;
//! Vertex indices
IndexArray m_vertices;
//! Normal indices
IndexArray m_normals;
//! Texture coordinates indices
IndexArray m_texturCoords;
//! Pointer to assigned material
Material *m_pMaterial;
//! \brief Default constructor
Face(aiPrimitiveType pt = aiPrimitiveType_POLYGON) :
mPrimitiveType(pt), m_vertices(), m_normals(), m_texturCoords(), m_pMaterial(nullptr) {
// empty
}
//! \brief Destructor
~Face() = default;
};
// ------------------------------------------------------------------------------------------------
//! \struct Object
//! \brief Stores all objects of an obj-file object definition
// ------------------------------------------------------------------------------------------------
struct Object {
enum ObjectType {
ObjType,
GroupType
};
//! Object name
std::string m_strObjName;
//! Transformation matrix, stored in OpenGL format
aiMatrix4x4 m_Transformation;
//! All sub-objects referenced by this object
std::vector<Object *> m_SubObjects;
/// Assigned meshes
std::vector<unsigned int> m_Meshes;
//! \brief Default constructor
Object() = default;
//! \brief Destructor
~Object() {
for (std::vector<Object *>::iterator it = m_SubObjects.begin(); it != m_SubObjects.end(); ++it) {
delete *it;
}
}
};
// ------------------------------------------------------------------------------------------------
//! \struct Material
//! \brief Data structure to store all material specific data
// ------------------------------------------------------------------------------------------------
struct Material {
//! Name of material description
aiString MaterialName;
//! Texture names
aiString texture;
aiString textureSpecular;
aiString textureAmbient;
aiString textureEmissive;
aiString textureBump;
aiString textureNormal;
aiString textureReflection[6];
aiString textureSpecularity;
aiString textureOpacity;
aiString textureDisp;
aiString textureRoughness;
aiString textureMetallic;
aiString textureSheen;
aiString textureRMA;
enum TextureType {
TextureDiffuseType = 0,
TextureSpecularType,
TextureAmbientType,
TextureEmissiveType,
TextureBumpType,
TextureNormalType,
TextureReflectionSphereType,
TextureReflectionCubeTopType,
TextureReflectionCubeBottomType,
TextureReflectionCubeFrontType,
TextureReflectionCubeBackType,
TextureReflectionCubeLeftType,
TextureReflectionCubeRightType,
TextureSpecularityType,
TextureOpacityType,
TextureDispType,
TextureRoughnessType,
TextureMetallicType,
TextureSheenType,
TextureRMAType,
TextureTypeCount
};
bool clamp[TextureTypeCount];
//! Ambient color
aiColor3D ambient;
//! Diffuse color
aiColor3D diffuse = aiColor3D(0.6f, 0.6f, 0.6f);
//! Specular color
aiColor3D specular;
//! Emissive color
aiColor3D emissive;
//! Alpha value
ai_real alpha = ai_real(1.0);
//! Shineness factor
ai_real shineness = ai_real(0.0);
//! Illumination model
int illumination_model = 1;
//! Index of refraction
ai_real ior = ai_real(1.0);
//! Transparency color
aiColor3D transparent = aiColor3D(1.0f, 1.0f, 1.0f);
//! Ambient occlusion
Maybe<ai_real> ambient_occlusion;
//! PBR Roughness
Maybe<ai_real> roughness;
//! PBR Metallic
Maybe<ai_real> metallic;
//! PBR Metallic
Maybe<aiColor3D> sheen;
//! PBR Sheen: an additional grazing component, primarily intended for cloth.
Maybe<ai_real> sheen_grazing;
//! PBR Sheen Tint: amount to tint sheen towards base color.
Maybe<ai_real> sheen_tint;
//! PBR Clearcoat
Maybe<ai_real> clearcoat;
//! PBR Clearcoat Thickness
Maybe<ai_real> clearcoat_thickness;
//! PBR Clearcoat Rougness
Maybe<ai_real> clearcoat_roughness;
//! PBR clearcoatGloss: controls clearcoat glossiness (0 = a “satin” appearance, 1 = a “gloss” appearance).
Maybe<ai_real> clearcoat_gloss;
//! PBR Anisotropy
ai_real anisotropy = ai_real(0.0);
//! PBR Anisotropy Rotation
Maybe<ai_real> anisotropy_rotation;
//! PBR Subsurface Scattering
Maybe<ai_real> subsurface_scattering;
//! PBR Specular Tint: a concession for artistic control that tints incident specular towards the base color.
Maybe<ai_real> specular_tint;
// See: https://disneyanimation.com/publications/physically-based-shading-at-disney/
//! bump map multiplier (normal map scalar)(-bm)
ai_real bump_multiplier = ai_real(1.0);
//! Constructor
Material() {
std::fill_n(clamp, static_cast<unsigned int>(TextureTypeCount), false);
}
// Destructor
~Material() = default;
};
// ------------------------------------------------------------------------------------------------
//! \struct Mesh
//! \brief Data structure to store a mesh
// ------------------------------------------------------------------------------------------------
struct Mesh {
static const unsigned int NoMaterial = ~0u;
/// The name for the mesh
std::string m_name;
/// Array with pointer to all stored faces
std::vector<Face*> m_Faces;
/// Assigned material
Material *m_pMaterial;
/// Number of stored indices.
unsigned int m_uiNumIndices;
/// Number of UV
unsigned int m_uiUVCoordinates[AI_MAX_NUMBER_OF_TEXTURECOORDS];
/// Material index.
unsigned int m_uiMaterialIndex;
/// True, if normals are stored.
bool m_hasNormals;
/// Constructor
explicit Mesh(const std::string &name) :
m_name(name),
m_pMaterial(nullptr),
m_uiNumIndices(0),
m_uiMaterialIndex(NoMaterial),
m_hasNormals(false) {
memset(m_uiUVCoordinates, 0, sizeof(unsigned int) * AI_MAX_NUMBER_OF_TEXTURECOORDS);
}
/// Destructor
~Mesh() {
for (std::vector<Face *>::iterator it = m_Faces.begin();
it != m_Faces.end(); ++it) {
delete *it;
}
}
};
// ------------------------------------------------------------------------------------------------
//! \struct Model
//! \brief Data structure to store all obj-specific model data
// ------------------------------------------------------------------------------------------------
struct Model {
using GroupMap = std::map<std::string, std::vector<unsigned int> *>;
using GroupMapIt = std::map<std::string, std::vector<unsigned int> *>::iterator;
using ConstGroupMapIt = std::map<std::string, std::vector<unsigned int> *>::const_iterator;
//! Model name
std::string mModelName;
//! List ob assigned objects
std::vector<Object *> mObjects;
//! Pointer to current object
ObjFile::Object *mCurrentObject;
//! Pointer to current material
ObjFile::Material *mCurrentMaterial;
//! Pointer to default material
ObjFile::Material *mDefaultMaterial;
//! Vector with all generated materials
std::vector<std::string> mMaterialLib;
//! Vector with all generated vertices
std::vector<aiVector3D> mVertices;
//! vector with all generated normals
std::vector<aiVector3D> mNormals;
//! vector with all vertex colors
std::vector<aiVector3D> mVertexColors;
//! Group map
GroupMap mGroups;
//! Group to face id assignment
std::vector<unsigned int> *mGroupFaceIDs;
//! Active group
std::string mActiveGroup;
//! Vector with generated texture coordinates
std::vector<aiVector3D> mTextureCoord;
//! Maximum dimension of texture coordinates
unsigned int mTextureCoordDim;
//! Current mesh instance
Mesh *mCurrentMesh;
//! Vector with stored meshes
std::vector<Mesh *> mMeshes;
//! Material map
std::map<std::string, Material*> mMaterialMap;
//! \brief The default class constructor
Model() :
mModelName(),
mCurrentObject(nullptr),
mCurrentMaterial(nullptr),
mDefaultMaterial(nullptr),
mGroupFaceIDs(nullptr),
mActiveGroup(),
mTextureCoordDim(0),
mCurrentMesh(nullptr) {
// empty
}
//! \brief The class destructor
~Model() {
for (auto & it : mObjects) {
delete it;
}
for (auto & Meshe : mMeshes) {
delete Meshe;
}
for (auto & Group : mGroups) {
delete Group.second;
}
for (auto & it : mMaterialMap) {
delete it.second;
}
}
};
// ------------------------------------------------------------------------------------------------
} // Namespace ObjFile
} // Namespace Assimp
#endif // OBJ_FILEDATA_H_INC

View file

@ -0,0 +1,800 @@
/*
---------------------------------------------------------------------------
Open Asset Import Library (assimp)
---------------------------------------------------------------------------
Copyright (c) 2006-2026, 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.
---------------------------------------------------------------------------
*/
#ifndef ASSIMP_BUILD_NO_OBJ_IMPORTER
#include "ObjFileImporter.h"
#include "ObjFileData.h"
#include "ObjFileParser.h"
#include <assimp/DefaultIOSystem.h>
#include <assimp/IOStreamBuffer.h>
#include <assimp/ai_assert.h>
#include <assimp/importerdesc.h>
#include <assimp/scene.h>
#include <assimp/DefaultLogger.hpp>
#include <assimp/Importer.hpp>
#include <assimp/ObjMaterial.h>
#include <memory>
static constexpr aiImporterDesc desc = {
"Wavefront Object Importer",
"",
"",
"surfaces not supported",
aiImporterFlags_SupportTextFlavour,
0,
0,
0,
0,
"obj"
};
static constexpr unsigned int ObjMinSize = 16u;
namespace Assimp {
using namespace std;
// ------------------------------------------------------------------------------------------------
// Default constructor
ObjFileImporter::ObjFileImporter() :
m_Buffer(),
m_pRootObject(nullptr),
m_strAbsPath(std::string(1, DefaultIOSystem().getOsSeparator())) {
// empty
}
// ------------------------------------------------------------------------------------------------
// Destructor.
ObjFileImporter::~ObjFileImporter() {
delete m_pRootObject;
}
// ------------------------------------------------------------------------------------------------
// Returns true if file is an obj file.
bool ObjFileImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
static const char *tokens[] = { "mtllib", "usemtl", "v ", "vt ", "vn ", "o ", "g ", "s ", "f " };
return BaseImporter::SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens), 200, false, true);
}
// ------------------------------------------------------------------------------------------------
const aiImporterDesc *ObjFileImporter::GetInfo() const {
return &desc;
}
// ------------------------------------------------------------------------------------------------
// Obj-file import implementation
void ObjFileImporter::InternReadFile(const std::string &file, aiScene *pScene, IOSystem *pIOHandler) {
if (m_pRootObject != nullptr) {
delete m_pRootObject;
m_pRootObject = nullptr;
}
// Read file into memory
static constexpr char mode[] = "rb";
auto streamCloser = [&](IOStream *pStream) {
pIOHandler->Close(pStream);
};
std::unique_ptr<IOStream, decltype(streamCloser)> fileStream(pIOHandler->Open(file, mode), streamCloser);
if (!fileStream) {
throw DeadlyImportError("Failed to open file ", file, ".");
}
// Get the file-size and validate it, throwing an exception when fails
size_t fileSize = fileStream->FileSize();
if (fileSize < ObjMinSize) {
throw DeadlyImportError("OBJ-file is too small.");
}
IOStreamBuffer<char> streamedBuffer;
streamedBuffer.open(fileStream.get());
// Allocate buffer and read file into it
//TextFileToBuffer( fileStream.get(),m_Buffer);
// Get the model name
std::string modelName, folderName;
std::string::size_type pos = file.find_last_of("\\/");
if (pos != std::string::npos) {
modelName = file.substr(pos + 1, file.size() - pos - 1);
folderName = file.substr(0, pos);
if (!folderName.empty()) {
pIOHandler->PushDirectory(folderName);
}
} else {
modelName = file;
}
// parse the file into a temporary representation
ObjFileParser parser(streamedBuffer, modelName, pIOHandler, m_progress, file);
// And create the proper return structures out of it
CreateDataFromImport(parser.GetModel(), pScene);
streamedBuffer.close();
// Clean up allocated storage for the next import
m_Buffer.clear();
// Pop directory stack
if (pIOHandler->StackSize() > 0) {
pIOHandler->PopDirectory();
}
}
// ------------------------------------------------------------------------------------------------
// Create the data from parsed obj-file
void ObjFileImporter::CreateDataFromImport(const ObjFile::Model *pModel, aiScene *pScene) {
if (pModel == nullptr) {
return;
}
// Create the root node of the scene
pScene->mRootNode = new aiNode;
if (!pModel->mModelName.empty()) {
// Set the name of the scene
pScene->mRootNode->mName.Set(pModel->mModelName);
} else {
// This is a fatal error, so break down the application
ai_assert(false);
}
if (!pModel->mObjects.empty()) {
unsigned int meshCount = 0;
unsigned int childCount = 0;
for (auto object : pModel->mObjects) {
if (object) {
++childCount;
meshCount += (unsigned int)object->m_Meshes.size();
}
}
// Allocate space for the child nodes on the root node
pScene->mRootNode->mChildren = new aiNode *[childCount];
// Create nodes for the whole scene
std::vector<std::unique_ptr<aiMesh>> MeshArray;
MeshArray.reserve(meshCount);
for (size_t index = 0; index < pModel->mObjects.size(); ++index) {
createNodes(pModel, pModel->mObjects[index], pScene->mRootNode, pScene, MeshArray);
}
ai_assert(pScene->mRootNode->mNumChildren == childCount);
// Create mesh pointer buffer for this scene
if (pScene->mNumMeshes > 0) {
pScene->mMeshes = new aiMesh *[MeshArray.size()];
for (size_t index = 0; index < MeshArray.size(); ++index) {
pScene->mMeshes[index] = MeshArray[index].release();
}
}
// Create all materials
createMaterials(pModel, pScene);
} else {
if (pModel->mVertices.empty()) {
return;
}
std::unique_ptr<aiMesh> mesh(new aiMesh);
mesh->mPrimitiveTypes = aiPrimitiveType_POINT;
unsigned int n = (unsigned int)pModel->mVertices.size();
mesh->mNumVertices = n;
mesh->mVertices = new aiVector3D[n];
memcpy(mesh->mVertices, pModel->mVertices.data(), n * sizeof(aiVector3D));
if (!pModel->mNormals.empty()) {
mesh->mNormals = new aiVector3D[n];
if (pModel->mNormals.size() < n) {
throw DeadlyImportError("OBJ: vertex normal index out of range");
}
memcpy(mesh->mNormals, pModel->mNormals.data(), n * sizeof(aiVector3D));
}
if (!pModel->mVertexColors.empty()) {
mesh->mColors[0] = new aiColor4D[mesh->mNumVertices];
for (unsigned int i = 0; i < n; ++i) {
if (i < pModel->mVertexColors.size()) {
const aiVector3D &color = pModel->mVertexColors[i];
mesh->mColors[0][i] = aiColor4D(color.x, color.y, color.z, 1.0);
} else {
throw DeadlyImportError("OBJ: vertex color index out of range");
}
}
}
pScene->mRootNode->mNumMeshes = 1;
pScene->mRootNode->mMeshes = new unsigned int[1];
pScene->mRootNode->mMeshes[0] = 0;
pScene->mMeshes = new aiMesh *[1];
pScene->mNumMeshes = 1;
pScene->mMeshes[0] = mesh.release();
}
}
// ------------------------------------------------------------------------------------------------
// Creates all nodes of the model
aiNode *ObjFileImporter::createNodes(const ObjFile::Model *pModel, const ObjFile::Object *pObject,
aiNode *pParent, aiScene *pScene,
std::vector<std::unique_ptr<aiMesh>> &MeshArray) {
if (nullptr == pObject || pModel == nullptr) {
return nullptr;
}
// Store older mesh size to be able to computes mesh offsets for new mesh instances
const size_t oldMeshSize = MeshArray.size();
aiNode *pNode = new aiNode;
pNode->mName = pObject->m_strObjName;
// If we have a parent node, store it
ai_assert(nullptr != pParent);
appendChildToParentNode(pParent, pNode);
for (size_t i = 0; i < pObject->m_Meshes.size(); ++i) {
unsigned int meshId = pObject->m_Meshes[i];
std::unique_ptr<aiMesh> pMesh = createTopology(pModel, pObject, meshId);
if (pMesh != nullptr) {
if (pMesh->mNumFaces > 0) {
MeshArray.push_back(std::move(pMesh));
}
}
}
// Create all nodes from the sub-objects stored in the current object
if (!pObject->m_SubObjects.empty()) {
size_t numChilds = pObject->m_SubObjects.size();
pNode->mNumChildren = static_cast<unsigned int>(numChilds);
pNode->mChildren = new aiNode *[numChilds];
pNode->mNumMeshes = 1;
pNode->mMeshes = new unsigned int[1];
}
// Set mesh instances into scene- and node-instances
const size_t meshSizeDiff = MeshArray.size() - oldMeshSize;
if (meshSizeDiff > 0) {
pNode->mMeshes = new unsigned int[meshSizeDiff];
pNode->mNumMeshes = static_cast<unsigned int>(meshSizeDiff);
size_t index = 0;
for (size_t i = oldMeshSize; i < MeshArray.size(); ++i) {
pNode->mMeshes[index] = pScene->mNumMeshes;
pScene->mNumMeshes++;
++index;
}
}
return pNode;
}
// ------------------------------------------------------------------------------------------------
// Create topology data
std::unique_ptr<aiMesh> ObjFileImporter::createTopology(const ObjFile::Model *pModel, const ObjFile::Object *pData, unsigned int meshIndex) {
if (nullptr == pData || pModel == nullptr) {
return nullptr;
}
// Create faces
ObjFile::Mesh *pObjMesh = pModel->mMeshes[meshIndex];
if (pObjMesh == nullptr) {
return nullptr;
}
if (pObjMesh->m_Faces.empty()) {
return nullptr;
}
std::unique_ptr<aiMesh> pMesh(new aiMesh);
if (!pObjMesh->m_name.empty()) {
pMesh->mName.Set(pObjMesh->m_name);
}
for (size_t index = 0; index < pObjMesh->m_Faces.size(); index++) {
const ObjFile::Face *inp = pObjMesh->m_Faces[index];
if (inp == nullptr) {
continue;
}
if (inp->mPrimitiveType == aiPrimitiveType_LINE) {
pMesh->mNumFaces += static_cast<unsigned int>(inp->m_vertices.size() - 1);
pMesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
} else if (inp->mPrimitiveType == aiPrimitiveType_POINT) {
pMesh->mNumFaces += static_cast<unsigned int>(inp->m_vertices.size());
pMesh->mPrimitiveTypes |= aiPrimitiveType_POINT;
} else {
++pMesh->mNumFaces;
if (inp->m_vertices.size() > 3) {
pMesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
} else {
pMesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
}
}
}
unsigned int uiIdxCount = 0u;
if (pMesh->mNumFaces > 0) {
pMesh->mFaces = new aiFace[pMesh->mNumFaces];
if (pObjMesh->m_uiMaterialIndex != ObjFile::Mesh::NoMaterial) {
pMesh->mMaterialIndex = pObjMesh->m_uiMaterialIndex;
}
unsigned int outIndex = 0u;
// Copy all data from all stored meshes
for (auto &face : pObjMesh->m_Faces) {
const ObjFile::Face *inp = face;
if (inp->mPrimitiveType == aiPrimitiveType_LINE) {
for (size_t i = 0; i < inp->m_vertices.size() - 1; ++i) {
aiFace &f = pMesh->mFaces[outIndex++];
uiIdxCount += f.mNumIndices = 2;
f.mIndices = new unsigned int[2];
}
continue;
} else if (inp->mPrimitiveType == aiPrimitiveType_POINT) {
for (size_t i = 0; i < inp->m_vertices.size(); ++i) {
aiFace &f = pMesh->mFaces[outIndex++];
uiIdxCount += f.mNumIndices = 1;
f.mIndices = new unsigned int[1];
}
continue;
}
aiFace *pFace = &pMesh->mFaces[outIndex++];
const unsigned int uiNumIndices = (unsigned int)face->m_vertices.size();
uiIdxCount += pFace->mNumIndices = (unsigned int)uiNumIndices;
if (pFace->mNumIndices > 0) {
pFace->mIndices = new unsigned int[uiNumIndices];
}
}
}
// Create mesh vertices
createVertexArray(pModel, pData, meshIndex, pMesh.get(), uiIdxCount);
return pMesh;
}
// ------------------------------------------------------------------------------------------------
// Creates a vertex array
void ObjFileImporter::createVertexArray(const ObjFile::Model *pModel,
const ObjFile::Object *pCurrentObject,
unsigned int uiMeshIndex,
aiMesh *pMesh,
unsigned int numIndices) {
// Checking preconditions
if (pCurrentObject == nullptr || pModel == nullptr || pMesh == nullptr) {
return;
}
// Break, if no faces are stored in object
if (pCurrentObject->m_Meshes.empty()) {
return;
}
// Get current mesh
ObjFile::Mesh *pObjMesh = pModel->mMeshes[uiMeshIndex];
if (nullptr == pObjMesh || pObjMesh->m_uiNumIndices < 1) {
return;
}
// Copy vertices of this mesh instance
pMesh->mNumVertices = numIndices;
if (pMesh->mNumVertices == 0) {
throw DeadlyImportError("OBJ: no vertices");
} else if (pMesh->mNumVertices > AI_MAX_VERTICES) {
throw DeadlyImportError("OBJ: Too many vertices");
}
pMesh->mVertices = new aiVector3D[pMesh->mNumVertices];
// Allocate buffer for normal vectors
if (!pModel->mNormals.empty() && pObjMesh->m_hasNormals)
pMesh->mNormals = new aiVector3D[pMesh->mNumVertices];
// Allocate buffer for vertex-color vectors
if (!pModel->mVertexColors.empty())
pMesh->mColors[0] = new aiColor4D[pMesh->mNumVertices];
// Allocate buffer for texture coordinates
if (!pModel->mTextureCoord.empty() && pObjMesh->m_uiUVCoordinates[0]) {
pMesh->mNumUVComponents[0] = pModel->mTextureCoordDim;
pMesh->mTextureCoords[0] = new aiVector3D[pMesh->mNumVertices];
}
// Copy vertices, normals and textures into aiMesh instance
bool normalsok = true, uvok = true;
unsigned int newIndex = 0, outIndex = 0;
for (auto sourceFace : pObjMesh->m_Faces) {
// Copy all index arrays
for (size_t vertexIndex = 0, outVertexIndex = 0; vertexIndex < sourceFace->m_vertices.size(); vertexIndex++) {
const unsigned int vertex = sourceFace->m_vertices.at(vertexIndex);
if (vertex >= pModel->mVertices.size()) {
throw DeadlyImportError("OBJ: vertex index out of range");
}
if (pMesh->mNumVertices <= newIndex) {
throw DeadlyImportError("OBJ: bad vertex index");
}
pMesh->mVertices[newIndex] = pModel->mVertices[vertex];
// Copy all normals
if (normalsok && !pModel->mNormals.empty() && vertexIndex < sourceFace->m_normals.size()) {
const unsigned int normal = sourceFace->m_normals.at(vertexIndex);
if (normal >= pModel->mNormals.size()) {
normalsok = false;
} else {
pMesh->mNormals[newIndex] = pModel->mNormals[normal];
}
}
// Copy all vertex colors
if (vertex < pModel->mVertexColors.size()) {
const aiVector3D &color = pModel->mVertexColors[vertex];
pMesh->mColors[0][newIndex] = aiColor4D(color.x, color.y, color.z, 1.0);
}
// Copy all texture coordinates
if (uvok && !pModel->mTextureCoord.empty() && vertexIndex < sourceFace->m_texturCoords.size()) {
const unsigned int tex = sourceFace->m_texturCoords.at(vertexIndex);
if (tex >= pModel->mTextureCoord.size()) {
uvok = false;
} else {
const aiVector3D &coord3d = pModel->mTextureCoord[tex];
pMesh->mTextureCoords[0][newIndex] = aiVector3D(coord3d.x, coord3d.y, coord3d.z);
}
}
// Get destination face
aiFace *pDestFace = &pMesh->mFaces[outIndex];
const bool last = (vertexIndex == sourceFace->m_vertices.size() - 1);
if (sourceFace->mPrimitiveType != aiPrimitiveType_LINE || !last) {
pDestFace->mIndices[outVertexIndex] = newIndex;
outVertexIndex++;
}
if (sourceFace->mPrimitiveType == aiPrimitiveType_POINT) {
outIndex++;
outVertexIndex = 0;
} else if (sourceFace->mPrimitiveType == aiPrimitiveType_LINE) {
outVertexIndex = 0;
if (!last)
outIndex++;
if (vertexIndex) {
if (!last) {
if (pMesh->mNumVertices <= newIndex + 1) {
throw DeadlyImportError("OBJ: bad vertex index");
}
pMesh->mVertices[newIndex + 1] = pMesh->mVertices[newIndex];
if (!sourceFace->m_normals.empty() && !pModel->mNormals.empty()) {
pMesh->mNormals[newIndex + 1] = pMesh->mNormals[newIndex];
}
if (!pModel->mTextureCoord.empty()) {
for (size_t i = 0; i < pMesh->GetNumUVChannels(); i++) {
pMesh->mTextureCoords[i][newIndex + 1] = pMesh->mTextureCoords[i][newIndex];
}
}
++newIndex;
}
pDestFace[-1].mIndices[1] = newIndex;
}
} else if (last) {
outIndex++;
}
++newIndex;
}
}
if (!normalsok) {
delete[] pMesh->mNormals;
pMesh->mNormals = nullptr;
}
if (!uvok) {
delete[] pMesh->mTextureCoords[0];
pMesh->mTextureCoords[0] = nullptr;
}
}
// ------------------------------------------------------------------------------------------------
// Counts all stored meshes
void ObjFileImporter::countObjects(const std::vector<ObjFile::Object *> &rObjects, int &iNumMeshes) {
iNumMeshes = 0;
if (rObjects.empty())
return;
iNumMeshes += static_cast<unsigned int>(rObjects.size());
for (auto object : rObjects) {
if (!object->m_SubObjects.empty()) {
countObjects(object->m_SubObjects, iNumMeshes);
}
}
}
// ------------------------------------------------------------------------------------------------
// Add clamp mode property to material if necessary
void ObjFileImporter::addTextureMappingModeProperty(aiMaterial *mat, aiTextureType type, int clampMode, int index) {
if (nullptr == mat) {
return;
}
mat->AddProperty<int>(&clampMode, 1, AI_MATKEY_MAPPINGMODE_U(type, index));
mat->AddProperty<int>(&clampMode, 1, AI_MATKEY_MAPPINGMODE_V(type, index));
}
// ------------------------------------------------------------------------------------------------
// Creates the material
void ObjFileImporter::createMaterials(const ObjFile::Model *pModel, aiScene *pScene) {
if (nullptr == pScene) {
return;
}
const unsigned int numMaterials = (unsigned int)pModel->mMaterialLib.size();
pScene->mNumMaterials = 0;
if (pModel->mMaterialLib.empty()) {
ASSIMP_LOG_DEBUG("OBJ: no materials specified");
return;
}
pScene->mMaterials = new aiMaterial *[numMaterials];
for (unsigned int matIndex = 0; matIndex < numMaterials; matIndex++) {
// Store material name
std::map<std::string, ObjFile::Material *>::const_iterator it;
it = pModel->mMaterialMap.find(pModel->mMaterialLib[matIndex]);
// No material found, use the default material
if (pModel->mMaterialMap.end() == it) {
continue;
}
aiMaterial *mat = new aiMaterial;
ObjFile::Material *pCurrentMaterial = it->second;
mat->AddProperty(&pCurrentMaterial->MaterialName, AI_MATKEY_NAME);
// convert illumination model
int sm = 0;
switch (pCurrentMaterial->illumination_model) {
case 0:
sm = aiShadingMode_NoShading;
break;
case 1:
sm = aiShadingMode_Gouraud;
break;
case 2:
sm = aiShadingMode_Phong;
break;
default:
sm = aiShadingMode_Gouraud;
ASSIMP_LOG_ERROR("OBJ: unexpected illumination model (0-2 recognized)");
}
mat->AddProperty<int>(&sm, 1, AI_MATKEY_SHADING_MODEL);
// Preserve the original illum value
mat->AddProperty<int>(&pCurrentMaterial->illumination_model, 1, AI_MATKEY_OBJ_ILLUM);
// Adding material colors
mat->AddProperty(&pCurrentMaterial->ambient, 1, AI_MATKEY_COLOR_AMBIENT);
mat->AddProperty(&pCurrentMaterial->diffuse, 1, AI_MATKEY_COLOR_DIFFUSE);
mat->AddProperty(&pCurrentMaterial->specular, 1, AI_MATKEY_COLOR_SPECULAR);
mat->AddProperty(&pCurrentMaterial->emissive, 1, AI_MATKEY_COLOR_EMISSIVE);
mat->AddProperty(&pCurrentMaterial->shineness, 1, AI_MATKEY_SHININESS);
mat->AddProperty(&pCurrentMaterial->alpha, 1, AI_MATKEY_OPACITY);
mat->AddProperty(&pCurrentMaterial->transparent, 1, AI_MATKEY_COLOR_TRANSPARENT);
if (pCurrentMaterial->roughness)
mat->AddProperty(&pCurrentMaterial->roughness.Get(), 1, AI_MATKEY_ROUGHNESS_FACTOR);
if (pCurrentMaterial->metallic)
mat->AddProperty(&pCurrentMaterial->metallic.Get(), 1, AI_MATKEY_METALLIC_FACTOR);
if (pCurrentMaterial->sheen)
mat->AddProperty(&pCurrentMaterial->sheen.Get(), 1, AI_MATKEY_SHEEN_COLOR_FACTOR);
if (pCurrentMaterial->clearcoat_thickness)
mat->AddProperty(&pCurrentMaterial->clearcoat_thickness.Get(), 1, AI_MATKEY_CLEARCOAT_FACTOR);
if (pCurrentMaterial->clearcoat_roughness)
mat->AddProperty(&pCurrentMaterial->clearcoat_roughness.Get(), 1, AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR);
mat->AddProperty(&pCurrentMaterial->anisotropy, 1, AI_MATKEY_ANISOTROPY_FACTOR);
// Adding refraction index
mat->AddProperty(&pCurrentMaterial->ior, 1, AI_MATKEY_REFRACTI);
// Adding textures
const int uvwIndex = 0;
if (0 != pCurrentMaterial->texture.length) {
mat->AddProperty(&pCurrentMaterial->texture, AI_MATKEY_TEXTURE_DIFFUSE(0));
mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_DIFFUSE(0));
if (pCurrentMaterial->clamp[ObjFile::Material::TextureDiffuseType]) {
addTextureMappingModeProperty(mat, aiTextureType_DIFFUSE);
}
}
if (0 != pCurrentMaterial->textureAmbient.length) {
mat->AddProperty(&pCurrentMaterial->textureAmbient, AI_MATKEY_TEXTURE_AMBIENT(0));
mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_AMBIENT(0));
if (pCurrentMaterial->clamp[ObjFile::Material::TextureAmbientType]) {
addTextureMappingModeProperty(mat, aiTextureType_AMBIENT);
}
}
if (0 != pCurrentMaterial->textureEmissive.length) {
mat->AddProperty(&pCurrentMaterial->textureEmissive, AI_MATKEY_TEXTURE_EMISSIVE(0));
mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_EMISSIVE(0));
}
if (0 != pCurrentMaterial->textureSpecular.length) {
mat->AddProperty(&pCurrentMaterial->textureSpecular, AI_MATKEY_TEXTURE_SPECULAR(0));
mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_SPECULAR(0));
if (pCurrentMaterial->clamp[ObjFile::Material::TextureSpecularType]) {
addTextureMappingModeProperty(mat, aiTextureType_SPECULAR);
}
}
if (0 != pCurrentMaterial->textureBump.length) {
mat->AddProperty(&pCurrentMaterial->textureBump, AI_MATKEY_TEXTURE_HEIGHT(0));
mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_HEIGHT(0));
if (pCurrentMaterial->bump_multiplier != 1.0) {
mat->AddProperty(&pCurrentMaterial->bump_multiplier, 1, AI_MATKEY_OBJ_BUMPMULT_HEIGHT(0));
}
if (pCurrentMaterial->clamp[ObjFile::Material::TextureBumpType]) {
addTextureMappingModeProperty(mat, aiTextureType_HEIGHT);
}
}
if (0 != pCurrentMaterial->textureNormal.length) {
mat->AddProperty(&pCurrentMaterial->textureNormal, AI_MATKEY_TEXTURE_NORMALS(0));
mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_NORMALS(0));
if (pCurrentMaterial->bump_multiplier != 1.0) {
mat->AddProperty(&pCurrentMaterial->bump_multiplier, 1, AI_MATKEY_OBJ_BUMPMULT_NORMALS(0));
}
if (pCurrentMaterial->clamp[ObjFile::Material::TextureNormalType]) {
addTextureMappingModeProperty(mat, aiTextureType_NORMALS);
}
}
if (0 != pCurrentMaterial->textureReflection[0].length) {
ObjFile::Material::TextureType type = 0 != pCurrentMaterial->textureReflection[1].length ?
ObjFile::Material::TextureReflectionCubeTopType :
ObjFile::Material::TextureReflectionSphereType;
unsigned count = type == ObjFile::Material::TextureReflectionSphereType ? 1 : 6;
for (unsigned i = 0; i < count; i++) {
mat->AddProperty(&pCurrentMaterial->textureReflection[i], AI_MATKEY_TEXTURE_REFLECTION(i));
mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_REFLECTION(i));
if (pCurrentMaterial->clamp[type])
addTextureMappingModeProperty(mat, aiTextureType_REFLECTION, 1, i);
}
}
if (0 != pCurrentMaterial->textureDisp.length) {
mat->AddProperty(&pCurrentMaterial->textureDisp, AI_MATKEY_TEXTURE_DISPLACEMENT(0));
mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_DISPLACEMENT(0));
if (pCurrentMaterial->clamp[ObjFile::Material::TextureDispType]) {
addTextureMappingModeProperty(mat, aiTextureType_DISPLACEMENT);
}
}
if (0 != pCurrentMaterial->textureOpacity.length) {
mat->AddProperty(&pCurrentMaterial->textureOpacity, AI_MATKEY_TEXTURE_OPACITY(0));
mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_OPACITY(0));
if (pCurrentMaterial->clamp[ObjFile::Material::TextureOpacityType]) {
addTextureMappingModeProperty(mat, aiTextureType_OPACITY);
}
}
if (0 != pCurrentMaterial->textureSpecularity.length) {
mat->AddProperty(&pCurrentMaterial->textureSpecularity, AI_MATKEY_TEXTURE_SHININESS(0));
mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_SHININESS(0));
if (pCurrentMaterial->clamp[ObjFile::Material::TextureSpecularityType]) {
addTextureMappingModeProperty(mat, aiTextureType_SHININESS);
}
}
if (0 != pCurrentMaterial->textureRoughness.length) {
mat->AddProperty(&pCurrentMaterial->textureRoughness, _AI_MATKEY_TEXTURE_BASE, aiTextureType_DIFFUSE_ROUGHNESS, 0);
mat->AddProperty(&uvwIndex, 1, _AI_MATKEY_UVWSRC_BASE, aiTextureType_DIFFUSE_ROUGHNESS, 0 );
if (pCurrentMaterial->clamp[ObjFile::Material::TextureRoughnessType]) {
addTextureMappingModeProperty(mat, aiTextureType_DIFFUSE_ROUGHNESS);
}
}
if (0 != pCurrentMaterial->textureMetallic.length) {
mat->AddProperty(&pCurrentMaterial->textureMetallic, _AI_MATKEY_TEXTURE_BASE, aiTextureType_METALNESS, 0);
mat->AddProperty(&uvwIndex, 1, _AI_MATKEY_UVWSRC_BASE, aiTextureType_METALNESS, 0 );
if (pCurrentMaterial->clamp[ObjFile::Material::TextureMetallicType]) {
addTextureMappingModeProperty(mat, aiTextureType_METALNESS);
}
}
if (0 != pCurrentMaterial->textureSheen.length) {
mat->AddProperty(&pCurrentMaterial->textureSheen, _AI_MATKEY_TEXTURE_BASE, aiTextureType_SHEEN, 0);
mat->AddProperty(&uvwIndex, 1, _AI_MATKEY_UVWSRC_BASE, aiTextureType_SHEEN, 0 );
if (pCurrentMaterial->clamp[ObjFile::Material::TextureSheenType]) {
addTextureMappingModeProperty(mat, aiTextureType_SHEEN);
}
}
if (0 != pCurrentMaterial->textureRMA.length) {
// NOTE: glTF importer places Rough/Metal/AO texture in Unknown so doing the same here for consistency.
mat->AddProperty(&pCurrentMaterial->textureRMA, _AI_MATKEY_TEXTURE_BASE, aiTextureType_UNKNOWN, 0);
mat->AddProperty(&uvwIndex, 1, _AI_MATKEY_UVWSRC_BASE, aiTextureType_UNKNOWN, 0 );
if (pCurrentMaterial->clamp[ObjFile::Material::TextureRMAType]) {
addTextureMappingModeProperty(mat, aiTextureType_UNKNOWN);
}
}
// Store material property info in material array in scene
pScene->mMaterials[pScene->mNumMaterials] = mat;
pScene->mNumMaterials++;
}
// Test number of created materials.
ai_assert(pScene->mNumMaterials == numMaterials);
}
// ------------------------------------------------------------------------------------------------
// Appends this node to the parent node
void ObjFileImporter::appendChildToParentNode(aiNode *pParent, aiNode *pChild) {
// Checking preconditions
if (pParent == nullptr || pChild == nullptr) {
ai_assert(nullptr != pParent);
ai_assert(nullptr != pChild);
return;
}
// Assign parent to child
pChild->mParent = pParent;
// Copy node instances into parent node
pParent->mNumChildren++;
pParent->mChildren[pParent->mNumChildren - 1] = pChild;
}
// ------------------------------------------------------------------------------------------------
} // Namespace Assimp
#endif // !! ASSIMP_BUILD_NO_OBJ_IMPORTER

View file

@ -0,0 +1,123 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2020, 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.
----------------------------------------------------------------------
*/
#pragma once
#ifndef OBJ_FILE_IMPORTER_H_INC
#define OBJ_FILE_IMPORTER_H_INC
#include <assimp/BaseImporter.h>
#include <assimp/material.h>
#include <memory>
#include <vector>
struct aiMesh;
struct aiNode;
namespace Assimp {
namespace ObjFile {
struct Object;
struct Model;
} // namespace ObjFile
// ------------------------------------------------------------------------------------------------
/// \class ObjFileImporter
/// \brief Imports a waveform obj file
// ------------------------------------------------------------------------------------------------
class ObjFileImporter final : public BaseImporter {
public:
/// \brief Default constructor
ObjFileImporter();
/// \brief Destructor
~ObjFileImporter() override;
/// \brief Returns whether the class can handle the format of the given file.
/// \remark See BaseImporter::CanRead() for details.
bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override;
protected:
//! \brief Appends the supported extension.
const aiImporterDesc *GetInfo() const override;
//! \brief File import implementation.
void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override;
//! \brief Create the data from imported content.
void CreateDataFromImport(const ObjFile::Model *pModel, aiScene *pScene);
//! \brief Creates all nodes stored in imported content.
aiNode *createNodes(const ObjFile::Model *pModel, const ObjFile::Object *pData,
aiNode *pParent, aiScene *pScene, std::vector<std::unique_ptr<aiMesh>> &MeshArray);
//! \brief Creates topology data like faces and meshes for the geometry.
std::unique_ptr<aiMesh> createTopology(const ObjFile::Model *pModel, const ObjFile::Object *pData,
unsigned int uiMeshIndex);
//! \brief Creates vertices from model.
void createVertexArray(const ObjFile::Model *pModel, const ObjFile::Object *pCurrentObject,
unsigned int uiMeshIndex, aiMesh *pMesh, unsigned int numIndices);
//! \brief Object counter helper method.
void countObjects(const std::vector<ObjFile::Object *> &rObjects, int &iNumMeshes);
//! \brief Material creation.
void createMaterials(const ObjFile::Model *pModel, aiScene *pScene);
/// @brief Adds special property for the used texture mapping mode of the model.
void addTextureMappingModeProperty(aiMaterial *mat, aiTextureType type, int clampMode = 1, int index = 0);
//! \brief Appends a child node to a parent node and updates the data structures.
void appendChildToParentNode(aiNode *pParent, aiNode *pChild);
private:
//! Data buffer
std::vector<char> m_Buffer;
//! Pointer to root object instance
ObjFile::Object *m_pRootObject;
//! Absolute pathname of model in file system
std::string m_strAbsPath;
};
// ------------------------------------------------------------------------------------------------
} // Namespace Assimp
#endif

View file

@ -0,0 +1,660 @@
/*
---------------------------------------------------------------------------
Open Asset Import Library (assimp)
---------------------------------------------------------------------------
Copyright (c) 2006-2026, 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.
---------------------------------------------------------------------------
*/
#ifndef ASSIMP_BUILD_NO_OBJ_IMPORTER
#include "ObjFileMtlImporter.h"
#include "ObjFileData.h"
#include "ObjTools.h"
#include <assimp/DefaultIOSystem.h>
#include <assimp/ParsingUtils.h>
#include <assimp/fast_atof.h>
#include <assimp/material.h>
#include <stdlib.h>
#include <assimp/DefaultLogger.hpp>
namespace Assimp {
// Material specific token (case insensitive compare)
static constexpr char DiffuseTexture[] = "map_Kd";
static constexpr char AmbientTexture[] = "map_Ka";
static constexpr char SpecularTexture[] = "map_Ks";
static constexpr char OpacityTexture[] = "map_d";
static constexpr char EmissiveTexture1[] = "map_emissive";
static constexpr char EmissiveTexture2[] = "map_Ke";
static constexpr char BumpTexture1[] = "map_bump";
static constexpr char BumpTexture2[] = "bump";
static constexpr char NormalTextureV1[] = "map_Kn";
static constexpr char NormalTextureV2[] = "norm";
static constexpr char ReflectionTexture[] = "refl";
static constexpr char DisplacementTexture1[] = "map_disp";
static constexpr char DisplacementTexture2[] = "disp";
static constexpr char SpecularityTexture[] = "map_ns";
static constexpr char RoughnessTexture[] = "map_Pr";
static constexpr char MetallicTexture[] = "map_Pm";
static constexpr char SheenTexture[] = "map_Ps";
static constexpr char RMATexture[] = "map_Ps";
// texture option specific token
static constexpr char BlendUOption[] = "-blendu";
static constexpr char BlendVOption[] = "-blendv";
static constexpr char BoostOption[] = "-boost";
static constexpr char ModifyMapOption[] = "-mm";
static constexpr char OffsetOption[] = "-o";
static constexpr char ScaleOption[] = "-s";
static constexpr char TurbulenceOption[] = "-t";
static constexpr char ResolutionOption[] = "-texres";
static constexpr char ClampOption[] = "-clamp";
static constexpr char BumpOption[] = "-bm";
static constexpr char ChannelOption[] = "-imfchan";
static constexpr char TypeOption[] = "-type";
// -------------------------------------------------------------------
// Constructor
ObjFileMtlImporter::ObjFileMtlImporter(std::vector<char> &buffer,
const std::string &strAbsPath,
ObjFile::Model *pModel) :
m_strAbsPath(strAbsPath),
m_DataIt(buffer.begin()),
m_DataItEnd(buffer.end()),
m_pModel(pModel),
m_uiLine(0),
m_buffer() {
ai_assert(nullptr != m_pModel);
m_buffer.resize(BUFFERSIZE);
std::fill(m_buffer.begin(), m_buffer.end(), '\0');
if (nullptr == m_pModel->mDefaultMaterial) {
m_pModel->mDefaultMaterial = new ObjFile::Material;
m_pModel->mDefaultMaterial->MaterialName.Set("default");
}
// Try with OS folder separator first
char folderSeparator = DefaultIOSystem().getOsSeparator();
std::size_t found = m_strAbsPath.find_last_of(folderSeparator);
if (found == std::string::npos) {
// Not found, try alternative folder separator
folderSeparator = (folderSeparator == '/' ? '\\' : '/');
found = m_strAbsPath.find_last_of(folderSeparator);
}
if (found != std::string::npos) {
m_strAbsPath = m_strAbsPath.substr(0, found + 1);
} else {
m_strAbsPath = "";
}
load();
}
// -------------------------------------------------------------------
// Loads the material description
void ObjFileMtlImporter::load() {
if (m_DataIt == m_DataItEnd)
return;
while (m_DataIt != m_DataItEnd) {
switch (*m_DataIt) {
case 'k':
case 'K': {
++m_DataIt;
if (*m_DataIt == 'a') // Ambient color
{
++m_DataIt;
if (m_pModel->mCurrentMaterial != nullptr)
getColorRGBA(&m_pModel->mCurrentMaterial->ambient);
} else if (*m_DataIt == 'd') {
// Diffuse color
++m_DataIt;
if (m_pModel->mCurrentMaterial != nullptr)
getColorRGBA(&m_pModel->mCurrentMaterial->diffuse);
} else if (*m_DataIt == 's') {
++m_DataIt;
if (m_pModel->mCurrentMaterial != nullptr)
getColorRGBA(&m_pModel->mCurrentMaterial->specular);
} else if (*m_DataIt == 'e') {
++m_DataIt;
if (m_pModel->mCurrentMaterial != nullptr)
getColorRGBA(&m_pModel->mCurrentMaterial->emissive);
}
m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
} break;
case 'T': {
++m_DataIt;
// Material transmission color
if (*m_DataIt == 'f') {
++m_DataIt;
if (m_pModel->mCurrentMaterial != nullptr)
getColorRGBA(&m_pModel->mCurrentMaterial->transparent);
} else if (*m_DataIt == 'r') {
// Material transmission alpha value
++m_DataIt;
ai_real d;
getFloatValue(d);
if (m_pModel->mCurrentMaterial != nullptr)
m_pModel->mCurrentMaterial->alpha = static_cast<ai_real>(1.0) - d;
}
m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
} break;
case 'd': {
if (*(m_DataIt + 1) == 'i' && *(m_DataIt + 2) == 's' && *(m_DataIt + 3) == 'p') {
// A displacement map
getTexture();
} else {
// Alpha value
++m_DataIt;
if (m_pModel->mCurrentMaterial != nullptr)
getFloatValue(m_pModel->mCurrentMaterial->alpha);
m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
}
} break;
case 'N':
case 'n': {
++m_DataIt;
switch (*m_DataIt) {
case 's': // Specular exponent
++m_DataIt;
if (m_pModel->mCurrentMaterial != nullptr)
getFloatValue(m_pModel->mCurrentMaterial->shineness);
break;
case 'i': // Index Of refraction
++m_DataIt;
if (m_pModel->mCurrentMaterial != nullptr)
getFloatValue(m_pModel->mCurrentMaterial->ior);
break;
case 'e': // New material
createMaterial();
break;
case 'o': // Norm texture
--m_DataIt;
getTexture();
break;
}
m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
} break;
case 'P':
{
++m_DataIt;
switch(*m_DataIt)
{
case 'r':
++m_DataIt;
if (m_pModel->mCurrentMaterial != nullptr)
getFloatValue(m_pModel->mCurrentMaterial->roughness);
break;
case 'm':
++m_DataIt;
if (m_pModel->mCurrentMaterial != nullptr)
getFloatValue(m_pModel->mCurrentMaterial->metallic);
break;
case 's':
++m_DataIt;
if (m_pModel->mCurrentMaterial != nullptr)
getColorRGBA(m_pModel->mCurrentMaterial->sheen);
break;
case 'c':
++m_DataIt;
if (*m_DataIt == 'r') {
++m_DataIt;
if (m_pModel->mCurrentMaterial != nullptr)
getFloatValue(m_pModel->mCurrentMaterial->clearcoat_roughness);
} else if (*m_DataIt == 't') {
++m_DataIt;
if (m_pModel->mCurrentMaterial != nullptr)
getFloatValue(m_pModel->mCurrentMaterial->clearcoat_thickness);
} else {
if (m_pModel->mCurrentMaterial != nullptr)
getFloatValue(m_pModel->mCurrentMaterial->clearcoat);
}
break;
}
m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
}
break;
case 'm': // Texture or metallic
{
// Save start of token (after 'm')
auto tokenStart = m_DataIt; // points to 'm'
auto tokenEnd = getNextDelimiter(m_DataIt, m_DataItEnd); // move iterator to end of token
std::string keyword(tokenStart, tokenEnd);
m_DataIt = getNextWord(tokenEnd, m_DataItEnd); // advance iterator
if (keyword.compare(0, 3, "map") == 0) {
// starts with "map", treat as texture map
m_DataIt = tokenStart;
getTexture();
} else if (keyword == "metallic" || keyword == "metal" || keyword == "metalness") {
// parse metallic float value instead of texture
getFloatIfMaterialValid(&ObjFile::Material::metallic);
}
m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
} break;
case 'b': // quick'n'dirty - for 'bump' sections
{
getTexture();
m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
} break;
case 'r': // refl (map) or roughness (float)
{
auto tokenStart = m_DataIt; // points to 'r'
auto tokenEnd = getNextDelimiter(m_DataIt, m_DataItEnd);
std::string keyword(tokenStart, tokenEnd);
m_DataIt = getNextWord(tokenEnd, m_DataItEnd);
if (keyword == "roughness" || keyword == "rough") {
getFloatIfMaterialValid(&ObjFile::Material::roughness);
} else if (keyword == "refl" || keyword == "reflection") {
m_DataIt = tokenStart;
getTexture();
}
m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
} break;
case 'i': // Illumination model
{
m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
if (m_pModel->mCurrentMaterial != nullptr)
getIlluminationModel(m_pModel->mCurrentMaterial->illumination_model);
m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
} break;
case 'a': {
auto tokenStart = m_DataIt;
auto tokenEnd = getNextDelimiter(m_DataIt, m_DataItEnd);
std::string keyword(tokenStart, tokenEnd);
m_DataIt = getNextWord(tokenEnd, m_DataItEnd);
if (keyword == "aniso" || keyword == "anisotropy") {
getFloatIfMaterialValid(&ObjFile::Material::anisotropy);
} else if (keyword == "ao") {
getFloatIfMaterialValid(&ObjFile::Material::ambient_occlusion);
} else if (keyword == "anisor" || ai_stdStrToLower(keyword) == "anisotropicrotation") {
getFloatIfMaterialValid(&ObjFile::Material::anisotropy_rotation);
} else {
ASSIMP_LOG_WARN("Unhandled keyword: ", keyword );
}
m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
} break;
case 's': {
auto tokenStart = m_DataIt;
auto tokenEnd = getNextDelimiter(m_DataIt, m_DataItEnd);
std::string keyword(tokenStart, tokenEnd);
m_DataIt = getNextWord(tokenEnd,m_DataItEnd);
if (keyword == "subsurface" || keyword == "scattering") {
getFloatIfMaterialValid(&ObjFile::Material::subsurface_scattering);
} else if (ai_stdStrToLower(keyword) == "speculartint") {
getFloatIfMaterialValid(&ObjFile::Material::specular_tint);
} else if (keyword == "sheen") {
getFloatIfMaterialValid(&ObjFile::Material::sheen_grazing);
} else if (ai_stdStrToLower(keyword) == "sheentint") {
getFloatIfMaterialValid(&ObjFile::Material::sheen_tint);
} else {
ASSIMP_LOG_WARN("Unhandled keyword: ", keyword );
}
m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
} break;
case 'c': {
auto tokenStart = m_DataIt;
auto tokenEnd = getNextDelimiter(m_DataIt, m_DataItEnd);
std::string keyword(tokenStart, tokenEnd);
m_DataIt = getNextWord(tokenEnd, m_DataItEnd);
if (ai_stdStrToLower(keyword) == "clearcoat") {
getFloatIfMaterialValid(&ObjFile::Material::clearcoat);
} else if (ai_stdStrToLower(keyword) == "clearcoatgloss") {
getFloatIfMaterialValid(&ObjFile::Material::clearcoat_gloss);
} else {
ASSIMP_LOG_WARN("Unhandled keyword: ", keyword );
}
m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
} break;
default: {
m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
} break;
}
}
}
// -------------------------------------------------------------------
// Loads a color definition
void ObjFileMtlImporter::getColorRGBA(aiColor3D *pColor) {
ai_assert(nullptr != pColor);
ai_real r(0.0), g(0.0), b(0.0);
m_DataIt = getFloat<DataArrayIt>(m_DataIt, m_DataItEnd, r);
pColor->r = r;
// we have to check if color is default 0 with only one token
if (!IsLineEnd(*m_DataIt)) {
m_DataIt = getFloat<DataArrayIt>(m_DataIt, m_DataItEnd, g);
m_DataIt = getFloat<DataArrayIt>(m_DataIt, m_DataItEnd, b);
}
pColor->g = g;
pColor->b = b;
}
// -------------------------------------------------------------------
void ObjFileMtlImporter::getColorRGBA(Maybe<aiColor3D> &value) {
aiColor3D v;
getColorRGBA(&v);
value = Maybe<aiColor3D>(v);
}
// -------------------------------------------------------------------
// Loads the kind of illumination model.
void ObjFileMtlImporter::getIlluminationModel(int &illum_model) {
m_DataIt = CopyNextWord<DataArrayIt>(m_DataIt, m_DataItEnd, &m_buffer[0], BUFFERSIZE);
illum_model = atoi(&m_buffer[0]);
}
// -------------------------------------------------------------------
// Loads a single float value.
void ObjFileMtlImporter::getFloatValue(ai_real &value) {
m_DataIt = CopyNextWord<DataArrayIt>(m_DataIt, m_DataItEnd, &m_buffer[0], BUFFERSIZE);
size_t len = std::strlen(&m_buffer[0]);
if (0 == len) {
value = 0.0f;
return;
}
value = (ai_real)fast_atof(&m_buffer[0]);
}
// -------------------------------------------------------------------
void ObjFileMtlImporter::getFloatValue(Maybe<ai_real> &value) {
m_DataIt = CopyNextWord<DataArrayIt>(m_DataIt, m_DataItEnd, &m_buffer[0], BUFFERSIZE);
size_t len = std::strlen(&m_buffer[0]);
if (len)
value = Maybe<ai_real>(fast_atof(&m_buffer[0]));
else
value = Maybe<ai_real>();
}
// -------------------------------------------------------------------
// Writes a loaded single float value if material not null
void ObjFileMtlImporter::getFloatIfMaterialValid(ai_real ObjFile::Material::*member) {
if (m_pModel != nullptr && m_pModel->mCurrentMaterial != nullptr) {
// This will call getFloatValue(ai_real&)
getFloatValue(m_pModel->mCurrentMaterial->*member);
}
}
// -------------------------------------------------------------------
void ObjFileMtlImporter::getFloatIfMaterialValid(Maybe<ai_real> ObjFile::Material::*member) {
// It can directly access `m_pModel` because it's part of the class
if (m_pModel != nullptr && m_pModel->mCurrentMaterial != nullptr) {
getFloatValue(m_pModel->mCurrentMaterial->*member);
}
}
// -------------------------------------------------------------------
// Creates a material from loaded data.
void ObjFileMtlImporter::createMaterial() {
std::string line;
while (!IsLineEnd(*m_DataIt)) {
line += *m_DataIt;
++m_DataIt;
}
std::vector<std::string> token;
const unsigned int numToken = tokenize<std::string>(line, token, " \t");
std::string name;
if (numToken == 1) {
name = AI_DEFAULT_MATERIAL_NAME;
} else {
// skip newmtl and all following white spaces
std::size_t first_ws_pos = line.find_first_of(" \t");
std::size_t first_non_ws_pos = line.find_first_not_of(" \t", first_ws_pos);
if (first_non_ws_pos != std::string::npos) {
name = line.substr(first_non_ws_pos);
}
}
name = ai_trim(name);
std::map<std::string, ObjFile::Material *>::iterator it = m_pModel->mMaterialMap.find(name);
if (m_pModel->mMaterialMap.end() == it) {
// New Material created
m_pModel->mCurrentMaterial = new ObjFile::Material();
m_pModel->mCurrentMaterial->MaterialName.Set(name);
m_pModel->mMaterialLib.push_back(name);
m_pModel->mMaterialMap[name] = m_pModel->mCurrentMaterial;
if (m_pModel->mCurrentMesh) {
m_pModel->mCurrentMesh->m_uiMaterialIndex = static_cast<unsigned int>(m_pModel->mMaterialLib.size() - 1);
}
} else {
// Use older material
m_pModel->mCurrentMaterial = it->second;
}
}
// -------------------------------------------------------------------
// Gets a texture name from data.
void ObjFileMtlImporter::getTexture() {
aiString *out = nullptr;
int clampIndex = -1;
if (m_pModel->mCurrentMaterial == nullptr) {
m_pModel->mCurrentMaterial = new ObjFile::Material();
m_pModel->mCurrentMaterial->MaterialName.Set("Empty_Material");
m_pModel->mMaterialMap["Empty_Material"] = m_pModel->mCurrentMaterial;
}
const char *pPtr(&(*m_DataIt));
if (!ASSIMP_strincmp(pPtr, DiffuseTexture, static_cast<unsigned int>(strlen(DiffuseTexture)))) {
// Diffuse texture
out = &m_pModel->mCurrentMaterial->texture;
clampIndex = ObjFile::Material::TextureDiffuseType;
} else if (!ASSIMP_strincmp(pPtr, AmbientTexture, static_cast<unsigned int>(strlen(AmbientTexture)))) {
// Ambient texture
out = &m_pModel->mCurrentMaterial->textureAmbient;
clampIndex = ObjFile::Material::TextureAmbientType;
} else if (!ASSIMP_strincmp(pPtr, SpecularTexture, static_cast<unsigned int>(strlen(SpecularTexture)))) {
// Specular texture
out = &m_pModel->mCurrentMaterial->textureSpecular;
clampIndex = ObjFile::Material::TextureSpecularType;
} else if (!ASSIMP_strincmp(pPtr, DisplacementTexture1, static_cast<unsigned int>(strlen(DisplacementTexture1))) ||
!ASSIMP_strincmp(pPtr, DisplacementTexture2, static_cast<unsigned int>(strlen(DisplacementTexture2)))) {
// Displacement texture
out = &m_pModel->mCurrentMaterial->textureDisp;
clampIndex = ObjFile::Material::TextureDispType;
} else if (!ASSIMP_strincmp(pPtr, OpacityTexture, static_cast<unsigned int>(strlen(OpacityTexture)))) {
// Opacity texture
out = &m_pModel->mCurrentMaterial->textureOpacity;
clampIndex = ObjFile::Material::TextureOpacityType;
} else if (!ASSIMP_strincmp(pPtr, EmissiveTexture1, static_cast<unsigned int>(strlen(EmissiveTexture1))) ||
!ASSIMP_strincmp(pPtr, EmissiveTexture2, static_cast<unsigned int>(strlen(EmissiveTexture2)))) {
// Emissive texture
out = &m_pModel->mCurrentMaterial->textureEmissive;
clampIndex = ObjFile::Material::TextureEmissiveType;
} else if (!ASSIMP_strincmp(pPtr, BumpTexture1, static_cast<unsigned int>(strlen(BumpTexture1))) ||
!ASSIMP_strincmp(pPtr, BumpTexture2, static_cast<unsigned int>(strlen(BumpTexture2)))) {
// Bump texture
out = &m_pModel->mCurrentMaterial->textureBump;
clampIndex = ObjFile::Material::TextureBumpType;
} else if (!ASSIMP_strincmp(pPtr, NormalTextureV1, static_cast<unsigned int>(strlen(NormalTextureV1))) || !ASSIMP_strincmp(pPtr, NormalTextureV2, static_cast<unsigned int>(strlen(NormalTextureV2)))) {
// Normal map
out = &m_pModel->mCurrentMaterial->textureNormal;
clampIndex = ObjFile::Material::TextureNormalType;
} else if (!ASSIMP_strincmp(pPtr, ReflectionTexture, static_cast<unsigned int>(strlen(ReflectionTexture)))) {
// Reflection texture(s)
//Do nothing here
return;
} else if (!ASSIMP_strincmp(pPtr, SpecularityTexture, static_cast<unsigned int>(strlen(SpecularityTexture)))) {
// Specularity scaling (glossiness)
out = &m_pModel->mCurrentMaterial->textureSpecularity;
clampIndex = ObjFile::Material::TextureSpecularityType;
} else if ( !ASSIMP_strincmp( pPtr, RoughnessTexture, static_cast<unsigned int>(strlen(RoughnessTexture)))) {
// PBR Roughness texture
out = & m_pModel->mCurrentMaterial->textureRoughness;
clampIndex = ObjFile::Material::TextureRoughnessType;
} else if ( !ASSIMP_strincmp( pPtr, MetallicTexture, static_cast<unsigned int>(strlen(MetallicTexture)))) {
// PBR Metallic texture
out = & m_pModel->mCurrentMaterial->textureMetallic;
clampIndex = ObjFile::Material::TextureMetallicType;
} else if (!ASSIMP_strincmp( pPtr, SheenTexture, static_cast<unsigned int>(strlen(SheenTexture)))) {
// PBR Sheen (reflectance) texture
out = & m_pModel->mCurrentMaterial->textureSheen;
clampIndex = ObjFile::Material::TextureSheenType;
} else if (!ASSIMP_strincmp( pPtr, RMATexture, static_cast<unsigned int>(strlen(RMATexture)))) {
// PBR Rough/Metal/AO texture
out = & m_pModel->mCurrentMaterial->textureRMA;
clampIndex = ObjFile::Material::TextureRMAType;
} else {
ASSIMP_LOG_ERROR("OBJ/MTL: Encountered unknown texture type");
return;
}
bool clamp = false;
getTextureOption(clamp, clampIndex, out);
m_pModel->mCurrentMaterial->clamp[clampIndex] = clamp;
std::string texture;
m_DataIt = getName<DataArrayIt>(m_DataIt, m_DataItEnd, texture);
if (nullptr != out) {
out->Set(m_strAbsPath + texture);
}
}
/* /////////////////////////////////////////////////////////////////////////////
* Texture Option
* /////////////////////////////////////////////////////////////////////////////
* According to http://en.wikipedia.org/wiki/Wavefront_.obj_file#Texture_options
* Texture map statement can contains various texture option, for example:
*
* map_Ka -o 1 1 1 some.png
* map_Kd -clamp on some.png
*
* So we need to parse and skip these options, and leave the last part which is
* the url of image, otherwise we will get a wrong url like "-clamp on some.png".
*
* Because aiMaterial supports clamp option, so we also want to return it
* /////////////////////////////////////////////////////////////////////////////
*/
void ObjFileMtlImporter::getTextureOption(bool &clamp, int &clampIndex, aiString *&out) {
m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
// If there is any more texture option
while (!isEndOfBuffer(m_DataIt, m_DataItEnd) && *m_DataIt == '-') {
const char *pPtr(&(*m_DataIt));
//skip option key and value
int skipToken = 1;
if (!ASSIMP_strincmp(pPtr, ClampOption, static_cast<unsigned int>(strlen(ClampOption)))) {
DataArrayIt it = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
char value[3];
CopyNextWord(it, m_DataItEnd, value, sizeof(value) / sizeof(*value));
if (!ASSIMP_strincmp(value, "on", 2)) {
clamp = true;
}
skipToken = 2;
} else if (!ASSIMP_strincmp(pPtr, TypeOption, static_cast<unsigned int>(strlen(TypeOption)))) {
DataArrayIt it = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
char value[12];
CopyNextWord(it, m_DataItEnd, value, sizeof(value) / sizeof(*value));
if (!ASSIMP_strincmp(value, "cube_top", 8)) {
clampIndex = ObjFile::Material::TextureReflectionCubeTopType;
out = &m_pModel->mCurrentMaterial->textureReflection[0];
} else if (!ASSIMP_strincmp(value, "cube_bottom", 11)) {
clampIndex = ObjFile::Material::TextureReflectionCubeBottomType;
out = &m_pModel->mCurrentMaterial->textureReflection[1];
} else if (!ASSIMP_strincmp(value, "cube_front", 10)) {
clampIndex = ObjFile::Material::TextureReflectionCubeFrontType;
out = &m_pModel->mCurrentMaterial->textureReflection[2];
} else if (!ASSIMP_strincmp(value, "cube_back", 9)) {
clampIndex = ObjFile::Material::TextureReflectionCubeBackType;
out = &m_pModel->mCurrentMaterial->textureReflection[3];
} else if (!ASSIMP_strincmp(value, "cube_left", 9)) {
clampIndex = ObjFile::Material::TextureReflectionCubeLeftType;
out = &m_pModel->mCurrentMaterial->textureReflection[4];
} else if (!ASSIMP_strincmp(value, "cube_right", 10)) {
clampIndex = ObjFile::Material::TextureReflectionCubeRightType;
out = &m_pModel->mCurrentMaterial->textureReflection[5];
} else if (!ASSIMP_strincmp(value, "sphere", 6)) {
clampIndex = ObjFile::Material::TextureReflectionSphereType;
out = &m_pModel->mCurrentMaterial->textureReflection[0];
}
skipToken = 2;
} else if (!ASSIMP_strincmp(pPtr, BumpOption, static_cast<unsigned int>(strlen(BumpOption)))) {
DataArrayIt it = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
getFloat(it, m_DataItEnd, m_pModel->mCurrentMaterial->bump_multiplier);
skipToken = 2;
} else if (!ASSIMP_strincmp(pPtr, BlendUOption, static_cast<unsigned int>(strlen(BlendUOption))) ||
!ASSIMP_strincmp(pPtr, BlendVOption, static_cast<unsigned int>(strlen(BlendVOption))) ||
!ASSIMP_strincmp(pPtr, BoostOption, static_cast<unsigned int>(strlen(BoostOption))) ||
!ASSIMP_strincmp(pPtr, ResolutionOption, static_cast<unsigned int>(strlen(ResolutionOption))) ||
!ASSIMP_strincmp(pPtr, ChannelOption, static_cast<unsigned int>(strlen(ChannelOption)))) {
skipToken = 2;
} else if (!ASSIMP_strincmp(pPtr, ModifyMapOption, static_cast<unsigned int>(strlen(ModifyMapOption)))) {
skipToken = 3;
} else if (!ASSIMP_strincmp(pPtr, OffsetOption, static_cast<unsigned int>(strlen(OffsetOption))) ||
!ASSIMP_strincmp(pPtr, ScaleOption, static_cast<unsigned int>(strlen(ScaleOption))) ||
!ASSIMP_strincmp(pPtr, TurbulenceOption, static_cast<unsigned int>(strlen(TurbulenceOption)))) {
skipToken = 4;
}
for (int i = 0; i < skipToken; ++i) {
m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
}
}
}
// -------------------------------------------------------------------
} // Namespace Assimp
#endif // !! ASSIMP_BUILD_NO_OBJ_IMPORTER

View file

@ -0,0 +1,115 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2026, 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.
----------------------------------------------------------------------*/
#ifndef OBJFILEMTLIMPORTER_H_INC
#define OBJFILEMTLIMPORTER_H_INC
#include <assimp/defs.h>
#include <string>
#include <vector>
#include "Common/Maybe.h"
struct aiColor3D;
struct aiString;
namespace Assimp {
namespace ObjFile {
struct Model;
struct Material;
} // namespace ObjFile
/**
* @class ObjFileMtlImporter
* @brief Loads the material description from a mtl file.
*/
class ObjFileMtlImporter {
public:
static const size_t BUFFERSIZE = 2048;
using DataArray = std::vector<char>;
using DataArrayIt = std::vector<char>::iterator;
using ConstDataArrayIt = std::vector<char>::const_iterator;
//! \brief The class default constructor
ObjFileMtlImporter(std::vector<char> &buffer, const std::string &strAbsPath,
ObjFile::Model *pModel);
//! \brief The class destructor
~ObjFileMtlImporter() = default;
ObjFileMtlImporter(const ObjFileMtlImporter &rOther) = delete;
ObjFileMtlImporter &operator=(const ObjFileMtlImporter &rOther) = delete;
private:
/// Load the whole material description
void load();
/// Get color data.
void getColorRGBA(aiColor3D *pColor);
void getColorRGBA(Maybe<aiColor3D> &value);
/// Get illumination model from loaded data
void getIlluminationModel(int &illum_model);
/// Gets a float value from data.
void getFloatValue(ai_real &value);
void getFloatValue(Maybe<ai_real> &value);
void getFloatIfMaterialValid(ai_real ObjFile::Material::*member);
void getFloatIfMaterialValid(Maybe<ai_real> ObjFile::Material::*member);
/// Creates a new material from loaded data.
void createMaterial();
/// Get texture name from loaded data.
void getTexture();
void getTextureOption(bool &clamp, int &clampIndex, aiString *&out);
private:
//! Absolute pathname
std::string m_strAbsPath;
//! Data iterator showing to the current position in data buffer
DataArrayIt m_DataIt;
//! Data iterator to end of buffer
DataArrayIt m_DataItEnd;
//! USed model instance
ObjFile::Model *m_pModel;
//! Current line in file
unsigned int m_uiLine;
//! Helper buffer
std::vector<char> m_buffer;
};
} // Namespace Assimp
#endif // OBJFILEMTLIMPORTER_H_INC

View file

@ -0,0 +1,885 @@
/*
---------------------------------------------------------------------------
Open Asset Import Library (assimp)
---------------------------------------------------------------------------
Copyright (c) 2006-2026, 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.
---------------------------------------------------------------------------
*/
#ifndef ASSIMP_BUILD_NO_OBJ_IMPORTER
#include "ObjFileParser.h"
#include "ObjFileData.h"
#include "ObjFileMtlImporter.h"
#include "ObjTools.h"
#include <assimp/BaseImporter.h>
#include <assimp/DefaultIOSystem.h>
#include <assimp/ParsingUtils.h>
#include <assimp/DefaultLogger.hpp>
#include <assimp/Importer.hpp>
#include <cstdlib>
#include <memory>
#include <utility>
namespace Assimp {
constexpr const char ObjFileParser::DEFAULT_MATERIAL[];
// -------------------------------------------------------------------
static bool isDataDefinitionEnd(const char *tmp) {
ai_assert(tmp != nullptr);
if (*tmp == '\\') {
++tmp;
if (IsLineEnd(*tmp)) {
return true;
}
}
return false;
}
// -------------------------------------------------------------------
static bool isNanOrInf(const char *in) {
ai_assert(in != nullptr);
// Look for "nan" or "inf", case insensitive
return ((in[0] == 'N' || in[0] == 'n') && ASSIMP_strincmp(in, "nan", 3) == 0) ||
((in[0] == 'I' || in[0] == 'i') && ASSIMP_strincmp(in, "inf", 3) == 0);
}
// -------------------------------------------------------------------
ObjFileParser::ObjFileParser() : mEnd(&mBuffer[Buffersize-1]+1) {
std::fill_n(mBuffer, Buffersize, '\0');
}
// -------------------------------------------------------------------
ObjFileParser::ObjFileParser(IOStreamBuffer<char> &streamBuffer, const std::string &modelName,
IOSystem *io, ProgressHandler *progress, const std::string &originalObjFileName) :
mIO(io),
mProgress(progress),
mOriginalObjFileName(originalObjFileName) {
std::fill_n(mBuffer, Buffersize, '\0');
// Create the model instance to store all the data
mModel.reset(new ObjFile::Model());
mModel->mModelName = modelName;
// create default material and store it
mModel->mDefaultMaterial = new ObjFile::Material;
mModel->mDefaultMaterial->MaterialName.Set(DEFAULT_MATERIAL);
mModel->mMaterialLib.emplace_back(DEFAULT_MATERIAL);
mModel->mMaterialMap[DEFAULT_MATERIAL] = mModel->mDefaultMaterial;
// Start parsing the file
parseFile(streamBuffer);
}
void ObjFileParser::setBuffer(std::vector<char> &buffer) {
mDataIt = buffer.begin();
mDataItEnd = buffer.end();
ai_assert(mDataIt < mDataItEnd);
if (!buffer.empty()) {
mEnd = &buffer[buffer.size() - 1] + 1;
}
}
// -------------------------------------------------------------------
ObjFile::Model *ObjFileParser::GetModel() const {
return mModel.get();
}
// -------------------------------------------------------------------
void ObjFileParser::parseFile(IOStreamBuffer<char> &streamBuffer) {
// only update every 100KB or it'll be too slow
// const unsigned int updateProgressEveryBytes = 100 * 1024;
const unsigned int bytesToProcess = static_cast<unsigned int>(streamBuffer.size());
const unsigned int progressTotal = bytesToProcess;
unsigned int processed = 0u;
size_t lastFilePos = 0u;
bool insideCstype = false;
std::vector<char> buffer;
while (streamBuffer.getNextDataLine(buffer, '\\')) {
mDataIt = buffer.begin();
mDataItEnd = buffer.end();
mEnd = &buffer[buffer.size() - 1] + 1;
if (processed == 0 && std::distance(mDataIt, mDataItEnd) >= 3 &&
static_cast<unsigned char>(*mDataIt) == 0xEF &&
static_cast<unsigned char>(*(mDataIt + 1)) == 0xBB &&
static_cast<unsigned char>(*(mDataIt + 2)) == 0xBF) {
mDataIt += 3; // skip BOM
}
// Handle progress reporting
const size_t filePos = streamBuffer.getFilePos();
if (lastFilePos < filePos) {
processed = static_cast<unsigned int>(filePos);
lastFilePos = filePos;
if (mProgress != nullptr) {
mProgress->UpdateFileRead(processed, progressTotal);
}
}
// handle c-stype section end (http://paulbourke.net/dataformats/obj/)
if (insideCstype) {
switch (*mDataIt) {
case 'e': {
std::string name;
getNameNoSpace(mDataIt, mDataItEnd, name);
insideCstype = name != "end";
} break;
default:
break;
}
goto pf_skip_line;
}
// parse line
switch (*mDataIt) {
case 'v': // Parse a vertex texture coordinate
{
++mDataIt;
if (*mDataIt == ' ' || *mDataIt == '\t') {
size_t numComponents = getNumComponentsInDataDefinition();
if (numComponents == 3) {
// read in vertex definition
getVector3(mModel->mVertices);
} else if (numComponents == 4) {
// read in vertex definition (homogeneous coords)
getHomogeneousVector3(mModel->mVertices);
} else if (numComponents == 6) {
// fill previous omitted vertex-colors by default
if (mModel->mVertexColors.size() < mModel->mVertices.size()) {
mModel->mVertexColors.resize(mModel->mVertices.size(), aiVector3D(0, 0, 0));
}
// read vertex and vertex-color
getTwoVectors3(mModel->mVertices, mModel->mVertexColors);
}
// append omitted vertex-colors as default for the end if any vertex-color exists
if (!mModel->mVertexColors.empty() && mModel->mVertexColors.size() < mModel->mVertices.size()) {
mModel->mVertexColors.resize(mModel->mVertices.size(), aiVector3D(0, 0, 0));
}
} else if (*mDataIt == 't') {
// read in texture coordinate ( 2D or 3D )
++mDataIt;
size_t dim = getTexCoordVector(mModel->mTextureCoord);
mModel->mTextureCoordDim = std::max(mModel->mTextureCoordDim, (unsigned int)dim);
} else if (*mDataIt == 'n') {
// Read in normal vector definition
++mDataIt;
getVector3(mModel->mNormals);
}
} break;
case 'p': // Parse a face, line or point statement
case 'l':
case 'f': {
getFace(*mDataIt == 'f' ? aiPrimitiveType_POLYGON : (*mDataIt == 'l' ? aiPrimitiveType_LINE : aiPrimitiveType_POINT));
} break;
case '#': // Parse a comment
{
skipComment();
} break;
case 'u': // Parse a material desc. setter
{
std::string name;
getNameNoSpace(mDataIt, mDataItEnd, name);
size_t nextSpace = name.find(' ');
if (nextSpace != std::string::npos)
name = name.substr(0, nextSpace);
if (name == "usemtl") {
getMaterialDesc();
}
} break;
case 'm': // Parse a material library or merging group ('mg')
{
std::string name;
getNameNoSpace(mDataIt, mDataItEnd, name);
size_t nextSpace = name.find(' ');
if (nextSpace != std::string::npos)
name = name.substr(0, nextSpace);
if (name == "mg")
skipGroupNumberAndResolution();
else if (name == "mtllib")
getMaterialLib();
else
goto pf_skip_line;
} break;
case 'g': // Parse group name
{
getGroupName();
} break;
case 's': // Parse group number
{
skipGroupNumber();
} break;
case 'o': // Parse object name
{
getObjectName();
} break;
case 'c': // handle cstype section start
{
std::string name;
getNameNoSpace(mDataIt, mDataItEnd, name);
insideCstype = name == "cstype";
goto pf_skip_line;
}
default: {
pf_skip_line:
mDataIt = skipLine<DataArrayIt>(mDataIt, mDataItEnd, mLine);
} break;
}
}
}
// -------------------------------------------------------------------
void ObjFileParser::copyNextWord(char *pBuffer, size_t length) {
size_t index = 0;
mDataIt = getNextWord<DataArrayIt>(mDataIt, mDataItEnd);
if (*mDataIt == '\\') {
++mDataIt;
++mDataIt;
mDataIt = getNextWord<DataArrayIt>(mDataIt, mDataItEnd);
}
while (mDataIt != mDataItEnd && !IsSpaceOrNewLine(*mDataIt)) {
pBuffer[index] = *mDataIt;
index++;
if (index == length - 1) {
break;
}
++mDataIt;
}
ai_assert(index < length);
pBuffer[index] = '\0';
}
// -------------------------------------------------------------------
size_t ObjFileParser::getNumComponentsInDataDefinition() {
size_t numComponents(0);
const char *tmp = &mDataIt[0];
bool end_of_definition = false;
while (!end_of_definition) {
if (isDataDefinitionEnd(tmp)) {
tmp += 2;
} else if (IsLineEnd(*tmp)) {
end_of_definition = true;
}
if (!SkipSpaces(&tmp, mEnd) || *tmp == '#') {
break;
}
const bool isNum(IsNumeric(*tmp) || isNanOrInf(tmp));
SkipToken(tmp, mEnd);
if (isNum) {
++numComponents;
}
if (!SkipSpaces(&tmp, mEnd) || *tmp == '#') {
break;
}
}
return numComponents;
}
// -------------------------------------------------------------------
size_t ObjFileParser::getTexCoordVector(std::vector<aiVector3D> &point3d_array) {
size_t numComponents = getNumComponentsInDataDefinition();
ai_real x, y, z;
if (2 == numComponents) {
copyNextWord(mBuffer, Buffersize);
x = fast_atof(mBuffer);
copyNextWord(mBuffer, Buffersize);
y = fast_atof(mBuffer);
z = 0.0;
} else if (3 == numComponents) {
copyNextWord(mBuffer, Buffersize);
x = fast_atof(mBuffer);
copyNextWord(mBuffer, Buffersize);
y = fast_atof(mBuffer);
copyNextWord(mBuffer, Buffersize);
z = fast_atof(mBuffer);
} else {
throw DeadlyImportError("OBJ: Invalid number of components");
}
// Coerce nan and inf to 0 as is the OBJ default value
if (!std::isfinite(x))
x = 0;
if (!std::isfinite(y))
y = 0;
if (!std::isfinite(z))
z = 0;
point3d_array.emplace_back(x, y, z);
mDataIt = skipLine<DataArrayIt>(mDataIt, mDataItEnd, mLine);
return numComponents;
}
// -------------------------------------------------------------------
void ObjFileParser::getVector3(std::vector<aiVector3D> &point3d_array) {
ai_real x, y, z;
copyNextWord(mBuffer, Buffersize);
x = fast_atof(mBuffer);
copyNextWord(mBuffer, Buffersize);
y = fast_atof(mBuffer);
copyNextWord(mBuffer, Buffersize);
z = fast_atof(mBuffer);
point3d_array.emplace_back(x, y, z);
mDataIt = skipLine<DataArrayIt>(mDataIt, mDataItEnd, mLine);
}
// -------------------------------------------------------------------
void ObjFileParser::getHomogeneousVector3(std::vector<aiVector3D> &point3d_array) {
ai_real x, y, z, w;
copyNextWord(mBuffer, Buffersize);
x = fast_atof(mBuffer);
copyNextWord(mBuffer, Buffersize);
y = fast_atof(mBuffer);
copyNextWord(mBuffer, Buffersize);
z = fast_atof(mBuffer);
copyNextWord(mBuffer, Buffersize);
w = fast_atof(mBuffer);
if (w == 0)
throw DeadlyImportError("OBJ: Invalid component in homogeneous vector (Division by zero)");
point3d_array.emplace_back(x / w, y / w, z / w);
mDataIt = skipLine<DataArrayIt>(mDataIt, mDataItEnd, mLine);
}
// -------------------------------------------------------------------
void ObjFileParser::getTwoVectors3(std::vector<aiVector3D> &point3d_array_a, std::vector<aiVector3D> &point3d_array_b) {
ai_real x, y, z;
copyNextWord(mBuffer, Buffersize);
x = fast_atof(mBuffer);
copyNextWord(mBuffer, Buffersize);
y = fast_atof(mBuffer);
copyNextWord(mBuffer, Buffersize);
z = fast_atof(mBuffer);
point3d_array_a.emplace_back(x, y, z);
copyNextWord(mBuffer, Buffersize);
x = fast_atof(mBuffer);
copyNextWord(mBuffer, Buffersize);
y = fast_atof(mBuffer);
copyNextWord(mBuffer, Buffersize);
z = fast_atof(mBuffer);
point3d_array_b.emplace_back(x, y, z);
mDataIt = skipLine<DataArrayIt>(mDataIt, mDataItEnd, mLine);
}
// -------------------------------------------------------------------
void ObjFileParser::getVector2(std::vector<aiVector2D> &point2d_array) {
ai_real x, y;
copyNextWord(mBuffer, Buffersize);
x = fast_atof(mBuffer);
copyNextWord(mBuffer, Buffersize);
y = fast_atof(mBuffer);
point2d_array.emplace_back(x, y);
mDataIt = skipLine<DataArrayIt>(mDataIt, mDataItEnd, mLine);
}
static constexpr char DefaultObjName[] = "defaultobject";
// -------------------------------------------------------------------
void ObjFileParser::getFace(aiPrimitiveType type) {
mDataIt = getNextToken<DataArrayIt>(mDataIt, mDataItEnd);
if (mDataIt == mDataItEnd || *mDataIt == '\0') {
return;
}
ObjFile::Face *face = new ObjFile::Face(type);
bool hasNormal = false;
const int vSize = static_cast<unsigned int>(mModel->mVertices.size());
const int vtSize = static_cast<unsigned int>(mModel->mTextureCoord.size());
const int vnSize = static_cast<unsigned int>(mModel->mNormals.size());
const bool vt = (!mModel->mTextureCoord.empty());
const bool vn = (!mModel->mNormals.empty());
int iPos = 0;
while (mDataIt < mDataItEnd) {
int iStep = 1;
if (IsLineEnd(*mDataIt) || *mDataIt == '#') {
break;
}
if (*mDataIt == '/') {
if (type == aiPrimitiveType_POINT) {
ASSIMP_LOG_ERROR("Obj: Separator unexpected in point statement");
}
++iPos;
} else if (IsSpaceOrNewLine(*mDataIt) || *mDataIt == '\v') {
iPos = 0;
} else {
//OBJ USES 1 Base ARRAYS!!!!
const int iVal = ::atoi(&(*mDataIt));
// increment iStep position based off of the sign and # of digits
int tmp = iVal;
if (iVal < 0) {
++iStep;
}
while ((tmp = tmp / 10) != 0) {
++iStep;
}
if (iPos == 1 && !vt && vn) {
iPos = 2; // skip texture coords for normals if there are no tex coords
}
if (iVal > 0) {
// Store parsed index
if (0 == iPos) {
face->m_vertices.push_back(iVal - 1);
} else if (1 == iPos) {
face->m_texturCoords.push_back(iVal - 1);
} else if (2 == iPos) {
face->m_normals.push_back(iVal - 1);
hasNormal = true;
} else {
reportErrorTokenInFace();
}
} else if (iVal < 0) {
// Store relatively index
if (0 == iPos) {
face->m_vertices.push_back(vSize + iVal);
} else if (1 == iPos) {
face->m_texturCoords.push_back(vtSize + iVal);
} else if (2 == iPos) {
face->m_normals.push_back(vnSize + iVal);
hasNormal = true;
} else {
reportErrorTokenInFace();
}
} else {
//On error, std::atoi will return 0 which is not a valid value
delete face;
throw DeadlyImportError("OBJ: Invalid face index.");
}
}
mDataIt += iStep;
}
if (face->m_vertices.empty()) {
ASSIMP_LOG_ERROR("Obj: Ignoring empty face");
// skip line and clean up
mDataIt = skipLine<DataArrayIt>(mDataIt, mDataItEnd, mLine);
delete face;
return;
}
// Set active material, if one set
if (nullptr != mModel->mCurrentMaterial) {
face->m_pMaterial = mModel->mCurrentMaterial;
} else {
face->m_pMaterial = mModel->mDefaultMaterial;
}
// Create a default object, if nothing is there
if (nullptr == mModel->mCurrentObject) {
createObject(DefaultObjName);
}
// Assign face to mesh
if (nullptr == mModel->mCurrentMesh) {
createMesh(DefaultObjName);
}
// Store the face
mModel->mCurrentMesh->m_Faces.emplace_back(face);
mModel->mCurrentMesh->m_uiNumIndices += static_cast<unsigned int>(face->m_vertices.size());
mModel->mCurrentMesh->m_uiUVCoordinates[0] += static_cast<unsigned int>(face->m_texturCoords.size());
if (!mModel->mCurrentMesh->m_hasNormals && hasNormal) {
mModel->mCurrentMesh->m_hasNormals = true;
}
// Skip the rest of the line
mDataIt = skipLine<DataArrayIt>(mDataIt, mDataItEnd, mLine);
}
// -------------------------------------------------------------------
void ObjFileParser::getMaterialDesc() {
// Get next data for material data
mDataIt = getNextToken<DataArrayIt>(mDataIt, mDataItEnd);
if (mDataIt == mDataItEnd) {
return;
}
char *pStart = &(*mDataIt);
while (mDataIt != mDataItEnd && !IsLineEnd(*mDataIt)) {
++mDataIt;
}
// In some cases we should ignore this 'usemtl' command, this variable helps us to do so
bool skip = false;
// Get name
std::string strName(pStart, &(*mDataIt));
strName = ai_trim(strName);
if (strName.empty()) {
skip = true;
}
// If the current mesh has the same material, we will ignore that 'usemtl' command
// There is no need to create another object or even mesh here
if (!skip) {
if (mModel->mCurrentMaterial && mModel->mCurrentMaterial->MaterialName == aiString(strName)) {
skip = true;
}
}
if (!skip) {
// Search for material
std::map<std::string, ObjFile::Material *>::iterator it = mModel->mMaterialMap.find(strName);
if (it == mModel->mMaterialMap.end()) {
// Not found, so we don't know anything about the material except for its name.
// This may be the case if the material library is missing. We don't want to lose all
// materials if that happens, so create a new named material instead of discarding it
// completely.
ASSIMP_LOG_ERROR("OBJ: failed to locate material ", strName, ", creating new material");
mModel->mCurrentMaterial = new ObjFile::Material();
mModel->mCurrentMaterial->MaterialName.Set(strName);
mModel->mMaterialLib.push_back(strName);
mModel->mMaterialMap[strName] = mModel->mCurrentMaterial;
} else {
// Found, using detected material
mModel->mCurrentMaterial = it->second;
}
if (needsNewMesh(strName)) {
auto newMeshName = mModel->mActiveGroup.empty() ? strName : mModel->mActiveGroup;
createMesh(newMeshName);
}
mModel->mCurrentMesh->m_uiMaterialIndex = getMaterialIndex(strName);
}
// Skip rest of line
mDataIt = skipLine<DataArrayIt>(mDataIt, mDataItEnd, mLine);
}
// -------------------------------------------------------------------
// Get a comment, values will be skipped
void ObjFileParser::skipComment() {
mDataIt = skipLine<DataArrayIt>(mDataIt, mDataItEnd, mLine);
}
// -------------------------------------------------------------------
void ObjFileParser::getMaterialLib() {
// Translate tuple
mDataIt = getNextToken<DataArrayIt>(mDataIt, mDataItEnd);
if (mDataIt == mDataItEnd) {
return;
}
char *pStart = &(*mDataIt);
while (mDataIt != mDataItEnd && !IsLineEnd(*mDataIt)) {
++mDataIt;
}
// Check for existence
const std::string strMatName(pStart, &(*mDataIt));
std::string absName;
// Check if directive is valid.
if (0 == strMatName.length()) {
ASSIMP_LOG_WARN("OBJ: no name for material library specified.");
return;
}
if (mIO->StackSize() > 0) {
std::string path = mIO->CurrentDirectory();
if ('/' != *path.rbegin()) {
path += '/';
}
absName += path;
absName += strMatName;
} else {
absName = strMatName;
}
std::unique_ptr<IOStream> pFile(mIO->Open(absName));
if (nullptr == pFile) {
ASSIMP_LOG_ERROR("OBJ: Unable to locate material file ", strMatName);
std::string strMatFallbackName = mOriginalObjFileName.substr(0, mOriginalObjFileName.length() - 3) + "mtl";
ASSIMP_LOG_INFO("OBJ: Opening fallback material file ", strMatFallbackName);
pFile.reset(mIO->Open(strMatFallbackName));
if (!pFile) {
ASSIMP_LOG_ERROR("OBJ: Unable to locate fallback material file ", strMatFallbackName);
mDataIt = skipLine<DataArrayIt>(mDataIt, mDataItEnd, mLine);
return;
}
}
// Import material library data from file.
// Some exporters (e.g. Silo) will happily write out empty
// material files if the model doesn't use any materials, so we
// allow that.
std::vector<char> buffer;
BaseImporter::TextFileToBuffer(pFile.get(), buffer, BaseImporter::ALLOW_EMPTY);
//m_pIO->Close(pFile);
// Importing the material library
ObjFileMtlImporter mtlImporter(buffer, strMatName, mModel.get());
}
// -------------------------------------------------------------------
void ObjFileParser::getNewMaterial() {
mDataIt = getNextToken<DataArrayIt>(mDataIt, mDataItEnd);
mDataIt = getNextWord<DataArrayIt>(mDataIt, mDataItEnd);
if (mDataIt == mDataItEnd) {
return;
}
char *pStart = &(*mDataIt);
std::string strMat(pStart, *mDataIt);
while (mDataIt != mDataItEnd && IsSpaceOrNewLine(*mDataIt)) {
++mDataIt;
}
auto it = mModel->mMaterialMap.find(strMat);
if (it == mModel->mMaterialMap.end()) {
// Show a warning, if material was not found
ASSIMP_LOG_WARN("OBJ: Unsupported material requested: ", strMat);
mModel->mCurrentMaterial = mModel->mDefaultMaterial;
} else {
// Set new material
if (needsNewMesh(strMat)) {
createMesh(strMat);
}
mModel->mCurrentMesh->m_uiMaterialIndex = getMaterialIndex(strMat);
}
mDataIt = skipLine<DataArrayIt>(mDataIt, mDataItEnd, mLine);
}
static constexpr int InvalidMaterialIndex = -1;
// -------------------------------------------------------------------
int ObjFileParser::getMaterialIndex(const std::string &strMaterialName) {
int mat_index = InvalidMaterialIndex;
if (strMaterialName.empty()) {
return mat_index;
}
for (size_t index = 0; index < mModel->mMaterialLib.size(); ++index) {
if (strMaterialName == mModel->mMaterialLib[index]) {
mat_index = (int)index;
break;
}
}
return mat_index;
}
// -------------------------------------------------------------------
// Getter for a group name.
void ObjFileParser::getGroupName() {
std::string groupName;
// here we skip 'g ' from line
mDataIt = getNextToken<DataArrayIt>(mDataIt, mDataItEnd);
mDataIt = getName<DataArrayIt>(mDataIt, mDataItEnd, groupName);
if (isEndOfBuffer(mDataIt, mDataItEnd)) {
return;
}
// Change active group, if necessary
if (mModel->mActiveGroup != groupName) {
// Search for already existing entry
ObjFile::Model::ConstGroupMapIt it = mModel->mGroups.find(groupName);
// We are mapping groups into the object structure
createObject(groupName);
// New group name, creating a new entry
if (it == mModel->mGroups.end()) {
std::vector<unsigned int> *pFaceIDArray = new std::vector<unsigned int>;
mModel->mGroups[groupName] = pFaceIDArray;
mModel->mGroupFaceIDs = (pFaceIDArray);
} else {
mModel->mGroupFaceIDs = (*it).second;
}
mModel->mActiveGroup = groupName;
}
mDataIt = skipLine<DataArrayIt>(mDataIt, mDataItEnd, mLine);
}
// -------------------------------------------------------------------
void ObjFileParser::skipGroupNumber() {
// Not used
mDataIt = skipLine<DataArrayIt>(mDataIt, mDataItEnd, mLine);
}
// -------------------------------------------------------------------
void ObjFileParser::skipGroupNumberAndResolution() {
// Not used
mDataIt = skipLine<DataArrayIt>(mDataIt, mDataItEnd, mLine);
}
// -------------------------------------------------------------------
// Stores values for a new object instance, name will be used to
// identify it.
void ObjFileParser::getObjectName() {
mDataIt = getNextToken<DataArrayIt>(mDataIt, mDataItEnd);
if (mDataIt == mDataItEnd) {
return;
}
char *pStart = &(*mDataIt);
while (mDataIt != mDataItEnd && !IsSpaceOrNewLine(*mDataIt)) {
++mDataIt;
}
std::string strObjectName(pStart, &(*mDataIt));
if (!strObjectName.empty()) {
// Reset current object
mModel->mCurrentObject = nullptr;
// Search for actual object
for (auto it = mModel->mObjects.begin(); it != mModel->mObjects.end(); ++it) {
if ((*it)->m_strObjName == strObjectName) {
mModel->mCurrentObject = *it;
break;
}
}
// Allocate a new object, if current one was not found before
if (mModel->mCurrentObject == nullptr) {
createObject(strObjectName);
}
}
mDataIt = skipLine<DataArrayIt>(mDataIt, mDataItEnd, mLine);
}
// -------------------------------------------------------------------
// Creates a new object instance
void ObjFileParser::createObject(const std::string &objName) {
ai_assert(nullptr != mModel);
mModel->mCurrentObject = new ObjFile::Object;
mModel->mCurrentObject->m_strObjName = objName;
mModel->mObjects.push_back(mModel->mCurrentObject);
createMesh(objName);
if (mModel->mCurrentMaterial) {
mModel->mCurrentMesh->m_uiMaterialIndex =
getMaterialIndex(mModel->mCurrentMaterial->MaterialName.data);
mModel->mCurrentMesh->m_pMaterial = mModel->mCurrentMaterial;
}
}
// -------------------------------------------------------------------
// Creates a new mesh
void ObjFileParser::createMesh(const std::string &meshName) {
ai_assert(nullptr != mModel);
mModel->mCurrentMesh = new ObjFile::Mesh(meshName);
mModel->mMeshes.push_back(mModel->mCurrentMesh);
auto meshId = static_cast<unsigned int>(mModel->mMeshes.size() - 1);
if (mModel->mCurrentObject != nullptr) {
mModel->mCurrentObject->m_Meshes.push_back(meshId);
} else {
ASSIMP_LOG_ERROR("OBJ: No object detected to attach a new mesh instance.");
}
}
// -------------------------------------------------------------------
// Returns true, if a new mesh must be created.
bool ObjFileParser::needsNewMesh(const std::string &materialName) {
// If no mesh data yet
if (mModel->mCurrentMesh == nullptr) {
return true;
}
bool newMat = false;
int matIdx = getMaterialIndex(materialName);
int curMatIdx = mModel->mCurrentMesh->m_uiMaterialIndex;
if (curMatIdx != int(ObjFile::Mesh::NoMaterial) && curMatIdx != matIdx
// no need create a new mesh if no faces in current
// lets say 'usemtl' goes straight after 'g'
&& !mModel->mCurrentMesh->m_Faces.empty()) {
// New material -> only one material per mesh, so we need to create a new
// material
newMat = true;
}
return newMat;
}
// -------------------------------------------------------------------
// Shows an error in parsing process.
void ObjFileParser::reportErrorTokenInFace() {
mDataIt = skipLine<DataArrayIt>(mDataIt, mDataItEnd, mLine);
ASSIMP_LOG_ERROR("OBJ: Not supported token in face description detected");
}
// -------------------------------------------------------------------
} // Namespace Assimp
#endif // !! ASSIMP_BUILD_NO_OBJ_IMPORTER

View file

@ -0,0 +1,159 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2020, 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.
----------------------------------------------------------------------
*/
#ifndef OBJ_FILEPARSER_H_INC
#define OBJ_FILEPARSER_H_INC
#include "ObjFileData.h"
#include <assimp/IOStreamBuffer.h>
#include <assimp/material.h>
#include <assimp/mesh.h>
#include <assimp/vector2.h>
#include <assimp/vector3.h>
#include <map>
#include <memory>
#include <string>
#include <vector>
namespace Assimp {
// Forward declarations
class ObjFileImporter;
class IOSystem;
class ProgressHandler;
// ------------------------------------------------------------------------------------------------
/// \class ObjFileParser
/// \brief Parser for a obj waveform file
// ------------------------------------------------------------------------------------------------
class ASSIMP_API ObjFileParser {
public:
static constexpr size_t Buffersize = 4096;
using DataArray = std::vector<char>;
using DataArrayIt = std::vector<char>::iterator;
using ConstDataArrayIt = std::vector<char>::const_iterator;
/// @brief The default constructor.
ObjFileParser();
/// @brief Constructor with data array.
ObjFileParser(IOStreamBuffer<char> &streamBuffer, const std::string &modelName, IOSystem *io, ProgressHandler *progress, const std::string &originalObjFileName);
/// @brief Destructor
~ObjFileParser() = default;
/// @brief If you want to load in-core data.
void setBuffer(std::vector<char> &buffer);
/// @brief Model getter.
ObjFile::Model *GetModel() const;
ObjFileParser(const ObjFileParser&) = delete;
ObjFileParser &operator=(const ObjFileParser& ) = delete;
protected:
/// Parse the loaded file
void parseFile(IOStreamBuffer<char> &streamBuffer);
/// Method to copy the new delimited word in the current line.
void copyNextWord(char *pBuffer, size_t length);
/// Get the number of components in a line.
size_t getNumComponentsInDataDefinition();
/// Stores the vector
size_t getTexCoordVector(std::vector<aiVector3D> &point3d_array);
/// Stores the following 3d vector.
void getVector3(std::vector<aiVector3D> &point3d_array);
/// Stores the following homogeneous vector as a 3D vector
void getHomogeneousVector3(std::vector<aiVector3D> &point3d_array);
/// Stores the following two 3d vectors on the line.
void getTwoVectors3(std::vector<aiVector3D> &point3d_array_a, std::vector<aiVector3D> &point3d_array_b);
/// Stores the following 3d vector.
void getVector2(std::vector<aiVector2D> &point2d_array);
/// Stores the following face.
void getFace(aiPrimitiveType type);
/// Reads the material description.
void getMaterialDesc();
/// Skip a comment.
void skipComment();
/// Gets a a material library.
void getMaterialLib();
/// Creates a new material.
void getNewMaterial();
/// Gets the group name from file.
void getGroupName();
/// Gets the group number from file.
void skipGroupNumber();
/// Gets the group number and resolution from file.
void skipGroupNumberAndResolution();
/// Returns the index of the material. Is -1 if not material was found.
int getMaterialIndex(const std::string &strMaterialName);
/// Parse object name
void getObjectName();
/// Creates a new object.
void createObject(const std::string &strObjectName);
/// Creates a new mesh.
void createMesh(const std::string &meshName);
/// Returns true, if a new mesh instance must be created.
bool needsNewMesh(const std::string &rMaterialName);
/// Error report in token
void reportErrorTokenInFace();
private:
/// Default material name
static constexpr const char DEFAULT_MATERIAL[] = AI_DEFAULT_MATERIAL_NAME;
//! Iterator to current position in buffer
DataArrayIt mDataIt{};
//! Iterator to end position of buffer
DataArrayIt mDataItEnd{};
//! Pointer to model instance
std::unique_ptr<ObjFile::Model> mModel{};
//! Current line (for debugging)
unsigned int mLine{ 0 };
//! Helper buffer
char mBuffer[Buffersize];
/// End of buffer
const char *mEnd{ nullptr };
/// Pointer to IO system instance.
IOSystem *mIO{ nullptr };
//! Pointer to progress handler
ProgressHandler *mProgress{ nullptr };
/// Path to the current model, name of the obj file where the buffer comes from
const std::string mOriginalObjFileName{};
};
} // Namespace Assimp
#endif

View file

@ -0,0 +1,284 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2026, 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 ObjTools.h
* @brief Some helpful templates for text parsing
*/
#ifndef OBJ_TOOLS_H_INC
#define OBJ_TOOLS_H_INC
#include <assimp/ParsingUtils.h>
#include <assimp/fast_atof.h>
#include <vector>
namespace Assimp {
/**
* @brief Returns true, if the last entry of the buffer is reached.
* @param[in] it Iterator of current position.
* @param[in] end Iterator with end of buffer.
* @return true, if the end of the buffer is reached.
*/
template <class char_t>
inline bool isEndOfBuffer(char_t it, char_t end) {
if (it == end) {
return true;
}
--end;
return (it == end);
}
/**
* @brief Returns next word separated by a space
* @param[in] pBuffer Pointer to data buffer
* @param[in] pEnd Pointer to end of buffer
* @return Pointer to next space
*/
template <class Char_T>
inline Char_T getNextWord(Char_T pBuffer, Char_T pEnd) {
while (!isEndOfBuffer(pBuffer, pEnd)) {
if (!IsSpaceOrNewLine(*pBuffer) || IsLineEnd(*pBuffer)) {
break;
}
++pBuffer;
}
return pBuffer;
}
/**
* @brief Returns next space
* @param[in] pBuffer Pointer to data buffer
* @param[in] pEnd Pointer to end of buffer
* @return Pointer to next space
*/
template <class Char_T>
inline Char_T getNextDelimiter(Char_T pBuffer, Char_T pEnd) {
while (!isEndOfBuffer(pBuffer, pEnd)) {
if (IsSpaceOrNewLine(*pBuffer)) {
break;
}
++pBuffer;
}
return pBuffer;
}
/**
* @brief Returns pointer a next token
* @param[in] pBuffer Pointer to data buffer
* @param[in] pEnd Pointer to end of buffer
* @return Pointer to next token
*/
template <class Char_T>
inline Char_T getNextToken(Char_T pBuffer, Char_T pEnd) {
pBuffer = getNextDelimiter(pBuffer, pEnd);
return getNextWord(pBuffer, pEnd);
}
/**
* @brief Skips a line
* @param[in] it Iterator set to current position
* @param[in] end Iterator set to end of scratch buffer for readout
* @param[out] uiLine Current line number in format
* @return Current-iterator with new position
*/
template <class char_t>
inline char_t skipLine(char_t it, char_t end, unsigned int &uiLine) {
if (it >= end) {
return it;
}
while (!isEndOfBuffer(it, end) && !IsLineEnd(*it)) {
++it;
}
if (it != end) {
++it;
++uiLine;
}
// fix .. from time to time there are spaces at the beginning of a material line
while (it != end && (*it == '\t' || *it == ' ')) {
++it;
}
return it;
}
/**
* @brief Get a name from the current line. Preserve space in the middle,
* but trim it at the end.
* @param[in] it set to current position
* @param[in] end set to end of scratch buffer for readout
* @param[out] name Separated name
* @return Current-iterator with new position
*/
template <class char_t>
inline char_t getName(char_t it, char_t end, std::string &name) {
name = "";
if (isEndOfBuffer(it, end)) {
return end;
}
char *pStart = &(*it);
while (!isEndOfBuffer(it, end) && !IsLineEnd(*it)) {
++it;
}
while (IsSpace(*it)) {
--it;
}
// Get name
// if there is no name, and the previous char is a separator, come back to start
while (&(*it) < pStart) {
++it;
}
std::string strName(pStart, &(*it));
if (!strName.empty()) {
name = strName;
}
return it;
}
/**
* @brief Get a name from the current line. Do not preserve space
* in the middle, but trim it at the end.
* @param it set to current position
* @param end set to end of scratch buffer for readout
* @param name Separated name
* @return Current-iterator with new position
*/
template <class char_t>
inline char_t getNameNoSpace(char_t it, char_t end, std::string &name) {
name = "";
if (isEndOfBuffer(it, end)) {
return end;
}
char *pStart = &(*it);
while (!isEndOfBuffer(it, end) && !IsLineEnd(*it) && !IsSpaceOrNewLine(*it)) {
++it;
}
while (isEndOfBuffer(it, end) || IsLineEnd(*it) || IsSpaceOrNewLine(*it)) {
--it;
}
++it;
// Get name
// if there is no name, and the previous char is a separator, come back to start
while (&(*it) < pStart) {
++it;
}
std::string strName(pStart, &(*it));
if (!strName.empty()) {
name = strName;
}
return it;
}
/**
* @brief Get next word from given line
* @param[in] it set to current position
* @param[in] end set to end of scratch buffer for readout
* @param[in] pBuffer Buffer for next word
* @param[in] length Buffer length
* @return Current-iterator with new position
*/
template <class char_t>
inline char_t CopyNextWord(char_t it, char_t end, char *pBuffer, size_t length) {
size_t index = 0;
it = getNextWord<char_t>(it, end);
while (!IsSpaceOrNewLine(*it) && !isEndOfBuffer(it, end)) {
pBuffer[index] = *it;
++index;
if (index == length - 1) {
break;
}
++it;
}
pBuffer[index] = '\0';
return it;
}
/**
* @brief Get next float from given line
* @param[in] it set to current position
* @param[in] end set to end of scratch buffer for readout
* @param[out] value Separated float value.
* @return Current-iterator with new position
*/
template <class char_t>
inline char_t getFloat(char_t it, char_t end, ai_real &value) {
static const size_t BUFFERSIZE = 1024;
char buffer[BUFFERSIZE] = {};
it = CopyNextWord<char_t>(it, end, buffer, BUFFERSIZE);
value = (ai_real)fast_atof(buffer);
return it;
}
/**
* @brief Checks for a line-end.
* @param[in] it Current iterator in string.
* @param[in] end End of the string.
* @return The trimmed string.
*/
template <class T>
bool hasLineEnd(T it, T end) {
bool hasLineEnd = false;
while (!isEndOfBuffer(it, end)) {
++it;
if (IsLineEnd(it)) {
hasLineEnd = true;
break;
}
}
return hasLineEnd;
}
} // Namespace Assimp
#endif // OBJ_TOOLS_H_INC