Skip to content

Instantly share code, notes, and snippets.

@xissburg
Created May 29, 2023 18:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save xissburg/013a45e578629929da538b9378539ab0 to your computer and use it in GitHub Desktop.
Save xissburg/013a45e578629929da538b9378539ab0 to your computer and use it in GitHub Desktop.
Ogre GLTF Importer
#define TINYGLTF_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "OgreGLTFImporter.hpp"
#include <OgreRoot.h>
#include <OgreItem.h>
#include <OgreMesh2.h>
#include <OgreSubMesh2.h>
#include <OgreMeshManager2.h>
#include <OgreSceneManager.h>
#include <OgreTextureGpu.h>
#include <OgreStagingTexture.h>
#include <OgreTextureGpuManager.h>
#include <OgrePixelFormatGpuUtils.h>
#include <OgreTextureFilters.h>
#include <Vao/OgreVaoManager.h>
#include <Vao/OgreIndexBufferPacked.h>
#include <OgreHardwareIndexBuffer.h>
#include <OgreHlms.h>
#include <OgreHlmsPbs.h>
#include <Hlms/Pbs/OgreHlmsPbsDatablock.h>
#include <OgreOldBone.h>
#include <OgreSkeleton.h>
#include <OgreOldSkeletonManager.h>
#include <Animation/OgreTagPoint.h>
#include <Animation/OgreSkeletonManager.h>
#include <Animation/OgreSkeletonDef.h>
#include <Animation/OgreSkeletonInstance.h>
#include <OgreLogManager.h>
#include <type_traits>
#include <algorithm>
#include <limits>
#include <cassert>
namespace OgreGLTF {
static constexpr auto AttributeNamePosition = "POSITION";
static constexpr auto AttributeNameNormal = "NORMAL";
static constexpr auto AttributeNameTangent = "TANGENT";
static constexpr auto AttributeNameTexcoord0 = "TEXCOORD_0";
static constexpr auto AttributeNameTexcoord1 = "TEXCOORD_1";
static constexpr auto AttributeNameColor0 = "COLOR_0";
static constexpr auto AttributeNameJoints0 = "JOINTS_0";
static constexpr auto AttributeNameWeights0 = "WEIGHTS_0";
struct Transform
{
Ogre::Vector3 position;
Ogre::Quaternion orientation;
};
Ogre::Vector3 toVector3(const std::vector<double> &v)
{
assert(v.size() >= 3);
return {static_cast<Ogre::Real>(v[0]),
static_cast<Ogre::Real>(v[1]),
static_cast<Ogre::Real>(v[2])};
}
Ogre::Quaternion toQuaternion(const std::vector<double> &v)
{
assert(v.size() >= 4);
return {static_cast<Ogre::Real>(v[3]),
static_cast<Ogre::Real>(v[0]),
static_cast<Ogre::Real>(v[1]),
static_cast<Ogre::Real>(v[2])};
}
Ogre::Matrix4 toMatrix(const std::vector<double> &v)
{
assert(v.size() == 16);
auto arr = std::array<Ogre::Real, 16>{};
std::transform(v.begin(), v.end(), arr.begin(), [] (auto &&s) { return static_cast<Ogre::Real>(s);});
return {arr.data()};
}
Ogre::VertexElementSemantic GetVertexElementSemantic(const std::string &type)
{
if (type == AttributeNamePosition) return Ogre::VES_POSITION;
if (type == AttributeNameNormal) return Ogre::VES_NORMAL;
if (type == AttributeNameTangent) return Ogre::VES_TANGENT;
if (type == AttributeNameTexcoord0) return Ogre::VES_TEXTURE_COORDINATES;
if (type == AttributeNameTexcoord1) return Ogre::VES_TEXTURE_COORDINATES;
if (type == AttributeNameColor0) return Ogre::VES_DIFFUSE;
if (type == AttributeNameJoints0) return Ogre::VES_BLEND_INDICES;
if (type == AttributeNameWeights0) return Ogre::VES_BLEND_WEIGHTS;
assert(false);
return Ogre::VES_COUNT;
}
Ogre::VertexElementType GetVertexElementType(const tinygltf::Accessor &accessor)
{
switch (accessor.type) {
case TINYGLTF_TYPE_VEC2:
switch (accessor.componentType) {
case TINYGLTF_COMPONENT_TYPE_DOUBLE:
return Ogre::VET_DOUBLE2;
case TINYGLTF_COMPONENT_TYPE_FLOAT:
return Ogre::VET_FLOAT2;
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
return Ogre::VET_USHORT2;
}
break;
case TINYGLTF_TYPE_VEC3:
switch (accessor.componentType) {
case TINYGLTF_COMPONENT_TYPE_DOUBLE:
return Ogre::VET_DOUBLE3;
case TINYGLTF_COMPONENT_TYPE_FLOAT:
return Ogre::VET_FLOAT3;
}
break;
case TINYGLTF_TYPE_VEC4:
switch (accessor.componentType) {
case TINYGLTF_COMPONENT_TYPE_DOUBLE:
return Ogre::VET_DOUBLE4;
case TINYGLTF_COMPONENT_TYPE_FLOAT:
return Ogre::VET_FLOAT4;
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
return Ogre::VET_USHORT4;
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
return Ogre::VET_UBYTE4;
}
break;
}
assert(false);
return Ogre::VET_FLOAT3;
}
Ogre::OperationType GetOperationType(const tinygltf::Primitive &primitive)
{
switch (primitive.mode) {
case TINYGLTF_MODE_LINE:
return Ogre::OT_LINE_LIST;
case TINYGLTF_MODE_LINE_LOOP:
return Ogre::OT_LINE_STRIP;
case TINYGLTF_MODE_POINTS:
return Ogre::OT_POINT_LIST;
case TINYGLTF_MODE_TRIANGLES:
return Ogre::OT_TRIANGLE_LIST;
case TINYGLTF_MODE_TRIANGLE_FAN:
return Ogre::OT_TRIANGLE_FAN;
case TINYGLTF_MODE_TRIANGLE_STRIP:
return Ogre::OT_TRIANGLE_STRIP;
}
assert(false);
return Ogre::OT_TRIANGLE_LIST;
}
template<typename SceneNodeType>
void AssignNodeTransform(const tinygltf::Node &node, SceneNodeType *sceneNode)
{
if (!node.translation.empty()) {
sceneNode->setPosition(toVector3(node.translation));
}
if (!node.rotation.empty()) {
sceneNode->setOrientation(toQuaternion(node.rotation));
}
if (!node.scale.empty()) {
sceneNode->setScale(toVector3(node.scale));
}
if (!node.matrix.empty()) {
auto matrix = toMatrix(node.matrix);
Ogre::Vector3 position;
Ogre::Quaternion orientation;
Ogre::Vector3 scale;
matrix.transpose().decomposition(position, scale, orientation);
sceneNode->setPosition(position);
sceneNode->setOrientation(orientation);
sceneNode->setScale(scale);
}
}
Ogre::IndexBufferPacked * CreateIndexBuffer(const tinygltf::Model &model,
const tinygltf::Primitive &primitive)
{
auto &accessor = model.accessors[primitive.indices];
auto &bufferView = model.bufferViews[accessor.bufferView];
auto &buffer = model.buffers[bufferView.buffer];
auto stride = accessor.ByteStride(bufferView);
auto offset = bufferView.byteOffset + accessor.byteOffset;
auto indexCount = accessor.count;
assert(stride > 0);
auto indexTypeByte = accessor.componentType == TINYGLTF_COMPONENT_TYPE_BYTE ||
accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE;
auto indexTypeShort = accessor.componentType == TINYGLTF_PARAMETER_TYPE_SHORT ||
accessor.componentType == TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT;
auto indexType = indexTypeByte || indexTypeShort ?
Ogre::IndexBufferPacked::IT_16BIT :
Ogre::IndexBufferPacked::IT_32BIT;
auto elementSize = indexTypeByte ? sizeof(uint8_t) : (indexTypeShort ? sizeof(uint16_t) : sizeof(uint32_t));
auto *sourceData = buffer.data.data();
auto *indexData = reinterpret_cast<uint8_t*>(OGRE_MALLOC_SIMD(elementSize * indexCount, Ogre::MEMCATEGORY_GEOMETRY));
Ogre::FreeOnDestructor bufferPtrContainer(indexData);
for (auto i = 0; i < indexCount; ++i) {
std::memcpy(indexData + i * elementSize, sourceData + offset + i * stride, elementSize);
}
auto *vaoManager = Ogre::Root::getSingleton().getRenderSystem()->getVaoManager();
auto *indexBuffer = vaoManager->createIndexBuffer(indexType, indexCount, Ogre::BT_IMMUTABLE, indexData, false);
return indexBuffer;
}
Ogre::VertexBufferPacked * CreateVertexBuffer(const tinygltf::Model &model,
const tinygltf::Primitive &primitive,
std::vector<Ogre::Aabb> &boundingBoxes)
{
auto vertexSize = 0;
auto numVertices = 0;
Ogre::VertexElement2Vec vertexElements;
std::vector<int> attributeSizes;
attributeSizes.reserve(primitive.attributes.size());
for (auto &attribute : primitive.attributes) {
auto &accessor = model.accessors[attribute.second];
//auto &bufferView = model.bufferViews[accessor.bufferView];
//auto &buffer = model.buffers[bufferView.buffer];
assert(numVertices == 0 || numVertices == accessor.count);
numVertices = accessor.count;
auto numElements = 0;
switch (accessor.type) {
case TINYGLTF_TYPE_VEC2:
numElements = 2;
break;
case TINYGLTF_TYPE_VEC3:
numElements = 3;
break;
case TINYGLTF_TYPE_VEC4:
numElements = 4;
break;
default:
assert(false);
}
auto componentSize = 0;
switch (accessor.componentType)
{
case TINYGLTF_COMPONENT_TYPE_DOUBLE:
componentSize = sizeof(double);
break;
case TINYGLTF_COMPONENT_TYPE_FLOAT:
componentSize = sizeof(float);
break;
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
componentSize = sizeof(unsigned short);
break;
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
componentSize = sizeof(unsigned char);
break;
default:
assert(false);
}
auto attributeSize = numElements * componentSize;
attributeSizes.push_back(attributeSize);
vertexSize += attributeSize;
auto semantic = GetVertexElementSemantic(attribute.first);
auto elementType = GetVertexElementType(accessor);
vertexElements.emplace_back(elementType, semantic);
if (semantic == Ogre::VES_POSITION) {
auto aabbMin = toVector3(accessor.minValues);
auto aabbMax = toVector3(accessor.maxValues);
boundingBoxes.push_back(Ogre::Aabb::newFromExtents(aabbMin, aabbMax));
}
}
auto vertexBufferSize = vertexSize * numVertices;
auto *vertexData = reinterpret_cast<uint8_t*>(OGRE_MALLOC_SIMD(vertexBufferSize, Ogre::MEMCATEGORY_GEOMETRY));
Ogre::FreeOnDestructor bufferPtrContainer(vertexData);
for (auto i = 0; i < numVertices; ++i) {
auto vertexOffset = 0;
auto attrIdx = 0;
for (auto &attribute : primitive.attributes) {
auto &accessor = model.accessors[attribute.second];
auto &bufferView = model.bufferViews[accessor.bufferView];
auto &buffer = model.buffers[bufferView.buffer];
auto stride = accessor.ByteStride(bufferView);
auto offset = bufferView.byteOffset + accessor.byteOffset;
auto *sourceData = buffer.data.data() + offset;
auto attributeSize = attributeSizes[attrIdx];
std::memcpy(vertexData + i * vertexSize + vertexOffset, sourceData + i * stride, attributeSize);
vertexOffset += attributeSize;
++attrIdx;
}
}
auto *vaoManager = Ogre::Root::getSingleton().getRenderSystem()->getVaoManager();
auto *vertexBuffer = vaoManager->createVertexBuffer(vertexElements, numVertices, Ogre::BT_IMMUTABLE, vertexData, false);
return vertexBuffer;
}
void CreateBoneAssignments(const tinygltf::Model &model, const tinygltf::Primitive &primitive,
Ogre::SubMesh *subMesh)
{
auto blendIndicesAttribute = std::find_if(
primitive.attributes.begin(), primitive.attributes.end(),
[] (auto &&attribute) {
return GetVertexElementSemantic(attribute.first) == Ogre::VES_BLEND_INDICES;
});
auto blendWeightsAttribute = std::find_if(
primitive.attributes.begin(), primitive.attributes.end(),
[] (auto &&attribute) {
return GetVertexElementSemantic(attribute.first) == Ogre::VES_BLEND_WEIGHTS;
});
if (blendIndicesAttribute == primitive.attributes.end() ||
blendWeightsAttribute == primitive.attributes.end())
{
return;
}
auto &indicesAccessor = model.accessors[blendIndicesAttribute->second];
auto &indicesBufferView = model.bufferViews[indicesAccessor.bufferView];
auto &indicesBuffer = model.buffers[indicesBufferView.buffer];
auto indicesStride = indicesAccessor.ByteStride(indicesBufferView);
auto indicesOffset = indicesBufferView.byteOffset + indicesAccessor.byteOffset;
auto *indicesData = indicesBuffer.data.data() + indicesOffset;
auto &weightsAccessor = model.accessors[blendWeightsAttribute->second];
auto &weightsBufferView = model.bufferViews[weightsAccessor.bufferView];
auto &weightsBuffer = model.buffers[weightsBufferView.buffer];
auto weightsStride = weightsAccessor.ByteStride(weightsBufferView);
auto weightsOffset = weightsBufferView.byteOffset + weightsAccessor.byteOffset;
auto *weightsData = weightsBuffer.data.data() + weightsOffset;
assert(indicesAccessor.count == weightsAccessor.count && indicesAccessor.count > 0);
auto numVertices = indicesAccessor.count;
constexpr auto numElements = 4;
std::array<Ogre::uint16, numElements> boneIndices;
std::array<Ogre::Real, numElements> weights;
for (size_t vertexIndex = 0; vertexIndex < numVertices; ++vertexIndex) {
switch (indicesAccessor.componentType) {
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
std::memcpy(boneIndices.data(), indicesData + vertexIndex * indicesStride, sizeof(boneIndices));
break;
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
{
std::array<Ogre::uint8, numElements> bytes;
std::memcpy(bytes.data(), indicesData + vertexIndex * indicesStride, sizeof(bytes));
std::transform(bytes.begin(), bytes.end(), boneIndices.begin(),
[] (auto &&s) {
return static_cast<Ogre::uint16>(s);
});
}
break;
default:
assert(false);
}
switch (weightsAccessor.componentType) {
case TINYGLTF_COMPONENT_TYPE_FLOAT:
if constexpr(std::is_same_v<Ogre::Real, float>) {
std::memcpy(weights.data(), weightsData + vertexIndex * weightsStride, sizeof(weights));
}
else {
std::array<float, numElements> floats;
std::memcpy(floats.data(), weightsData + vertexIndex * weightsStride, sizeof(floats));
std::transform(floats.begin(), floats.end(), weights.begin(),
[] (auto &&s) {
return static_cast<Ogre::Real>(s);
});
}
break;
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
{
std::array<unsigned short, numElements> shorts;
std::memcpy(shorts.data(), weightsData + vertexIndex * weightsStride, sizeof(shorts));
std::transform(shorts.begin(), shorts.end(), weights.begin(),
[] (auto &&s) {
return static_cast<Ogre::Real>(s) / Ogre::Real(std::numeric_limits<unsigned short>::max());
});
}
break;
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
{
std::array<unsigned char, numElements> bytes;
std::memcpy(bytes.data(), weightsData + vertexIndex * weightsStride, sizeof(bytes));
std::transform(bytes.begin(), bytes.end(), weights.begin(),
[] (auto &&s) {
return static_cast<Ogre::Real>(s) / Ogre::Real(std::numeric_limits<unsigned char>::max());
});
}
break;
default:
assert(false);
}
for (auto i = 0; i < numElements; ++i) {
auto assignment = Ogre::VertexBoneAssignment(vertexIndex, boneIndices[i], weights[i]);
subMesh->addBoneAssignment(assignment);
}
}
subMesh->_compileBoneAssignments();
}
void CreateMorphTargets(const tinygltf::Model &model, const tinygltf::Primitive &primitive,
Ogre::SubMesh *subMesh)
{
// Data for morph targets is always VEC3 of FLOAT.
std::vector<const float *> positionData;
std::vector<const float *> normalData;
std::vector<Ogre::String> targetNames;
size_t numVertices = 0;
//size_t targetIndex = 0;
for (auto &target : primitive.targets) {
if (target.count(AttributeNamePosition)) {
auto accessorIndex = target.at(AttributeNamePosition);
auto &accessor = model.accessors[accessorIndex];
auto &bufferView = model.bufferViews[accessor.bufferView];
auto &buffer = model.buffers[bufferView.buffer];
//auto stride = accessor.ByteStride(bufferView);
auto offset = bufferView.byteOffset + accessor.byteOffset;
auto *data = reinterpret_cast<const float *>(buffer.data.data() + offset);
positionData.push_back(data);
numVertices = accessor.count;
}
if (target.count(AttributeNameNormal)) {
auto accessorIndex = target.at(AttributeNameNormal);
auto &accessor = model.accessors[accessorIndex];
auto &bufferView = model.bufferViews[accessor.bufferView];
auto &buffer = model.buffers[bufferView.buffer];
//auto stride = accessor.ByteStride(bufferView);
auto offset = bufferView.byteOffset + accessor.byteOffset;
auto *data = reinterpret_cast<const float *>(buffer.data.data() + offset);
normalData.push_back(data);
}
}
if (!positionData.empty()) {
auto normalPtr = normalData.empty() ? nullptr : normalData.data();
auto namesPtr = targetNames.empty() ? nullptr : targetNames.data();
subMesh->createPoses(positionData.data(), normalPtr, positionData.size(), numVertices, namesPtr);
}
}
Ogre::MeshPtr CreateMesh(const tinygltf::Model &model, const tinygltf::Mesh &mesh)
{
auto &meshManager = Ogre::MeshManager::getSingleton();
auto ogreMesh = meshManager.createManual(mesh.name, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
auto *vaoManager = Ogre::Root::getSingleton().getRenderSystem()->getVaoManager();
auto boundingBoxes = std::vector<Ogre::Aabb>();
boundingBoxes.reserve(mesh.primitives.size());
assert(mesh.primitives.size() > 0);
for (auto &primitive : mesh.primitives) {
auto *indexBuffer = CreateIndexBuffer(model, primitive);
auto *vertexBuffer = CreateVertexBuffer(model, primitive, boundingBoxes);
auto opType = GetOperationType(primitive);
auto *vao = vaoManager->createVertexArrayObject({vertexBuffer}, indexBuffer, opType);
auto *subMesh = ogreMesh->createSubMesh();
subMesh->mVao[Ogre::VpNormal].push_back(vao);
subMesh->mVao[Ogre::VpShadow].push_back(vao);
CreateBoneAssignments(model, primitive, subMesh);
CreateMorphTargets(model, primitive, subMesh);
}
auto boundingBox = boundingBoxes.front();
for (size_t i = 1; i < boundingBoxes.size(); ++i) {
boundingBox.merge(boundingBoxes[i]);
}
ogreMesh->_setBounds(boundingBox, true);
return ogreMesh;
}
Ogre::PixelFormatGpu ConvertPixelType(int pixelType, int numComponents)
{
if (numComponents == 3) {
switch (pixelType) {
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
return Ogre::PFG_RGB8_UNORM;
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
return Ogre::PFG_RGB16_UNORM;
case TINYGLTF_COMPONENT_TYPE_FLOAT:
return Ogre::PFG_RGB32_FLOAT;
default:
assert(false);
}
}
else if (numComponents == 4) {
switch (pixelType) {
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
return Ogre::PFG_RGBA8_UNORM;
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
return Ogre::PFG_RGBA16_UNORM;
case TINYGLTF_COMPONENT_TYPE_FLOAT:
return Ogre::PFG_RGBA32_FLOAT;
default:
assert(false);
}
}
assert(false);
return {};
}
Ogre::TextureGpu * CreateTexture(Ogre::TextureGpuManager *textureManager, const tinygltf::Image &image, const std::string &name)
{
auto pixelFormat = ConvertPixelType(image.pixel_type, image.component);
auto sizeInBytes = Ogre::PixelFormatGpuUtils::calculateSizeBytes(image.width, image.height, 1, 1, pixelFormat, 1, 4);
auto buffer = reinterpret_cast<std::uint8_t*>(OGRE_MALLOC_SIMD(sizeInBytes, Ogre::MEMCATEGORY_RESOURCE));
auto ogreImage = Ogre::Image2();
ogreImage.loadDynamicImage(buffer, image.width, image.height, 1u, Ogre::TextureTypes::Type2D, pixelFormat, true, 1);
std::memcpy(buffer, image.image.data(), sizeInBytes);
ogreImage.generateMipmaps(true, Ogre::Image2::FILTER_GAUSSIAN_HIGH);
auto flags = Ogre::TextureFlags::ManualTexture | Ogre::TextureFlags::AutomaticBatching;
auto *ogreTexture = textureManager->createOrRetrieveTexture(
name, Ogre::GpuPageOutStrategy::Discard,
flags, Ogre::TextureTypes::Type2D,
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
Ogre::TextureFilter::FilterTypes::TypeGenerateDefaultMipmaps);
ogreTexture->setResolution(ogreImage.getWidth(), ogreImage.getHeight());
ogreTexture->setNumMipmaps(ogreImage.getNumMipmaps());
ogreTexture->setPixelFormat(ogreImage.getPixelFormat());
ogreTexture->scheduleTransitionTo(Ogre::GpuResidency::Resident);
ogreImage.uploadTo(ogreTexture, 0, ogreTexture->getNumMipmaps() - 1u);
return ogreTexture;
}
Ogre::TextureAddressingMode ConvertSamplerAddressingMode(int wrapMode)
{
switch (wrapMode) {
case TINYGLTF_TEXTURE_WRAP_REPEAT:
return Ogre::TextureAddressingMode::TAM_WRAP;
case TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE:
return Ogre::TextureAddressingMode::TAM_CLAMP;
case TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT:
return Ogre::TextureAddressingMode::TAM_MIRROR;
}
assert(false);
return Ogre::TextureAddressingMode::TAM_WRAP;
}
Ogre::FilterOptions ConvertSamplerFilterOption(int filter)
{
switch (filter) {
case TINYGLTF_TEXTURE_FILTER_NEAREST:
case TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST:
case TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR:
return Ogre::FilterOptions::FO_POINT;
case TINYGLTF_TEXTURE_FILTER_LINEAR:
case TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST:
case TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR:
return Ogre::FilterOptions::FO_LINEAR;
}
return Ogre::FilterOptions::FO_NONE;
}
Ogre::FilterOptions ConvertSamplerFilterMipmapOption(int filter)
{
switch (filter) {
case TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST:
case TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST:
return Ogre::FilterOptions::FO_POINT;
case TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR:
case TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR:
return Ogre::FilterOptions::FO_LINEAR;
}
return Ogre::FilterOptions::FO_NONE;
}
Ogre::HlmsSamplerblock CreateSamplerBlock(const tinygltf::Sampler &sampler)
{
auto samplerBlock = Ogre::HlmsSamplerblock();
samplerBlock.mU = ConvertSamplerAddressingMode(sampler.wrapS);
samplerBlock.mV = ConvertSamplerAddressingMode(sampler.wrapT);
samplerBlock.mW = ConvertSamplerAddressingMode(sampler.wrapR);
if (sampler.minFilter != -1) {
samplerBlock.mMinFilter = ConvertSamplerFilterOption(sampler.minFilter);
samplerBlock.mMipFilter = ConvertSamplerFilterMipmapOption(sampler.minFilter);
}
if (sampler.magFilter != -1) {
samplerBlock.mMagFilter = ConvertSamplerFilterOption(sampler.magFilter);
}
return samplerBlock;
}
Ogre::HlmsPbsDatablock * CreateMaterial(const tinygltf::Model &model, const tinygltf::Material &material, Ogre::HlmsPbs *hlms)
{
auto materialId = Ogre::IdString(material.name);
auto *datablock = static_cast<Ogre::HlmsPbsDatablock *>(
hlms->createDatablock(materialId, material.name,
Ogre::HlmsMacroblock{},
Ogre::HlmsBlendblock{},
Ogre::HlmsParamVec{}));
datablock->setWorkflow(Ogre::HlmsPbsDatablock::Workflows::MetallicWorkflow);
auto &pbr = material.pbrMetallicRoughness;
datablock->setMetalness(pbr.metallicFactor);
datablock->setRoughness(pbr.roughnessFactor);
if (!pbr.baseColorFactor.empty()) {
auto baseColor = toVector3(material.pbrMetallicRoughness.baseColorFactor);
datablock->setDiffuse(baseColor);
auto alpha = material.pbrMetallicRoughness.baseColorFactor[3];
auto transparencyMode = alpha == 1 ? Ogre::HlmsPbsDatablock::None : Ogre::HlmsPbsDatablock::Transparent;
datablock->setTransparency(alpha, transparencyMode);
}
if (pbr.baseColorTexture.index >= 0) {
auto &texture = model.textures[pbr.baseColorTexture.index];
assert(texture.source >= 0);
auto &image = model.images[texture.source];
auto name = "glTF_" + (image.name.empty() ? "" : image.name + "_") + std::to_string(texture.source);
auto &root = Ogre::Root::getSingleton();
auto *textureManager = root.getRenderSystem()->getTextureGpuManager();
auto *ogreTexture = textureManager->findTextureNoThrow(name);
if (!ogreTexture) {
ogreTexture = CreateTexture(textureManager, image, name);
}
datablock->setTexture(Ogre::PbsTextureTypes::PBSM_DIFFUSE, ogreTexture);
datablock->setTextureUvSource(Ogre::PbsTextureTypes::PBSM_DIFFUSE, pbr.baseColorTexture.texCoord);
if (texture.sampler >= 0) {
auto &sampler = model.samplers[texture.sampler];
auto samplerBlock = CreateSamplerBlock(sampler);
datablock->setSamplerblock(Ogre::PbsTextureTypes::PBSM_DIFFUSE, samplerBlock);
}
}
return datablock;
}
void AssignMaterials(const tinygltf::Model &model, const tinygltf::Mesh &mesh, Ogre::HlmsPbs *hlms, Ogre::Item *item)
{
for (size_t i = 0; i < mesh.primitives.size(); ++i) {
auto materialIndex = mesh.primitives[i].material;
if (materialIndex < 0) {
continue;
}
auto &material = model.materials[materialIndex];
auto materialId = Ogre::IdString(material.name);
auto *datablock = static_cast<Ogre::HlmsPbsDatablock *>(hlms->getDatablock(materialId));
if (datablock == nullptr) {
datablock = CreateMaterial(model, material, hlms);
}
auto *subItem = item->getSubItem(i);
subItem->setDatablock(datablock);
}
}
void CreateChildBones(const tinygltf::Model &model, const tinygltf::Skin &skin,
const tinygltf::Node &jointNode, const std::string &skinName,
Ogre::v1::Skeleton *skeleton, Ogre::v1::OldBone *parentBone)
{
for (auto childNodeIndex : jointNode.children) {
auto &childNode = model.nodes[childNodeIndex];
if (childNode.mesh >= 0) {
continue;
}
auto childJointIt = std::find(skin.joints.begin(), skin.joints.end(), childNodeIndex);
auto childJointIndex = std::distance(skin.joints.begin(), childJointIt);
auto childBoneName = childNode.name.empty() ? skinName + std::to_string(childJointIndex) : childNode.name;
auto *childBone = skeleton->createBone(childBoneName, childJointIndex);
parentBone->addChild(childBone);
AssignNodeTransform(childNode, childBone);
CreateChildBones(model, skin, childNode, skinName, skeleton, childBone);
}
}
std::vector<int> FindRootJoints(const tinygltf::Model &model, const tinygltf::Skin &skin)
{
std::vector<int> rootJoints;
std::vector<int> childJoints;
for (auto jointIndex : skin.joints) {
auto &jointNode = model.nodes[jointIndex];
childJoints.insert(childJoints.end(), jointNode.children.begin(), jointNode.children.end());
}
for (auto jointIndex : skin.joints) {
if (std::find(childJoints.begin(), childJoints.end(), jointIndex) == childJoints.end()) {
rootJoints.push_back(jointIndex);
}
}
return rootJoints;
}
Ogre::v1::SkeletonPtr CreateSkeleton(const tinygltf::Model &model,
const tinygltf::Skin &skin,
const std::string &skinName,
const Transform &parentTransform)
{
auto &skeletonManager = Ogre::v1::OldSkeletonManager::getSingleton();
auto isManual = true; // Set this to true to avoid lots of grief.
auto skeleton = skeletonManager.create(skinName, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, isManual);
auto rootJoints = FindRootJoints(model, skin);
for (auto nodeIndex : rootJoints) {
auto &jointNode = model.nodes[nodeIndex];
auto jointIt = std::find(skin.joints.begin(), skin.joints.end(), nodeIndex);
auto jointIndex = std::distance(skin.joints.begin(), jointIt);
auto boneName = jointNode.name.empty() ? skinName + std::to_string(jointIndex) : jointNode.name;
auto *rootBone = skeleton->createBone(boneName, jointIndex);
AssignNodeTransform(jointNode, rootBone);
// Root bones must be in world space because they don't have a parent bone.
auto rootWorldPos = parentTransform.position + parentTransform.orientation * rootBone->getPosition();
rootBone->setPosition(rootWorldPos);
rootBone->setOrientation(parentTransform.orientation * rootBone->getOrientation());
CreateChildBones(model, skin, jointNode, skinName, skeleton.get(), rootBone);
}
skeleton->setBindingPose();
return skeleton;
}
void AssignSkin(const tinygltf::Model &model, const tinygltf::Skin &skin,
Ogre::MeshPtr ogreMesh, const Transform &parentTransform)
{
static uint64_t skeletonID = 0;
auto skinName = skin.name.empty() ? "unnamedSkeleton" + std::to_string(skeletonID++) : skin.name;
auto &skeletonManager = Ogre::v1::OldSkeletonManager::getSingleton();
auto skeleton = skeletonManager.getByName(skinName);
if (!skeleton) {
skeleton = CreateSkeleton(model, skin, skinName, parentTransform);
}
ogreMesh->_notifySkeleton(skeleton);
}
void CreateTagPoints(Context &ctx, const tinygltf::Model &model,
const tinygltf::Skin &skin, const tinygltf::Node &jointNode,
Ogre::SkeletonInstance *skeleton, const Transform &parentTransform);
Ogre::Item * CreateItem(Context &ctx, const tinygltf::Model &model,
const tinygltf::Node &node, const tinygltf::Mesh &mesh,
const Transform &parentTransform)
{
// The same mesh can be referenced by multiple nodes.
auto ogreMesh = Ogre::MeshManager::getSingleton().getByName(mesh.name);
if (!ogreMesh) {
ogreMesh = CreateMesh(model, mesh);
}
if (node.skin >= 0) {
auto &skin = model.skins[node.skin];
AssignSkin(model, skin, ogreMesh, parentTransform);
}
auto *item = ctx.sceneManager->createItem(ogreMesh);
auto *hlms = static_cast<Ogre::HlmsPbs *>(ctx.root->getHlmsManager()->getHlms(Ogre::HlmsTypes::HLMS_PBS));
AssignMaterials(model, mesh, hlms, item);
auto *skeleton = item->getSkeletonInstance();
if (skeleton) {
assert(node.skin < model.skins.size());
auto &skin = model.skins[node.skin];
auto rootJoints = FindRootJoints(model, skin);
for (auto nodeIndex : rootJoints) {
auto &jointNode = model.nodes[nodeIndex];
CreateTagPoints(ctx, model, skin, jointNode, skeleton, parentTransform);
}
}
return item;
}
void CreateSceneNode(Context &ctx, const tinygltf::Model &model,
const tinygltf::Node &node, Ogre::SceneNode *sceneNode,
const Transform &parentTransform)
{
sceneNode->setName(node.name);
AssignNodeTransform(node, sceneNode);
if (node.mesh >= 0) {
auto &mesh = model.meshes[node.mesh];
auto *item = CreateItem(ctx, model, node, mesh, parentTransform);
sceneNode->attachObject(item);
}
auto transform = Transform{};
transform.position = parentTransform.position + parentTransform.orientation * sceneNode->getPosition();
transform.orientation = parentTransform.orientation * sceneNode->getOrientation();
for (auto childIndex : node.children) {
auto &child = model.nodes[childIndex];
auto isJoint = false;
for (auto &skin : model.skins) {
if (std::find(skin.joints.begin(), skin.joints.end(), childIndex) != skin.joints.end()) {
isJoint = true;
break;
}
}
if (!isJoint) {
auto *childSceneNode = sceneNode->createChildSceneNode();
CreateSceneNode(ctx, model, child, childSceneNode, transform);
}
}
}
void CreateTagPoints(Context &ctx, const tinygltf::Model &model,
const tinygltf::Skin &skin, const tinygltf::Node &jointNode,
Ogre::SkeletonInstance *skeleton, const Transform &parentTransform)
{
auto skinName = skeleton->getDefinition()->getNameStr();
auto position = jointNode.translation.empty() ? Ogre::Vector3::ZERO : toVector3(jointNode.translation);
auto orientation = jointNode.rotation.empty() ? Ogre::Quaternion::IDENTITY : toQuaternion(jointNode.rotation);
auto transform = Transform{};
transform.position = parentTransform.position + parentTransform.orientation * position;
transform.orientation = parentTransform.orientation * orientation;
for (auto childNodeIndex : jointNode.children) {
auto &childNode = model.nodes[childNodeIndex];
if (childNode.mesh >= 0) {
auto childJointIt = std::find(skin.joints.begin(), skin.joints.end(), childNodeIndex);
auto childJointIndex = std::distance(skin.joints.begin(), childJointIt);
auto childBoneName = childNode.name.empty() ? skinName + std::to_string(childJointIndex) : childNode.name;
auto *parentBone = skeleton->getBone(jointNode.name);
auto *tagPoint = ctx.sceneManager->createTagPoint();
parentBone->addTagPoint(tagPoint);
CreateSceneNode(ctx, model, childNode, tagPoint, transform);
}
else {
CreateTagPoints(ctx, model, skin, childNode, skeleton, transform);
}
}
}
Ogre::SceneNode * Import(Context &ctx, const std::string &path)
{
tinygltf::TinyGLTF loader;
auto dotPos = path.find_last_of(".");
if (dotPos == std::string::npos) {
return nullptr;
}
auto extension = path.substr(dotPos + 1);
std::transform(extension.begin(), extension.end(), extension.begin(),
[] (auto &&c) { return std::tolower(c); });
auto model = tinygltf::Model{};
auto errors = std::string{};
auto warnings = std::string{};
auto success = false;
if (extension == "gltf") {
success = loader.LoadASCIIFromFile(&model, &errors, &warnings, path);
}
else if (extension == "glb") {
success = loader.LoadBinaryFromFile(&model, &errors, &warnings, path);
}
if (!warnings.empty()) {
Ogre::LogManager::getSingleton().logMessage("GLTF Import warnings: " + warnings);
}
if (!errors.empty()) {
Ogre::LogManager::getSingleton().logMessage("GLTF Import errors: " + errors);
}
if (!success) {
return nullptr;
}
if (ctx.parent == nullptr) {
ctx.parent = ctx.sceneManager->getRootSceneNode();
}
auto nodeIndex = model.scenes[0].nodes[0];
auto &node = model.nodes[nodeIndex];
auto *sceneNode = ctx.parent->createChildSceneNode();
auto transform = Transform{ctx.parent->_getDerivedPositionUpdated(),
ctx.parent->_getDerivedOrientationUpdated()};
CreateSceneNode(ctx, model, node, sceneNode, transform);
return sceneNode;
}
namespace internal {
Ogre::SceneNode * CreateSceneNode(Context &ctx, const tinygltf::Model &model, int nodeIndex) {
auto &node = model.nodes[nodeIndex];
auto *sceneNode = ctx.parent->createChildSceneNode();
auto transform = Transform{ctx.parent->_getDerivedPositionUpdated(),
ctx.parent->_getDerivedOrientationUpdated()};
CreateSceneNode(ctx, model, node, sceneNode, transform);
return sceneNode;
}
Ogre::SceneNode * LoadGLTFFileAndReturnParent(Context &ctx, tinygltf::Model &model, const std::string &path) {
tinygltf::TinyGLTF loader;
auto dotPos = path.find_last_of(".");
if (dotPos == std::string::npos) {
return nullptr;
}
auto extension = path.substr(dotPos + 1);
std::transform(extension.begin(), extension.end(), extension.begin(),
[] (auto &&c) { return std::tolower(c); });
auto errors = std::string{};
auto warnings = std::string{};
auto success = false;
if (extension == "gltf") {
success = loader.LoadASCIIFromFile(&model, &errors, &warnings, path);
}
else if (extension == "glb") {
success = loader.LoadBinaryFromFile(&model, &errors, &warnings, path);
}
if (!warnings.empty()) {
Ogre::LogManager::getSingleton().logMessage("GLTF Import warnings: " + warnings);
}
if (!errors.empty()) {
Ogre::LogManager::getSingleton().logMessage("GLTF Import errors: " + errors);
}
if (!success) {
return nullptr;
}
if (ctx.parent == nullptr) {
return ctx.sceneManager->getRootSceneNode();
}
return ctx.parent;
}
}
}
#include <tiny_gltf.h>
#include <string>
namespace Ogre{
class Root;
class SceneManager;
class SceneNode;
}
namespace OgreGLTF {
struct Context
{
Ogre::Root *root;
Ogre::SceneManager *sceneManager;
Ogre::SceneNode *parent {nullptr};
};
namespace internal {
Ogre::SceneNode * CreateSceneNode(Context &ctx, const tinygltf::Model &model, int nodeIndex);
Ogre::SceneNode * LoadGLTFFileAndReturnParent(Context &ctx, tinygltf::Model &model, const std::string &path);
}
/**
* Imports contents of GLTF file as children of `ctx.parent` or the root scene node
* in case that is null. `func` is called for each scene node created.
*/
template<typename Func = void(Ogre::SceneNode *)>
bool Import(Context &ctx, const std::string &path, Func func = [] (auto *) {})
{
auto model = tinygltf::Model{};
ctx.parent = internal::LoadGLTFFileAndReturnParent(ctx, model, path);
for (auto &scene : model.scenes) {
for (auto &nodeIndex : scene.nodes) {
auto *sceneNode = internal::CreateSceneNode(ctx, model, nodeIndex);
func(sceneNode);
}
}
return true;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment