Skip to content

Commit

Permalink
#5722: FbxModelLoader implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
codereader committed Aug 22, 2021
1 parent eb87959 commit 1560c0c
Show file tree
Hide file tree
Showing 10 changed files with 246 additions and 8 deletions.
1 change: 1 addition & 0 deletions radiantcore/CMakeLists.txt
Expand Up @@ -160,6 +160,7 @@ add_library(radiantcore MODULE
model/import/AseModelLoader.cpp
model/import/ModelImporterBase.cpp
model/import/openfbx/ofbx.cpp
model/import/FbxModelLoader.cpp
model/picomodel/PicoModelLoader.cpp
model/picomodel/PicoModelModule.cpp
model/StaticModel.cpp
Expand Down
4 changes: 4 additions & 0 deletions radiantcore/model/ModelFormatManager.cpp
Expand Up @@ -9,6 +9,7 @@

#include "module/StaticModule.h"

#include "import/FbxModelLoader.h"
#include "export/AseExporter.h"
#include "export/Lwo2Exporter.h"
#include "export/WavefrontExporter.h"
Expand Down Expand Up @@ -40,6 +41,9 @@ void ModelFormatManager::initialiseModule(const IApplicationContext& ctx)
sigc::mem_fun(this, &ModelFormatManager::postModuleInitialisation)
);

// Register the built-in model importers
registerImporter(std::make_shared<FbxModelLoader>());

// Register the built-in model exporters
registerExporter(std::make_shared<AseExporter>());
registerExporter(std::make_shared<Lwo2Exporter>());
Expand Down
6 changes: 0 additions & 6 deletions radiantcore/model/StaticModelSurface.h
Expand Up @@ -60,19 +60,13 @@ class StaticModelSurface :
GLuint _dlProgramNoVCol;

private:

// Get a colour vector from an unsigned char array (may be NULL)
Vector3 getColourVector(unsigned char* array);

// Calculate tangent and bitangent vectors for all vertices.
void calculateTangents();

// Create the display lists
GLuint compileProgramList(bool includeColour);
void createDisplayLists();

std::string cleanupShaderName(const std::string& mapName);

public:
// Move-construct this static model surface from the given vertex- and index array
StaticModelSurface(std::vector<ArbitraryMeshVertex>&& vertices, std::vector<unsigned int>&& indices);
Expand Down
149 changes: 149 additions & 0 deletions radiantcore/model/import/FbxModelLoader.cpp
@@ -0,0 +1,149 @@
#include "FbxModelLoader.h"

#include <istream>

#include "openfbx/ofbx.h"

#include "os/path.h"
#include "string/case_conv.h"
#include "stream/ScopedArchiveBuffer.h"

#include "FbxSurface.h"
#include "../StaticModel.h"
#include "../StaticModelSurface.h"

namespace model
{

FbxModelLoader::FbxModelLoader() :
ModelImporterBase("FBX")
{}

namespace
{

inline ArbitraryMeshVertex ConstructMeshVertex(const ofbx::Geometry& geometry, int index)
{
auto vertices = geometry.getVertices();
auto normals = geometry.getNormals();
auto uvs = geometry.getUVs();
auto colours = geometry.getColors();

return ArbitraryMeshVertex(
Vertex3f(vertices[index].x, vertices[index].y, vertices[index].z),
normals != nullptr ? Normal3f(normals[index].x, normals[index].y, normals[index].z) : Normal3f(1, 0, 0),
uvs != nullptr ? TexCoord2f(uvs[index].x, 1.0 - uvs[index].y) : TexCoord2f(0, 0), // invert v
colours != nullptr ? Vector3(colours[index].x, colours[index].y, colours[index].z) : Vector3(1, 1, 1)
);
}

}

IModelPtr FbxModelLoader::loadModelFromPath(const std::string& path)
{
// Open an ArchiveFile to load
auto file = path_is_absolute(path.c_str()) ?
GlobalFileSystem().openFileInAbsolutePath(path) :
GlobalFileSystem().openFile(path);

if (!file)
{
rError() << "Failed to load model " << path << std::endl;
return IModelPtr();
}

// Load the model data from the given stream
archive::ScopedArchiveBuffer data(*file);

auto scene = ofbx::load(static_cast<ofbx::u8*>(data.buffer),
static_cast<int>(data.length), (ofbx::u64)ofbx::LoadFlags::TRIANGULATE);

if (!scene)
{
rError() << "Failed to load FBX model " << path << std::endl;
return IModelPtr();
}

std::vector<FbxSurface> surfaces;

for (int meshIndex = 0; meshIndex < scene->getMeshCount(); ++meshIndex)
{
auto mesh = scene->getMesh(meshIndex);
auto geometry = mesh->getGeometry();

// Assign the materials for each surface
for (int m = 0; m < mesh->getMaterialCount(); ++m)
{
auto material = mesh->getMaterial(m);
surfaces.emplace_back().setMaterial(material->name);
}

if (surfaces.empty())
{
surfaces.emplace_back().setMaterial("Material"); // create at least one surface
}

auto materials = geometry->getMaterials();
auto faceIndices = geometry->getFaceIndices();

for (int i = 0; i < geometry->getIndexCount(); i += 3)
{
// Material index is assigned per triangle
auto polyIndex = i / 3;
auto materialIndex = materials ? materials[polyIndex] : 0; // put into first material by default

// Reverse the poly indices to get the CCW order
auto indexA = (faceIndices[i + 2] * -1) - 1; // last index is negative and 1-based
auto indexB = faceIndices[i + 1];
auto indexC = faceIndices[i + 0];

auto& surface = surfaces[materialIndex];

surface.addVertex(ConstructMeshVertex(*geometry, indexA));
surface.addVertex(ConstructMeshVertex(*geometry, indexB));
surface.addVertex(ConstructMeshVertex(*geometry, indexC));
}

// Apply the global transformation matrix
auto t = geometry->getGlobalTransform();
#if 0
auto transform = Matrix4::byColumns(
t.m[0], t.m[1], t.m[2], t.m[3],
t.m[4], t.m[5], t.m[6], t.m[7],
t.m[8], t.m[9], t.m[10], t.m[11],
t.m[12], t.m[13], t.m[14], t.m[15]
);
#endif
// "Objects in the FBX SDK are always created in the right handed, Y-Up axis system"
auto transform = Matrix4::getIdentity();

if (scene->getGlobalSettings()->UpAxis == ofbx::UpVector_AxisY)
{
transform = transform.getPremultipliedBy(Matrix4::getRotationForEulerXYZDegrees(Vector3(90, 0, 0)));
}
}

// Construct a set of static surfaces from the FBX surfaces, destroying them on the go
std::vector<StaticModelSurfacePtr> staticSurfaces;

for (auto& fbxSurface : surfaces)
{
auto& staticSurface = staticSurfaces.emplace_back(std::make_shared<StaticModelSurface>(
std::move(fbxSurface.getVertexArray()), std::move(fbxSurface.getIndexArray())));

staticSurface->setDefaultMaterial(fbxSurface.getMaterial());
staticSurface->setActiveMaterial(staticSurface->getDefaultMaterial());
}

surfaces.clear();

auto staticModel = std::make_shared<StaticModel>(staticSurfaces);

// Set the filename
staticModel->setFilename(os::getFilename(file->getName()));
staticModel->setModelPath(path);

return staticModel;
}

}
18 changes: 18 additions & 0 deletions radiantcore/model/import/FbxModelLoader.h
@@ -0,0 +1,18 @@
#pragma once

#include "ModelImporterBase.h"

namespace model
{

class FbxModelLoader :
public ModelImporterBase
{
public:
FbxModelLoader();

// Load the given model from the path, VFS or absolute
IModelPtr loadModelFromPath(const std::string& name) override;
};

} // namespace model
60 changes: 60 additions & 0 deletions radiantcore/model/import/FbxSurface.h
@@ -0,0 +1,60 @@
#pragma once

#include <vector>
#include <string>
#include <unordered_map>

#include "render/ArbitraryMeshVertex.h"
#include "render/VertexHashing.h"

namespace model
{

class FbxSurface
{
private:
std::vector<unsigned int> indices;
std::vector<ArbitraryMeshVertex> vertices;
std::string material;

// Hash index to share vertices with the same set of attributes
std::unordered_map<ArbitraryMeshVertex, std::size_t> vertexIndices;

public:
std::vector<ArbitraryMeshVertex>& getVertexArray()
{
return vertices;
}

std::vector<unsigned int>& getIndexArray()
{
return indices;
}

const std::string& getMaterial() const
{
return material;
}

void setMaterial(const std::string& newMaterial)
{
material = newMaterial;
}

void addVertex(const ArbitraryMeshVertex& vertex)
{
// Try to look up an existing vertex or add a new index
auto emplaceResult = vertexIndices.try_emplace(vertex, vertices.size());

if (emplaceResult.second)
{
// This was a new vertex, copy it to the vertex array
vertices.emplace_back(emplaceResult.first->first);
}

// The emplaceResult now points to a valid index in the vertex array
indices.emplace_back(static_cast<unsigned int>(emplaceResult.first->second));
}
};

}
2 changes: 1 addition & 1 deletion test/ModelExport.cpp
Expand Up @@ -311,7 +311,7 @@ TEST_F(ModelExportTest, ConvertFbxToAse)
EXPECT_TRUE(exportedModel) << "No FBX model has been created";
EXPECT_EQ(exportedModel->getSurfaceCount(), 1);
EXPECT_EQ(exportedModel->getSurface(0).getDefaultMaterial(), "phong1");
EXPECT_EQ(exportedModel->getVertexCount(), 8);
EXPECT_EQ(exportedModel->getVertexCount(), 24);
EXPECT_EQ(exportedModel->getPolyCount(), 12);
}

Expand Down
2 changes: 1 addition & 1 deletion test/Models.cpp
Expand Up @@ -449,7 +449,7 @@ TEST_F(ModelTest, LoadFbxModel)
EXPECT_TRUE(model) << "No FBX model has been loaded";
EXPECT_EQ(model->getSurfaceCount(), 1);
EXPECT_EQ(model->getSurface(0).getDefaultMaterial(), "phong1");
EXPECT_EQ(model->getVertexCount(), 8);
EXPECT_EQ(model->getVertexCount(), 24);
EXPECT_EQ(model->getPolyCount(), 12);
}

Expand Down
3 changes: 3 additions & 0 deletions tools/msvc/DarkRadiantCore.vcxproj
Expand Up @@ -155,6 +155,7 @@
<ClCompile Include="..\..\radiantcore\model\export\WavefrontExporter.cpp" />
<ClCompile Include="..\..\radiantcore\model\import\AseModel.cpp" />
<ClCompile Include="..\..\radiantcore\model\import\AseModelLoader.cpp" />
<ClCompile Include="..\..\radiantcore\model\import\FbxModelLoader.cpp" />
<ClCompile Include="..\..\radiantcore\model\import\ModelImporterBase.cpp" />
<ClCompile Include="..\..\radiantcore\model\import\openfbx\ofbx.cpp" />
<ClCompile Include="..\..\radiantcore\model\md5\MD5Anim.cpp" />
Expand Down Expand Up @@ -869,6 +870,8 @@
<ClInclude Include="..\..\radiantcore\model\export\WavefrontExporter.h" />
<ClInclude Include="..\..\radiantcore\model\import\AseModel.h" />
<ClInclude Include="..\..\radiantcore\model\import\AseModelLoader.h" />
<ClInclude Include="..\..\radiantcore\model\import\FbxModelLoader.h" />
<ClInclude Include="..\..\radiantcore\model\import\FbxSurface.h" />
<ClInclude Include="..\..\radiantcore\model\import\ModelImporterBase.h" />
<ClInclude Include="..\..\radiantcore\model\import\openfbx\ofbx.h" />
<ClInclude Include="..\..\radiantcore\model\md5\MD5Anim.h" />
Expand Down
9 changes: 9 additions & 0 deletions tools/msvc/DarkRadiantCore.vcxproj.filters
Expand Up @@ -1063,6 +1063,9 @@
<ClCompile Include="..\..\radiantcore\model\import\openfbx\ofbx.cpp">
<Filter>src\model\import\openfbx</Filter>
</ClCompile>
<ClCompile Include="..\..\radiantcore\model\import\FbxModelLoader.cpp">
<Filter>src\model\import</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\radiantcore\modulesystem\ModuleLoader.h">
Expand Down Expand Up @@ -2163,5 +2166,11 @@
<ClInclude Include="..\..\radiantcore\model\import\openfbx\ofbx.h">
<Filter>src\model\import\openfbx</Filter>
</ClInclude>
<ClInclude Include="..\..\radiantcore\model\import\FbxModelLoader.h">
<Filter>src\model\import</Filter>
</ClInclude>
<ClInclude Include="..\..\radiantcore\model\import\FbxSurface.h">
<Filter>src\model\import</Filter>
</ClInclude>
</ItemGroup>
</Project>

0 comments on commit 1560c0c

Please sign in to comment.