Created
May 29, 2023 18:34
-
-
Save xissburg/013a45e578629929da538b9378539ab0 to your computer and use it in GitHub Desktop.
Ogre GLTF Importer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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