#include #include #include #include #include #include #include #include #include #include #include #include "StreamBufferAdapter.hpp" #include "SceneAsset.hpp" #include "Log.hpp" #include "ResourceManagement.hpp" #include "SceneAssetAnimation.hpp" using namespace std::chrono; namespace polyvox { using namespace std; using namespace filament; using namespace filament::gltfio; using namespace image; using namespace utils; SceneAsset::SceneAsset(FilamentAsset *asset, Engine *engine, NameComponentManager *ncm, LoadResource loadResource, FreeResource freeResource) : _asset(asset), _engine(engine), _ncm(ncm), _loadResource(loadResource), _freeResource(freeResource) { _animator = _asset->getInstance()->getAnimator(); for (int i = 0; i < _animator->getAnimationCount(); i++) { _embeddedAnimationStatus.push_back( GLTFAnimation(false,false)); } Log("Created animation buffers for %d", _embeddedAnimationStatus.size()); } SceneAsset::~SceneAsset() { // most other destructor work is deferred to SceneAssetLoader so we don't need to do anything here if(_texture) { _engine->destroy(_texture); _texture = nullptr; } } void SceneAsset::setMorphTargetWeights(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::setAnimation( const float* const morphData, int numMorphWeights, const BoneAnimation* const boneAnimations, int numBoneAnimations, int numFrames, float frameLengthInMs) { auto filamentInstance = _asset->getInstance(); size_t skinCount = filamentInstance->getSkinCount(); if(skinCount > 1) { Log("WARNING - skin count > 1 not currently implemented. This will probably not work"); } auto transforms = make_unique>(); auto numFloats = numFrames * 7; for(int i = 0; i < numBoneAnimations; i++) { auto boneIndices = make_unique>(); boneIndices->resize(boneAnimations[i].numBones); for(int j = 0; j < boneAnimations[i].numBones; j++) { boneIndices->at(j) = getBoneIndex(boneAnimations[i].boneNames[j]); } auto meshTargets = make_unique>(); for(int j = 0; j < _asset->getEntityCount(); j++) { for(int k = 0; k < boneAnimations[i].numMeshTargets;k++) { auto meshName = boneAnimations[i].meshNames[k]; auto entity = _asset->getEntities()[j]; auto nameInstance = _ncm->getInstance(entity); if(strcmp(meshName,_ncm->getName(nameInstance))==0) { meshTargets->push_back(entity); } } } auto frameData = make_unique>( boneAnimations[i].data, boneAnimations[i].data + (numFloats * sizeof(float)) ); transforms->push_back(BoneTransformTarget( boneIndices, meshTargets, frameData )); } _runtimeAnimationBuffer = std::make_unique( morphData, numMorphWeights, transforms, numFrames, frameLengthInMs ); } void SceneAsset::updateAnimations() { updateRuntimeAnimation(); updateEmbeddedAnimations(); } void SceneAsset::updateRuntimeAnimation() { if (!_runtimeAnimationBuffer) { return; } if (_runtimeAnimationBuffer->frameNumber == -1) { _runtimeAnimationBuffer->startTime = high_resolution_clock::now(); } duration dur = high_resolution_clock::now() - _runtimeAnimationBuffer->startTime; int frameNumber = static_cast(dur.count() / _runtimeAnimationBuffer->mFrameLengthInMs); // if the animation has finished, return early if (frameNumber >= _runtimeAnimationBuffer->mNumFrames) { _runtimeAnimationBuffer = nullptr; return; } if (frameNumber > _runtimeAnimationBuffer->frameNumber) { _runtimeAnimationBuffer->frameNumber = frameNumber; if(_runtimeAnimationBuffer->mMorphFrameData) { auto morphFramePtrOffset = frameNumber * _runtimeAnimationBuffer->mNumMorphWeights; setMorphTargetWeights(_runtimeAnimationBuffer->mMorphFrameData + morphFramePtrOffset, _runtimeAnimationBuffer->mNumMorphWeights); } if(_runtimeAnimationBuffer->mTargets->size() > 0) { for(auto& target : *(_runtimeAnimationBuffer->mTargets)) { setBoneTransform( target.skinIndex, *(target.mBoneIndices), *(target.mMeshTargets), *(target.mBoneData), frameNumber ); } } } } size_t SceneAsset::getBoneIndex(const char* name) { auto filamentInstance = _asset->getInstance(); int skinIndex = 0; const utils::Entity* joints = filamentInstance->getJointsAt(skinIndex); size_t numJoints = filamentInstance->getJointCountAt(skinIndex); int boneIndex = -1; for(int i =0; i < numJoints; i++) { const char* jointName = _ncm->getName(_ncm->getInstance(joints[i])); if(strcmp(jointName, name) == 0) { boneIndex = i; break; } } if(boneIndex == -1) { Log("Failed to find bone index %d for bone %s", name); } return boneIndex; } void SceneAsset::setBoneTransform( uint8_t skinIndex, const vector& boneIndices, const vector& targets, const vector data, int frameNumber) { auto filamentInstance = _asset->getInstance(); RenderableManager &rm = _engine->getRenderableManager(); TransformManager &transformManager = _engine->getTransformManager(); auto frameDataOffset = frameNumber * 7; for(auto& target : targets) { auto renderable = rm.getInstance(target); math::mat4f inverseGlobalTransform = inverse( transformManager.getWorldTransform( transformManager.getInstance(target) ) ); for(auto boneIndex : boneIndices) { utils::Entity joint = filamentInstance->getJointsAt(skinIndex)[boneIndex]; math::mat4f localTransform(math::quatf{ data[frameDataOffset+6], data[frameDataOffset+3], data[frameDataOffset+4], data[frameDataOffset+5] }); const math::mat4f& inverseBindMatrix = filamentInstance->getInverseBindMatricesAt(skinIndex)[boneIndex]; auto jointInstance = transformManager.getInstance(joint); math::mat4f globalJointTransform = transformManager.getWorldTransform(jointInstance); math::mat4f boneTransform = inverseGlobalTransform * globalJointTransform * localTransform * inverseBindMatrix; rm.setBones( renderable, &boneTransform, 1, boneIndex); } } } void SceneAsset::playAnimation(int index, bool loop, bool reverse) { if (index > _animator->getAnimationCount() - 1) { Log("Asset does not contain an animation at index %d", index); } else { const char* name = _animator->getAnimationName(index); Log("Playing animation %d : %s", index, name); if (_embeddedAnimationStatus[index].started) { Log("Animation already playing, call stop first."); } else { Log("Starting animation at index %d with loop : %d and reverse %d ", index, loop, reverse); _embeddedAnimationStatus[index].play = true; _embeddedAnimationStatus[index].loop = loop; _embeddedAnimationStatus[index].reverse = reverse; } } } void SceneAsset::stopAnimation(int index) { Log("Stopping animation %d", index); // TODO - does this need to be threadsafe? _embeddedAnimationStatus[index].play = false; _embeddedAnimationStatus[index].started = false; } void SceneAsset::loadTexture(const char* resourcePath, int renderableIndex) { Log("Loading texture at %s for renderableIndex %d", resourcePath, renderableIndex); string rp(resourcePath); if(_texture) { _engine->destroy(_texture); _texture = nullptr; } ResourceBuffer imageResource = _loadResource(rp.c_str()); StreamBufferAdapter sb((char *)imageResource.data, (char *)imageResource.data + imageResource.size); istream *inputStream = new std::istream(&sb); LinearImage *image = new LinearImage(ImageDecoder::decode( *inputStream, rp.c_str(), ImageDecoder::ColorSpace::SRGB)); if (!image->isValid()) { Log("Invalid image : %s", rp.c_str()); return; } uint32_t channels = image->getChannels(); uint32_t w = image->getWidth(); uint32_t h = image->getHeight(); _texture = Texture::Builder() .width(w) .height(h) .levels(0xff) .format(channels == 3 ? Texture::InternalFormat::RGB16F : Texture::InternalFormat::RGBA16F) .sampler(Texture::Sampler::SAMPLER_2D) .build(*_engine); Texture::PixelBufferDescriptor::Callback freeCallback = [](void *buf, size_t, void *data) { delete reinterpret_cast(data); }; Texture::PixelBufferDescriptor buffer( image->getPixelRef(), size_t(w * h * channels * sizeof(float)), channels == 3 ? Texture::Format::RGB : Texture::Format::RGBA, Texture::Type::FLOAT, freeCallback); _texture->setImage(*_engine, 0, std::move(buffer)); setTexture(); delete inputStream; _freeResource(imageResource.id); } void SceneAsset::setTexture() { MaterialInstance* const* inst = _asset->getInstance()->getMaterialInstances(); size_t mic = _asset->getInstance()->getMaterialInstanceCount(); Log("Material instance count : %d", mic); auto sampler = TextureSampler(); inst[0]->setParameter("baseColorIndex",0); inst[0]->setParameter("baseColorMap",_texture,sampler); } void SceneAsset::updateEmbeddedAnimations() { auto now = high_resolution_clock::now(); int animationIndex = 0; bool playing = false; for (auto &status : _embeddedAnimationStatus) { if (status.play == false) { continue; } playing = true; float animationLength = _animator->getAnimationDuration(animationIndex); duration elapsed = duration_cast>(now - status.startedAt); float animationTimeOffset = 0; bool finished = false; bool fading = false; // if the animation hasn't started yet, start the animation at time zero if (!status.started) { status.started = true; status.startedAt = now; // if the animation has finished } else if (elapsed.count() >= animationLength) { // if we aren't looping, just mark the animation as finished if(!status.loop) { finished = true; // otherwise, cross-fade between the end of the animation and the start frame over 1 second } else { // if 1 second has elapsed, if(elapsed.count() >= animationLength + 0.3) { // reset the start time to zero status.startedAt = now; // otherwise, apply the first frame of the animation, then cross-fade with the last frame over 1 second } else { fading = true; } } } else { animationTimeOffset = elapsed.count(); } if (finished) { Log("Animation %d finished", animationIndex); status.play = false; status.started = false; } else { if(status.reverse) { animationTimeOffset = _animator->getAnimationDuration(animationIndex) - animationTimeOffset; } _animator->applyAnimation(animationIndex, animationTimeOffset); if(fading) { // Log("Fading at %0f offset %f", elapsed.count() - animationLength, animationTimeOffset); _animator->applyCrossFade(animationIndex, animationLength - 0.05, (elapsed.count() - animationLength) / 0.3); } } animationIndex++; } if(playing) _animator->updateBoneMatrices(); } unique_ptr> SceneAsset::getAnimationNames() { size_t count = _animator->getAnimationCount(); unique_ptr> names = make_unique>(); for (size_t i = 0; i < count; i++) { names->push_back(_animator->getAnimationName(i)); } return names; } unique_ptr> SceneAsset::getMorphTargetNames(const char *meshName) { if (!_asset) { Log("No asset, ignoring call."); return nullptr; } // Log("Retrieving morph target names for mesh %s", meshName); unique_ptr> names = make_unique>(); const Entity *entities = _asset->getEntities(); for (int i = 0; i < _asset->getEntityCount(); i++) { Entity e = entities[i]; auto inst = _ncm->getInstance(e); const char *name = _ncm->getName(inst); 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; } Log("Transforming asset to unit cube."); auto &tm = _engine->getTransformManager(); FilamentInstance* inst = _asset->getInstance(); auto aabb = inst->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(inst->getRoot()), transform); } void SceneAsset::updateTransform() { auto &tm = _engine->getTransformManager(); auto transform = _position * _rotation * math::mat4f::scaling(_scale); tm.setTransform(tm.getInstance(_asset->getRoot()), transform); } void SceneAsset::setScale(float scale) { _scale = scale; updateTransform(); } void SceneAsset::setPosition(float x, float y, float z) { Log("Setting position to %f %f %f", x, y, z); _position = math::mat4f::translation(math::float3(x,y,z)); updateTransform(); } void SceneAsset::setRotation(float rads, float x, float y, float z) { Log("Rotating %f radians around axis %f %f %f", rads, x, y, z); _rotation = math::mat4f::rotation(rads, math::float3(x,y,z)); updateTransform(); } const utils::Entity *SceneAsset::getCameraEntities() { return _asset->getCameraEntities(); } size_t SceneAsset::getCameraEntityCount() { return _asset->getCameraEntityCount(); } const Entity* SceneAsset::getLightEntities() const noexcept { return _asset->getLightEntities(); } size_t SceneAsset::getLightEntityCount() const noexcept { return _asset->getLightEntityCount(); } } // namespace polyvox