FBX2glTF/src/RawModel.cpp

553 lines
20 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>
#if defined( __unix__ )
#include <algorithm>
#endif
#include "FBX2glTF.h"
#include "utils/String_Utils.h"
#include "utils/Image_Utils.h"
#include "RawModel.h"
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 (Gltf::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.shadingModel.c_str(), material.type, material.textures, material.ambientFactor,
material.diffuseFactor, material.specularFactor, material.emissiveFactor, material.shininess);
}
int RawModel::AddMaterial(
const char *name, const char *shadingModel, const RawMaterialType materialType,
const int textures[RAW_TEXTURE_USAGE_MAX], const Vec3f ambientFactor,
const Vec4f diffuseFactor, const Vec3f specularFactor,
const Vec3f emissiveFactor, float shinineness)
{
for (size_t i = 0; i < materials.size(); i++) {
if (materials[i].name != name) {
continue;
}
if (materials[i].shadingModel != shadingModel) {
continue;
}
if (materials[i].type != materialType) {
continue;
}
if (materials[i].ambientFactor != ambientFactor ||
materials[i].diffuseFactor != diffuseFactor ||
materials[i].specularFactor != specularFactor ||
materials[i].emissiveFactor != emissiveFactor ||
materials[i].shininess != shinineness) {
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.shadingModel = shadingModel;
material.type = materialType;
material.ambientFactor = ambientFactor;
material.diffuseFactor = diffuseFactor;
material.specularFactor = specularFactor;
material.emissiveFactor = emissiveFactor;
material.shininess = shinineness;
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 (Gltf::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 (Gltf::StringUtils::CompareNoCase(nodes[i].name.c_str(), node.name) == 0) {
return (int) i;
}
}
nodes.emplace_back(node);
return (int) nodes.size() - 1;
}
int RawModel::AddCameraPerspective(
const char *name, const char *nodeName, const float aspectRatio, const float fovDegreesX, const float fovDegreesY, const float nearZ,
const float farZ)
{
RawCamera camera;
camera.name = name;
camera.nodeName = nodeName;
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 char *nodeName, const float magX, const float magY, const float nearZ, const float farZ)
{
RawCamera camera;
camera.name = name;
camera.nodeName = nodeName;
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 char *name, const char *parentName)
{
assert(name[0] != '\0');
for (size_t i = 0; i < nodes.size(); i++) {
if (Gltf::StringUtils::CompareNoCase(nodes[i].name, name) == 0) {
return (int) i;
}
}
RawNode joint;
joint.isJoint = false;
joint.name = name;
joint.parentName = parentName;
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::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, const int maxModelVertices, 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 ||
model->GetVertexCount() > maxModelVertices - 3 ||
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<std::string> &jointNames = surfaces[sortedTriangles[i].surfaceIndex].jointNames;
for (const auto &jointName : jointNames) {
const int nodeIndex = GetNodeByName(jointName.c_str());
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::GetNodeByName(const char *name) const
{
for (size_t i = 0; i < nodes.size(); i++) {
if (nodes[i].name == name) {
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;
}