Skip to content

Commit

Permalink
Implements GLTF Draco extension support (#1287)
Browse files Browse the repository at this point in the history
  • Loading branch information
Meakk authored Feb 28, 2024
1 parent 61eec04 commit c4af5eb
Show file tree
Hide file tree
Showing 20 changed files with 385 additions and 1 deletion.
1 change: 1 addition & 0 deletions .cppcheck.supp
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ unknownMacro:library/VTKExtensions/Applicative/vtkF3DObjectFactory.cxx
unusedVariable:*factory.cxx*
constParameter:library/src/image.cxx
invalidPointerCast:plugins/native/module/vtkF3DSplatReader.cxx
invalidPointerCast:plugins/draco/module/vtkF3DGLTFDocumentLoader.cxx
2 changes: 1 addition & 1 deletion .github/actions/vtk_commit_sha
Original file line number Diff line number Diff line change
@@ -1 +1 @@
be69fa3a50de292857e69702885576ac6f114805
697646db54ce4de95ff5adb500799e49feee4935
7 changes: 7 additions & 0 deletions application/testing/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,13 @@ if(F3D_PLUGIN_BUILD_DRACO)
f3d_test(NAME TestDRACO DATA suzanne.drc DEFAULT_LIGHTS ARGS --load-plugins=draco)
f3d_test(NAME TestDRACOColoring DATA suzanne.drc DEFAULT_LIGHTS ARGS --scalars --comp=0 --load-plugins=draco)

# Needs https://gitlab.kitware.com/vtk/vtk/-/merge_requests/10884
if(VTK_VERSION VERSION_GREATER_EQUAL 9.3.20240214)
f3d_test(NAME TestGLTFDracoImporter DATA Box_draco.gltf ARGS --load-plugins=draco --camera-position=-1.6,1.3,2.7)
f3d_test(NAME TestGLTFDracoReader DATA Box_draco.gltf ARGS --load-plugins=draco --geometry-only --camera-position=-1.6,1.3,2.7)
f3d_test(NAME TestGLTFDracoReaderWithoutCompression DATA BoxAnimated.gltf ARGS --load-plugins=draco --geometry-only --animation-time=2 DEFAULT_LIGHTS)
endif()

if(NOT F3D_MACOS_BUNDLE)
file(COPY "${F3D_SOURCE_DIR}/plugins/draco/configs/config.d/" DESTINATION "${CMAKE_BINARY_DIR}/share/f3d/configs/config_build.d")
f3d_test(NAME TestDefaultConfigFileDraco DATA suzanne.drc CONFIG config_build LONG_TIMEOUT DEFAULT_LIGHTS)
Expand Down
9 changes: 9 additions & 0 deletions library/src/factory.cxx.in
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ reader* factory::getReader(const std::string& fileName)
}
}

if (bestReader)
{
log::debug("The best reader for \"" + fileName + "\"is \"" + bestReader->getName() + "\"");
}
else
{
log::debug("No reader found for \"" + fileName + "\"");
}

return bestReader;
}

Expand Down
14 changes: 14 additions & 0 deletions plugins/draco/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@ f3d_plugin_declare_reader(
FORMAT_DESCRIPTION "Draco"
)

# Needs https://gitlab.kitware.com/vtk/vtk/-/merge_requests/10884
if(VTK_VERSION VERSION_GREATER_EQUAL 9.3.20240214)
f3d_plugin_declare_reader(
NAME GLTFDraco
SCORE 90
EXTENSIONS gltf glb
MIMETYPES model/gltf-binary model/gltf+json
VTK_READER vtkF3DGLTFReader
VTK_IMPORTER vtkF3DGLTFImporter
FORMAT_DESCRIPTION "GL Transmission Format"
CUSTOM_CODE "${CMAKE_CURRENT_SOURCE_DIR}/gltf.inl"
)
endif()

set(rpaths "")
get_target_property(target_type draco::draco TYPE)
if (target_type STREQUAL SHARED_LIBRARY)
Expand Down
17 changes: 17 additions & 0 deletions plugins/draco/gltf.inl
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
void applyCustomReader(vtkAlgorithm* algo, const std::string&) const override
{
vtkGLTFReader* gltfReader = vtkGLTFReader::SafeDownCast(algo);

// Enable all animations in the GLTFReader
// Specifying a non-zero framerate in the next call is not needed after VTK 9.2.20230603 : VTK_VERSION_CHECK(9, 2, 20230603)
gltfReader->SetFrameRate(30);
gltfReader->ApplyDeformationsToGeometryOn();
gltfReader->UpdateInformation(); // Read model metadata to get the number of animations
for (vtkIdType i = 0; i < gltfReader->GetNumberOfAnimations(); i++)
{
gltfReader->EnableAnimation(i);
}
// It is needed to update the information directly in order to recover it later
// Not entirely understood, TODO
gltfReader->UpdateInformation();
}
9 changes: 9 additions & 0 deletions plugins/draco/module/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ set(classes
vtkF3DDracoReader
)

# Needs https://gitlab.kitware.com/vtk/vtk/-/merge_requests/10884
if(VTK_VERSION VERSION_GREATER_EQUAL 9.3.20240214)
set(classes ${classes}
vtkF3DGLTFDocumentLoader
vtkF3DGLTFImporter
vtkF3DGLTFReader
)
endif()

set(_no_install "")
if(VTK_VERSION VERSION_GREATER_EQUAL 9.2.20220928)
set(_no_install "NO_INSTALL")
Expand Down
2 changes: 2 additions & 0 deletions plugins/draco/module/vtk.module
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ DESCRIPTION
DEPENDS
VTK::CommonCore
VTK::CommonExecutionModel
VTK::IOGeometry
VTK::IOImport
draco::draco
TEST_DEPENDS
VTK::TestingCore
Expand Down
173 changes: 173 additions & 0 deletions plugins/draco/module/vtkF3DGLTFDocumentLoader.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
#include "vtkF3DGLTFDocumentLoader.h"

#include <vtkObjectFactory.h>

#include <algorithm>

#include "draco/compression/decode.h"

namespace
{

//----------------------------------------------------------------------------
template<typename T>
std::vector<char> DecodeIndexBuffer(const std::unique_ptr<draco::Mesh>& mesh)
{
std::vector<char> outBuffer(mesh->num_faces() * 3 * sizeof(T));

for (draco::FaceIndex f(0); f < mesh->num_faces(); ++f)
{
const draco::Mesh::Face& face = mesh->face(f);

T indices[3] = { static_cast<T>(face[0].value()), static_cast<T>(face[1].value()),
static_cast<T>(face[2].value()) };

std::copy(
indices, indices + 3, reinterpret_cast<T*>(outBuffer.data() + f.value() * sizeof(indices)));
}

return outBuffer;
}

//----------------------------------------------------------------------------
std::vector<char> DecodeIndexBuffer(
const std::unique_ptr<draco::Mesh>& mesh, vtkGLTFDocumentLoader::ComponentType compType)
{
// indexing using float does not make sense
assert(compType != vtkGLTFDocumentLoader::ComponentType::FLOAT);

switch (compType)
{
case vtkGLTFDocumentLoader::ComponentType::BYTE:
return DecodeIndexBuffer<int8_t>(mesh);
case vtkGLTFDocumentLoader::ComponentType::UNSIGNED_BYTE:
return DecodeIndexBuffer<uint16_t>(mesh);
case vtkGLTFDocumentLoader::ComponentType::SHORT:
return DecodeIndexBuffer<int16_t>(mesh);
case vtkGLTFDocumentLoader::ComponentType::UNSIGNED_SHORT:
return DecodeIndexBuffer<uint16_t>(mesh);
case vtkGLTFDocumentLoader::ComponentType::UNSIGNED_INT:
return DecodeIndexBuffer<uint32_t>(mesh);
default:
break;
}

return {};
}

//----------------------------------------------------------------------------
template<typename T>
std::vector<char> DecodeVertexBuffer(
const std::unique_ptr<draco::Mesh>& mesh, const draco::PointAttribute* attribute)
{
std::vector<char> outBuffer(mesh->num_points() * attribute->num_components() * sizeof(T));
std::vector<T> values(attribute->num_components());

size_t byteOffset = 0;
for (draco::PointIndex i(0); i < mesh->num_points(); ++i)
{
attribute->ConvertValue<T>(
attribute->mapped_index(i), attribute->num_components(), values.data());

std::copy(values.begin(), values.end(), reinterpret_cast<T*>(outBuffer.data() + byteOffset));

byteOffset += sizeof(T) * attribute->num_components();
}

return outBuffer;
}

//----------------------------------------------------------------------------
std::vector<char> DecodeVertexBuffer(vtkGLTFDocumentLoader::ComponentType compType,
const std::unique_ptr<draco::Mesh>& mesh, int attIndex)
{
(void)compType;

const draco::PointAttribute* attribute = mesh->GetAttributeByUniqueId(attIndex);

assert(compType == vtkGLTFDocumentLoader::ComponentType::FLOAT);

return DecodeVertexBuffer<float>(mesh, attribute);
}
}

//----------------------------------------------------------------------------
vtkStandardNewMacro(vtkF3DGLTFDocumentLoader);

//----------------------------------------------------------------------------
std::vector<std::string> vtkF3DGLTFDocumentLoader::GetSupportedExtensions()
{
std::vector<std::string> extensions = this->Superclass::GetSupportedExtensions();
extensions.emplace_back("KHR_draco_mesh_compression");
return extensions;
}

//----------------------------------------------------------------------------
void vtkF3DGLTFDocumentLoader::PrepareData()
{
std::shared_ptr<Model> model = this->GetInternalModel();

for (size_t i = 0; i < model->Meshes.size(); i++)
{
for (Primitive& primitive : model->Meshes[i].Primitives)
{
// check if Draco metadata is present
auto& dracoMetaData = primitive.ExtensionMetaData.KHRDracoMetaData;
if (dracoMetaData.BufferView >= 0)
{
auto& view = model->BufferViews[primitive.ExtensionMetaData.KHRDracoMetaData.BufferView];
auto& buffer = model->Buffers[view.Buffer];

draco::DecoderBuffer decoderBuffer;
decoderBuffer.Init(buffer.data() + view.ByteOffset, view.ByteLength);
auto decodeResult = draco::Decoder().DecodeMeshFromBuffer(&decoderBuffer);
if (decodeResult.ok())
{
const std::unique_ptr<draco::Mesh>& mesh = decodeResult.value();

// handle index buffer
if (primitive.IndicesId >= 0)
{
auto& accessor = model->Accessors[primitive.IndicesId];

model->Buffers.emplace_back(::DecodeIndexBuffer(mesh, accessor.ComponentTypeValue));

vtkGLTFDocumentLoader::BufferView decodedIndexBufferView;
decodedIndexBufferView.Buffer = static_cast<int>(model->Buffers.size() - 1);
decodedIndexBufferView.ByteLength = model->Buffers.back().size();
decodedIndexBufferView.ByteOffset = 0;
decodedIndexBufferView.ByteStride = 0;
decodedIndexBufferView.Target =
static_cast<int>(vtkGLTFDocumentLoader::Target::ARRAY_BUFFER);
model->BufferViews.emplace_back(std::move(decodedIndexBufferView));

accessor.BufferView = static_cast<int>(model->BufferViews.size() - 1);
accessor.Count = static_cast<int>(mesh->num_faces() * 3);
}

// handle vertex attributes
for (const auto& attrib : dracoMetaData.AttributeIndices)
{
auto& attrAccessor = model->Accessors[primitive.AttributeIndices[attrib.first]];

model->Buffers.emplace_back(
::DecodeVertexBuffer(attrAccessor.ComponentTypeValue, mesh, attrib.second));

vtkGLTFDocumentLoader::BufferView decodedBufferView;
decodedBufferView.Buffer = static_cast<int>(model->Buffers.size() - 1);
decodedBufferView.ByteLength = model->Buffers.back().size();
decodedBufferView.ByteOffset = 0;
decodedBufferView.ByteStride = 0;
decodedBufferView.Target =
static_cast<int>(vtkGLTFDocumentLoader::Target::ELEMENT_ARRAY_BUFFER);
model->BufferViews.emplace_back(std::move(decodedBufferView));

attrAccessor.BufferView = static_cast<int>(model->BufferViews.size() - 1);
attrAccessor.Count = static_cast<int>(mesh->num_points());
attrAccessor.ByteOffset = 0;
}
}
}
}
}
}
40 changes: 40 additions & 0 deletions plugins/draco/module/vtkF3DGLTFDocumentLoader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* @class vtkF3DGLTFDocumentLoader
* @brief Specialized GLTF document loader with Draco buffer decoding
*
* This class subclasses vtkGLTFDocumentLoader to handle Draco metadata
*/

#ifndef vtkF3DGLTFDocumentLoader_h
#define vtkF3DGLTFDocumentLoader_h

#include <vtkGLTFDocumentLoader.h>

class vtkF3DGLTFDocumentLoader : public vtkGLTFDocumentLoader
{
public:
static vtkF3DGLTFDocumentLoader* New();
vtkTypeMacro(vtkF3DGLTFDocumentLoader, vtkGLTFDocumentLoader);

/**
* Overridden to add KHR_draco_mesh_compression support
*/
std::vector<std::string> GetSupportedExtensions() override;

/**
* Overridden to handle Draco metadata and modify the GLTF model.
* This will take information in metadata to decode buffers and replace
* the buffer view in the accessors to point to the new decoded buffers.
*/
void PrepareData() override;

protected:
vtkF3DGLTFDocumentLoader() = default;
~vtkF3DGLTFDocumentLoader() override = default;

private:
vtkF3DGLTFDocumentLoader(const vtkF3DGLTFDocumentLoader&) = delete;
void operator=(const vtkF3DGLTFDocumentLoader&) = delete;
};

#endif
14 changes: 14 additions & 0 deletions plugins/draco/module/vtkF3DGLTFImporter.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#include "vtkF3DGLTFImporter.h"

#include "vtkF3DGLTFDocumentLoader.h"

#include <vtkObjectFactory.h>

//----------------------------------------------------------------------------
vtkStandardNewMacro(vtkF3DGLTFImporter);

//----------------------------------------------------------------------------
void vtkF3DGLTFImporter::InitializeLoader()
{
this->Loader = vtkSmartPointer<vtkF3DGLTFDocumentLoader>::New();
}
34 changes: 34 additions & 0 deletions plugins/draco/module/vtkF3DGLTFImporter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* @class vtkF3DGLTFImporter
* @brief VTK GLTF importer with Draco support
*
* Subclasses the native importer to initialize our own loader.
* @sa vtkF3DGLTFDocumentLoader
*/

#ifndef vtkF3DGLTFImporter_h
#define vtkF3DGLTFImporter_h

#include <vtkGLTFImporter.h>

class vtkF3DGLTFImporter : public vtkGLTFImporter
{
public:
static vtkF3DGLTFImporter* New();
vtkTypeMacro(vtkF3DGLTFImporter, vtkGLTFImporter);

protected:
vtkF3DGLTFImporter() = default;
~vtkF3DGLTFImporter() override = default;

/**
* Overridden to instantiate our own document loader
*/
void InitializeLoader() override;

private:
vtkF3DGLTFImporter(const vtkF3DGLTFImporter&) = delete;
void operator=(const vtkF3DGLTFImporter&) = delete;
};

#endif
14 changes: 14 additions & 0 deletions plugins/draco/module/vtkF3DGLTFReader.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#include "vtkF3DGLTFReader.h"

#include "vtkF3DGLTFDocumentLoader.h"

#include <vtkObjectFactory.h>

//----------------------------------------------------------------------------
vtkStandardNewMacro(vtkF3DGLTFReader);

//----------------------------------------------------------------------------
void vtkF3DGLTFReader::InitializeLoader()
{
this->Loader = vtkSmartPointer<vtkF3DGLTFDocumentLoader>::New();
}
Loading

0 comments on commit c4af5eb

Please sign in to comment.