223 bool operator==(
const ObjIndex& other)
const {
224 return position == other.position &&
225 texcoord == other.texcoord &&
226 normal == other.normal;
230 struct ObjIndexHasher {
231 size_t operator()(
const ObjIndex& index)
const {
232 size_t seed =
static_cast<size_t>(index.position + 1);
233 seed ^=
static_cast<size_t>(index.texcoord + 1) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
234 seed ^=
static_cast<size_t>(index.normal + 1) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
239 struct ObjMeshBuilder {
240 std::string name =
"default";
241 std::vector<SimpleVertex> vertices;
242 std::vector<unsigned int> indices;
243 std::unordered_map<ObjIndex, unsigned int, ObjIndexHasher> vertexLookup;
246 auto fixIndex = [](
int index,
int count) ->
int {
247 if (index > 0)
return index - 1;
248 if (index < 0)
return count + index;
253 const float lengthSq = value.x * value.x + value.y * value.y + value.z * value.z;
254 if (lengthSq <= 1e-20f) {
258 const float invLength = 1.0f / std::sqrt(lengthSq);
259 value.x *= invLength;
260 value.y *= invLength;
261 value.z *= invLength;
264 auto parseFaceVertex = [&](
const std::string& token,
267 int normalCount) -> ObjIndex {
269 size_t firstSlash = token.find(
'/');
270 size_t secondSlash = token.find(
'/', firstSlash == std::string::npos ? token.size() : firstSlash + 1);
272 const std::string positionToken = token.substr(0, firstSlash);
273 const std::string texcoordToken =
274 (firstSlash == std::string::npos) ? std::string() :
275 (secondSlash == std::string::npos ? token.substr(firstSlash + 1) : token.substr(firstSlash + 1, secondSlash - firstSlash - 1));
276 const std::string normalToken = (secondSlash == std::string::npos) ? std::string() : token.substr(secondSlash + 1);
278 if (!positionToken.empty()) result.position = fixIndex(std::stoi(positionToken), positionCount);
279 if (!texcoordToken.empty()) result.texcoord = fixIndex(std::stoi(texcoordToken), texcoordCount);
280 if (!normalToken.empty()) result.normal = fixIndex(std::stoi(normalToken), normalCount);
286 for (
size_t i = 0; i + 2 < mesh.m_index.size(); i += 3) {
297 const float denominator = du1 * dv2 - du2 * dv1;
298 const float invDenominator = std::fabs(denominator) < 1e-8f ? 0.0f : 1.0f / denominator;
301 (edge1.
x * dv2 - edge2.
x * dv1) * invDenominator,
302 (edge1.
y * dv2 - edge2.
y * dv1) * invDenominator,
303 (edge1.
z * dv2 - edge2.
z * dv1) * invDenominator);
305 (edge2.
x * du1 - edge1.
x * du2) * invDenominator,
306 (edge2.
y * du1 - edge1.
y * du2) * invDenominator,
307 (edge2.
z * du1 - edge1.
z * du2) * invDenominator);
318 normalize(vertex.Normal);
320 const float tangentDotNormal =
321 vertex.Tangent.x * vertex.Normal.x +
322 vertex.Tangent.y * vertex.Normal.y +
323 vertex.Tangent.z * vertex.Normal.z;
324 vertex.Tangent = vertex.Tangent - (vertex.Normal * tangentDotNormal);
325 normalize(vertex.Tangent);
328 vertex.Normal.y * vertex.Tangent.z - vertex.Normal.z * vertex.Tangent.y,
329 vertex.Normal.z * vertex.Tangent.x - vertex.Normal.x * vertex.Tangent.z,
330 vertex.Normal.x * vertex.Tangent.y - vertex.Normal.y * vertex.Tangent.x);
331 normalize(vertex.Bitangent);
335 auto flushMesh = [&](ObjMeshBuilder& builder, std::vector<MeshComponent>& meshes) {
336 if (builder.indices.empty() || builder.vertices.empty()) {
337 builder = ObjMeshBuilder{};
342 mesh.
m_name = builder.name;
343 mesh.
m_vertex = std::move(builder.vertices);
344 mesh.
m_index = std::move(builder.indices);
347 computeTangents(mesh);
348 meshes.push_back(std::move(mesh));
349 builder = ObjMeshBuilder{};
352 std::ifstream file(filePath);
353 if (!file.is_open()) {
354 ERROR(
"ModelLoader",
"LoadOBJModel", (
"Unable to open OBJ file: " + filePath).c_str());
358 std::vector<EU::Vector3> positions;
359 std::vector<EU::Vector2> texcoords;
360 std::vector<EU::Vector3> normals;
361 std::vector<MeshComponent> loadedMeshes;
362 ObjMeshBuilder currentMesh;
363 std::string currentGroupName =
"default";
364 currentMesh.name = currentGroupName;
367 while (std::getline(file, line)) {
368 if (line.empty() || line[0] ==
'#') {
372 std::istringstream stream(line);
376 if (command ==
"v") {
378 stream >> position.
x >> position.
y >> position.
z;
379 positions.push_back(position);
381 else if (command ==
"vt") {
383 stream >> uv.
x >> uv.
y;
385 texcoords.push_back(uv);
387 else if (command ==
"vn") {
389 stream >> normal.
x >> normal.
y >> normal.
z;
391 normals.push_back(normal);
393 else if (command ==
"g" || command ==
"o") {
394 flushMesh(currentMesh, loadedMeshes);
395 stream >> currentGroupName;
396 if (currentGroupName.empty()) {
397 currentGroupName =
"default";
399 currentMesh.name = currentGroupName;
401 else if (command ==
"usemtl") {
402 if (!currentMesh.indices.empty()) {
403 flushMesh(currentMesh, loadedMeshes);
405 currentMesh.name = currentGroupName;
407 else if (command ==
"f") {
408 std::vector<unsigned int> polygonIndices;
410 while (stream >> token) {
411 const ObjIndex objIndex = parseFaceVertex(
413 static_cast<int>(positions.size()),
414 static_cast<int>(texcoords.size()),
415 static_cast<int>(normals.size()));
417 auto it = currentMesh.vertexLookup.find(objIndex);
418 if (it == currentMesh.vertexLookup.end()) {
420 if (objIndex.position >= 0 && objIndex.position <
static_cast<int>(positions.size())) {
421 vertex.
Position = positions[objIndex.position];
423 if (objIndex.texcoord >= 0 && objIndex.texcoord <
static_cast<int>(texcoords.size())) {
424 vertex.TextureCoordinate = texcoords[objIndex.texcoord];
427 vertex.TextureCoordinate =
EU::Vector2(0.0f, 0.0f);
429 if (objIndex.normal >= 0 && objIndex.normal <
static_cast<int>(normals.size())) {
430 vertex.Normal = normals[objIndex.normal];
438 const unsigned int newIndex =
static_cast<unsigned int>(currentMesh.vertices.size());
439 currentMesh.vertices.push_back(vertex);
440 currentMesh.vertexLookup[objIndex] = newIndex;
441 polygonIndices.push_back(newIndex);
444 polygonIndices.push_back(it->second);
448 for (
size_t i = 1; i + 1 < polygonIndices.size(); ++i) {
449 currentMesh.indices.push_back(polygonIndices[0]);
450 currentMesh.indices.push_back(polygonIndices[i]);
451 currentMesh.indices.push_back(polygonIndices[i + 1]);
456 flushMesh(currentMesh, loadedMeshes);
475 FbxMesh* mesh = node->GetMesh();
478 if (mesh->GetElementNormalCount() == 0)
479 mesh->GenerateNormals(
true,
true);
481 const char* uvSetName =
nullptr;
483 FbxStringList uvSets; mesh->GetUVSetNames(uvSets);
484 if (uvSets.GetCount() > 0) uvSetName = uvSets[0];
487 if (mesh->GetElementTangentCount() == 0 && uvSetName)
488 mesh->GenerateTangentsData(uvSetName);
490 const FbxGeometryElementUV* uvElem = (mesh->GetElementUVCount() > 0) ? mesh->GetElementUV(0) :
nullptr;
491 const FbxGeometryElementTangent* tanElem = (mesh->GetElementTangentCount() > 0) ? mesh->GetElementTangent(0) :
nullptr;
492 const FbxGeometryElementBinormal* binElem = (mesh->GetElementBinormalCount() > 0) ? mesh->GetElementBinormal(0) :
nullptr;
494 std::vector<SimpleVertex> vertices;
495 std::vector<unsigned int> indices;
496 vertices.reserve(mesh->GetPolygonCount() * 3);
497 indices.reserve(mesh->GetPolygonCount() * 3);
499 auto readV2 = [](
const FbxGeometryElementUV* elem,
int cpIdx,
int pvIdx) -> FbxVector2 {
500 if (!elem)
return FbxVector2(0, 0);
501 using E = FbxGeometryElement;
503 if (elem->GetMappingMode() == E::eByControlPoint)
504 idx = (elem->GetReferenceMode() == E::eIndexToDirect) ? elem->GetIndexArray().GetAt(cpIdx) : cpIdx;
506 idx = (elem->GetReferenceMode() == E::eIndexToDirect) ? elem->GetIndexArray().GetAt(pvIdx) : pvIdx;
507 return elem->GetDirectArray().GetAt(idx);
509 auto readV4 = [](
auto* elem,
int cpIdx,
int pvIdx) -> FbxVector4 {
510 if (!elem)
return FbxVector4(0, 0, 0, 0);
511 using E = FbxGeometryElement;
513 if (elem->GetMappingMode() == E::eByControlPoint)
514 idx = (elem->GetReferenceMode() == E::eIndexToDirect) ? elem->GetIndexArray().GetAt(cpIdx) : cpIdx;
516 idx = (elem->GetReferenceMode() == E::eIndexToDirect) ? elem->GetIndexArray().GetAt(pvIdx) : pvIdx;
517 return elem->GetDirectArray().GetAt(idx);
520 for (
int p = 0; p < mesh->GetPolygonCount(); ++p)
522 const int polySize = mesh->GetPolygonSize(p);
523 std::vector<unsigned> cornerIdx; cornerIdx.reserve(polySize);
525 for (
int v = 0; v < polySize; ++v)
527 const int cpIndex = mesh->GetPolygonVertex(p, v);
528 const int pvIndex = mesh->GetPolygonVertexIndex(p) + v;
532 FbxVector4 P = mesh->GetControlPointAt(cpIndex);
533 out.
Position = { (float)P[0], (
float)P[1], (float)P[2] };
535 FbxVector4 N(0, 1, 0, 0);
536 mesh->GetPolygonVertexNormal(p, v, N);
538 out.Normal = { (float)N[0], (
float)N[1], (float)N[2] };
540 if (uvElem && uvSetName) {
541 int uvIdx = mesh->GetTextureUVIndex(p, v);
542 FbxVector2 uv = (uvIdx >= 0) ? uvElem->GetDirectArray().GetAt(uvIdx)
543 : readV2(uvElem, cpIndex, pvIndex);
544 out.TextureCoordinate = { (float)uv[0], 1.0f - (
float)uv[1] };
547 out.TextureCoordinate = { 0.0f, 0.0f };
551 FbxVector4 T = readV4(tanElem, cpIndex, pvIndex);
552 out.Tangent = { (float)T[0], (
float)T[1], (float)T[2] };
554 else out.Tangent = { 0,0,0 };
557 FbxVector4 B = readV4(binElem, cpIndex, pvIndex);
558 out.Bitangent = { (float)B[0], (
float)B[1], (float)B[2] };
560 else out.Bitangent = { 0,0,0 };
562 cornerIdx.push_back((
unsigned)vertices.size());
563 vertices.push_back(out);
566 for (
int k = 1; k + 1 < polySize; ++k) {
567 indices.push_back(cornerIdx[0]);
568 indices.push_back(cornerIdx[k + 1]);
569 indices.push_back(cornerIdx[k]);
573 if (mesh->GetElementTangentCount() == 0 || mesh->GetElementBinormalCount() == 0)
579 for (
size_t i = 0; i + 2 < indices.size(); i += 3)
593 float denom = du1 * dv2 - du2 * dv1;
594 float r = (std::fabs(denom) < 1e-8f) ? 0.0f : 1.0f / denom;
608 bool autoDetectMirror =
true;
609 bool forceFlipWinding =
true;
611 bool mirrored =
true;
612 if (autoDetectMirror) {
614 geo.SetT(node->GetGeometricTranslation(FbxNode::eSourcePivot));
615 geo.SetR(node->GetGeometricRotation(FbxNode::eSourcePivot));
616 geo.SetS(node->GetGeometricScaling(FbxNode::eSourcePivot));
617 FbxAMatrix world = node->EvaluateGlobalTransform() * geo;
619 FbxVector4 S = world.GetS();
620 double detScale = S[0] * S[1] * S[2];
621 mirrored = (detScale < 0.0);
624 if (mirrored || forceFlipWinding) {
625 for (
size_t i = 0; i + 2 < indices.size(); i += 3)
626 std::swap(indices[i + 1], indices[i + 2]);
628 for (
auto& v : vertices) {
629 v.Normal = { v.Normal.x, v.Normal.y, v.Normal.z };
630 v.Tangent = { v.Tangent.x, v.Tangent.y, v.Tangent.z };
631 v.Bitangent = { v.Bitangent.x, v.Bitangent.y, v.Bitangent.z };
636 auto norm3 = [](
EU::Vector3& v) {
float l = std::sqrt(
EU::EMax(1e-20f, v.x * v.x + v.y * v.y + v.z * v.z)); v.x /= l; v.y /= l; v.z /= l; };
639 return EU::Vector3(a.
y * b.z - a.
z * b.y, a.
z * b.x - a.
x * b.z, a.
x * b.y - a.
y * b.x);
642 for (
auto& v : vertices)
645 float dTN = dot3(v.Tangent, v.Normal);
646 v.Tangent = sub3(v.Tangent,
EU::Vector3(v.Normal.x * dTN, v.Normal.y * dTN, v.Normal.z * dTN));
650 float hand = (dot3(Bcalc, v.Bitangent) < 0.0f) ? -1.0f : 1.0f;
651 v.Bitangent = { Bcalc.
x * hand, Bcalc.
y * hand, Bcalc.
z * hand };
656 mc.
m_name = node->GetName();
658 mc.
m_index = std::move(indices);
703 std::ifstream stream(cachePath, std::ios::binary);
704 if (!stream.is_open()) {
709 uint32_t version = 0;
710 uint32_t meshCount = 0;
711 uint32_t textureCount = 0;
713 stream.read(
reinterpret_cast<char*
>(&magic),
sizeof(magic));
714 stream.read(
reinterpret_cast<char*
>(&version),
sizeof(version));
715 stream.read(
reinterpret_cast<char*
>(&meshCount),
sizeof(meshCount));
716 stream.read(
reinterpret_cast<char*
>(&textureCount),
sizeof(textureCount));
718 if (!stream.good() || magic != kModelCacheMagic || version != kModelCacheVersion) {
722 std::vector<MeshComponent> loadedMeshes;
723 std::vector<std::string> loadedTextures;
724 loadedMeshes.reserve(meshCount);
725 loadedTextures.reserve(textureCount);
727 for (uint32_t i = 0; i < textureCount; ++i) {
728 std::string textureName;
729 if (!ReadString(stream, textureName)) {
732 loadedTextures.push_back(std::move(textureName));
735 for (uint32_t i = 0; i < meshCount; ++i) {
737 if (!ReadString(stream, mesh.
m_name)) {
741 uint32_t vertexCount = 0;
742 uint32_t indexCount = 0;
743 stream.read(
reinterpret_cast<char*
>(&vertexCount),
sizeof(vertexCount));
744 stream.read(
reinterpret_cast<char*
>(&indexCount),
sizeof(indexCount));
745 if (!stream.good()) {
750 mesh.
m_index.resize(indexCount);
751 if (vertexCount > 0) {
754 if (indexCount > 0) {
755 stream.read(
reinterpret_cast<char*
>(mesh.
m_index.data()),
sizeof(
unsigned int) * indexCount);
757 if (!stream.good()) {
762 mesh.
m_numIndex =
static_cast<int>(indexCount);
763 loadedMeshes.push_back(std::move(mesh));
769 const std::wstring cachePathW(cachePath.begin(), cachePath.end());
770 MESSAGE(
"ModelLoader",
"BinaryCache",
771 L
"Loaded binary cache '" << cachePathW << L
"'")