FBX2glTF/src/RawModel.cpp

639 lines
22 KiB
C++

/**
* 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 <vector>
#include <string>
#include <unordered_map>
#include <cmath>
#include <map>
#include <set>
#if defined( __unix__ )
#include <algorithm>
#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<RawMatProps> 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<RawSurface> 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<RawMaterial> 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<RawTexture> 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<RawVertex> 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<std::function<Vec2f(Vec2f)>> &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<RawModel> &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<RawTriangle> sortedTriangles;
bool invertedTransparencySort = true;
if (invertedTransparencySort) {
// Split the triangles into opaque and transparent triangles.
std::vector<RawTriangle> opaqueTriangles;
std::vector<RawTriangle> 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<long> &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<int> 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();
}