decouple assets from viewer to allow independent addition/removal/animation/etc

This commit is contained in:
Nick Fisher
2022-08-13 00:25:56 +10:00
parent 485e2bc0f4
commit e51577cf6b
30 changed files with 1124 additions and 993 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -30,6 +30,10 @@
#include <string>
#include <chrono>
#include "SceneAssetLoader.hpp"
#include "SceneAsset.hpp"
#include "SceneResources.hpp"
using namespace std;
using namespace filament;
using namespace filament::math;
@@ -39,55 +43,6 @@ using namespace camutils;
namespace polyvox {
typedef std::chrono::time_point<std::chrono::high_resolution_clock> time_point_t;
struct EmbeddedAnimationBuffer {
EmbeddedAnimationBuffer(int animationIndex, float duration, bool loop) : animationIndex(animationIndex), duration(duration), loop(loop) {}
bool hasStarted = false;
int animationIndex;
float duration = 0;
time_point_t lastTime;
bool loop;
};
struct ResourceBuffer {
ResourceBuffer(const void* data, const uint32_t size, const uint32_t id) : data(data), size(size), id(id) {};
ResourceBuffer& operator=(ResourceBuffer other)
{
data = other.data;
size = other.size;
id = other.id;
return *this;
}
const void* data;
uint32_t size;
uint32_t id;
};
using LoadResource = std::function<ResourceBuffer(const char* uri)>;
using FreeResource = std::function<void (ResourceBuffer)>;
struct MorphAnimationBuffer {
MorphAnimationBuffer(float* frameData,
int numWeights,
int numFrames,
float frameLength) : frameData(frameData), numWeights(numWeights), numFrames(numFrames), frameLengthInMs(frameLength) {
}
int frameIndex = -1;
int numFrames;
float frameLengthInMs;
time_point_t startTime;
float* frameData;
int numWeights;
};
class FilamentViewer {
public:
FilamentViewer(void* layer, LoadResource loadResource, FreeResource freeResource);
@@ -99,42 +54,16 @@ namespace polyvox {
void loadIbl(const char* const iblUri);
void removeIbl();
void loadGlb(const char* const uri);
void loadGltf(const char* const uri, const char* relativeResourcePath);
void removeAsset();
SceneAsset* loadGlb(const char* const uri);
SceneAsset* loadGltf(const char* const uri, const char* relativeResourcePath);
void removeAsset(SceneAsset* asset);
void updateViewportAndCameraProjection(int height, int width, float scaleFactor);
void render();
unique_ptr<vector<string>> getTargetNames(const char* meshName);
unique_ptr<vector<string>> getAnimationNames();
Manipulator<float>* manipulator;
///
/// Manually set the weights for all morph targets in the assets to the provided values.
/// See [animateWeights] if you want to automatically
///
void applyWeights(float* weights, int count);
///
/// Update the asset's morph target weights every "frame" (which is an arbitrary length of time, i.e. this is not the same as a frame at the framerate of the underlying rendering framework).
/// Accordingly:
/// length(data) = numWeights * numFrames
/// total_animation_duration_in_ms = number_of_frames * frameLengthInMs
///
void animateWeights(float* data, int numWeights, int numFrames, float frameLengthInMs);
///
/// Play an embedded animation (i.e. an animation node embedded in the GLTF asset). If [loop] is true, the animation will repeat indefinitely.
///
void playAnimation(int index, bool loop);
///
/// Immediately stop the currently playing animation. NOOP if no animation is playing.
///
void stopAnimation();
bool setCamera(const char* nodeName);
bool setCamera(SceneAsset* asset, const char* nodeName);
void destroySwapChain();
void createSwapChain(void* surface);
@@ -161,10 +90,10 @@ namespace polyvox {
SwapChain* _swapChain = nullptr;
Animator* _animator;
vector<SceneAsset*> _assets;
AssetLoader* _assetLoader;
FilamentAsset* _asset = nullptr;
SceneAssetLoader* _sceneAssetLoader;
NameComponentManager* _ncm;
std::mutex mtx; // mutex to ensure thread safety when removing assets
@@ -184,24 +113,15 @@ namespace polyvox {
float _cameraFocalLength = 0.0f;
void updateMorphAnimation();
void updateEmbeddedAnimation();
// animation flags;
bool isAnimating;
unique_ptr<MorphAnimationBuffer> _morphAnimationBuffer;
unique_ptr<EmbeddedAnimationBuffer> _embeddedAnimationBuffer;
// these flags relate to the textured quad we use for rendering unlit background images
Texture* _imageTexture = nullptr;
Entity* _imageEntity = nullptr;
VertexBuffer* _imageVb = nullptr;
IndexBuffer* _imageIb = nullptr;
Material* _imageMaterial = nullptr;
TextureSampler _imageSampler;
ColorGrading *colorGrading = nullptr;
};

View File

@@ -1,3 +1,8 @@
#pragma once
#ifndef POLYVOX_FILAMENT_LOG_H
#define POLYVOX_FILAMENT_LOG_H
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#elif defined __ANDROID__
@@ -7,7 +12,7 @@
#include <stdio.h>
#endif
void Log(const char *fmt, ...) {
static void Log(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
@@ -23,3 +28,5 @@ void Log(const char *fmt, ...) {
va_end(args);
}
#endif

193
ios/src/SceneAsset.cpp Normal file
View File

@@ -0,0 +1,193 @@
#include <chrono>
#include "SceneResources.hpp"
#include "SceneAsset.hpp"
#include "Log.hpp"
#include <gltfio/Animator.h>
#include <gltfio/AssetLoader.h>
#include <gltfio/FilamentAsset.h>
#include <gltfio/ResourceLoader.h>
#include <gltfio/TextureProvider.h>
#include <filament/TransformManager.h>
using namespace std::chrono;
namespace polyvox {
using namespace std;
using namespace filament;
using namespace filament::gltfio;
using namespace utils;
SceneAsset::SceneAsset(FilamentAsset* asset, Engine* engine, NameComponentManager* ncm)
: _asset(asset), _engine(engine), _ncm(ncm) {
_animator = _asset->getAnimator();
}
SceneAsset::~SceneAsset() { _asset = nullptr; }
void SceneAsset::applyWeights(float *weights, int count) {
RenderableManager& rm = _engine->getRenderableManager();
for (size_t i = 0, c = _asset->getEntityCount(); i != c; ++i) {
auto inst = rm.getInstance(_asset->getEntities()[i]);
rm.setMorphWeights(inst, weights, count);
}
}
void SceneAsset::animateWeights(float *data, int numWeights, int numFrames,
float frameLengthInMs) {
Log("Making morph animation buffer with %d weights across %d frames and "
"frame length %f ms ",
numWeights, numFrames, frameLengthInMs);
_morphAnimationBuffer = std::make_unique<MorphAnimationStatus>(
data, numWeights, numFrames, frameLengthInMs);
}
void SceneAsset::updateAnimations() {
updateMorphAnimation();
updateEmbeddedAnimation();
}
void SceneAsset::updateMorphAnimation() {
if (!_morphAnimationBuffer) {
return;
}
if (_morphAnimationBuffer->frameIndex == -1) {
_morphAnimationBuffer->frameIndex++;
_morphAnimationBuffer->startTime = high_resolution_clock::now();
applyWeights(_morphAnimationBuffer->frameData,
_morphAnimationBuffer->numWeights);
} else {
duration<double, std::milli> dur =
high_resolution_clock::now() - _morphAnimationBuffer->startTime;
int frameIndex =
static_cast<int>(dur.count() / _morphAnimationBuffer->frameLengthInMs);
if (frameIndex > _morphAnimationBuffer->numFrames - 1) {
duration<double, std::milli> dur =
high_resolution_clock::now() - _morphAnimationBuffer->startTime;
Log("Morph animation completed in %f ms (%d frames at framerate %f), "
"final frame was %d",
dur.count(), _morphAnimationBuffer->numFrames,
1000 / _morphAnimationBuffer->frameLengthInMs,
_morphAnimationBuffer->frameIndex);
_morphAnimationBuffer = nullptr;
} else if (frameIndex != _morphAnimationBuffer->frameIndex) {
Log("Rendering frame %d (of a total %d)", frameIndex,
_morphAnimationBuffer->numFrames);
_morphAnimationBuffer->frameIndex = frameIndex;
auto framePtrOffset = frameIndex * _morphAnimationBuffer->numWeights;
applyWeights(_morphAnimationBuffer->frameData + framePtrOffset,
_morphAnimationBuffer->numWeights);
}
}
}
void SceneAsset::playAnimation(int index, bool loop) {
if (index > _animator->getAnimationCount() - 1) {
Log("Asset does not contain an animation at index %d", index);
} else {
_boneAnimationStatus = make_unique<BoneAnimationStatus>(
index, _animator->getAnimationDuration(index), loop);
}
}
void SceneAsset::stopAnimation() {
// TODO - does this need to be threadsafe?
_boneAnimationStatus = nullptr;
}
void SceneAsset::updateEmbeddedAnimation() {
if (!_boneAnimationStatus) {
return;
}
duration<double> dur = duration_cast<duration<double>>(
high_resolution_clock::now() - _boneAnimationStatus->lastTime);
float startTime = 0;
if (!_boneAnimationStatus->hasStarted) {
_boneAnimationStatus->hasStarted = true;
_boneAnimationStatus->lastTime = high_resolution_clock::now();
} else if (dur.count() >= _boneAnimationStatus->duration) {
if (_boneAnimationStatus->loop) {
_boneAnimationStatus->lastTime = high_resolution_clock::now();
} else {
_boneAnimationStatus = nullptr;
return;
}
} else {
startTime = dur.count();
}
_animator->applyAnimation(_boneAnimationStatus->animationIndex, startTime);
_animator->updateBoneMatrices();
}
unique_ptr<vector<string>> SceneAsset::getAnimationNames() {
size_t count = _animator->getAnimationCount();
Log("Found %d animations in asset.", count);
unique_ptr<vector<string>> names = make_unique<vector<string>>();
for (size_t i = 0; i < count; i++) {
names->push_back(_animator->getAnimationName(i));
}
return names;
}
unique_ptr<vector<string>> SceneAsset::getTargetNames(const char *meshName) {
if (!_asset) {
Log("No asset, ignoring call.");
return nullptr;
}
Log("Retrieving morph target names for mesh %s", meshName);
unique_ptr<vector<string>> names = make_unique<vector<string>>();
const Entity *entities = _asset->getEntities();
RenderableManager &rm = _engine->getRenderableManager();
for (int i = 0; i < _asset->getEntityCount(); i++) {
Entity e = entities[i];
auto inst = _ncm->getInstance(e);
const char *name = _ncm->getName(inst);
Log("Got entity instance name %s", name);
if (strcmp(name, meshName) == 0) {
size_t count = _asset->getMorphTargetCountAt(e);
for (int j = 0; j < count; j++) {
const char *morphName = _asset->getMorphTargetNameAt(e, j);
names->push_back(morphName);
}
break;
}
}
return names;
}
void SceneAsset::transformToUnitCube()
{
if (!_asset)
{
Log("No asset, cannot transform.");
return;
}
auto &tm = _engine->getTransformManager();
auto aabb = _asset->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);
tm.setTransform(tm.getInstance(_asset->getRoot()), transform);
}
const utils::Entity* SceneAsset::getCameraEntities() {
return _asset->getCameraEntities();
}
size_t SceneAsset::getCameraEntityCount() {
return _asset->getCameraEntityCount();
}
} // namespace polyvox

83
ios/src/SceneAsset.hpp Normal file
View File

@@ -0,0 +1,83 @@
#pragma once
#include <filament/Engine.h>
#include <filament/RenderableManager.h>
#include <filament/Renderer.h>
#include <filament/Scene.h>
#include <gltfio/AssetLoader.h>
#include <gltfio/FilamentAsset.h>
#include <gltfio/ResourceLoader.h>
#include <utils/NameComponentManager.h>
#include "SceneResources.hpp"
namespace polyvox {
using namespace filament;
using namespace filament::gltfio;
using namespace utils;
using namespace std;
class SceneAsset {
friend class SceneAssetLoader;
public:
SceneAsset(FilamentAsset* asset, Engine* engine, NameComponentManager* ncm);
~SceneAsset();
unique_ptr<vector<string>> getTargetNames(const char* meshName);
unique_ptr<vector<string>> getAnimationNames();
///
/// Update the bone/morph target animations to reflect the current frame (if applicable).
///
void updateAnimations();
///
/// Immediately stop the currently playing animation. NOOP if no animation is playing.
///
void stopAnimation();
///
/// Play an embedded animation (i.e. an animation node embedded in the GLTF asset). If [loop] is true, the animation will repeat indefinitely.
///
void playAnimation(int index, bool loop);
///
/// Manually set the weights for all morph targets in the assets to the provided values.
/// See [animateWeights] if you want to automatically
///
void applyWeights(float* weights, int count);
///
/// Update the asset's morph target weights every "frame" (which is an arbitrary length of time, i.e. this is not the same as a frame at the framerate of the underlying rendering framework).
/// Accordingly:
/// length(data) = numWeights * numFrames
/// total_animation_duration_in_ms = number_of_frames * frameLengthInMs
///
void animateWeights(float* data, int numWeights, int numFrames, float frameLengthInMs);
void transformToUnitCube();
const utils::Entity* getCameraEntities();
size_t getCameraEntityCount();
private:
FilamentAsset* _asset = nullptr;
Engine* _engine = nullptr;
NameComponentManager* _ncm;
void updateMorphAnimation();
void updateEmbeddedAnimation();
Animator* _animator;
// animation flags;
bool isAnimating;
unique_ptr<MorphAnimationStatus> _morphAnimationBuffer;
unique_ptr<BoneAnimationStatus> _boneAnimationStatus;
};
}

View File

@@ -0,0 +1,123 @@
#include "SceneAssetLoader.hpp"
#include "Log.hpp"
#include <gltfio/Animator.h>
namespace polyvox {
using namespace filament;
using namespace filament::gltfio;
SceneAssetLoader::SceneAssetLoader(LoadResource loadResource,
FreeResource freeResource,
AssetLoader *assetLoader,
ResourceLoader *resourceLoader,
NameComponentManager *ncm, Engine *engine,
Scene *scene)
: _loadResource(loadResource), _freeResource(freeResource),
_assetLoader(assetLoader), _resourceLoader(resourceLoader), _ncm(ncm),
_engine(engine), _scene(scene) {}
SceneAsset *SceneAssetLoader::fromGltf(const char *uri,
const char *relativeResourcePath) {
ResourceBuffer rbuf = _loadResource(uri);
// Parse the glTF file and create Filament entities.
Log("Creating asset from JSON");
FilamentAsset *asset =
_assetLoader->createAssetFromJson((uint8_t *)rbuf.data, rbuf.size);
Log("Created asset from JSON");
if (!asset) {
Log("Unable to parse asset");
return nullptr;
}
Log("Loading relative resources");
const char *const *const resourceUris = asset->getResourceUris();
const size_t resourceUriCount = asset->getResourceUriCount();
Log("Loading %d resources for asset", resourceUriCount);
for (size_t i = 0; i < resourceUriCount; i++) {
string uri =
string(relativeResourcePath) + string("/") + string(resourceUris[i]);
Log("Creating resource buffer for resource at %s", uri.c_str());
ResourceBuffer buf = _loadResource(uri.c_str());
// using FunctionCallback = std::function<void(void*, unsigned int, void
// *)>; auto cb = [&] (void * ptr, unsigned int len, void * misc) {
// };
// FunctionCallback fcb = cb;
ResourceLoader::BufferDescriptor b(buf.data, buf.size);
_resourceLoader->addResourceData(resourceUris[i], std::move(b));
_freeResource(buf);
}
_resourceLoader->loadResources(asset);
const Entity *entities = asset->getEntities();
RenderableManager &rm = _engine->getRenderableManager();
for (int i = 0; i < asset->getEntityCount(); i++) {
Entity e = entities[i];
auto inst = rm.getInstance(e);
rm.setCulling(inst, false);
}
asset->getAnimator()->updateBoneMatrices();
_scene->addEntities(asset->getEntities(), asset->getEntityCount());
Log("Loaded relative resources");
asset->releaseSourceData();
Log("Load complete for GLTF at URI %s", uri);
return new SceneAsset(asset, _engine, _ncm);
}
SceneAsset *SceneAssetLoader::fromGlb(const char *uri) {
Log("Loading GLB at URI %s", uri);
ResourceBuffer rbuf = _loadResource(uri);
FilamentAsset *asset = _assetLoader->createAssetFromBinary(
(const uint8_t *)rbuf.data, rbuf.size);
if (!asset) {
Log("Unknown error loading GLB asset.");
return nullptr;
}
int entityCount = asset->getEntityCount();
_scene->addEntities(asset->getEntities(), entityCount);
Log("Added %d entities to scene", entityCount);
_resourceLoader->loadResources(asset);
const Entity *entities = asset->getEntities();
RenderableManager &rm = _engine->getRenderableManager();
for (int i = 0; i < asset->getEntityCount(); i++) {
Entity e = entities[i];
auto inst = rm.getInstance(e);
// check this
rm.setCulling(inst, false);
}
_freeResource(rbuf);
asset->getAnimator()->updateBoneMatrices();
asset->releaseSourceData();
Log("Successfully loaded GLB.");
return new SceneAsset(asset, _engine, _ncm);
}
void SceneAssetLoader::remove(SceneAsset *asset) {
_resourceLoader->evictResourceData();
_scene->removeEntities(asset->_asset->getEntities(),
asset->_asset->getEntityCount());
_assetLoader->destroyAsset(asset->_asset);
}
} // namespace polyvox

View File

@@ -0,0 +1,42 @@
#pragma once
#include <filament/Scene.h>
#include <gltfio/AssetLoader.h>
#include <gltfio/FilamentAsset.h>
#include <gltfio/ResourceLoader.h>
#include "SceneResources.hpp"
#include "SceneAsset.hpp"
namespace polyvox {
using namespace filament;
using namespace filament::gltfio;
using namespace utils;
class SceneAssetLoader {
public:
SceneAssetLoader(
LoadResource loadResource,
FreeResource freeResource,
AssetLoader* assetLoader,
ResourceLoader* resourceLoader,
NameComponentManager* ncm,
Engine* engine,
Scene* scene);
SceneAsset* fromGltf(const char* uri, const char* relativeResourcePath);
SceneAsset* fromGlb(const char* uri);
void remove(SceneAsset* asset);
private:
LoadResource _loadResource;
FreeResource _freeResource;
AssetLoader* _assetLoader;
ResourceLoader* _resourceLoader;
NameComponentManager* _ncm;
Scene* _scene;
Engine* _engine;
};
}

View File

@@ -0,0 +1,84 @@
#pragma once
#include <functional>
namespace polyvox {
//
// Pairs a memory buffer with an ID that can be used to unload the backing asset if needed.
// Use this when you want to load an asset from a resource that requires more than just `free` on the underlying buffer.
// e.g.
// ```
// uint64_t id = get_next_resource_id();
// AAsset *asset = AAssetManager_open(am, name, AASSET_MODE_BUFFER);
// off_t length = AAsset_getLength(asset);
// const void * buffer = AAsset_getBuffer(asset);
// uint8_t *buf = new uint8_t[length ];
// memcpy(buf,buffer, length);
// ResourceBuffer rb(buf, length, id);
// ...
// ...
// (elsewhere)
// AAsset* asset = get_asset_from_id(rb.id);
// AAsset_close(asset);
// free_asset_id(rb.id);
//
struct ResourceBuffer {
ResourceBuffer(const void* data, const uint32_t size, const uint32_t id) : data(data), size(size), id(id) {};
ResourceBuffer& operator=(ResourceBuffer other)
{
data = other.data;
size = other.size;
id = other.id;
return *this;
}
const void* data;
uint32_t size;
uint32_t id;
};
//
// Typedef for any function that loads a resource into a ResourceBuffer from an asset URI.
//
using LoadResource = std::function<ResourceBuffer(const char* uri)>;
//
// Typedef for any function that frees a ResourceBuffer.
//
using FreeResource = std::function<void (ResourceBuffer)>;
typedef std::chrono::time_point<std::chrono::high_resolution_clock> time_point_t;
//
// Holds the current state of a bone animation embeded in a GLTF asset.
//
struct BoneAnimationStatus {
BoneAnimationStatus(int animationIndex, float duration, bool loop) : animationIndex(animationIndex), duration(duration), loop(loop) {}
bool hasStarted = false;
int animationIndex;
float duration = 0;
time_point_t lastTime;
bool loop;
};
//
// Holds the current state of a morph-target animation in a GLTF asset.
//
struct MorphAnimationStatus {
MorphAnimationStatus(float* frameData,
int numWeights,
int numFrames,
float frameLength) : frameData(frameData), numWeights(numWeights), numFrames(numFrames), frameLengthInMs(frameLength) {
}
int frameIndex = -1;
int numFrames;
float frameLengthInMs;
time_point_t startTime;
float* frameData;
int numWeights;
};
}

View File

@@ -7,11 +7,11 @@ using namespace std;
namespace polyvox {
class streambuf : public std::streambuf
class StreamBufferAdapter : public std::streambuf
{
public:
streambuf(const char *begin, const char *end);
~streambuf() {
StreamBufferAdapter(const char *begin, const char *end);
~StreamBufferAdapter() {
}
streamsize size();
@@ -26,16 +26,16 @@ class streambuf : public std::streambuf
};
streambuf::streambuf(const char *begin, const char *end)
StreamBufferAdapter::StreamBufferAdapter(const char *begin, const char *end)
{
setg((char*)begin, (char*)begin, (char*)end);
}
streamsize streambuf::size() {
streamsize StreamBufferAdapter::size() {
return egptr() - eback();
}
streambuf::int_type streambuf::underflow()
streambuf::int_type StreamBufferAdapter::underflow()
{
if (gptr() == egptr()) {
return traits_type::eof();
@@ -43,7 +43,7 @@ streambuf::int_type streambuf::underflow()
return *(gptr());
}
streambuf::int_type streambuf::uflow()
streambuf::int_type StreamBufferAdapter::uflow()
{
if (gptr() == egptr()) {
return traits_type::eof();
@@ -53,7 +53,7 @@ streambuf::int_type streambuf::uflow()
return *(gptr());
}
streambuf::int_type streambuf::pbackfail(int_type ch)
streambuf::int_type StreamBufferAdapter::pbackfail(int_type ch)
{
if (gptr() == eback() || (ch != traits_type::eof() && ch != gptr()[-1]))
return traits_type::eof();
@@ -61,12 +61,12 @@ streambuf::int_type streambuf::pbackfail(int_type ch)
return *(gptr());
}
streamsize streambuf::showmanyc()
streamsize StreamBufferAdapter::showmanyc()
{
return egptr() - gptr();
}
streampos streambuf::seekoff(streamoff off, ios_base::seekdir way, ios_base::openmode which = ios_base::in) {
streampos StreamBufferAdapter::seekoff(streamoff off, ios_base::seekdir way, ios_base::openmode which = ios_base::in) {
if(way == ios_base::beg) {
setg(eback(), eback()+off, egptr());
} else if(way == ios_base::cur) {
@@ -77,7 +77,7 @@ streampos streambuf::seekoff(streamoff off, ios_base::seekdir way, ios_base::ope
return gptr() - eback();
}
streampos streambuf::seekpos(streampos sp, ios_base::openmode which = ios_base::in) {
streampos StreamBufferAdapter::seekpos(streampos sp, ios_base::openmode which = ios_base::in) {
return seekoff(sp - pos_type(off_type(0)), std::ios_base::beg, which);
}
}

View File

@@ -5,11 +5,15 @@
namespace polyvox {
class streambuf : public std::streambuf
//
// A generic adapter to expose any contiguous section of memory as a std::streambuf.
// Mostly for Android/iOS assets which may not be able to be directly loaded as streams.
//
class StreamBufferAdapter : public std::streambuf
{
public:
streambuf(const char *begin, const char *end);
~streambuf() {
StreamBufferAdapter(const char *begin, const char *end);
~StreamBufferAdapter() {
}
streamsize size();