feature!:

This is a breaking change needed to fully implement instancing and stencil highlighting.

Previously, users would work directly with entities (on the Dart side, ThermionEntity), e.g.

final entity = await viewer.loadGlb("some.glb");

However, Filament "entities" are a lower-level abstraction.

Loading a glTF file, for example, inserts multiple entities into the scene.

For example, each mesh, light, and camera within a glTF asset will be assigned an entity. A top-level (non-renderable) entity will also be created for the glTF asset, which can be used to transform the entire hierarchy.

"Asset" is a better representation for loading/inserting objects into the scene; think of this as a bundle of entities.

Unless you need to work directly with transforms, instancing, materials and renderables, you can work directly with ThermionAsset.
This commit is contained in:
Nick Fisher
2024-11-21 15:04:10 +08:00
parent 9ada6aae64
commit ed444b0615
195 changed files with 18061 additions and 12628 deletions

View File

@@ -0,0 +1,521 @@
#include <memory>
#include <stack>
#include <unordered_set>
#include <vector>
#include <filament/Engine.h>
#include <filament/TransformManager.h>
#include <filament/RenderableManager.h>
#include <gltfio/Animator.h>
#include "Log.hpp"
#include "scene/AnimationManager.hpp"
#include "scene/SceneAsset.hpp"
#include "scene/GltfSceneAssetInstance.hpp"
namespace thermion
{
using namespace filament;
using namespace utils;
AnimationManager::AnimationManager(Engine *engine, Scene *scene) : _engine(engine), _scene(scene)
{
auto &transformManager = _engine->getTransformManager();
auto &renderableManager = _engine->getRenderableManager();
_animationComponentManager = std::make_unique<AnimationComponentManager>(transformManager, renderableManager);
}
AnimationManager::~AnimationManager()
{
_animationComponentManager = std::nullptr_t();
}
bool AnimationManager::setMorphAnimationBuffer(
utils::Entity entity,
const float *const morphData,
const uint32_t *const morphIndices,
int numMorphTargets,
int numFrames,
float frameLengthInMs)
{
std::lock_guard lock(_mutex);
if (!_animationComponentManager->hasComponent(entity))
{
_animationComponentManager->addAnimationComponent(entity);
}
MorphAnimation morphAnimation;
morphAnimation.meshTarget = entity;
morphAnimation.frameData.clear();
morphAnimation.frameData.insert(
morphAnimation.frameData.begin(),
morphData,
morphData + (numFrames * numMorphTargets));
morphAnimation.frameLengthInMs = frameLengthInMs;
morphAnimation.morphIndices.resize(numMorphTargets);
for (int i = 0; i < numMorphTargets; i++)
{
morphAnimation.morphIndices[i] = morphIndices[i];
}
morphAnimation.durationInSecs = (frameLengthInMs * numFrames) / 1000.0f;
morphAnimation.start = high_resolution_clock::now();
morphAnimation.lengthInFrames = static_cast<int>(
morphAnimation.durationInSecs * 1000.0f /
frameLengthInMs);
auto animationComponentInstance = _animationComponentManager->getInstance(entity);
auto &animationComponent = _animationComponentManager->elementAt<0>(animationComponentInstance);
auto &morphAnimations = animationComponent.morphAnimations;
morphAnimations.emplace_back(morphAnimation);
return true;
}
void AnimationManager::clearMorphAnimationBuffer(
utils::Entity entity)
{
std::lock_guard lock(_mutex);
auto animationComponentInstance = _animationComponentManager->getInstance(entity);
auto &animationComponent = _animationComponentManager->elementAt<0>(animationComponentInstance);
auto &morphAnimations = animationComponent.morphAnimations;
morphAnimations.clear();
}
void AnimationManager::resetToRestPose(GltfSceneAssetInstance *instance)
{
std::lock_guard lock(_mutex);
auto filamentInstance = instance->getInstance();
auto skinCount = filamentInstance->getSkinCount();
TransformManager &transformManager = _engine->getTransformManager();
//
// To reset the skeleton to its rest pose, we could just call animator->resetBoneMatrices(),
// which sets all bone matrices to the identity matrix. However, any subsequent calls to animator->updateBoneMatrices()
// may result in unexpected poses, because that method uses each bone's transform to calculate
// the bone matrices (and resetBoneMatrices does not affect this transform).
// To "fully" reset the bone, we need to set its local transform (i.e. relative to its parent)
// to its original orientation in rest pose.
//
// This can be calculated as:
//
// auto rest = inverse(parentTransformInModelSpace) * bindMatrix
//
// (where bindMatrix is the inverse of the inverseBindMatrix).
//
// The only requirement is that parent bone transforms are reset before child bone transforms.
// glTF/Filament does not guarantee that parent bones are listed before child bones under a FilamentInstance.
// We ensure that parents are reset before children by:
// - pushing all bones onto a stack
// - iterate over the stack
// - look at the bone at the top of the stack
// - if the bone already been reset, pop and continue iterating over the stack
// - otherwise
// - if the bone has a parent that has not been reset, push the parent to the top of the stack and continue iterating
// - otherwise
// - pop the bone, reset its transform and mark it as completed
for (int skinIndex = 0; skinIndex < skinCount; skinIndex++)
{
std::unordered_set<Entity, Entity::Hasher> joints;
std::unordered_set<Entity, Entity::Hasher> completed;
std::stack<Entity> stack;
auto transforms = getBoneRestTranforms(instance, skinIndex);
for (int i = 0; i < filamentInstance->getJointCountAt(skinIndex); i++)
{
auto restTransform = transforms[i];
const auto &joint = filamentInstance->getJointsAt(skinIndex)[i];
auto transformInstance = transformManager.getInstance(joint);
transformManager.setTransform(transformInstance, restTransform);
}
}
filamentInstance->getAnimator()->updateBoneMatrices();
return;
}
std::vector<math::mat4f> AnimationManager::getBoneRestTranforms(GltfSceneAssetInstance *instance, int skinIndex)
{
std::vector<math::mat4f> transforms;
auto filamentInstance = instance->getInstance();
auto skinCount = filamentInstance->getSkinCount();
TransformManager &transformManager = _engine->getTransformManager();
transforms.resize(filamentInstance->getJointCountAt(skinIndex));
//
// To reset the skeleton to its rest pose, we could just call animator->resetBoneMatrices(),
// which sets all bone matrices to the identity matrix. However, any subsequent calls to animator->updateBoneMatrices()
// may result in unexpected poses, because that method uses each bone's transform to calculate
// the bone matrices (and resetBoneMatrices does not affect this transform).
// To "fully" reset the bone, we need to set its local transform (i.e. relative to its parent)
// to its original orientation in rest pose.
//
// This can be calculated as:
//
// auto rest = inverse(parentTransformInModelSpace) * bindMatrix
//
// (where bindMatrix is the inverse of the inverseBindMatrix).
//
// The only requirement is that parent bone transforms are reset before child bone transforms.
// glTF/Filament does not guarantee that parent bones are listed before child bones under a FilamentInstance.
// We ensure that parents are reset before children by:
// - pushing all bones onto a stack
// - iterate over the stack
// - look at the bone at the top of the stack
// - if the bone already been reset, pop and continue iterating over the stack
// - otherwise
// - if the bone has a parent that has not been reset, push the parent to the top of the stack and continue iterating
// - otherwise
// - pop the bone, reset its transform and mark it as completed
std::vector<Entity> joints;
std::unordered_set<Entity, Entity::Hasher> completed;
std::stack<Entity> stack;
for (int i = 0; i < filamentInstance->getJointCountAt(skinIndex); i++)
{
const auto &joint = filamentInstance->getJointsAt(skinIndex)[i];
joints.push_back(joint);
stack.push(joint);
}
while (!stack.empty())
{
const auto &joint = stack.top();
// if we've already handled this node previously (e.g. when we encountered it as a parent), then skip
if (completed.find(joint) != completed.end())
{
stack.pop();
continue;
}
const auto transformInstance = transformManager.getInstance(joint);
auto parent = transformManager.getParent(transformInstance);
// we need to handle parent joints before handling their children
// therefore, if this joint has a parent that hasn't been handled yet,
// push the parent to the top of the stack and start the loop again
const auto &jointIter = std::find(joints.begin(), joints.end(), joint);
auto parentIter = std::find(joints.begin(), joints.end(), parent);
if (parentIter != joints.end() && completed.find(parent) == completed.end())
{
stack.push(parent);
continue;
}
// otherwise let's get the inverse bind matrix for the joint
math::mat4f inverseBindMatrix;
bool found = false;
for (int i = 0; i < filamentInstance->getJointCountAt(skinIndex); i++)
{
if (filamentInstance->getJointsAt(skinIndex)[i] == joint)
{
inverseBindMatrix = filamentInstance->getInverseBindMatricesAt(skinIndex)[i];
found = true;
break;
}
}
ASSERT_PRECONDITION(found, "Failed to find inverse bind matrix for joint %d", joint);
// now we need to ascend back up the hierarchy to calculate the modelSpaceTransform
math::mat4f modelSpaceTransform;
while (parentIter != joints.end())
{
const auto transformInstance = transformManager.getInstance(parent);
const auto parentIndex = distance(joints.begin(), parentIter);
const auto transform = transforms[parentIndex];
modelSpaceTransform = transform * modelSpaceTransform;
parent = transformManager.getParent(transformInstance);
parentIter = std::find(joints.begin(), joints.end(), parent);
}
const auto bindMatrix = inverse(inverseBindMatrix);
const auto inverseModelSpaceTransform = inverse(modelSpaceTransform);
const auto jointIndex = distance(joints.begin(), jointIter);
transforms[jointIndex] = inverseModelSpaceTransform * bindMatrix;
completed.insert(joint);
stack.pop();
}
return transforms;
}
void AnimationManager::updateBoneMatrices(GltfSceneAssetInstance *instance)
{
instance->getInstance()->getAnimator()->updateBoneMatrices();
}
bool AnimationManager::addBoneAnimation(GltfSceneAssetInstance *instance,
int skinIndex,
int boneIndex,
const float *const frameData,
int numFrames,
float frameLengthInMs,
float fadeOutInSecs,
float fadeInInSecs,
float maxDelta)
{
std::lock_guard lock(_mutex);
BoneAnimation animation;
animation.boneIndex = boneIndex;
animation.frameData.clear();
const auto &inverseBindMatrix = instance->getInstance()->getInverseBindMatricesAt(skinIndex)[boneIndex];
for (int i = 0; i < numFrames; i++)
{
math::mat4f frame(
frameData[i * 16],
frameData[(i * 16) + 1],
frameData[(i * 16) + 2],
frameData[(i * 16) + 3],
frameData[(i * 16) + 4],
frameData[(i * 16) + 5],
frameData[(i * 16) + 6],
frameData[(i * 16) + 7],
frameData[(i * 16) + 8],
frameData[(i * 16) + 9],
frameData[(i * 16) + 10],
frameData[(i * 16) + 11],
frameData[(i * 16) + 12],
frameData[(i * 16) + 13],
frameData[(i * 16) + 14],
frameData[(i * 16) + 15]);
animation.frameData.push_back(frame);
}
animation.frameLengthInMs = frameLengthInMs;
animation.start = std::chrono::high_resolution_clock::now();
animation.reverse = false;
animation.durationInSecs = (frameLengthInMs * numFrames) / 1000.0f;
animation.lengthInFrames = numFrames;
animation.frameLengthInMs = frameLengthInMs;
animation.fadeOutInSecs = fadeOutInSecs;
animation.fadeInInSecs = fadeInInSecs;
animation.maxDelta = maxDelta;
animation.skinIndex = skinIndex;
if (!_animationComponentManager->hasComponent(instance->getInstance()->getRoot()))
{
Log("ERROR: specified entity is not animatable (has no animation component attached).");
return false;
}
auto animationComponentInstance = _animationComponentManager->getInstance(instance->getInstance()->getRoot());
auto &animationComponent = _animationComponentManager->elementAt<0>(animationComponentInstance);
auto &boneAnimations = animationComponent.boneAnimations;
boneAnimations.emplace_back(animation);
return true;
}
void AnimationManager::playGltfAnimation(GltfSceneAssetInstance *instance, int index, bool loop, bool reverse, bool replaceActive, float crossfade, float startOffset)
{
std::lock_guard lock(_mutex);
if (index < 0)
{
Log("ERROR: glTF animation index must be greater than zero.");
return;
}
if (!_animationComponentManager->hasComponent(instance->getEntity()))
{
Log("ERROR: specified entity is not animatable (has no animation component attached).");
return;
}
auto animationComponentInstance = _animationComponentManager->getInstance(instance->getEntity());
auto &animationComponent = _animationComponentManager->elementAt<0>(animationComponentInstance);
if (replaceActive)
{
if (animationComponent.gltfAnimations.size() > 0)
{
auto &last = animationComponent.gltfAnimations.back();
animationComponent.fadeGltfAnimationIndex = last.index;
animationComponent.fadeDuration = crossfade;
auto now = high_resolution_clock::now();
auto elapsedInSecs = float(std::chrono::duration_cast<std::chrono::milliseconds>(now - last.start).count()) / 1000.0f;
animationComponent.fadeOutAnimationStart = elapsedInSecs;
animationComponent.gltfAnimations.clear();
}
else
{
animationComponent.fadeGltfAnimationIndex = -1;
animationComponent.fadeDuration = 0.0f;
}
}
else if (crossfade > 0)
{
Log("ERROR: crossfade only supported when replaceActive is true.");
return;
}
else
{
animationComponent.fadeGltfAnimationIndex = -1;
animationComponent.fadeDuration = 0.0f;
}
GltfAnimation animation;
animation.startOffset = startOffset;
animation.index = index;
animation.start = std::chrono::high_resolution_clock::now();
animation.loop = loop;
animation.reverse = reverse;
animation.durationInSecs = instance->getInstance()->getAnimator()->getAnimationDuration(index);
bool found = false;
// don't play the animation if it's already running
for (int i = 0; i < animationComponent.gltfAnimations.size(); i++)
{
if (animationComponent.gltfAnimations[i].index == index)
{
found = true;
break;
}
}
if (!found)
{
animationComponent.gltfAnimations.push_back(animation);
}
}
void AnimationManager::stopGltfAnimation(GltfSceneAssetInstance *instance, int index)
{
auto animationComponentInstance = _animationComponentManager->getInstance(instance->getEntity());
auto &animationComponent = _animationComponentManager->elementAt<0>(animationComponentInstance);
auto erased = std::remove_if(animationComponent.gltfAnimations.begin(),
animationComponent.gltfAnimations.end(),
[=](GltfAnimation &anim)
{ return anim.index == index; });
animationComponent.gltfAnimations.erase(erased,
animationComponent.gltfAnimations.end());
return;
}
void AnimationManager::setMorphTargetWeights(utils::Entity entity, const float *const weights, const int count)
{
RenderableManager &rm = _engine->getRenderableManager();
auto renderableInstance = rm.getInstance(entity);
rm.setMorphWeights(
renderableInstance,
weights,
count);
}
void AnimationManager::setGltfAnimationFrame(GltfSceneAssetInstance *instance, int animationIndex, int animationFrame)
{
auto offset = 60 * animationFrame * 1000; // TODO - don't hardcore 60fps framerate
instance->getInstance()->getAnimator()->applyAnimation(animationIndex, offset);
instance->getInstance()->getAnimator()->updateBoneMatrices();
return;
}
float AnimationManager::getGltfAnimationDuration(GltfSceneAssetInstance *instance, int animationIndex)
{
return instance->getInstance()->getAnimator()->getAnimationDuration(animationIndex);
}
std::vector<std::string> AnimationManager::getGltfAnimationNames(GltfSceneAssetInstance *instance)
{
std::vector<std::string> names;
size_t count = instance->getInstance()->getAnimator()->getAnimationCount();
for (size_t i = 0; i < count; i++)
{
names.push_back(instance->getInstance()->getAnimator()->getAnimationName(i));
}
return names;
}
std::vector<std::string> AnimationManager::getMorphTargetNames(GltfSceneAsset *asset, EntityId childEntity)
{
std::vector<std::string> names;
auto filamentAsset = asset->getAsset();
const utils::Entity targetEntity = utils::Entity::import(childEntity);
size_t count = filamentAsset->getMorphTargetCountAt(targetEntity);
for (int j = 0; j < count; j++)
{
const char *morphName = filamentAsset->getMorphTargetNameAt(targetEntity, j);
names.push_back(morphName);
}
return names;
}
vector<Entity> AnimationManager::getBoneEntities(GltfSceneAssetInstance *instance, int skinIndex)
{
auto *joints = instance->getInstance()->getJointsAt(skinIndex);
auto jointCount = instance->getInstance()->getJointCountAt(skinIndex);
std::vector<Entity> boneEntities(joints, joints + jointCount);
return boneEntities;
}
void AnimationManager::update()
{
std::lock_guard lock(_mutex);
_animationComponentManager->update();
}
math::mat4f AnimationManager::getInverseBindMatrix(GltfSceneAssetInstance *instance, int skinIndex, int boneIndex)
{
return instance->getInstance()->getInverseBindMatricesAt(skinIndex)[boneIndex];
}
bool AnimationManager::setBoneTransform(GltfSceneAssetInstance *instance, int32_t skinIndex, int boneIndex, math::mat4f transform)
{
std::lock_guard lock(_mutex);
RenderableManager &rm = _engine->getRenderableManager();
const auto &renderableInstance = rm.getInstance(instance->getEntity());
if (!renderableInstance.isValid())
{
Log("Specified entity is not a renderable. You probably provided the ultimate parent entity of a glTF asset, which is non-renderable. ");
return false;
}
rm.setBones(
renderableInstance,
&transform,
1,
boneIndex);
return true;
}
bool AnimationManager::addAnimationComponent(EntityId entity)
{
_animationComponentManager->addAnimationComponent(utils::Entity::import(entity));
return true;
}
void AnimationManager::removeAnimationComponent(EntityId entity)
{
_animationComponentManager->removeComponent(utils::Entity::import(entity));
}
}

View File

@@ -0,0 +1,240 @@
#include "math.h"
#include <vector>
#include <filament/Engine.h>
#include <filament/Frustum.h>
#include <filament/RenderableManager.h>
#include <filament/Texture.h>
#include <filament/TransformManager.h>
#include <filament/Viewport.h>
#include <filament/geometry/SurfaceOrientation.h>
#include "scene/CustomGeometry.hpp"
#include "Log.hpp"
namespace thermion
{
using namespace filament;
CustomGeometry::CustomGeometry(float *vertices, uint32_t numVertices,
float *normals, uint32_t numNormals, float *uvs,
uint32_t numUvs, uint16_t *indices,
uint32_t numIndices,
MaterialInstance* materialInstance,
RenderableManager::PrimitiveType primitiveType,
Engine *engine)
: numVertices(numVertices), numIndices(numIndices), _materialInstance(materialInstance), _engine(engine)
{
this->primitiveType = primitiveType;
this->vertices = new float[numVertices];
std::memcpy(this->vertices, vertices, numVertices * sizeof(float));
if (numNormals > 0)
{
this->normals = new float[numNormals];
std::memcpy(this->normals, normals, numNormals * sizeof(float));
}
if (numUvs > 0)
{
this->uvs = new float[numUvs];
std::memcpy(this->uvs, uvs, numUvs * sizeof(float));
}
else
{
this->uvs = nullptr;
}
this->indices = new uint16_t[numIndices];
std::memcpy(this->indices, indices, numIndices * sizeof(uint16_t));
computeBoundingBox();
indexBuffer = IndexBuffer::Builder()
.indexCount(numIndices)
.bufferType(IndexBuffer::IndexType::USHORT)
.build(*_engine);
indexBuffer->setBuffer(*_engine,
IndexBuffer::BufferDescriptor(
this->indices,
indexBuffer->getIndexCount() * sizeof(uint16_t),
[](void *buf, size_t,
void *data)
{
free((void *)buf);
}));
std::vector<filament::math::float2> *uvData;
if (this->uvs != nullptr)
{
uvData = new std::vector<filament::math::float2>(
(filament::math::float2 *)this->uvs,
(filament::math::float2 *)(this->uvs + numVertices * 2));
}
else
{
uvData = new std::vector<filament::math::float2>(
numVertices, filament::math::float2{0.0f, 0.0f});
}
// Create dummy vertex color data (white color for all vertices)
auto dummyColors = new std::vector<filament::math::float4>(
numVertices, filament::math::float4{1.0f, 1.0f, 1.0f, 1.0f});
auto vertexBufferBuilder =
VertexBuffer::Builder()
.vertexCount(numVertices)
.attribute(VertexAttribute::POSITION, 0,
VertexBuffer::AttributeType::FLOAT3)
.attribute(VertexAttribute::UV0, 1,
VertexBuffer::AttributeType::FLOAT2)
.attribute(VertexAttribute::UV1, 2,
VertexBuffer::AttributeType::FLOAT2)
.attribute(VertexAttribute::COLOR, 3,
VertexBuffer::AttributeType::FLOAT4);
if (this->normals)
{
vertexBufferBuilder.bufferCount(5).attribute(
VertexAttribute::TANGENTS, 4,
filament::VertexBuffer::AttributeType::FLOAT4);
}
else
{
vertexBufferBuilder = vertexBufferBuilder.bufferCount(4);
}
vertexBuffer = vertexBufferBuilder.build(*_engine);
vertexBuffer->setBufferAt(
*_engine, 0,
VertexBuffer::BufferDescriptor(
this->vertices, numVertices * sizeof(math::float3),
[](void *buf, size_t, void *data)
{
free((void *)buf);
}));
// Set UV0 buffer
vertexBuffer->setBufferAt(
*_engine, 1,
VertexBuffer::BufferDescriptor(
uvData->data(), uvData->size() * sizeof(math::float2),
[](void *buf, size_t, void *data)
{
delete static_cast<std::vector<math::float2> *>(data);
},
uvData));
// Set UV1 buffer (reusing UV0 data)
vertexBuffer->setBufferAt(*_engine, 2,
VertexBuffer::BufferDescriptor(
uvData->data(),
uvData->size() * sizeof(math::float2),
[](void *buf, size_t, void *data)
{
// Do nothing here, as we're reusing the same
// data as UV0
},
nullptr));
// Set vertex color buffer
vertexBuffer->setBufferAt(
*_engine, 3,
VertexBuffer::BufferDescriptor(
dummyColors->data(), dummyColors->size() * sizeof(math::float4),
[](void *buf, size_t, void *data)
{
delete static_cast<std::vector<math::float4> *>(data);
},
dummyColors));
if (this->normals)
{
assert(this->primitiveType == RenderableManager::PrimitiveType::TRIANGLES);
std::vector<filament::math::ushort3> triangles;
for (int i = 0; i < numIndices; i += 3)
{
filament::math::ushort3 triangle;
triangle.x = this->indices[i];
triangle.y = this->indices[i + 1];
triangle.z = this->indices[i + 2];
triangles.push_back(triangle);
}
// Create a SurfaceOrientation builder
geometry::SurfaceOrientation::Builder builder;
builder.vertexCount(numVertices)
.normals((filament::math::float3 *)normals)
.positions((filament::math::float3 *)this->vertices)
.triangleCount(triangles.size())
.triangles(triangles.data());
// Build the SurfaceOrientation object
auto orientation = builder.build();
// Retrieve the quaternions
auto quats = new std::vector<filament::math::quatf>(numVertices);
orientation->getQuats(quats->data(), numVertices);
vertexBuffer->setBufferAt(*_engine, 4,
VertexBuffer::BufferDescriptor(
quats->data(),
quats->size() * sizeof(math::quatf),
[](void *buf, size_t, void *data)
{
delete (std::vector<math::quatf> *)data;
},
(void *)quats));
}
}
utils::Entity CustomGeometry::createInstance(MaterialInstance *materialInstance) {
auto entity = utils::EntityManager::get().create();
RenderableManager::Builder builder(1);
builder.boundingBox(boundingBox)
.geometry(0, primitiveType, vertexBuffer, indexBuffer, 0, numIndices)
.culling(true)
.receiveShadows(true)
.castShadows(true);
builder.material(0, materialInstance);
builder.build(*_engine, entity);
return entity;
}
CustomGeometry::~CustomGeometry()
{
_engine->destroy(vertexBuffer);
_engine->destroy(indexBuffer);
}
void CustomGeometry::computeBoundingBox()
{
float minX = FLT_MAX, minY = FLT_MAX, minZ = FLT_MAX;
float maxX = -FLT_MAX, maxY = -FLT_MAX, maxZ = -FLT_MAX;
for (uint32_t i = 0; i < numVertices; i += 3)
{
minX = std::min(vertices[i], minX);
minY = std::min(vertices[i + 1], minY);
minZ = std::min(vertices[i + 2], minZ);
maxX = std::max(vertices[i], maxX);
maxY = std::max(vertices[i + 1], maxY);
maxZ = std::max(vertices[i + 2], maxZ);
}
boundingBox = Box{{minX, minY, minZ}, {maxX, maxY, maxZ}};
}
} // namespace thermion

View File

@@ -0,0 +1,125 @@
#include <vector>
#include <gltfio/MaterialProvider.h>
#include <filament/Engine.h>
#include <filament/Frustum.h>
#include <filament/RenderableManager.h>
#include <filament/Texture.h>
#include <filament/TransformManager.h>
#include <filament/Viewport.h>
#include <filament/geometry/SurfaceOrientation.h>
#include "Log.hpp"
#include "scene/GeometrySceneAsset.hpp"
#include "scene/GeometrySceneAssetBuilder.hpp"
namespace thermion
{
using namespace filament;
GeometrySceneAsset::GeometrySceneAsset(
bool isInstance,
Engine *engine,
VertexBuffer *vertexBuffer,
IndexBuffer *indexBuffer,
MaterialInstance **materialInstances,
size_t materialInstanceCount,
RenderableManager::PrimitiveType primitiveType,
Box boundingBox)
: _isInstance(isInstance),
_engine(engine), _vertexBuffer(vertexBuffer), _indexBuffer(indexBuffer), _materialInstances(materialInstances), _materialInstanceCount(materialInstanceCount), _primitiveType(primitiveType), _boundingBox(boundingBox)
{
_entity = utils::EntityManager::get().create();
RenderableManager::Builder builder(1);
builder.boundingBox(_boundingBox)
.geometry(0, _primitiveType, _vertexBuffer, _indexBuffer)
.culling(true)
.receiveShadows(true)
.castShadows(true);
for (int i = 0; i < materialInstanceCount; i++)
{
builder.material(i, materialInstances[i]);
}
builder.build(*_engine, _entity);
}
GeometrySceneAsset::~GeometrySceneAsset()
{
if (_engine)
{
if (_vertexBuffer && !_isInstance)
_engine->destroy(_vertexBuffer);
if (_indexBuffer && !_isInstance)
_engine->destroy(_indexBuffer);
}
}
SceneAsset *GeometrySceneAsset::createInstance(MaterialInstance **materialInstances, size_t materialInstanceCount)
{
if (_isInstance)
{
Log("Cannot create an instance from another instance. Ensure you are calling createInstance with the original asset.");
return nullptr;
}
std::unique_ptr<GeometrySceneAsset> instance = std::make_unique<GeometrySceneAsset>(
true,
_engine,
_vertexBuffer,
_indexBuffer,
materialInstances,
materialInstanceCount,
_primitiveType,
_boundingBox);
auto *raw = instance.get();
_instances.push_back(std::move(instance));
return raw;
}
// std::unique_ptr<GeometrySceneAsset> GeometrySceneAsset::create(
// float *vertices, uint32_t numVertices,
// float *normals, uint32_t numNormals,
// float *uvs, uint32_t numUvs,
// uint16_t *indices, uint32_t numIndices,
// MaterialInstance **materialInstances,
// size_t materialInstanceCount,
// RenderableManager::PrimitiveType primitiveType,
// Engine *engine)
// {
// // Setup texture if needed
// if (asset && uvs && numUvs > 0 &&
// asset->getMaterialInstance() &&
// asset->getMaterialInstance()->getMaterial()->hasParameter("baseColorMap"))
// {
// static constexpr uint32_t textureSize = 1;
// static constexpr uint32_t white = 0x00ffffff;
// auto texture = Texture::Builder()
// .width(textureSize)
// .height(textureSize)
// .levels(1)
// .format(Texture::InternalFormat::RGBA8)
// .build(*engine);
// filament::backend::PixelBufferDescriptor pbd(
// &white, 4, Texture::Format::RGBA, Texture::Type::UBYTE);
// texture->setImage(*engine, 0, std::move(pbd));
// TextureSampler sampler(
// TextureSampler::MinFilter::NEAREST,
// TextureSampler::MagFilter::NEAREST);
// sampler.setWrapModeS(TextureSampler::WrapMode::REPEAT);
// sampler.setWrapModeT(TextureSampler::WrapMode::REPEAT);
// asset->getMaterialInstance()->setParameter("baseColorMap", texture, sampler);
// }
// return asset;
// }
} // namespace thermion

View File

@@ -0,0 +1,367 @@
#include <filament/Engine.h>
#include <filament/RenderableManager.h>
#include <filament/TransformManager.h>
#include <utils/Entity.h>
#include <utils/EntityManager.h>
#include <gltfio/math.h>
#include "scene/Gizmo.hpp"
#include "scene/SceneManager.hpp"
#include "material/unlit_fixed_size.h"
#include "Log.hpp"
namespace thermion
{
using namespace filament::gltfio;
// First, create the black cube at the center
// The axes widgets will be parented to this entity
Entity Gizmo::createParentEntity()
{
auto &transformManager = _engine->getTransformManager();
auto &entityManager = _engine->getEntityManager();
auto parent = entityManager.create();
auto *parentMaterialInstance = _material->createInstance();
parentMaterialInstance->setParameter("baseColorFactor", math::float4{0.0f, 1.0f, 1.0f, 1.0f});
parentMaterialInstance->setParameter("scale", 4.0f);
_materialInstances.push_back(parentMaterialInstance);
// Create center cube vertices
float centerCubeSize = 0.1f;
float *centerCubeVertices = new float[8 * 3]{
-centerCubeSize, -centerCubeSize, -centerCubeSize,
centerCubeSize, -centerCubeSize, -centerCubeSize,
centerCubeSize, centerCubeSize, -centerCubeSize,
-centerCubeSize, centerCubeSize, -centerCubeSize,
-centerCubeSize, -centerCubeSize, centerCubeSize,
centerCubeSize, -centerCubeSize, centerCubeSize,
centerCubeSize, centerCubeSize, centerCubeSize,
-centerCubeSize, centerCubeSize, centerCubeSize};
// Create center cube indices
uint16_t *centerCubeIndices = new uint16_t[36]{
0, 1, 2, 2, 3, 0,
1, 5, 6, 6, 2, 1,
5, 4, 7, 7, 6, 5,
4, 0, 3, 3, 7, 4,
3, 2, 6, 6, 7, 3,
4, 5, 1, 1, 0, 4};
auto centerCubeVb = VertexBuffer::Builder()
.vertexCount(8)
.bufferCount(1)
.attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT3)
.build(*_engine);
centerCubeVb->setBufferAt(*_engine, 0, VertexBuffer::BufferDescriptor(centerCubeVertices, 8 * sizeof(filament::math::float3), [](void *buffer, size_t size, void *)
{ delete[] static_cast<float *>(buffer); }));
auto centerCubeIb = IndexBuffer::Builder().indexCount(36).bufferType(IndexBuffer::IndexType::USHORT).build(*_engine);
centerCubeIb->setBuffer(*_engine, IndexBuffer::BufferDescriptor(
centerCubeIndices, 36 * sizeof(uint16_t),
[](void *buffer, size_t size, void *)
{ delete[] static_cast<uint16_t *>(buffer); }));
RenderableManager::Builder(1)
.boundingBox({{-centerCubeSize, -centerCubeSize, -centerCubeSize},
{centerCubeSize, centerCubeSize, centerCubeSize}})
.material(0, parentMaterialInstance)
.layerMask(0xFF, 1u << SceneManager::LAYERS::OVERLAY)
.priority(7)
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES, centerCubeVb, centerCubeIb, 0, 36)
.culling(false)
.build(*_engine, parent);
auto parentTransformInstance = transformManager.getInstance(parent);
math::mat4f cubeTransform;
transformManager.setTransform(parentTransformInstance, cubeTransform);
return parent;
}
Gizmo::Gizmo(Engine *engine, View *view, Scene *scene, Material *material) : _engine(engine), _view(view), _scene(scene), _material(material)
{
auto parent = createParentEntity();
auto x = createAxisEntity(Gizmo::Axis::X, parent);
auto y = createAxisEntity(Gizmo::Axis::Y, parent);
auto z = createAxisEntity(Gizmo::Axis::Z, parent);
auto xHitTest = createHitTestEntity(Gizmo::Axis::X, parent);
auto yHitTest = createHitTestEntity(Gizmo::Axis::Y, parent);
auto zHitTest = createHitTestEntity(Gizmo::Axis::Z, parent);
_entities = std::vector{parent, x, y, z, xHitTest, yHitTest, zHitTest};
_parent = parent;
_x = x;
_y = y;
_z = z;
_xHitTest = xHitTest;
_yHitTest = yHitTest;
_zHitTest = zHitTest;
}
Entity Gizmo::createAxisEntity(Gizmo::Axis axis, Entity parent)
{
auto &entityManager = _engine->getEntityManager();
auto &transformManager = _engine->getTransformManager();
auto *materialInstance = _material->createInstance();
_materialInstances.push_back(materialInstance);
auto entity = entityManager.create();
auto baseColor = inactiveColors[axis];
// Line and arrow vertices
float lineLength = 0.6f;
float lineWidth = 0.008f;
float arrowLength = 0.06f;
float arrowWidth = 0.02f;
float *vertices = new float[13 * 3]{
// Line vertices (8 vertices)
-lineWidth, -lineWidth, 0.0f,
lineWidth, -lineWidth, 0.0f,
lineWidth, lineWidth, 0.0f,
-lineWidth, lineWidth, 0.0f,
-lineWidth, -lineWidth, lineLength,
lineWidth, -lineWidth, lineLength,
lineWidth, lineWidth, lineLength,
-lineWidth, lineWidth, lineLength,
// Arrow vertices (5 vertices)
0.0f, 0.0f, lineLength + arrowLength, // Tip of the arrow
-arrowWidth, -arrowWidth, lineLength, // Base of the arrow
arrowWidth, -arrowWidth, lineLength,
arrowWidth, arrowWidth, lineLength,
-arrowWidth, arrowWidth, lineLength};
// Line and arrow indices
uint16_t *indices = new uint16_t[24 + 18]{
// Line indices (24 indices)
0, 1, 5, 5, 4, 0,
1, 2, 6, 6, 5, 1,
2, 3, 7, 7, 6, 2,
3, 0, 4, 4, 7, 3,
// // Arrow indices (18 indices)
8, 9, 10, // Front face
8, 10, 11, // Right face
8, 11, 12, // Back face
8, 12, 9, // Left face
9, 12, 11, 11, 10, 9 // Base of the arrow
};
auto vb = VertexBuffer::Builder()
.vertexCount(13)
.bufferCount(1)
.attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT3)
.build(*_engine);
vb->setBufferAt(*_engine, 0, VertexBuffer::BufferDescriptor(vertices, 13 * sizeof(filament::math::float3), [](void *buffer, size_t size, void *)
{ delete[] static_cast<float *>(buffer); }));
auto ib = IndexBuffer::Builder().indexCount(42).bufferType(IndexBuffer::IndexType::USHORT).build(*_engine);
ib->setBuffer(*_engine, IndexBuffer::BufferDescriptor(
indices, 42 * sizeof(uint16_t),
[](void *buffer, size_t size, void *)
{ delete[] static_cast<uint16_t *>(buffer); }));
materialInstance->setParameter("baseColorFactor", baseColor);
materialInstance->setParameter("scale", 4.0f);
materialInstance->setDepthCulling(false);
materialInstance->setDepthFunc(MaterialInstance::DepthFunc::A);
RenderableManager::Builder(1)
.boundingBox({{-arrowWidth, -arrowWidth, 0},
{arrowWidth, arrowWidth, lineLength + arrowLength}})
.material(0, materialInstance)
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES, vb, ib, 0, 42)
.priority(6)
.layerMask(0xFF, 1u << SceneManager::LAYERS::OVERLAY)
.culling(false)
.receiveShadows(false)
.castShadows(false)
.build(*_engine, entity);
auto transformInstance = transformManager.getInstance(entity);
transformManager.setTransform(transformInstance, getRotationForAxis(axis));
// parent the axis to the center cube
auto parentTransformInstance = transformManager.getInstance(parent);
transformManager.setParent(transformInstance, parentTransformInstance);
return entity;
}
Gizmo::~Gizmo()
{
_scene->removeEntities(_entities.data(), _entities.size());
for (auto entity : _entities)
{
_engine->destroy(entity);
}
for (auto *materialInstance : _materialInstances)
{
_engine->destroy(materialInstance);
}
}
Entity Gizmo::createHitTestEntity(Gizmo::Axis axis, Entity parent)
{
auto &entityManager = EntityManager::get();
auto &transformManager = _engine->getTransformManager();
auto parentTransformInstance = transformManager.getInstance(parent);
float volumeWidth = 0.2f;
float volumeLength = 1.2f;
float volumeDepth = 0.2f;
float *volumeVertices = new float[8 * 3]{
-volumeWidth / 2, -volumeDepth / 2, 0,
volumeWidth / 2, -volumeDepth / 2, 0,
volumeWidth / 2, -volumeDepth / 2, volumeLength,
-volumeWidth / 2, -volumeDepth / 2, volumeLength,
-volumeWidth / 2, volumeDepth / 2, 0,
volumeWidth / 2, volumeDepth / 2, 0,
volumeWidth / 2, volumeDepth / 2, volumeLength,
-volumeWidth / 2, volumeDepth / 2, volumeLength};
uint16_t *volumeIndices = new uint16_t[36]{
0, 1, 2, 2, 3, 0, // Bottom face
4, 5, 6, 6, 7, 4, // Top face
0, 4, 7, 7, 3, 0, // Left face
1, 5, 6, 6, 2, 1, // Right face
0, 1, 5, 5, 4, 0, // Front face
3, 2, 6, 6, 7, 3 // Back face
};
auto volumeVb = VertexBuffer::Builder()
.vertexCount(8)
.bufferCount(1)
.attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT3)
.build(*_engine);
volumeVb->setBufferAt(*_engine, 0, VertexBuffer::BufferDescriptor(volumeVertices, 8 * sizeof(filament::math::float3), [](void *buffer, size_t size, void *)
{ delete[] static_cast<float *>(buffer); }));
auto volumeIb = IndexBuffer::Builder()
.indexCount(36)
.bufferType(IndexBuffer::IndexType::USHORT)
.build(*_engine);
volumeIb->setBuffer(*_engine, IndexBuffer::BufferDescriptor(
volumeIndices, 36 * sizeof(uint16_t),
[](void *buffer, size_t size, void *)
{ delete[] static_cast<uint16_t *>(buffer); }));
auto entity = entityManager.create();
auto *materialInstance = _material->createInstance();
_materialInstances.push_back(materialInstance);
materialInstance->setParameter("baseColorFactor", math::float4{0.0f, 0.0f, 0.0f, 0.0f});
materialInstance->setParameter("scale", 4.0f);
RenderableManager::Builder(1)
.boundingBox({{-volumeWidth / 2, -volumeDepth / 2, 0}, {volumeWidth / 2, volumeDepth / 2, volumeLength}})
.material(0, materialInstance)
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES, volumeVb, volumeIb, 0, 36)
.priority(7)
.layerMask(0xFF, 1u << SceneManager::LAYERS::OVERLAY)
.culling(false)
.receiveShadows(false)
.castShadows(false)
.build(*_engine, entity);
auto transformInstance = transformManager.getInstance(entity);
transformManager.setTransform(transformInstance, getRotationForAxis(axis));
// Parent the picking volume to the center cube
transformManager.setParent(transformInstance, parentTransformInstance);
return entity;
}
void Gizmo::highlight(Gizmo::Axis axis)
{
auto &rm = _engine->getRenderableManager();
auto entity = getEntityForAxis(axis);
if (entity.isNull())
{
return;
}
auto renderableInstance = rm.getInstance(entity);
if (!renderableInstance.isValid())
{
Log("Invalid renderable for axis");
return;
}
auto *materialInstance = rm.getMaterialInstanceAt(renderableInstance, 0);
math::float4 baseColor = activeColors[axis];
materialInstance->setParameter("baseColorFactor", baseColor);
}
void Gizmo::unhighlight(Gizmo::Axis axis)
{
auto &rm = _engine->getRenderableManager();
auto entity = getEntityForAxis(axis);
if (entity.isNull())
{
return;
}
auto renderableInstance = rm.getInstance(entity);
if (!renderableInstance.isValid())
{
Log("Invalid renderable for axis");
return;
}
auto *materialInstance = rm.getMaterialInstanceAt(renderableInstance, 0);
math::float4 baseColor = inactiveColors[axis];
materialInstance->setParameter("baseColorFactor", baseColor);
}
void Gizmo::pick(uint32_t x, uint32_t y, GizmoPickCallback callback)
{
auto handler = new Gizmo::PickCallbackHandler(this, callback);
_view->pick(x, y, [=](filament::View::PickingQueryResult const &result)
{
handler->handle(result);
delete handler; });
}
bool Gizmo::isGizmoEntity(Entity e)
{
for (int i = 0; i < 7; i++)
{
if (e == _entities[i])
{
return true;
}
}
return false;
}
math::mat4f Gizmo::getRotationForAxis(Gizmo::Axis axis)
{
math::mat4f transform;
switch (axis)
{
case Axis::X:
transform = math::mat4f::rotation(math::F_PI_2, math::float3{0, 1, 0});
break;
case Axis::Y:
transform = math::mat4f::rotation(-math::F_PI_2, math::float3{1, 0, 0});
break;
case Axis::Z:
break;
}
return transform;
}
}

View File

@@ -0,0 +1,61 @@
#include "scene/GltfSceneAsset.hpp"
#include "scene/GltfSceneAssetInstance.hpp"
#include "gltfio/FilamentInstance.h"
#include "Log.hpp"
namespace thermion
{
GltfSceneAsset::~GltfSceneAsset()
{
_instances.clear();
_asset->releaseSourceData();
_assetLoader->destroyAsset(_asset);
}
SceneAsset *GltfSceneAsset::createInstance(MaterialInstance **materialInstances, size_t materialInstanceCount)
{
auto instanceNumber = _instances.size();
if (instanceNumber > _asset->getAssetInstanceCount() - 1)
{
Log("No instances available for reuse. When loading the asset, you must pre-allocate the number of instances you wish to make available for use. Try increasing this number.");
return std::nullptr_t();
}
Log("Creating instance %d", instanceNumber);
auto instance = _asset->getAssetInstances()[instanceNumber];
instance->recomputeBoundingBoxes();
instance->getAnimator()->updateBoneMatrices();
auto& rm = _engine->getRenderableManager();
if(materialInstanceCount > 0) {
for(int i = 0; i < instance->getEntityCount(); i++) {
auto renderableInstance = rm.getInstance(instance->getEntities()[i]);
if(!renderableInstance.isValid()) {
Log("Instance is not renderable");
} else {
for(int i = 0; i < materialInstanceCount; i++) {
rm.setMaterialInstanceAt(renderableInstance, i, materialInstances[i]);
}
}
}
}
std::unique_ptr<GltfSceneAssetInstance> sceneAssetInstance = std::make_unique<GltfSceneAssetInstance>(
instance,
_engine,
materialInstances,
materialInstanceCount
);
auto *raw = sceneAssetInstance.get();
_instances.push_back(std::move(sceneAssetInstance));
return raw;
}
}

View File

@@ -0,0 +1,14 @@
#include "scene/GltfSceneAssetInstance.hpp"
#include "gltfio/FilamentInstance.h"
#include "Log.hpp"
namespace thermion
{
GltfSceneAssetInstance::~GltfSceneAssetInstance()
{
}
}

View File

@@ -0,0 +1,349 @@
// #include <filament/Engine.h>
// #include <filament/RenderableManager.h>
// #include <filament/TransformManager.h>
// #include <gltfio/math.h>
// #include <utils/Entity.h>
// #include <utils/EntityManager.h>
// #include <math.h>
// #include "scene/SceneManager.hpp"
// namespace thermion {
// using namespace filament::gltfio;
// RotationGizmo::RotationGizmo(Engine* engine, View* view, Scene* scene, Material* material)
// : _engine(engine), _view(view), _scene(scene), _material(material) {
// auto& entityManager = EntityManager::get();
// auto& transformManager = _engine->getTransformManager();
// // Create center cube
// auto parentEntity = entityManager.create();
// auto* parentMaterialInstance = _material->createInstance();
// parentMaterialInstance->setParameter("baseColorFactor", math::float4{0.0f, 0.0f, 0.0f, 1.0f});
// parentMaterialInstance->setParameter("scale", 4.0f);
// _entities[0] = parentEntity;
// _materialInstances[0] = parentMaterialInstance;
// // Create center cube geometry
// float centerCubeSize = 0.01f;
// float* centerCubeVertices = new float[8 * 3]{
// -centerCubeSize, -centerCubeSize, -centerCubeSize,
// centerCubeSize, -centerCubeSize, -centerCubeSize,
// centerCubeSize, centerCubeSize, -centerCubeSize,
// -centerCubeSize, centerCubeSize, -centerCubeSize,
// -centerCubeSize, -centerCubeSize, centerCubeSize,
// centerCubeSize, -centerCubeSize, centerCubeSize,
// centerCubeSize, centerCubeSize, centerCubeSize,
// -centerCubeSize, centerCubeSize, centerCubeSize
// };
// uint16_t* centerCubeIndices = new uint16_t[36]{
// 0, 1, 2, 2, 3, 0,
// 1, 5, 6, 6, 2, 1,
// 5, 4, 7, 7, 6, 5,
// 4, 0, 3, 3, 7, 4,
// 3, 2, 6, 6, 7, 3,
// 4, 5, 1, 1, 0, 4
// };
// auto centerCubeVb = VertexBuffer::Builder()
// .vertexCount(8)
// .bufferCount(1)
// .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT3)
// .build(*engine);
// centerCubeVb->setBufferAt(*engine, 0,
// VertexBuffer::BufferDescriptor(centerCubeVertices, 8 * sizeof(filament::math::float3),
// [](void* buffer, size_t size, void*) { delete[] static_cast<float*>(buffer); }));
// auto centerCubeIb = IndexBuffer::Builder()
// .indexCount(36)
// .bufferType(IndexBuffer::IndexType::USHORT)
// .build(*engine);
// centerCubeIb->setBuffer(*engine,
// IndexBuffer::BufferDescriptor(centerCubeIndices, 36 * sizeof(uint16_t),
// [](void* buffer, size_t size, void*) { delete[] static_cast<uint16_t*>(buffer); }));
// RenderableManager::Builder(1)
// .boundingBox({{-centerCubeSize, -centerCubeSize, -centerCubeSize},
// {centerCubeSize, centerCubeSize, centerCubeSize}})
// .material(0, parentMaterialInstance)
// .layerMask(0xFF, 1u << SceneManager::LAYERS::OVERLAY)
// .priority(7)
// .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, centerCubeVb, centerCubeIb, 0, 36)
// .culling(false)
// .build(*engine, parentEntity);
// // Create rotation circles
// constexpr int segments = 32;
// float radius = 0.5f;
// float* vertices;
// uint16_t* indices;
// int vertexCount, indexCount;
// createCircle(radius, segments, vertices, indices, vertexCount, indexCount);
// auto vb = VertexBuffer::Builder()
// .vertexCount(vertexCount)
// .bufferCount(1)
// .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT3)
// .build(*engine);
// vb->setBufferAt(*engine, 0,
// VertexBuffer::BufferDescriptor(vertices, vertexCount * sizeof(filament::math::float3),
// [](void* buffer, size_t size, void*) { delete[] static_cast<float*>(buffer); }));
// auto ib = IndexBuffer::Builder()
// .indexCount(indexCount)
// .bufferType(IndexBuffer::IndexType::USHORT)
// .build(*engine);
// ib->setBuffer(*engine,
// IndexBuffer::BufferDescriptor(indices, indexCount * sizeof(uint16_t),
// [](void* buffer, size_t size, void*) { delete[] static_cast<uint16_t*>(buffer); }));
// // Create the three circular rotation handles
// for (int i = 0; i < 3; i++) {
// auto* materialInstance = _material->createInstance();
// auto entity = entityManager.create();
// _entities[i + 1] = entity;
// _materialInstances[i + 1] = materialInstance;
// auto baseColor = inactiveColors[i];
// math::mat4f transform;
// switch (i) {
// case Axis::X:
// transform = math::mat4f::rotation(math::F_PI_2, math::float3{0, 1, 0});
// break;
// case Axis::Y:
// transform = math::mat4f::rotation(math::F_PI_2, math::float3{1, 0, 0});
// break;
// case Axis::Z:
// break;
// }
// materialInstance->setParameter("baseColorFactor", baseColor);
// materialInstance->setParameter("scale", 4.0f);
// RenderableManager::Builder(1)
// .boundingBox({{-radius, -radius, -0.01f}, {radius, radius, 0.01f}})
// .material(0, materialInstance)
// .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, vb, ib, 0, indexCount)
// .priority(6)
// .layerMask(0xFF, 1u << SceneManager::LAYERS::OVERLAY)
// .culling(false)
// .receiveShadows(false)
// .castShadows(false)
// .build(*engine, entity);
// auto transformInstance = transformManager.getInstance(entity);
// transformManager.setTransform(transformInstance, transform);
// transformManager.setParent(transformInstance, transformManager.getInstance(parentEntity));
// }
// createHitTestEntities();
// setVisibility(true);
// }
// void RotationGizmo::createCircle(float radius, int segments, float*& vertices, uint16_t*& indices, int& vertexCount, int& indexCount) {
// vertexCount = segments * 2;
// indexCount = segments * 6;
// vertices = new float[vertexCount * 3];
// indices = new uint16_t[indexCount];
// float thickness = 0.01f;
// // Generate vertices for inner and outer circles
// for (int i = 0; i < segments; i++) {
// float angle = (2.0f * M_PI * i) / segments;
// float x = cosf(angle);
// float y = sinf(angle);
// // Inner circle vertex
// vertices[i * 6] = x * (radius - thickness);
// vertices[i * 6 + 1] = y * (radius - thickness);
// vertices[i * 6 + 2] = 0.0f;
// // Outer circle vertex
// vertices[i * 6 + 3] = x * (radius + thickness);
// vertices[i * 6 + 4] = y * (radius + thickness);
// vertices[i * 6 + 5] = 0.0f;
// }
// // Generate indices for triangles
// for (int i = 0; i < segments; i++) {
// int next = (i + 1) % segments;
// // First triangle
// indices[i * 6] = i * 2;
// indices[i * 6 + 1] = i * 2 + 1;
// indices[i * 6 + 2] = next * 2 + 1;
// // Second triangle
// indices[i * 6 + 3] = i * 2;
// indices[i * 6 + 4] = next * 2 + 1;
// indices[i * 6 + 5] = next * 2;
// }
// }
// void RotationGizmo::createHitTestEntities() {
// auto& entityManager = EntityManager::get();
// auto& transformManager = _engine->getTransformManager();
// float radius = 0.5f;
// float thickness = 0.1f;
// // Create hit test volumes for each rotation circle
// for (int i = 4; i < 7; i++) {
// _entities[i] = entityManager.create();
// _materialInstances[i] = _material->createInstance();
// _materialInstances[i]->setParameter("baseColorFactor", math::float4{0.0f, 0.0f, 0.0f, 0.0f});
// _materialInstances[i]->setParameter("scale", 4.0f);
// math::mat4f transform;
// switch (i - 4) {
// case Axis::X:
// transform = math::mat4f::rotation(math::F_PI_2, math::float3{0, 1, 0});
// break;
// case Axis::Y:
// transform = math::mat4f::rotation(math::F_PI_2, math::float3{1, 0, 0});
// break;
// case Axis::Z:
// break;
// }
// // Create a thicker invisible volume aroun
// // Create a thicker invisible volume around each rotation circle for hit testing
// float* volumeVertices;
// uint16_t* volumeIndices;
// int volumeVertexCount, volumeIndexCount;
// createCircle(radius, 32, volumeVertices, volumeIndices, volumeVertexCount, volumeIndexCount);
// auto volumeVb = VertexBuffer::Builder()
// .vertexCount(volumeVertexCount)
// .bufferCount(1)
// .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT3)
// .build(*_engine);
// volumeVb->setBufferAt(*_engine, 0,
// VertexBuffer::BufferDescriptor(volumeVertices, volumeVertexCount * sizeof(filament::math::float3),
// [](void* buffer, size_t size, void*) { delete[] static_cast<float*>(buffer); }));
// auto volumeIb = IndexBuffer::Builder()
// .indexCount(volumeIndexCount)
// .bufferType(IndexBuffer::IndexType::USHORT)
// .build(*_engine);
// volumeIb->setBuffer(*_engine,
// IndexBuffer::BufferDescriptor(volumeIndices, volumeIndexCount * sizeof(uint16_t),
// [](void* buffer, size_t size, void*) { delete[] static_cast<uint16_t*>(buffer); }));
// RenderableManager::Builder(1)
// .boundingBox({{-radius, -radius, -thickness/2}, {radius, radius, thickness/2}})
// .material(0, _materialInstances[i])
// .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, volumeVb, volumeIb, 0, volumeIndexCount)
// .priority(7)
// .layerMask(0xFF, 1u << SceneManager::LAYERS::OVERLAY)
// .culling(false)
// .receiveShadows(false)
// .castShadows(false)
// .build(*_engine, _entities[i]);
// auto instance = transformManager.getInstance(_entities[i]);
// transformManager.setTransform(instance, transform);
// transformManager.setParent(instance, transformManager.getInstance(_entities[0]));
// }
// }
// RotationGizmo::~RotationGizmo() {
// _scene->removeEntities(_entities, 7);
// for (int i = 0; i < 7; i++) {
// _engine->destroy(_entities[i]);
// _engine->destroy(_materialInstances[i]);
// }
// }
// void RotationGizmo::highlight(Entity entity) {
// auto& rm = _engine->getRenderableManager();
// auto renderableInstance = rm.getInstance(entity);
// auto materialInstance = rm.getMaterialInstanceAt(renderableInstance, 0);
// math::float4 baseColor;
// if (entity == x()) {
// baseColor = activeColors[Axis::X];
// } else if (entity == y()) {
// baseColor = activeColors[Axis::Y];
// } else if (entity == z()) {
// baseColor = activeColors[Axis::Z];
// } else {
// baseColor = math::float4{1.0f, 1.0f, 1.0f, 1.0f};
// }
// materialInstance->setParameter("baseColorFactor", baseColor);
// }
// void RotationGizmo::unhighlight() {
// auto& rm = _engine->getRenderableManager();
// for (int i = 0; i < 3; i++) {
// auto renderableInstance = rm.getInstance(_entities[i + 1]);
// auto materialInstance = rm.getMaterialInstanceAt(renderableInstance, 0);
// materialInstance->setParameter("baseColorFactor", inactiveColors[i]);
// }
// }
// void RotationGizmo::pick(uint32_t x, uint32_t y, PickCallback callback) {
// auto handler = new RotationGizmo::PickCallbackHandler(this, callback);
// _view->pick(x, y, [=](filament::View::PickingQueryResult const& result) {
// handler->handle(result);
// delete handler;
// });
// }
// void RotationGizmo::PickCallbackHandler::handle(filament::View::PickingQueryResult const& result) {
// auto x = static_cast<int32_t>(result.fragCoords.x);
// auto y = static_cast<int32_t>(result.fragCoords.y);
// for (int i = 0; i < 7; i++) {
// if (_gizmo->_entities[i] == result.renderable) {
// if (i < 4) {
// return;
// }
// _gizmo->highlight(_gizmo->_entities[i - 4]);
// _callback(static_cast<Axis>(i - 4), x, y, _gizmo->_view);
// return;
// }
// }
// _gizmo->unhighlight();
// }
// bool RotationGizmo::isGizmoEntity(Entity e) {
// for (int i = 0; i < 7; i++) {
// if (e == _entities[i]) {
// return true;
// }
// }
// return false;
// }
// void RotationGizmo::setVisibility(bool visible) {
// if (visible) {
// _scene->addEntities(_entities, 7);
// } else {
// _scene->removeEntities(_entities, 7);
// }
// }
// }

View File

@@ -0,0 +1,946 @@
#include <memory>
#include <string>
#include <sstream>
#include <thread>
#include <vector>
#include <unordered_set>
#include <stack>
#include <filament/Engine.h>
#include <filament/TransformManager.h>
#include <filament/Texture.h>
#include <filament/RenderableManager.h>
#include <filament/Viewport.h>
#include <filament/Frustum.h>
#include <utils/EntityManager.h>
#include <gltfio/Animator.h>
#include <gltfio/AssetLoader.h>
#include <gltfio/FilamentAsset.h>
#include <gltfio/ResourceLoader.h>
#include <gltfio/TextureProvider.h>
#include <gltfio/math.h>
#include <gltfio/materials/uberarchive.h>
#include <imageio/ImageDecoder.h>
#include "material/FileMaterialProvider.hpp"
#include "material/UnlitMaterialProvider.hpp"
#include "material/unlit.h"
#include "StreamBufferAdapter.hpp"
#include "Log.hpp"
#include "scene/SceneManager.hpp"
#include "scene/CustomGeometry.hpp"
#include "scene/GeometrySceneAsset.hpp"
#include "scene/GltfSceneAsset.hpp"
#include "scene/Gizmo.hpp"
#include "scene/SceneAsset.hpp"
#include "scene/GeometrySceneAssetBuilder.hpp"
#include "UnprojectTexture.hpp"
extern "C"
{
#include "material/image.h"
#include "material/unlit_fixed_size.h"
}
namespace thermion
{
using namespace std::chrono;
using namespace image;
using namespace utils;
using namespace filament;
using namespace filament::gltfio;
using std::unique_ptr;
SceneManager::SceneManager(const ResourceLoaderWrapperImpl *const resourceLoaderWrapper,
Engine *engine,
Scene *scene,
const char *uberArchivePath,
Camera *mainCamera)
: _resourceLoaderWrapper(resourceLoaderWrapper),
_engine(engine),
_scene(scene),
_mainCamera(mainCamera)
{
_stbDecoder = createStbProvider(_engine);
_ktxDecoder = createKtx2Provider(_engine);
_gltfResourceLoader = new ResourceLoader({.engine = _engine,
.normalizeSkinningWeights = true});
if (uberArchivePath)
{
auto uberdata = resourceLoaderWrapper->load(uberArchivePath);
if (!uberdata.data)
{
Log("Failed to load ubershader material. This is fatal.");
}
_ubershaderProvider = gltfio::createUbershaderProvider(_engine, uberdata.data, uberdata.size);
resourceLoaderWrapper->free(uberdata);
}
else
{
_ubershaderProvider = gltfio::createUbershaderProvider(
_engine, UBERARCHIVE_DEFAULT_DATA, UBERARCHIVE_DEFAULT_SIZE);
}
_unlitMaterialProvider = new UnlitMaterialProvider(_engine, UNLIT_PACKAGE, UNLIT_UNLIT_SIZE);
utils::EntityManager &em = utils::EntityManager::get();
_ncm = new NameComponentManager(em);
_assetLoader = AssetLoader::create({_engine, _ubershaderProvider, _ncm, &em});
_gltfResourceLoader->addTextureProvider("image/ktx2", _ktxDecoder);
_gltfResourceLoader->addTextureProvider("image/png", _stbDecoder);
_gltfResourceLoader->addTextureProvider("image/jpeg", _stbDecoder);
auto &tm = _engine->getTransformManager();
_collisionComponentManager = std::make_unique<CollisionComponentManager>(tm);
_animationManager = std::make_unique<AnimationManager>(_engine, _scene);
_gridOverlay = new GridOverlay(*_engine);
_scene->addEntity(_gridOverlay->sphere());
_scene->addEntity(_gridOverlay->grid());
_unlitFixedSizeMaterial =
Material::Builder()
.package(UNLIT_FIXED_SIZE_UNLIT_FIXED_SIZE_DATA, UNLIT_FIXED_SIZE_UNLIT_FIXED_SIZE_SIZE)
.build(*_engine);
}
SceneManager::~SceneManager()
{
for (auto camera : _cameras)
{
auto entity = camera->getEntity();
_engine->destroyCameraComponent(entity);
_engine->getEntityManager().destroy(entity);
}
_engine->destroy(_unlitFixedSizeMaterial);
_cameras.clear();
_gridOverlay->destroy();
destroyAll();
_gltfResourceLoader->asyncCancelLoad();
_ubershaderProvider->destroyMaterials();
_animationManager = std::nullptr_t();
_collisionComponentManager = std::nullptr_t();
delete _ncm;
delete _gltfResourceLoader;
delete _stbDecoder;
delete _ktxDecoder;
delete _ubershaderProvider;
AssetLoader::destroy(&_assetLoader);
}
Gizmo *SceneManager::createGizmo(View *view, Scene *scene)
{
auto gizmo = std::make_unique<Gizmo>(_engine, view, scene, _unlitFixedSizeMaterial);
auto *raw =gizmo.get();
_sceneAssets.push_back(std::move(gizmo));
return raw;
}
int SceneManager::getInstanceCount(EntityId entityId)
{
auto entity = utils::Entity::import(entityId);
for (auto &asset : _sceneAssets)
{
if (asset->getEntity() == entity)
{
return asset->getInstanceCount();
}
}
return -1;
}
void SceneManager::getInstances(EntityId entityId, EntityId *out)
{
auto entity = utils::Entity::import(entityId);
for (auto &asset : _sceneAssets)
{
if (asset->getEntity() == entity)
{
for (int i = 0; i < asset->getInstanceCount(); i++)
{
out[i] = Entity::smuggle(asset->getInstanceAt(i)->getEntity());
}
return;
}
}
}
SceneAsset *SceneManager::loadGltf(const char *uri,
const char *relativeResourcePath,
int numInstances,
bool keepData)
{
if (numInstances < 1)
{
return std::nullptr_t();
}
ResourceBuffer rbuf = _resourceLoaderWrapper->load(uri);
std::vector<FilamentInstance *> instances(numInstances);
FilamentAsset *asset = _assetLoader->createInstancedAsset((uint8_t *)rbuf.data, rbuf.size, instances.data(), numInstances);
if (!asset)
{
Log("Unable to load glTF asset at %d", uri);
return std::nullptr_t();
}
const char *const *const resourceUris = asset->getResourceUris();
const size_t resourceUriCount = asset->getResourceUriCount();
std::vector<ResourceBuffer> resourceBuffers;
for (size_t i = 0; i < resourceUriCount; i++)
{
std::string uri = std::string(relativeResourcePath) + std::string("/") + std::string(resourceUris[i]);
ResourceBuffer buf = _resourceLoaderWrapper->load(uri.c_str());
resourceBuffers.push_back(buf);
ResourceLoader::BufferDescriptor b(buf.data, buf.size);
_gltfResourceLoader->addResourceData(resourceUris[i], std::move(b));
}
#ifdef __EMSCRIPTEN__
if (!_gltfResourceLoader->asyncBeginLoad(asset))
{
Log("Unknown error loading glTF asset");
_resourceLoaderWrapper->free(rbuf);
for (auto &rb : resourceBuffers)
{
_resourceLoaderWrapper->free(rb);
}
return 0;
}
while (_gltfResourceLoader->asyncGetLoadProgress() < 1.0f)
{
_gltfResourceLoader->asyncUpdateLoad();
}
#else
// load resources synchronously
if (!_gltfResourceLoader->loadResources(asset))
{
Log("Unknown error loading glTF asset");
_resourceLoaderWrapper->free(rbuf);
for (auto &rb : resourceBuffers)
{
_resourceLoaderWrapper->free(rb);
}
return std::nullptr_t();
}
#endif
auto sceneAsset = std::make_unique<GltfSceneAsset>(
asset,
_assetLoader,
_engine);
auto filamentInstance = asset->getInstance();
size_t entityCount = filamentInstance->getEntityCount();
_scene->addEntities(filamentInstance->getEntities(), entityCount);
for (auto &rb : resourceBuffers)
{
_resourceLoaderWrapper->free(rb);
}
_resourceLoaderWrapper->free(rbuf);
auto lights = asset->getLightEntities();
_scene->addEntities(lights, asset->getLightEntityCount());
sceneAsset->createInstance();
auto entityId = Entity::smuggle(sceneAsset->getEntity());
auto *raw = sceneAsset.get();
_sceneAssets.push_back(std::move(sceneAsset));
Log("Finished loading glTF from %s", uri);
return raw;
}
void SceneManager::setVisibilityLayer(EntityId entityId, int layer)
{
utils::Entity entity = utils::Entity::import(entityId);
for (auto &asset : _sceneAssets)
{
if (asset->getEntity() == entity)
{
asset->setLayer(_engine->getRenderableManager(), layer);
}
}
}
SceneAsset *SceneManager::loadGlbFromBuffer(const uint8_t *data, size_t length, int numInstances, bool keepData, int priority, int layer, bool loadResourcesAsync)
{
auto &rm = _engine->getRenderableManager();
std::vector<FilamentInstance *> instances(numInstances);
FilamentAsset *asset = _assetLoader->createInstancedAsset((const uint8_t *)data, length, instances.data(), numInstances);
Log("Created instanced asset.");
if (!asset)
{
Log("Unknown error loading GLB asset.");
return std::nullptr_t();
}
#ifdef __EMSCRIPTEN__
if (!_gltfResourceLoader->asyncBeginLoad(asset))
{
Log("Unknown error loading glb asset");
return 0;
}
while (_gltfResourceLoader->asyncGetLoadProgress() < 1.0f)
{
_gltfResourceLoader->asyncUpdateLoad();
}
#else
if (loadResourcesAsync)
{
if (!_gltfResourceLoader->asyncBeginLoad(asset))
{
Log("Unknown error loading glb asset");
return 0;
}
}
else
{
if (!_gltfResourceLoader->loadResources(asset))
{
Log("Unknown error loading glb asset");
return 0;
}
}
#endif
auto sceneAsset = std::make_unique<GltfSceneAsset>(
asset,
_assetLoader,
_engine);
auto sceneAssetInstance = sceneAsset->createInstance();
sceneAssetInstance->addAllEntities(_scene);
sceneAssetInstance->setPriority(_engine->getRenderableManager(), priority);
sceneAssetInstance->setLayer(_engine->getRenderableManager(), layer);
auto *raw = sceneAsset.get();
_sceneAssets.push_back(std::move(sceneAsset));
return raw;
}
SceneAsset *SceneManager::createInstance(SceneAsset *asset, MaterialInstance **materialInstances, size_t materialInstanceCount)
{
std::lock_guard lock(_mutex);
auto instance = asset->createInstance(materialInstances, materialInstanceCount);
if (instance)
{
instance->addAllEntities(_scene);
}
else
{
Log("Failed to create instance");
}
return instance;
}
SceneAsset *SceneManager::loadGlb(const char *uri, int numInstances, bool keepData)
{
ResourceBuffer rbuf = _resourceLoaderWrapper->load(uri);
auto entity = loadGlbFromBuffer((const uint8_t *)rbuf.data, rbuf.size, numInstances, keepData);
_resourceLoaderWrapper->free(rbuf);
return entity;
}
bool SceneManager::removeFromScene(EntityId entityId)
{
_scene->remove(Entity::import(entityId));
return true;
}
bool SceneManager::addToScene(EntityId entityId)
{
_scene->addEntity(Entity::import(entityId));
return true;
}
void SceneManager::destroyAll()
{
std::lock_guard lock(_mutex);
for (auto &asset : _sceneAssets)
{
asset->removeAllEntities(_scene);
}
_sceneAssets.clear();
for (auto *texture : _textures)
{
_engine->destroy(texture);
}
for (auto *materialInstance : _materialInstances)
{
_engine->destroy(materialInstance);
}
_textures.clear();
_materialInstances.clear();
}
void SceneManager::destroy(SceneAsset *asset)
{
std::lock_guard lock(_mutex);
auto it = std::remove_if(_sceneAssets.begin(), _sceneAssets.end(), [=](auto &sceneAsset)
{ return sceneAsset.get() == asset; });
if (it != _sceneAssets.end())
{
auto entity = (*it)->getEntity();
_collisionComponentManager->removeComponent(entity);
_animationManager->removeAnimationComponent(utils::Entity::smuggle(entity));
for (int i = 0; i < (*it)->getChildEntityCount(); i++)
{
auto childEntity = (*it)->getChildEntities()[i];
_collisionComponentManager->removeComponent(childEntity);
_animationManager->removeAnimationComponent(utils::Entity::smuggle(childEntity));
}
(*it)->removeAllEntities(_scene);
_sceneAssets.erase(it, _sceneAssets.end());
return;
}
}
Texture *SceneManager::createTexture(const uint8_t *data, size_t length, const char *name)
{
// Create an input stream from the data
std::istringstream stream(std::string(reinterpret_cast<const char *>(data), length));
// Decode the image
image::LinearImage linearImage = image::ImageDecoder::decode(stream, name, image::ImageDecoder::ColorSpace::SRGB);
if (!linearImage.isValid())
{
Log("Failed to decode image.");
return nullptr;
}
uint32_t w = linearImage.getWidth();
uint32_t h = linearImage.getHeight();
uint32_t channels = linearImage.getChannels();
Texture::InternalFormat textureFormat = channels == 3 ? Texture::InternalFormat::RGB16F
: Texture::InternalFormat::RGBA16F;
Texture::Format bufferFormat = channels == 3 ? Texture::Format::RGB
: Texture::Format::RGBA;
Texture *texture = Texture::Builder()
.width(w)
.height(h)
.levels(1)
.format(textureFormat)
.sampler(Texture::Sampler::SAMPLER_2D)
.build(*_engine);
if (!texture)
{
Log("Failed to create texture: ");
return nullptr;
}
Texture::PixelBufferDescriptor buffer(
linearImage.getPixelRef(),
size_t(w * h * channels * sizeof(float)),
bufferFormat,
Texture::Type::FLOAT);
texture->setImage(*_engine, 0, std::move(buffer));
Log("Created texture: %s (%d x %d, %d channels)", name, w, h, channels);
_textures.insert(texture);
return texture;
}
bool SceneManager::applyTexture(EntityId entityId, Texture *texture, const char *parameterName, int materialIndex)
{
auto entity = Entity::import(entityId);
if (entity.isNull())
{
Log("Entity %d is null?", entityId);
return false;
}
RenderableManager &rm = _engine->getRenderableManager();
auto renderable = rm.getInstance(entity);
if (!renderable.isValid())
{
Log("Renderable not valid, was the entity id correct (%d)?", entityId);
return false;
}
MaterialInstance *mi = rm.getMaterialInstanceAt(renderable, materialIndex);
if (!mi)
{
Log("ERROR: material index must be less than number of material instances");
return false;
}
auto sampler = TextureSampler();
mi->setParameter(parameterName, texture, sampler);
Log("Applied texture to entity %d", entityId);
return true;
}
void SceneManager::destroyTexture(Texture *texture)
{
if (_textures.find(texture) == _textures.end())
{
Log("Warning: couldn't find texture");
}
_textures.erase(texture);
_engine->destroy(texture);
}
void SceneManager::addCollisionComponent(EntityId entityId, void (*onCollisionCallback)(const EntityId entityId1, const EntityId entityId2), bool affectsTransform)
{
std::lock_guard lock(_mutex);
utils::Entity entity = utils::Entity::import(entityId);
for (auto &asset : _sceneAssets)
{
auto *instance = reinterpret_cast<GltfSceneAssetInstance *>(asset->getInstanceByEntity(entity));
if (instance)
{
auto collisionInstance = _collisionComponentManager->addComponent(instance->getInstance()->getRoot());
_collisionComponentManager->elementAt<0>(collisionInstance) = instance->getInstance()->getBoundingBox();
_collisionComponentManager->elementAt<1>(collisionInstance) = onCollisionCallback;
_collisionComponentManager->elementAt<2>(collisionInstance) = affectsTransform;
return;
}
}
}
void SceneManager::removeCollisionComponent(EntityId entityId)
{
std::lock_guard lock(_mutex);
utils::Entity entity = utils::Entity::import(entityId);
_collisionComponentManager->removeComponent(entity);
}
void SceneManager::testCollisions(EntityId entityId)
{
utils::Entity entity = utils::Entity::import(entityId);
for (auto &asset : _sceneAssets)
{
auto *instance = reinterpret_cast<GltfSceneAssetInstance *>(asset->getInstanceByEntity(entity));
if (instance)
{
const auto &tm = _engine->getTransformManager();
auto transformInstance = tm.getInstance(entity);
auto worldTransform = tm.getWorldTransform(transformInstance);
auto aabb = instance->getInstance()->getBoundingBox();
aabb = aabb.transform(worldTransform);
_collisionComponentManager->collides(entity, aabb);
}
}
}
void SceneManager::update()
{
_animationManager->update();
_updateTransforms();
}
void SceneManager::_updateTransforms()
{
std::lock_guard lock(_mutex);
// auto &tm = _engine->getTransformManager();
// tm.openLocalTransformTransaction();
// for (const auto &[entityId, transformUpdate] : _transformUpdates)
// {
// const auto &pos = _instances.find(entityId);
// bool isCollidable = true;
// Entity entity;
// filament::TransformManager::Instance transformInstance;
// filament::math::mat4f transform;
// Aabb boundingBox;
// if (pos == _instances.end())
// {
// isCollidable = false;
// entity = Entity::import(entityId);
// }
// else
// {
// const auto *instance = pos->second;
// entity = instance->getRoot();
// boundingBox = instance->getBoundingBox();
// }
// transformInstance = tm.getInstance(entity);
// transform = tm.getTransform(transformInstance);
// if (isCollidable)
// {
// auto transformedBB = boundingBox.transform(transform);
// auto collisionAxes = _collisionComponentManager->collides(entity, transformedBB);
// if (collisionAxes.size() == 1)
// {
// // auto globalAxis = collisionAxes[0];
// // globalAxis *= norm(relativeTranslation);
// // auto newRelativeTranslation = relativeTranslation + globalAxis;
// // translation -= relativeTranslation;
// // translation += newRelativeTranslation;
// // transform = composeMatrix(translation, rotation, scale);
// }
// else if (collisionAxes.size() > 1)
// {
// // translation -= relativeTranslation;
// // transform = composeMatrix(translation, rotation, scale);
// }
// }
// tm.setTransform(transformInstance, transformUpdate);
// }
// tm.commitLocalTransformTransaction();
// _transformUpdates.clear();
}
void SceneManager::queueRelativePositionUpdateFromViewportVector(View *view, EntityId entityId, float viewportCoordX, float viewportCoordY)
{
// Get the camera and viewport
const auto &camera = view->getCamera();
const auto &vp = view->getViewport();
// Convert viewport coordinates to NDC space
float ndcX = (2.0f * viewportCoordX) / vp.width - 1.0f;
float ndcY = 1.0f - (2.0f * viewportCoordY) / vp.height;
// Get the current position of the entity
auto &tm = _engine->getTransformManager();
auto entity = Entity::import(entityId);
auto transformInstance = tm.getInstance(entity);
auto currentTransform = tm.getTransform(transformInstance);
// get entity model origin in camera space
auto entityPositionInCameraSpace = camera.getViewMatrix() * currentTransform * filament::math::float4{0.0f, 0.0f, 0.0f, 1.0f};
// get entity model origin in clip space
auto entityPositionInClipSpace = camera.getProjectionMatrix() * entityPositionInCameraSpace;
auto entityPositionInNdcSpace = entityPositionInClipSpace / entityPositionInClipSpace.w;
// Viewport coords in NDC space (use entity position in camera space Z to project onto near plane)
math::float4 ndcNearPlanePos = {ndcX, ndcY, -1.0f, 1.0f};
math::float4 ndcFarPlanePos = {ndcX, ndcY, 0.99f, 1.0f};
math::float4 ndcEntityPlanePos = {ndcX, ndcY, entityPositionInNdcSpace.z, 1.0f};
// Get viewport coords in clip space
math::float4 nearPlaneInClipSpace = Camera::inverseProjection(camera.getProjectionMatrix()) * ndcNearPlanePos;
auto nearPlaneInCameraSpace = nearPlaneInClipSpace / nearPlaneInClipSpace.w;
math::float4 farPlaneInClipSpace = Camera::inverseProjection(camera.getProjectionMatrix()) * ndcFarPlanePos;
auto farPlaneInCameraSpace = farPlaneInClipSpace / farPlaneInClipSpace.w;
math::float4 entityPlaneInClipSpace = Camera::inverseProjection(camera.getProjectionMatrix()) * ndcEntityPlanePos;
auto entityPlaneInCameraSpace = entityPlaneInClipSpace / entityPlaneInClipSpace.w;
auto entityPlaneInWorldSpace = camera.getModelMatrix() * entityPlaneInCameraSpace;
}
void SceneManager::queueTransformUpdates(EntityId *entities, math::mat4 *transforms, int numEntities)
{
std::lock_guard lock(_mutex);
for (int i = 0; i < numEntities; i++)
{
auto entity = entities[i];
const auto &pos = _transformUpdates.find(entity);
if (pos == _transformUpdates.end())
{
_transformUpdates.emplace(entity, transforms[i]);
}
auto curr = _transformUpdates[entity];
_transformUpdates[entity] = curr;
}
}
Aabb3 SceneManager::getRenderableBoundingBox(EntityId entityId)
{
auto &rm = _engine->getRenderableManager();
auto instance = rm.getInstance(Entity::import(entityId));
if (!instance.isValid())
{
return Aabb3{};
}
auto box = rm.getAxisAlignedBoundingBox(instance);
return Aabb3{box.center.x, box.center.y, box.center.z, box.halfExtent.x, box.halfExtent.y, box.halfExtent.z};
}
Aabb2 SceneManager::getScreenSpaceBoundingBox(View *view, EntityId entityId)
{
const auto &camera = view->getCamera();
const auto &viewport = view->getViewport();
auto &tcm = _engine->getTransformManager();
auto &rcm = _engine->getRenderableManager();
// Get the projection and view matrices
math::mat4 projMatrix = camera.getProjectionMatrix();
math::mat4 viewMatrix = camera.getViewMatrix();
math::mat4 vpMatrix = projMatrix * viewMatrix;
auto entity = Entity::import(entityId);
auto renderable = rcm.getInstance(entity);
auto worldTransform = tcm.getWorldTransform(tcm.getInstance(entity));
// Get the axis-aligned bounding box in model space
Box aabb = rcm.getAxisAlignedBoundingBox(renderable);
auto min = aabb.getMin();
auto max = aabb.getMax();
// Transform the 8 corners of the AABB to clip space
std::array<math::float4, 8> corners = {
worldTransform * math::float4(min.x, min.y, min.z, 1.0f),
worldTransform * math::float4(max.x, min.y, min.z, 1.0f),
worldTransform * math::float4(min.x, max.y, min.z, 1.0f),
worldTransform * math::float4(max.x, max.y, min.z, 1.0f),
worldTransform * math::float4(min.x, min.y, max.z, 1.0f),
worldTransform * math::float4(max.x, min.y, max.z, 1.0f),
worldTransform * math::float4(min.x, max.y, max.z, 1.0f),
worldTransform * math::float4(max.x, max.y, max.z, 1.0f)};
// Project corners to clip space and convert to viewport space
float minX = std::numeric_limits<float>::max();
float minY = std::numeric_limits<float>::max();
float maxX = std::numeric_limits<float>::lowest();
float maxY = std::numeric_limits<float>::lowest();
for (const auto &corner : corners)
{
math::float4 clipSpace = vpMatrix * corner;
// Check if the point is behind the camera
if (clipSpace.w <= 0)
{
continue; // Skip this point
}
// Perform perspective division
math::float3 ndcSpace = clipSpace.xyz / clipSpace.w;
// Clamp NDC coordinates to [-1, 1] range
ndcSpace.x = std::max(-1.0f, std::min(1.0f, ndcSpace.x));
ndcSpace.y = std::max(-1.0f, std::min(1.0f, ndcSpace.y));
// Convert NDC to viewport space
float viewportX = (ndcSpace.x * 0.5f + 0.5f) * viewport.width;
float viewportY = (1.0f - (ndcSpace.y * 0.5f + 0.5f)) * viewport.height; // Flip Y-axis
minX = std::min(minX, viewportX);
minY = std::min(minY, viewportY);
maxX = std::max(maxX, viewportX);
maxY = std::max(maxY, viewportY);
}
return Aabb2{minX, minY, maxX, maxY};
}
static filament::gltfio::MaterialKey getDefaultUnlitMaterialConfig(int numUvs)
{
filament::gltfio::MaterialKey config;
memset(&config, 0, sizeof(config));
config.unlit = false;
config.doubleSided = false;
config.useSpecularGlossiness = false;
config.alphaMode = filament::gltfio::AlphaMode::OPAQUE;
config.hasBaseColorTexture = numUvs > 0;
config.baseColorUV = 0;
config.hasVertexColors = false;
return config;
}
SceneAsset *SceneManager::createGeometry(
float *vertices,
uint32_t numVertices,
float *normals,
uint32_t numNormals,
float *uvs,
uint32_t numUvs,
uint16_t *indices,
uint32_t numIndices,
filament::RenderableManager::PrimitiveType primitiveType,
filament::MaterialInstance **materialInstances,
size_t materialInstanceCount,
bool keepData)
{
utils::Entity entity;
auto builder = GeometrySceneAssetBuilder(_engine)
.vertices(vertices, numVertices)
.indices(indices, numIndices)
.primitiveType(primitiveType);
if (normals)
{
builder.normals(normals, numNormals);
}
if (uvs)
{
builder.uvs(uvs, numUvs);
}
builder.materials(materialInstances, materialInstanceCount);
auto sceneAsset = builder.build();
if (!sceneAsset)
{
Log("Failed to create geometry");
return std::nullptr_t();
}
entity = sceneAsset->getEntity();
_scene->addEntity(entity);
auto *raw = sceneAsset.get();
_sceneAssets.push_back(std::move(sceneAsset));
return raw;
}
void SceneManager::destroy(filament::MaterialInstance *instance)
{
auto it = std::find(_materialInstances.begin(), _materialInstances.end(), instance);
if (it != _materialInstances.end())
{
_materialInstances.erase(it);
}
_engine->destroy(instance);
}
MaterialInstance *SceneManager::createUnlitFixedSizeMaterialInstance()
{
auto instance = _unlitFixedSizeMaterial->createInstance();
instance->setParameter("scale", 1.0f);
return instance;
}
MaterialInstance *SceneManager::createUnlitMaterialInstance()
{
UvMap uvmap;
auto instance = _unlitMaterialProvider->createMaterialInstance(nullptr, &uvmap);
instance->setParameter("baseColorFactor", filament::math::float4{1.0f, 1.0f, 1.0f, 1.0f});
instance->setParameter("baseColorIndex", -1);
_materialInstances.push_back(instance);
return instance;
}
Camera *SceneManager::createCamera()
{
auto entity = EntityManager::get().create();
auto camera = _engine->createCamera(entity);
_cameras.push_back(camera);
return camera;
}
void SceneManager::destroyCamera(Camera *camera)
{
auto entity = camera->getEntity();
_engine->destroyCameraComponent(entity);
_engine->getEntityManager().destroy(entity);
auto it = std::find(_cameras.begin(), _cameras.end(), camera);
if (it != _cameras.end())
{
_cameras.erase(it);
}
}
size_t SceneManager::getCameraCount()
{
return _cameras.size() + 1;
}
Camera *SceneManager::getCameraAt(size_t index)
{
if (index == 0)
{
return _mainCamera;
}
if (index - 1 > _cameras.size() - 1)
{
return nullptr;
}
return _cameras[index - 1];
}
void SceneManager::transformToUnitCube(EntityId entityId)
{
auto entity = utils::Entity::import(entityId);
for (auto &asset : _sceneAssets)
{
auto *instance = reinterpret_cast<GltfSceneAssetInstance *>(asset->getInstanceByEntity(entity));
if (instance)
{
auto &transformManager = _engine->getTransformManager();
const auto &entity = utils::Entity::import(entityId);
auto transformInstance = transformManager.getInstance(entity);
if (!transformInstance)
{
return;
}
auto aabb = instance->getInstance()->getBoundingBox();
auto center = aabb.center();
auto halfExtent = aabb.extent();
auto maxExtent = max(halfExtent) * 2;
auto scaleFactor = 2.0f / maxExtent;
auto transform = math::mat4f::scaling(scaleFactor) * math::mat4f::translation(-center);
transformManager.setTransform(transformManager.getInstance(entity), transform);
return;
}
}
}
} // namespace thermion