Files
WickedEngine/Editor/ModelImporter_OBJ.cpp
T
Dennis Brakhane b962a7221e OBJ import: ensure we only skip identical vertices (#1444)
just hashing will give false positives, especially on Linux where
std::hash<int> is identity.

Fixes #1405
2026-01-02 14:31:24 +01:00

285 lines
8.4 KiB
C++

#include "stdafx.h"
#include "wiScene.h"
#include "ModelImporter.h"
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
#define TINYOBJLOADER_IMPLEMENTATION
#include "tiny_obj_loader.h"
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
using namespace wi::graphics;
using namespace wi::scene;
using namespace wi::ecs;
struct membuf : std::streambuf
{
membuf(char* begin, char* end) {
this->setg(begin, begin, end);
}
};
// Custom material file reader:
class MaterialFileReader : public tinyobj::MaterialReader {
public:
explicit MaterialFileReader(const std::string& mtl_basedir)
: m_mtlBaseDir(mtl_basedir) {}
virtual ~MaterialFileReader() {}
virtual bool operator()(const std::string& matId,
std::vector<tinyobj::material_t>* materials,
std::map<std::string, int>* matMap, std::string* err)
{
std::string filepath;
if (!m_mtlBaseDir.empty()) {
filepath = std::string(m_mtlBaseDir) + matId;
}
else {
filepath = matId;
}
wi::vector<uint8_t> filedata;
if (!wi::helper::FileRead(filepath, filedata))
{
std::string ss;
ss += "WARN: Material file [ " + filepath + " ] not found.\n";
if (err) {
(*err) += ss;
}
return false;
}
membuf sbuf((char*)filedata.data(), (char*)filedata.data() + filedata.size());
std::istream matIStream(&sbuf);
std::string warning;
LoadMtl(matMap, materials, &matIStream, &warning);
if (!warning.empty()) {
if (err) {
(*err) += warning;
}
}
return true;
}
private:
std::string m_mtlBaseDir;
};
// Transform the data from OBJ space to engine-space:
static const bool transform_to_LH = true;
struct index_and_mat
{
tinyobj::index_t index;
int materialIndex;
constexpr bool operator==(const index_and_mat& other) const
{
return std::tuple(index.normal_index, index.texcoord_index, index.vertex_index, materialIndex)
== std::tuple(other.index.normal_index, other.index.texcoord_index, other.index.vertex_index, other.materialIndex);
}
};
template<>
struct std::hash<index_and_mat>
{
constexpr std::size_t operator()(const index_and_mat& o) const noexcept
{
size_t vertexHash = 0;
wi::helper::hash_combine(vertexHash, o.index.vertex_index);
wi::helper::hash_combine(vertexHash, o.index.normal_index);
wi::helper::hash_combine(vertexHash, o.index.texcoord_index);
wi::helper::hash_combine(vertexHash, o.materialIndex);
return vertexHash;
}
};
void ImportModel_OBJ(const std::string& fileName, Scene& scene)
{
std::string directory = wi::helper::GetDirectoryFromPath(fileName);
std::string name = wi::helper::GetFileNameFromPath(fileName);
tinyobj::attrib_t obj_attrib;
std::vector<tinyobj::shape_t> obj_shapes;
std::vector<tinyobj::material_t> obj_materials;
std::string obj_errors;
wi::vector<uint8_t> filedata;
bool success = wi::helper::FileRead(fileName, filedata);
if (success)
{
membuf sbuf((char*)filedata.data(), (char*)filedata.data() + filedata.size());
std::istream in(&sbuf);
MaterialFileReader matFileReader(directory);
success = tinyobj::LoadObj(&obj_attrib, &obj_shapes, &obj_materials, &obj_errors, &in, &matFileReader, true);
}
else
{
obj_errors = "Failed to read file: " + fileName;
}
if (!obj_errors.empty())
{
wi::backlog::post(obj_errors, wi::backlog::LogLevel::Error);
}
if (success)
{
Entity rootEntity = CreateEntity();
scene.transforms.Create(rootEntity);
scene.names.Create(rootEntity) = name;
// Load material library:
wi::vector<Entity> materialLibrary = {};
for (auto& obj_material : obj_materials)
{
Entity materialEntity = scene.Entity_CreateMaterial(obj_material.name);
scene.Component_Attach(materialEntity, rootEntity);
MaterialComponent& material = *scene.materials.GetComponent(materialEntity);
material.baseColor = XMFLOAT4(obj_material.diffuse[0], obj_material.diffuse[1], obj_material.diffuse[2], 1);
material.textures[MaterialComponent::BASECOLORMAP].name = obj_material.diffuse_texname;
material.textures[MaterialComponent::DISPLACEMENTMAP].name = obj_material.displacement_texname;
material.emissiveColor.x = obj_material.emission[0];
material.emissiveColor.y = obj_material.emission[1];
material.emissiveColor.z = obj_material.emission[2];
material.emissiveColor.w = std::max(obj_material.emission[0], std::max(obj_material.emission[1], obj_material.emission[2]));
//material.refractionIndex = obj_material.ior;
material.metalness = obj_material.metallic;
material.textures[MaterialComponent::NORMALMAP].name = obj_material.normal_texname;
material.textures[MaterialComponent::SURFACEMAP].name = obj_material.specular_texname;
material.roughness = obj_material.roughness;
if (material.textures[MaterialComponent::NORMALMAP].name.empty())
{
material.textures[MaterialComponent::NORMALMAP].name = obj_material.bump_texname;
}
if (material.textures[MaterialComponent::SURFACEMAP].name.empty())
{
material.textures[MaterialComponent::SURFACEMAP].name = obj_material.specular_highlight_texname;
}
for (auto& x : material.textures)
{
if (!x.name.empty())
{
x.name = directory + x.name;
}
}
material.CreateRenderData();
materialLibrary.push_back(materialEntity); // for subset-indexing...
}
if (materialLibrary.empty())
{
// Create default material if nothing was found:
Entity materialEntity = scene.Entity_CreateMaterial("OBJImport_defaultMaterial");
scene.Component_Attach(materialEntity, rootEntity);
MaterialComponent& material = *scene.materials.GetComponent(materialEntity);
materialLibrary.push_back(materialEntity); // for subset-indexing...
}
// Load objects, meshes:
for (auto& shape : obj_shapes)
{
Entity objectEntity = scene.Entity_CreateObject(shape.name);
scene.Component_Attach(objectEntity, rootEntity);
Entity meshEntity = scene.Entity_CreateMesh(shape.name + "_mesh");
scene.Component_Attach(meshEntity, rootEntity);
ObjectComponent& object = *scene.objects.GetComponent(objectEntity);
MeshComponent& mesh = *scene.meshes.GetComponent(meshEntity);
object.meshID = meshEntity;
wi::unordered_map<int, int> registered_materialIndices = {};
wi::unordered_map<index_and_mat, uint32_t> uniqueVertices = {};
for (size_t i = 0; i < shape.mesh.indices.size(); i += 3)
{
tinyobj::index_t reordered_indices[] = {
shape.mesh.indices[i + 0],
shape.mesh.indices[i + 1],
shape.mesh.indices[i + 2],
};
// todo: option param would be better
bool flipCulling = false;
if (flipCulling)
{
reordered_indices[1] = shape.mesh.indices[i + 2];
reordered_indices[2] = shape.mesh.indices[i + 1];
}
for (auto& index : reordered_indices)
{
XMFLOAT3 pos = XMFLOAT3(
obj_attrib.vertices[index.vertex_index * 3 + 0],
obj_attrib.vertices[index.vertex_index * 3 + 1],
obj_attrib.vertices[index.vertex_index * 3 + 2]
);
XMFLOAT3 nor = XMFLOAT3(0, 0, 0);
if (!obj_attrib.normals.empty())
{
nor = XMFLOAT3(
obj_attrib.normals[index.normal_index * 3 + 0],
obj_attrib.normals[index.normal_index * 3 + 1],
obj_attrib.normals[index.normal_index * 3 + 2]
);
}
XMFLOAT2 tex = XMFLOAT2(0, 0);
if (index.texcoord_index >= 0 && !obj_attrib.texcoords.empty())
{
tex = XMFLOAT2(
obj_attrib.texcoords[index.texcoord_index * 2 + 0],
1 - obj_attrib.texcoords[index.texcoord_index * 2 + 1]
);
}
int materialIndex = std::max(0, shape.mesh.material_ids[i / 3]); // this indexes the material library
if (registered_materialIndices.count(materialIndex) == 0)
{
registered_materialIndices[materialIndex] = (int)mesh.subsets.size();
mesh.subsets.push_back(MeshComponent::MeshSubset());
mesh.subsets.back().materialID = materialLibrary[materialIndex];
mesh.subsets.back().indexOffset = (uint32_t)mesh.indices.size();
}
if (transform_to_LH)
{
pos.z *= -1;
nor.z *= -1;
}
const index_and_mat im = {index, materialIndex};
if (uniqueVertices.count(im) == 0)
{
uniqueVertices[im] = (uint32_t)mesh.vertex_positions.size();
mesh.vertex_positions.push_back(pos);
mesh.vertex_normals.push_back(nor);
mesh.vertex_uvset_0.push_back(tex);
}
mesh.indices.push_back(uniqueVertices[im]);
mesh.subsets.back().indexCount++;
}
}
mesh.CreateRenderData();
}
}
else
{
wi::helper::messageBox("OBJ import failed! Check backlog for errors!", "Error!");
}
}