/** * Copyright (c) 2014-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ #include #include #include #include #include #include #if defined( __unix__ ) #include #endif #include "FBX2glTF.h" #include "utils/String_Utils.h" #include "utils/Image_Utils.h" #include "RawModel.h" extern bool verboseOutput; bool RawVertex::operator==(const RawVertex &other) const { return (position == other.position) && (normal == other.normal) && (tangent == other.tangent) && (binormal == other.binormal) && (color == other.color) && (uv0 == other.uv0) && (uv1 == other.uv1) && (jointIndices == other.jointIndices) && (jointWeights == other.jointWeights) && (polarityUv0 == other.polarityUv0) && (blendSurfaceIx == other.blendSurfaceIx) && (blends == other.blends); } size_t RawVertex::Difference(const RawVertex &other) const { size_t attributes = 0; if (position != other.position) { attributes |= RAW_VERTEX_ATTRIBUTE_POSITION; } if (normal != other.normal) { attributes |= RAW_VERTEX_ATTRIBUTE_NORMAL; } if (tangent != other.tangent) { attributes |= RAW_VERTEX_ATTRIBUTE_TANGENT; } if (binormal != other.binormal) { attributes |= RAW_VERTEX_ATTRIBUTE_BINORMAL; } if (color != other.color) { attributes |= RAW_VERTEX_ATTRIBUTE_COLOR; } if (uv0 != other.uv0) { attributes |= RAW_VERTEX_ATTRIBUTE_UV0; } if (uv1 != other.uv1) { attributes |= RAW_VERTEX_ATTRIBUTE_UV1; } // Always need both or neither. if (jointIndices != other.jointIndices) { attributes |= RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS; } if (jointWeights != other.jointWeights) { attributes |= RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS; } return attributes; } RawModel::RawModel() : vertexAttributes(0) { } void RawModel::AddVertexAttribute(const RawVertexAttribute attrib) { vertexAttributes |= attrib; } int RawModel::AddVertex(const RawVertex &vertex) { auto it = vertexHash.find(vertex); if (it != vertexHash.end()) { return it->second; } vertexHash.emplace(vertex, (int) vertices.size()); vertices.push_back(vertex); return (int) vertices.size() - 1; } int RawModel::AddTriangle(const int v0, const int v1, const int v2, const int materialIndex, const int surfaceIndex) { const RawTriangle triangle = {{v0, v1, v2}, materialIndex, surfaceIndex}; triangles.push_back(triangle); return (int) triangles.size() - 1; } int RawModel::AddTexture(const std::string &name, const std::string &fileName, const std::string &fileLocation, RawTextureUsage usage) { if (name.empty()) { return -1; } for (size_t i = 0; i < textures.size(); i++) { if (StringUtils::CompareNoCase(textures[i].name, name) == 0 && textures[i].usage == usage) { return (int) i; } } const ImageProperties properties = GetImageProperties(!fileLocation.empty() ? fileLocation.c_str() : fileName.c_str()); RawTexture texture; texture.name = name; texture.width = properties.width; texture.height = properties.height; texture.mipLevels = (int) ceilf(log2f(std::max((float) properties.width, (float) properties.height))); texture.usage = usage; texture.occlusion = (properties.occlusion == IMAGE_TRANSPARENT) ? RAW_TEXTURE_OCCLUSION_TRANSPARENT : RAW_TEXTURE_OCCLUSION_OPAQUE; texture.fileName = fileName; texture.fileLocation = fileLocation; textures.emplace_back(texture); return (int) textures.size() - 1; } int RawModel::AddMaterial(const RawMaterial &material) { return AddMaterial(material.name.c_str(), material.type, material.textures, material.info); } int RawModel::AddMaterial( const char *name, const RawMaterialType materialType, const int textures[RAW_TEXTURE_USAGE_MAX], std::shared_ptr materialInfo) { for (size_t i = 0; i < materials.size(); i++) { if (materials[i].name != name) { continue; } if (materials[i].type != materialType) { continue; } if (*(materials[i].info) != *materialInfo) { continue; } bool match = true; for (int j = 0; match && j < RAW_TEXTURE_USAGE_MAX; j++) { match = match && (materials[i].textures[j] == textures[j]); } if (match) { return (int) i; } } RawMaterial material; material.name = name; material.type = materialType; material.info = materialInfo; for (int i = 0; i < RAW_TEXTURE_USAGE_MAX; i++) { material.textures[i] = textures[i]; } materials.emplace_back(material); return (int) materials.size() - 1; } int RawModel::AddSurface(const RawSurface &surface) { for (size_t i = 0; i < surfaces.size(); i++) { if (StringUtils::CompareNoCase(surfaces[i].name, surface.name) == 0) { return (int) i; } } surfaces.emplace_back(surface); return (int) (surfaces.size() - 1); } int RawModel::AddSurface(const char *name, const long surfaceId) { assert(name[0] != '\0'); for (size_t i = 0; i < surfaces.size(); i++) { if (surfaces[i].id == surfaceId) { return (int) i; } } RawSurface surface; surface.id = surfaceId; surface.name = name; surface.bounds.Clear(); surface.discrete = false; surfaces.emplace_back(surface); return (int) (surfaces.size() - 1); } int RawModel::AddAnimation(const RawAnimation &animation) { animations.emplace_back(animation); return (int) (animations.size() - 1); } int RawModel::AddNode(const RawNode &node) { for (size_t i = 0; i < nodes.size(); i++) { if (nodes[i].id == node.id) { return (int)i; } } nodes.emplace_back(node); return (int) nodes.size() - 1; } int RawModel::AddCameraPerspective( const char *name, const long nodeId, const float aspectRatio, const float fovDegreesX, const float fovDegreesY, const float nearZ, const float farZ) { RawCamera camera; camera.name = name; camera.nodeId = nodeId; camera.mode = RawCamera::CAMERA_MODE_PERSPECTIVE; camera.perspective.aspectRatio = aspectRatio; camera.perspective.fovDegreesX = fovDegreesX; camera.perspective.fovDegreesY = fovDegreesY; camera.perspective.nearZ = nearZ; camera.perspective.farZ = farZ; cameras.emplace_back(camera); return (int) cameras.size() - 1; } int RawModel::AddCameraOrthographic( const char *name, const long nodeId, const float magX, const float magY, const float nearZ, const float farZ) { RawCamera camera; camera.name = name; camera.nodeId = nodeId; camera.mode = RawCamera::CAMERA_MODE_ORTHOGRAPHIC; camera.orthographic.magX = magX; camera.orthographic.magY = magY; camera.orthographic.nearZ = nearZ; camera.orthographic.farZ = farZ; cameras.emplace_back(camera); return (int) cameras.size() - 1; } int RawModel::AddNode(const long id, const char *name, const long parentId) { assert(name[0] != '\0'); for (size_t i = 0; i < nodes.size(); i++) { if (nodes[i].id == id ) { return (int) i; } } RawNode joint; joint.isJoint = false; joint.id = id; joint.name = name; joint.parentId = parentId; joint.surfaceId = 0; joint.translation = Vec3f(0, 0, 0); joint.rotation = Quatf(0, 0, 0, 1); joint.scale = Vec3f(1, 1, 1); nodes.emplace_back(joint); return (int) nodes.size() - 1; } void RawModel::Condense() { // Only keep surfaces that are referenced by one or more triangles. { std::vector oldSurfaces = surfaces; surfaces.clear(); for (auto &triangle : triangles) { const RawSurface &surface = oldSurfaces[triangle.surfaceIndex]; const int surfaceIndex = AddSurface(surface.name.c_str(), surface.id); surfaces[surfaceIndex] = surface; triangle.surfaceIndex = surfaceIndex; } } // Only keep materials that are referenced by one or more triangles. { std::vector oldMaterials = materials; materials.clear(); for (auto &triangle : triangles) { const RawMaterial &material = oldMaterials[triangle.materialIndex]; const int materialIndex = AddMaterial(material); materials[materialIndex] = material; triangle.materialIndex = materialIndex; } } // Only keep textures that are referenced by one or more materials. { std::vector oldTextures = textures; textures.clear(); for (auto &material : materials) { for (int j = 0; j < RAW_TEXTURE_USAGE_MAX; j++) { if (material.textures[j] >= 0) { const RawTexture &texture = oldTextures[material.textures[j]]; const int textureIndex = AddTexture(texture.name, texture.fileName, texture.fileLocation, texture.usage); textures[textureIndex] = texture; material.textures[j] = textureIndex; } } } } // Only keep vertices that are referenced by one or more triangles. { std::vector oldVertices = vertices; vertexHash.clear(); vertices.clear(); for (auto &triangle : triangles) { for (int j = 0; j < 3; j++) { triangle.verts[j] = AddVertex(oldVertices[triangle.verts[j]]); } } } } void RawModel::TransformGeometry(ComputeNormalsOption normals) { switch(normals) { case ComputeNormalsOption::NEVER: break; case ComputeNormalsOption::MISSING: if ((vertexAttributes & RAW_VERTEX_ATTRIBUTE_NORMAL) != 0) { break; } // otherwise fall through case ComputeNormalsOption::BROKEN: case ComputeNormalsOption::ALWAYS: size_t computedNormalsCount = this->CalculateNormals(normals == ComputeNormalsOption::BROKEN); vertexAttributes |= RAW_VERTEX_ATTRIBUTE_NORMAL; if (verboseOutput) { if (normals == ComputeNormalsOption::BROKEN) { fmt::printf("Repaired %lu empty normals.\n", computedNormalsCount); } else { fmt::printf("Computed %lu normals.\n", computedNormalsCount); } } break; } } void RawModel::TransformTextures(const std::vector> &transforms) { for (auto &vertice : vertices) { if ((vertexAttributes & RAW_VERTEX_ATTRIBUTE_UV0) != 0) { for (const auto &fun : transforms) { vertice.uv0 = fun(vertice.uv0); } } if ((vertexAttributes & RAW_VERTEX_ATTRIBUTE_UV1) != 0) { for (const auto &fun : transforms) { vertice.uv1 = fun(vertice.uv1); } } } } struct TriangleModelSortPos { static bool Compare(const RawTriangle &a, const RawTriangle &b) { if (a.materialIndex != b.materialIndex) { return a.materialIndex < b.materialIndex; } if (a.surfaceIndex != b.surfaceIndex) { return a.surfaceIndex < b.surfaceIndex; } return a.verts[0] < b.verts[0]; } }; struct TriangleModelSortNeg { static bool Compare(const RawTriangle &a, const RawTriangle &b) { if (a.materialIndex != b.materialIndex) { return a.materialIndex < b.materialIndex; } if (a.surfaceIndex != b.surfaceIndex) { return a.surfaceIndex < b.surfaceIndex; } return a.verts[0] > b.verts[0]; } }; void RawModel::CreateMaterialModels( std::vector &materialModels, bool shortIndices, const int keepAttribs, const bool forceDiscrete) const { // Sort all triangles based on material first, then surface, then first vertex index. std::vector sortedTriangles; bool invertedTransparencySort = true; if (invertedTransparencySort) { // Split the triangles into opaque and transparent triangles. std::vector opaqueTriangles; std::vector transparentTriangles; for (const auto &triangle : triangles) { const int materialIndex = triangle.materialIndex; if (materialIndex < 0) { opaqueTriangles.push_back(triangle); continue; } const int textureIndex = materials[materialIndex].textures[RAW_TEXTURE_USAGE_DIFFUSE]; if (textureIndex < 0) { if (vertices[triangle.verts[0]].color.w < 1.0f || vertices[triangle.verts[1]].color.w < 1.0f || vertices[triangle.verts[2]].color.w < 1.0f) { transparentTriangles.push_back(triangle); continue; } opaqueTriangles.push_back(triangle); continue; } if (textures[textureIndex].occlusion == RAW_TEXTURE_OCCLUSION_TRANSPARENT) { transparentTriangles.push_back(triangle); } else { opaqueTriangles.push_back(triangle); } } // Sort the opaque triangles. std::sort(opaqueTriangles.begin(), opaqueTriangles.end(), TriangleModelSortPos::Compare); // Sort the transparent triangles in the reverse direction. std::sort(transparentTriangles.begin(), transparentTriangles.end(), TriangleModelSortNeg::Compare); // Add the triangles to the sorted list. for (const auto &opaqueTriangle : opaqueTriangles) { sortedTriangles.push_back(opaqueTriangle); } for (const auto &transparentTriangle : transparentTriangles) { sortedTriangles.push_back(transparentTriangle); } } else { sortedTriangles = triangles; std::sort(sortedTriangles.begin(), sortedTriangles.end(), TriangleModelSortPos::Compare); } // Overestimate the number of models that will be created to avoid massive reallocation. int discreteCount = 0; for (const auto &surface : surfaces) { discreteCount += (surface.discrete != false); } materialModels.clear(); materialModels.reserve(materials.size() + discreteCount); const RawVertex defaultVertex; // Create a separate model for each material. RawModel *model; for (size_t i = 0; i < sortedTriangles.size(); i++) { if (sortedTriangles[i].materialIndex < 0 || sortedTriangles[i].surfaceIndex < 0) { continue; } if (i == 0 || (shortIndices && model->GetVertexCount() >= 0xFFFE) || sortedTriangles[i].materialIndex != sortedTriangles[i - 1].materialIndex || (sortedTriangles[i].surfaceIndex != sortedTriangles[i - 1].surfaceIndex && (forceDiscrete || surfaces[sortedTriangles[i].surfaceIndex].discrete || surfaces[sortedTriangles[i - 1].surfaceIndex].discrete))) { materialModels.resize(materialModels.size() + 1); model = &materialModels[materialModels.size() - 1]; } // FIXME: will have to unlink from the nodes, transform both surfaces into a // common space, and reparent to a new node with appropriate transform. const int prevSurfaceCount = model->GetSurfaceCount(); const int materialIndex = model->AddMaterial(materials[sortedTriangles[i].materialIndex]); const int surfaceIndex = model->AddSurface(surfaces[sortedTriangles[i].surfaceIndex]); RawSurface &rawSurface = model->GetSurface(surfaceIndex); if (model->GetSurfaceCount() > prevSurfaceCount) { const std::vector &jointIds = surfaces[sortedTriangles[i].surfaceIndex].jointIds; for (const auto &jointId : jointIds) { const int nodeIndex = GetNodeById(jointId); assert(nodeIndex != -1); model->AddNode(GetNode(nodeIndex)); } rawSurface.bounds.Clear(); } int verts[3]; for (int j = 0; j < 3; j++) { RawVertex vertex = vertices[sortedTriangles[i].verts[j]]; if (keepAttribs != -1) { int keep = keepAttribs; if ((keepAttribs & RAW_VERTEX_ATTRIBUTE_POSITION) != 0) { keep |= RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS; } if ((keepAttribs & RAW_VERTEX_ATTRIBUTE_AUTO) != 0) { keep |= RAW_VERTEX_ATTRIBUTE_POSITION; const RawMaterial &mat = model->GetMaterial(materialIndex); if (mat.textures[RAW_TEXTURE_USAGE_DIFFUSE] != -1) { keep |= RAW_VERTEX_ATTRIBUTE_UV0; } if (mat.textures[RAW_TEXTURE_USAGE_NORMAL] != -1) { keep |= RAW_VERTEX_ATTRIBUTE_NORMAL | RAW_VERTEX_ATTRIBUTE_TANGENT | RAW_VERTEX_ATTRIBUTE_BINORMAL | RAW_VERTEX_ATTRIBUTE_UV0; } if (mat.textures[RAW_TEXTURE_USAGE_SPECULAR] != -1) { keep |= RAW_VERTEX_ATTRIBUTE_NORMAL | RAW_VERTEX_ATTRIBUTE_UV0; } if (mat.textures[RAW_TEXTURE_USAGE_EMISSIVE] != -1) { keep |= RAW_VERTEX_ATTRIBUTE_UV1; } } if ((keep & RAW_VERTEX_ATTRIBUTE_POSITION) == 0) { vertex.position = defaultVertex.position; } if ((keep & RAW_VERTEX_ATTRIBUTE_NORMAL) == 0) { vertex.normal = defaultVertex.normal; } if ((keep & RAW_VERTEX_ATTRIBUTE_TANGENT) == 0) { vertex.tangent = defaultVertex.tangent; } if ((keep & RAW_VERTEX_ATTRIBUTE_BINORMAL) == 0) { vertex.binormal = defaultVertex.binormal; } if ((keep & RAW_VERTEX_ATTRIBUTE_COLOR) == 0) { vertex.color = defaultVertex.color; } if ((keep & RAW_VERTEX_ATTRIBUTE_UV0) == 0) { vertex.uv0 = defaultVertex.uv0; } if ((keep & RAW_VERTEX_ATTRIBUTE_UV1) == 0) { vertex.uv1 = defaultVertex.uv1; } if ((keep & RAW_VERTEX_ATTRIBUTE_JOINT_INDICES) == 0) { vertex.jointIndices = defaultVertex.jointIndices; } if ((keep & RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS) == 0) { vertex.jointWeights = defaultVertex.jointWeights; } } verts[j] = model->AddVertex(vertex); model->vertexAttributes |= vertex.Difference(defaultVertex); rawSurface.bounds.AddPoint(vertex.position); } model->AddTriangle(verts[0], verts[1], verts[2], materialIndex, surfaceIndex); } } int RawModel::GetNodeById(const long nodeId) const { for (size_t i = 0; i < nodes.size(); i++) { if (nodes[i].id == nodeId) { return (int) i; } } return -1; } int RawModel::GetSurfaceById(const long surfaceId) const { for (size_t i = 0; i < surfaces.size(); i++) { if (surfaces[i].id == surfaceId) { return (int)i; } } return -1; } Vec3f RawModel::getFaceNormal(int verts[3]) const { const float l0 = (vertices[verts[1]].position - vertices[verts[0]].position ).LengthSquared(); const float l1 = (vertices[verts[2]].position - vertices[verts[1]].position ).LengthSquared(); const float l2 = (vertices[verts[0]].position - vertices[verts[2]].position ).LengthSquared(); const int index = ( l0 > l1 ) ? ( l0 > l2 ? 2 : 1 ) : ( l1 > l2 ? 0 : 1 ); const Vec3f e0 = vertices[verts[(index + 1) % 3]].position - vertices[verts[index]].position; const Vec3f e1 = vertices[verts[(index + 2) % 3]].position - vertices[verts[index]].position; if (e0.LengthSquared() < FLT_MIN || e1.LengthSquared() < FLT_MIN) { return Vec3f { 0.0f }; } auto result = Vec3f::CrossProduct(e0, e1); auto resultLengthSquared = result.LengthSquared(); if (resultLengthSquared < FLT_MIN) { return Vec3f { 0.0f }; } float edgeDot = std::max(-1.0f, std::min(1.0f, Vec3f::DotProduct(e0, e1))); float angle = acos(edgeDot); float area = resultLengthSquared / 2.0f; return result.Normalized() * angle * area; } size_t RawModel::CalculateNormals(bool onlyBroken) { Vec3f averagePos = Vec3f { 0.0f }; std::set brokenVerts; for (int vertIx = 0; vertIx < vertices.size(); vertIx ++) { RawVertex &vertex = vertices[vertIx]; averagePos += (vertex.position / vertices.size()); if (onlyBroken && (vertex.normal.LengthSquared() >= FLT_MIN)) { continue; } vertex.normal = Vec3f { 0.0f }; if (onlyBroken) { brokenVerts.emplace(vertIx); } } for (auto &triangle : triangles) { bool relevant = false; for (int vertIx : triangle.verts) { relevant |= (brokenVerts.count(vertIx) > 0); } if (!relevant) { continue; } Vec3f faceNormal = this->getFaceNormal(triangle.verts); for (int vertIx : triangle.verts) { if (!onlyBroken || brokenVerts.count(vertIx) > 0) { vertices[vertIx].normal += faceNormal; } } } for (int vertIx = 0; vertIx < vertices.size(); vertIx ++) { if (onlyBroken && brokenVerts.count(vertIx) == 0) { continue; } RawVertex &vertex = vertices[vertIx]; if (vertex.normal.LengthSquared() < FLT_MIN) { vertex.normal = vertex.position - averagePos; if (vertex.normal.LengthSquared() < FLT_MIN) { vertex.normal = Vec3f { 0.0f, 1.0f, 0.0f }; continue; } } vertex.normal.Normalize(); } return onlyBroken ? brokenVerts.size() : vertices.size(); }