From 62c4be05630a39786f0c3b21dff7bed33c06ecc3 Mon Sep 17 00:00:00 2001 From: Nick Fisher Date: Thu, 27 Apr 2023 16:32:32 +0800 Subject: [PATCH] fix dynamic bone animations --- example/lib/main.dart | 34 +- ios/include/AssetManager.hpp | 34 +- ios/include/PolyvoxFilamentApi.h | 21 +- ios/include/SceneAsset.hpp | 56 +-- ios/include/material/FileMaterialProvider.hpp | 2 +- ios/src/AssetManager.cpp | 327 ++++++++++-------- ios/src/PolyvoxFilamentApi.cpp | 15 +- lib/animations/animation_builder.dart | 4 +- lib/animations/bone_animation_data.dart | 1 + lib/animations/bone_driver.dart | 79 +++-- lib/animations/csv_animation.dart | 50 ++- .../live_link_face_bone_driver.dart | 12 + lib/animations/morph_animation_data.dart | 3 +- lib/filament_controller.dart | 20 +- lib/generated_bindings.dart | 291 +++++++--------- test/bone_driver_test.dart | 109 +++--- 16 files changed, 538 insertions(+), 520 deletions(-) create mode 100644 lib/animations/live_link_face_bone_driver.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 3c6d9654..db424975 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,12 +1,13 @@ import 'dart:math'; +import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:vector_math/vector_math.dart' as v; import 'package:polyvox_filament/filament_controller.dart'; +import 'package:polyvox_filament/animations/bone_animation_data.dart'; import 'package:polyvox_filament/filament_gesture_detector.dart'; import 'package:polyvox_filament/filament_widget.dart'; import 'package:polyvox_filament/animations/animation_builder.dart'; -import 'package:polyvox_filament/animations/animations.dart'; void main() { runApp(const MyApp()); @@ -101,6 +102,7 @@ class _MyAppState extends State with SingleTickerProviderStateMixin { break; case 9: for (int i = 0; i < _animationNames.length; i++) { + print("Playing animation ${_animationNames[i]}"); _filamentController.playAnimation(_cube!, i, loop: _loop); } @@ -202,15 +204,25 @@ class _MyAppState extends State with SingleTickerProviderStateMixin { _filamentController.clearLights(); break; case 32: - _filamentController.setCameraModelMatrix(List.filled(16, 1.0)); + var frameData = Float32List.fromList( + List.generate(120, (i) => i / 120).expand((x) { + var vals = List.filled(7, x); + vals[3] = 1.0; + // vals[4] = 0; + vals[5] = 0; + vals[6] = 0; + return vals; + }).toList()); - // _filamentController.setBoneTransform( - // _cube!, - // "Bone.001", - // "Cube.001", - // BoneTransform([Vec3(x: 0, y: 0.0, z: 0.0)], - // [Quaternion(x: 1, y: 1, z: 1, w: 1)])); - break; + _filamentController.setBoneAnimation(_cube!, [ + BoneAnimationData("Bone.001", "Cube.001", frameData, 1000.0 / 60.0) + ]); + // , + // "Bone.001", + // "Cube.001", + // BoneTransform([Vec3(x: 0, y: 0.0, z: 0.0)], + // [Quaternion(x: 1, y: 1, z: 1, w: 1)])); + // break; } } @@ -260,9 +272,7 @@ class _MyAppState extends State with SingleTickerProviderStateMixin { _item(value: 21, child: Text('swap cube texture')), _item(value: 22, child: Text('transform to unit cube')), _item(value: 23, child: Text('set position to 1, 1, -1')), - _item( - value: 32, - child: Text('set bone transform to 1, 1, -1')), + _item(value: 32, child: Text('construct bone animation')), _item(value: 24, child: Text('rotate by pi around Y axis')), _item(value: 5, child: Text('load flight helmet')), _item(value: 6, child: Text('remove cube')), diff --git a/ios/include/AssetManager.hpp b/ios/include/AssetManager.hpp index 84b8a35b..7b2da476 100644 --- a/ios/include/AssetManager.hpp +++ b/ios/include/AssetManager.hpp @@ -40,21 +40,22 @@ namespace polyvox { size_t getLightEntityCount(EntityId e) const noexcept; void updateAnimations(); - bool setBoneAnimationBuffer( - EntityId entity, - int length, - const char** const boneNames, - const char** const meshNames, - const float* const frameData, - int numFrames, - float frameLengthInMs); + bool setMorphAnimationBuffer( - EntityId entity, - const char* entityName, - const float* const morphData, + EntityId entityId, + const char* entityName, + const float* const morphData, int numMorphWeights, int numFrames, float frameLengthInMs); + bool setBoneAnimationBuffer( + EntityId entity, + const float* const frameData, + int numFrames, + int numBones, + const char** const boneNames, + const char* const meshName, + float frameLengthInMs); void playAnimation(EntityId e, int index, bool loop, bool reverse); void stopAnimation(EntityId e, int index); void setMorphTargetWeights(const char* const entityName, float *weights, int count); @@ -74,18 +75,15 @@ namespace polyvox { vector _assets; tsl::robin_map _entityIdLookup; - void setBoneTransform( - FilamentInstance* instance, - vector animations, - int frameNumber - ); - utils::Entity findEntityByName( SceneAsset asset, const char* entityName ); - inline void updateTransform(SceneAsset asset); + inline void updateTransform(SceneAsset& asset); + + inline void setBoneTransform(SceneAsset& asset, int frameNumber); + }; diff --git a/ios/include/PolyvoxFilamentApi.h b/ios/include/PolyvoxFilamentApi.h index 01eae3e6..8f92756f 100644 --- a/ios/include/PolyvoxFilamentApi.h +++ b/ios/include/PolyvoxFilamentApi.h @@ -57,29 +57,16 @@ bool set_morph_animation( int numFrames, float frameLengthInMs); - void set_bone_animation( +void set_bone_animation( void* assetManager, EntityId asset, - int length, - const char** const boneNames, - const char** const meshNames, const float* const frameData, int numFrames, + int numBones, + const char** const boneNames, + const char* const meshName, float frameLengthInMs); -// void set_bone_transform( -// EntityId asset, -// const char* boneName, -// const char* entityName, -// float transX, -// float transY, -// float transZ, -// float quatX, -// float quatY, -// float quatZ, -// float quatW -// ); - void play_animation(void* assetManager, EntityId asset, int index, bool loop, bool reverse); void set_animation_frame(void* assetManager, EntityId asset, int animationIndex, int animationFrame); void stop_animation(void* assetManager, EntityId asset, int index); diff --git a/ios/include/SceneAsset.hpp b/ios/include/SceneAsset.hpp index 616cbd9f..1eb93924 100644 --- a/ios/include/SceneAsset.hpp +++ b/ios/include/SceneAsset.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -36,35 +37,6 @@ namespace polyvox { bool mReverse = false; float mDuration = 0; bool mAnimating = false; - - // AnimationStatus() { - // Log("default constr"); - // } - - // AnimationStatus(AnimationStatus& a) { - // mStart = a.mStart; - // mLoop = a.mLoop; - // mReverse = a.mReverse; - // mDuration = a.mDuration; - // mFrameNumber = a.mFrameNumber; - // } - - // AnimationStatus& operator=(AnimationStatus a) { - // mStart = a.mStart; - // mLoop = a.mLoop; - // mReverse = a.mReverse; - // mDuration = a.mDuration; - // mFrameNumber = a.mFrameNumber; - // return *this; - // } - - // AnimationStatus(AnimationStatus&& a) { - // mStart = a.mStart; - // mLoop = a.mLoop; - // mReverse = a.mReverse; - // mDuration = a.mDuration; - // mFrameNumber = a.mFrameNumber; - // } }; // @@ -78,24 +50,18 @@ namespace polyvox { int mNumMorphWeights = 0; }; - /// - /// Frame data for the bones/meshes specified by [mBoneIndices] and [mMeshTargets]. - /// This is mainly used as a wrapper for animation data being transferred from the Dart to the native side. - /// - struct BoneAnimationData { - size_t skinIndex = 0; - uint8_t mBoneIndex; - utils::Entity mMeshTarget; - vector mFrameData; - }; - // - // Use this to manually construct a buffer of frame data for bone animations. + // Use this to construct a dynamic (i.e. non-glTF embedded) bone animation. + // Only a single animation is supported at any time (i.e you can't blend animations). + // Multiple bones are supported but these must be skinned to a single mesh target. // struct BoneAnimationBuffer { + utils::Entity mMeshTarget; + vector mBones; + size_t skinIndex = 0; int mNumFrames = -1; float mFrameLengthInMs = 0; - vector mAnimations; + vector mFrameData; }; struct SceneAsset { @@ -129,10 +95,12 @@ namespace polyvox { mAnimations.resize(2 + mAnimator->getAnimationCount()); - for(int i=2; i < mAnimations.size(); i++) { - mAnimations[i].mDuration = mAnimator->getAnimationDuration(i-2); + for(int i=0; i < mAnimations.size() - 2; i++) { + mAnimations[i].mDuration = mAnimator->getAnimationDuration(i); } } + + }; } diff --git a/ios/include/material/FileMaterialProvider.hpp b/ios/include/material/FileMaterialProvider.hpp index f4a41c2e..606028a1 100644 --- a/ios/include/material/FileMaterialProvider.hpp +++ b/ios/include/material/FileMaterialProvider.hpp @@ -18,7 +18,7 @@ namespace polyvox { public: - FileMaterialProvider(Engine* engine, void* const data, size_t size) { + FileMaterialProvider(Engine* engine, const void* const data, const size_t size) { _m = Material::Builder() .package(data, size) .build(*engine); diff --git a/ios/src/AssetManager.cpp b/ios/src/AssetManager.cpp index 80995664..5c1f83cb 100644 --- a/ios/src/AssetManager.cpp +++ b/ios/src/AssetManager.cpp @@ -52,8 +52,13 @@ AssetManager::AssetManager(ResourceLoaderWrapper* resourceLoaderWrapper, _ubershaderProvider = gltfio::createUbershaderProvider( _engine, UBERARCHIVE_DEFAULT_DATA, UBERARCHIVE_DEFAULT_SIZE); EntityManager &em = EntityManager::get(); - _assetLoader = AssetLoader::create({_engine, _ubershaderProvider, _ncm, &em }); + _unlitProvider = new UnlitMaterialProvider(_engine); + + // auto rb = _resourceLoaderWrapper->load("file:///mnt/hdd_2tb/home/hydroxide/projects/polyvox/flutter/polyvox_filament/materials/toon.filamat"); + // auto toonProvider = new FileMaterialProvider(_engine, rb.data, (size_t) rb.size); + + _assetLoader = AssetLoader::create({_engine, _ubershaderProvider, _ncm, &em }); _gltfResourceLoader->addTextureProvider("image/png", _stbDecoder); _gltfResourceLoader->addTextureProvider("image/jpeg", _stbDecoder); @@ -213,13 +218,43 @@ void AssetManager::updateAnimations() { RenderableManager &rm = _engine->getRenderableManager(); for (auto& asset : _assets) { + if(!asset.mAnimating) { continue; } asset.mAnimating = false; + // GLTF animations + for(int j = 0; j < asset.mAnimations.size() - 2; j++) { + AnimationStatus& anim = asset.mAnimations[j]; + + if(!anim.mAnimating) { + // Log("Skipping anim at %d", j); + continue; + } + + auto elapsed = float(std::chrono::duration_cast(now - anim.mStart).count()) / 1000.0f; + + if(anim.mLoop || elapsed < anim.mDuration) { + asset.mAnimator->applyAnimation(j, elapsed); + asset.mAnimating = true; + // Log("Applying at %f", elapsed); + } else if(elapsed - anim.mDuration < 0.3) { + // cross-fade + // animator->applyCrossFade(j-2, anim.mDuration - 0.05, elapsed / 0.3); + // asset.mAnimating = true; + // anim.mStart = time_point_t::max(); + } else { + // stop + anim.mStart = time_point_t::max(); + anim.mAnimating = false; + Log("Finished"); + } + asset.mAnimator->updateBoneMatrices(); + } + // dynamically constructed morph animation - AnimationStatus& morphAnimation = asset.mAnimations[0]; + AnimationStatus& morphAnimation = asset.mAnimations[asset.mAnimations.size() - 2]; if(morphAnimation.mAnimating) { @@ -254,70 +289,109 @@ void AssetManager::updateAnimations() { } } - // // bone animation - // AnimationStatus boneAnimation = asset.mAnimations[1]; - // elapsed = (now - boneAnimation.mStart).count(); + // bone animation + AnimationStatus boneAnimation = asset.mAnimations[asset.mAnimations.size() - 1]; + if(boneAnimation.mAnimating) { + auto elapsed = float( + std::chrono::duration_cast( + now - boneAnimation.mStart + ).count()) / 1000.0f; + int lengthInFrames = static_cast( + boneAnimation.mDuration * 1000.0f / + asset.mBoneAnimationBuffer.mFrameLengthInMs + ); - // lengthInFrames = static_cast(boneAnimation.mDuration / asset.mBoneAnimationBuffer.mFrameLengthInMs); + // if more time has elapsed than the animation duration && not looping + // mark the animation as complete + if(elapsed >= boneAnimation.mDuration && !boneAnimation.mLoop) { + boneAnimation.mStart = time_point_t::max(); + boneAnimation.mAnimating = false; + Log("FINISHED"); + } else { - // if(elapsed >= boneAnimation.mDuration) { - // if(boneAnimation.mLoop) { - // boneAnimation.mStart = now; - // if(boneAnimation.mReverse) { - // boneAnimation.mFrameNumber = lengthInFrames; - // } - // asset.mAnimating = true; - // } else { - // boneAnimation.mStart = time_point_t::max(); - // } - // } else { - // asset.mAnimating = true; - // } + asset.mAnimating = true; + int frameNumber = static_cast(elapsed * 1000.0f / asset.mBoneAnimationBuffer.mFrameLengthInMs) % lengthInFrames; - // frameNumber = static_cast(elapsed / asset.mBoneAnimationBuffer.mFrameLengthInMs); - // if(frameNumber < lengthInFrames) { - // if(boneAnimation.mReverse) { - // frameNumber = lengthInFrames - frameNumber; - // } - // boneAnimation.mFrameNumber = frameNumber; - // setBoneTransform( - // asset.mAsset->getInstance(), - // asset.mBoneAnimationBuffer.mAnimations, - // frameNumber - // ); - // } + // offset from the end if reverse + if(boneAnimation.mReverse) { + frameNumber = lengthInFrames - frameNumber; + } - // GLTF animations - - int j = -1; - for(AnimationStatus& anim : asset.mAnimations) { - j++; - if(j < 2) { - continue; - } - - if(!anim.mAnimating) { - continue; - } - - auto elapsed = float(std::chrono::duration_cast(now - anim.mStart).count()) / 1000.0f; - - if(anim.mLoop || elapsed < anim.mDuration) { - asset.mAnimator->applyAnimation(j-2, elapsed); - asset.mAnimating = true; - } else if(elapsed - anim.mDuration < 0.3) { - // cross-fade - // animator->applyCrossFade(j-2, anim.mDuration - 0.05, elapsed / 0.3); - // asset.mAnimating = true; - // anim.mStart = time_point_t::max(); - } else { - // stop - anim.mStart = time_point_t::max(); + setBoneTransform( + asset, + frameNumber + ); } } - if(asset.mAnimating) { - asset.mAnimator->updateBoneMatrices(); - } + } +} + + void AssetManager::setBoneTransform(SceneAsset& asset, int frameNumber) { + + RenderableManager& rm = _engine->getRenderableManager(); + + const auto& filamentInstance = asset.mAsset->getInstance(); + + TransformManager &transformManager = _engine->getTransformManager(); + + int skinIndex = 0; + + math::mat4f inverseGlobalTransform = inverse( + transformManager.getWorldTransform( + transformManager.getInstance(asset.mBoneAnimationBuffer.mMeshTarget) + ) + ); + + auto renderable = rm.getInstance(asset.mBoneAnimationBuffer.mMeshTarget); + + for(int i = 0; i < asset.mBoneAnimationBuffer.mBones.size(); i++) { + auto mBoneIndex = asset.mBoneAnimationBuffer.mBones[i]; + auto frameDataOffset = (frameNumber * asset.mBoneAnimationBuffer.mBones.size() * 7) + asset.mBoneAnimationBuffer.mBones[i]; + + utils::Entity joint = filamentInstance->getJointsAt(skinIndex)[mBoneIndex]; + if(joint.isNull()) { + Log("ERROR : joint not found"); + continue; + } + // RenderableManager::Bone bone { math::quatf{ + // asset.mBoneAnimationBuffer.mFrameData[frameDataOffset+6], + // asset.mBoneAnimationBuffer.mFrameData[frameDataOffset+3], + // asset.mBoneAnimationBuffer.mFrameData[frameDataOffset+4], + // asset.mBoneAnimationBuffer.mFrameData[frameDataOffset+5] + // }, + // math::float3 { + // asset.mBoneAnimationBuffer.mFrameData[frameDataOffset+0], + // asset.mBoneAnimationBuffer.mFrameData[frameDataOffset+1], + // asset.mBoneAnimationBuffer.mFrameData[frameDataOffset+2] + // } + + // }; + // rm.setBones( + // renderable, + // &bone, + // 1, + // mBoneIndex + // ); + const math::mat4f localTransform(math::quatf{ + asset.mBoneAnimationBuffer.mFrameData[frameDataOffset+3], + asset.mBoneAnimationBuffer.mFrameData[frameDataOffset+4], + asset.mBoneAnimationBuffer.mFrameData[frameDataOffset+5], + asset.mBoneAnimationBuffer.mFrameData[frameDataOffset+6] + }); + const math::mat4f& inverseBindMatrix = filamentInstance->getInverseBindMatricesAt(skinIndex)[mBoneIndex]; + auto jointInstance = transformManager.getInstance(joint); + math::mat4f globalJointTransform = transformManager.getWorldTransform(jointInstance); + + math::mat4f boneTransform = inverseGlobalTransform * globalJointTransform * inverseBindMatrix * localTransform; + + rm.setBones( + renderable, + &boneTransform, + 1, + mBoneIndex + ); + + } } @@ -394,7 +468,7 @@ bool AssetManager::setMorphAnimationBuffer( asset.mMorphAnimationBuffer.mFrameLengthInMs = frameLengthInMs; asset.mMorphAnimationBuffer.mNumMorphWeights = numMorphWeights; - AnimationStatus& animation = asset.mAnimations[0]; + AnimationStatus& animation = asset.mAnimations[asset.mAnimations.size() - 2]; animation.mDuration = (frameLengthInMs * numFrames) / 1000.0f; animation.mStart = high_resolution_clock::now(); animation.mAnimating = true; @@ -407,21 +481,27 @@ bool AssetManager::setMorphAnimationBuffer( } bool AssetManager::setBoneAnimationBuffer( - EntityId entity, - int length, - const char** const boneNames, - const char** const meshNames, + EntityId entityId, const float* const frameData, int numFrames, + int numBones, + const char** const boneNames, + const char* const meshName, float frameLengthInMs) { - const auto& pos = _entityIdLookup.find(entity); + const auto& pos = _entityIdLookup.find(entityId); if(pos == _entityIdLookup.end()) { Log("ERROR: asset not found for entity."); return false; } auto& asset = _assets[pos->second]; + auto entity = findEntityByName(asset, meshName); + if(!entity) { + Log("Mesh target %s for bone animation could not be found", meshName); + return false; + } + auto filamentInstance = asset.mAsset->getInstance(); size_t skinCount = filamentInstance->getSkinCount(); @@ -433,92 +513,60 @@ bool AssetManager::setBoneAnimationBuffer( int skinIndex = 0; const utils::Entity* joints = filamentInstance->getJointsAt(skinIndex); size_t numJoints = filamentInstance->getJointCountAt(skinIndex); - vector boneIndices; - for(int i = 0; i < length; i++) { + + asset.mBoneAnimationBuffer.mBones.clear(); + for(int i = 0; i < numBones; i++) { + Log("Bone %s", boneNames[i]); for(int j = 0; j < numJoints; j++) { const char* jointName = _ncm->getName(_ncm->getInstance(joints[j])); - if(strcmp(jointName, boneNames[i]) == 0) { - boneIndices.push_back(j); + if(strcmp(jointName, boneNames[i]) == 0) { + asset.mBoneAnimationBuffer.mBones.push_back(j); break; } } } - if(boneIndices.size() != length) { + if(asset.mBoneAnimationBuffer.mBones.size() != numBones) { Log("Failed to find one or more bone indices"); return false; } - - asset.mBoneAnimationBuffer.mAnimations.clear(); - for(int i = 0; i < length; i++) { - BoneAnimationData boneAnimationData; - boneAnimationData.mBoneIndex = boneIndices[i]; + asset.mBoneAnimationBuffer.mFrameData.clear(); + // 7 == locX, locY, locZ, rotW, rotX, rotY, rotZ + asset.mBoneAnimationBuffer.mFrameData.resize(numFrames * numBones * 7); + asset.mBoneAnimationBuffer.mFrameData.insert( + asset.mBoneAnimationBuffer.mFrameData.begin(), + frameData, + frameData + numFrames * numBones * 7 + ); - auto entity = findEntityByName(asset, meshNames[i]); + Log("%d frames for %d bones", numFrames, numBones); - if(!entity) { - Log("Mesh target %s for bone animation could not be found", meshNames[i]); - return false; - } + // for(int i = 0; i < numFrames * numBones * 7; i++) { + // Log("Frame data @ %d is %f", i, frameData[i]); + // } + + asset.mBoneAnimationBuffer.mFrameLengthInMs = frameLengthInMs; + asset.mBoneAnimationBuffer.mNumFrames = numFrames; - boneAnimationData.mMeshTarget = entity; + asset.mBoneAnimationBuffer.mMeshTarget = entity; + auto& animation = asset.mAnimations[asset.mAnimations.size() - 1]; + animation.mStart = std::chrono::high_resolution_clock::now(); + animation.mAnimating = true; + animation.mReverse = false; + animation.mDuration = (frameLengthInMs * numFrames) / 1000.0f; + asset.mAnimating = true; - boneAnimationData.mFrameData.insert( - boneAnimationData.mFrameData.begin(), - frameData[i * numFrames * 7 * sizeof(float)], // 7 == x, y, z, w + three euler angles - frameData[(i+1) * numFrames * 7 * sizeof(float)] - ); + // // Log(", set start to %f and duration to %f", ); + // Log("Successfully set bone animation buffer, set start to %d, dur is %f", + // std::chrono::duration_cast(asset.mAnimations[1].mStart.time_since_epoch()).count(), + // asset.mAnimations[1].mDuration + // ); - asset.mBoneAnimationBuffer.mAnimations.push_back(boneAnimationData); - } return true; } -void AssetManager::setBoneTransform( - FilamentInstance* filamentInstance, - vector animations, - int frameNumber) { - RenderableManager &rm = _engine->getRenderableManager(); - - TransformManager &transformManager = _engine->getTransformManager(); - - auto frameDataOffset = frameNumber * 7; - - int skinIndex = 0; - - for(auto& animation : animations) { - - math::mat4f inverseGlobalTransform = inverse( - transformManager.getWorldTransform( - transformManager.getInstance(animation.mMeshTarget) - ) - ); - - utils::Entity joint = filamentInstance->getJointsAt(skinIndex)[animation.mBoneIndex]; - - math::mat4f localTransform(math::quatf{ - animation.mFrameData[frameDataOffset+6], - animation.mFrameData[frameDataOffset+3], - animation.mFrameData[frameDataOffset+4], - animation.mFrameData[frameDataOffset+5] - }); - - const math::mat4f& inverseBindMatrix = filamentInstance->getInverseBindMatricesAt(animation.skinIndex)[animation.mBoneIndex]; - auto jointInstance = transformManager.getInstance(joint); - math::mat4f globalJointTransform = transformManager.getWorldTransform(jointInstance); - - math::mat4f boneTransform = inverseGlobalTransform * globalJointTransform * localTransform * inverseBindMatrix; - auto renderable = rm.getInstance(animation.mMeshTarget); - rm.setBones( - renderable, - &boneTransform, - 1, - animation.mBoneIndex - ); - } -} void AssetManager::playAnimation(EntityId e, int index, bool loop, bool reverse) { const auto& pos = _entityIdLookup.find(e); @@ -527,11 +575,12 @@ void AssetManager::playAnimation(EntityId e, int index, bool loop, bool reverse) return; } auto& asset = _assets[pos->second]; - - asset.mAnimations[index+2].mAnimating = true; - asset.mAnimations[index+2].mStart = std::chrono::high_resolution_clock::now(); - asset.mAnimations[index+2].mLoop = loop; - asset.mAnimations[index+2].mReverse = reverse; + Log("Playing animation at %d", index); + + asset.mAnimations[index].mStart = std::chrono::high_resolution_clock::now(); + asset.mAnimations[index].mLoop = loop; + asset.mAnimations[index].mReverse = reverse; + asset.mAnimations[index].mAnimating = true; // Log("new start %d, dur is %f", std::chrono::duration_cast(asset.mAnimations[index+2].mStart.time_since_epoch()).count(), asset.mAnimations[index+2].mDuration); asset.mAnimating = true; } @@ -543,7 +592,7 @@ void AssetManager::stopAnimation(EntityId entityId, int index) { return; } auto& asset = _assets[pos->second]; - asset.mAnimations[index+2].mStart = time_point_t::max(); + asset.mAnimations[index].mStart = time_point_t::max(); } void AssetManager::loadTexture(EntityId entity, const char* resourcePath, int renderableIndex) { @@ -702,7 +751,7 @@ void AssetManager::transformToUnitCube(EntityId entity) { tm.setTransform(tm.getInstance(inst->getRoot()), transform); } -void AssetManager::updateTransform(SceneAsset asset) { +void AssetManager::updateTransform(SceneAsset& asset) { auto &tm = _engine->getTransformManager(); auto transform = asset.mPosition * asset.mRotation * math::mat4f::scaling(asset.mScale); diff --git a/ios/src/PolyvoxFilamentApi.cpp b/ios/src/PolyvoxFilamentApi.cpp index 265e4ca5..be0cdff5 100644 --- a/ios/src/PolyvoxFilamentApi.cpp +++ b/ios/src/PolyvoxFilamentApi.cpp @@ -358,20 +358,20 @@ extern "C" { FLUTTER_PLUGIN_EXPORT void set_bone_animation( void* assetManager, EntityId asset, - int length, - const char** const boneNames, - const char** const meshNames, const float* const frameData, int numFrames, + int numBones, + const char** const boneNames, + const char* const meshName, float frameLengthInMs) { //std::packaged_task lambda([=]() mutable { ((AssetManager*)assetManager)->setBoneAnimationBuffer( asset, - length, - boneNames, - meshNames, frameData, - numFrames, + numFrames, + numBones, + boneNames, + meshName, frameLengthInMs ); //}); @@ -417,7 +417,6 @@ extern "C" { bool reverse) { //std::packaged_task lambda([=]() mutable { - std::cout << "Playing animation" << std::endl; ((AssetManager*)assetManager)->playAnimation(asset, index, loop, reverse); //}); // auto fut = _tp->add_task(lambda); diff --git a/lib/animations/animation_builder.dart b/lib/animations/animation_builder.dart index 45455cac..15bd1df2 100644 --- a/lib/animations/animation_builder.dart +++ b/lib/animations/animation_builder.dart @@ -7,7 +7,7 @@ import 'package:vector_math/vector_math.dart'; class AnimationBuilder { final FilamentController controller; - BoneAnimationData? BoneAnimationData; + // BoneAnimationData? BoneAnimationData; double _frameLengthInMs = 0; double _duration = 0; @@ -16,7 +16,7 @@ class AnimationBuilder { double? _interpMorphStartValue; double? _interpMorphEndValue; - List? _BoneAnimationDatas = null; + // List? _BoneAnimationDatas = null; FilamentEntity asset; String meshName; diff --git a/lib/animations/bone_animation_data.dart b/lib/animations/bone_animation_data.dart index cb694b01..7bf9b7e3 100644 --- a/lib/animations/bone_animation_data.dart +++ b/lib/animations/bone_animation_data.dart @@ -5,6 +5,7 @@ import 'package:vector_math/vector_math.dart'; /// Model class for bone animation frame data. /// To create dynamic/runtime bone animations (as distinct from animations embedded in a glTF asset), create an instance containing the relevant /// data and pass to the [setBoneAnimation] method on a [FilamentController]. +/// [frameData] is laid out as [locX, locY, locZ, rotW, rotX, rotY, rotZ] /// class BoneAnimationData { final String boneName; diff --git a/lib/animations/bone_driver.dart b/lib/animations/bone_driver.dart index eeb0237a..e404efca 100644 --- a/lib/animations/bone_driver.dart +++ b/lib/animations/bone_driver.dart @@ -13,45 +13,64 @@ import 'package:vector_math/vector_math.dart'; /// 4) min/max rotation values (corresponding to -1/1 on the blendshape) /// +class Transformation { + final Quaternion rotation; + late final Vector3 translation; + + Transformation(this.rotation, {Vector3? translation}) { + this.translation = translation ?? Vector3.zero(); + } +} + class BoneDriver { final String bone; - final String blendshape; + final Map + transformations; // maps a blendshape key to a Transformation - late final Vector3 transMin; - late final Vector3 transMax; - late final Quaternion rotMin; - late final Quaternion rotMax; - - BoneDriver(this.bone, this.blendshape, this.rotMin, this.rotMax, - Vector3? transMin, Vector3? transMax) { - this.transMin = transMin ?? Vector3.zero(); - this.transMax = transMax ?? Vector3.zero(); - } - - factory BoneDriver.fromJsonObject(dynamic jsonObject) { - return BoneDriver( - jsonObject["bone"], - jsonObject["blendshape"], - Quaternion.fromFloat32List(Float32List.fromList(jsonObject["rotMin"])), - Quaternion.fromFloat32List(Float32List.fromList(jsonObject["rotMax"])), - Vector3.fromFloat32List(Float32List.fromList(jsonObject["transMin"])), - Vector3.fromFloat32List(Float32List.fromList(jsonObject["transMax"])), - ); - } + BoneDriver(this.bone, this.transformations); // // Accepts a Float32List containing [numFrames] frames of data for a single morph target weight (for efficiency, this must be unravelled to a single contiguous Float32List). // Returns a generator that yields [numFrames] Quaternions, each representing the (weighted) rotation/translation specified by the mapping of this BoneDriver. // - Iterable transform(List morphTargetFrameData) sync* { - for (int i = 0; i < morphTargetFrameData.length; i++) { - var weight = (morphTargetFrameData[i] / 2) + 0.5; + Iterable transform( + Map> morphTargetFrameData) sync* { + assert(setEquals( + morphTargetFrameData.keys.toSet(), transformations.keys.toSet())); + var numFrames = morphTargetFrameData.values.first.length; + assert(morphTargetFrameData.values.every((x) => x.length == numFrames)); + for (int frameNum = 0; frameNum < numFrames; frameNum++) { + var rotations = transformations.keys.map((blendshape) { + var weight = morphTargetFrameData[blendshape]![frameNum]; + var rotation = transformations[blendshape]!.rotation.clone(); + rotation.x *= weight; + rotation.y *= weight; + rotation.z *= weight; + return rotation; + }).toList(); - yield Quaternion( - rotMin.x + (weight * (rotMax.x - rotMin.x)), - rotMin.y + (weight * (rotMax.y - rotMin.y)), - rotMin.z + (weight * (rotMax.z - rotMin.z)), - 1.0); + yield rotations.fold( + rotations.first, (Quaternion a, Quaternion b) => a * b); + // todo - bone translations } } + + factory BoneDriver.fromJsonObject(dynamic jsonObject) { + throw Exception("TODO"); + // return BoneDriver( + // jsonObject["bone"], + // Map.fromIterable(jsonObject["blendshape"].map((bsName, quats) { + // var q = quats.map(()) + // MapEntry(k, + } } + + + + + // } + // yield Quaternion( + // rotMin.x + (weight * (rotMax.x - rotMin.x)), + // rotMin.y + (weight * (rotMax.y - rotMin.y)), + // rotMin.z + (weight * (rotMax.z - rotMin.z)), + // 1.0); \ No newline at end of file diff --git a/lib/animations/csv_animation.dart b/lib/animations/csv_animation.dart index b46d35d5..1b9182e5 100644 --- a/lib/animations/csv_animation.dart +++ b/lib/animations/csv_animation.dart @@ -15,40 +15,52 @@ class DynamicAnimation { final List boneAnimation; factory DynamicAnimation.load(String meshName, String csvPath, - {String? boneDriverConfigPath}) { + {List? boneDrivers, + String? boneDriverConfigPath, + double? framerate}) { // create a MorphAnimationData instance from the given CSV var llf = _loadLiveLinkFaceCSV(csvPath); + var frameLengthInMs = 1000 / (framerate ?? 60.0); var morphNames = llf .item1; //.where((name) => !boneDrivers.any((element) => element.blendshape == name)); - var morphAnimationData = MorphAnimationData( - meshName, - llf.item2, - morphNames, - 1000 / 60.0, - ); + var morphAnimationData = + MorphAnimationData(meshName, llf.item2, morphNames, frameLengthInMs); final boneAnimations = []; // if applicable, load the bone driver config if (boneDriverConfigPath != null) { - var boneData = json.decode(File(boneDriverConfigPath).readAsStringSync()); - // for each driver - for (var key in boneData.keys()) { - var driver = BoneDriver.fromJsonObject(boneData[key]); + if (boneDrivers != null) { + throw Exception( + "Specify either boneDrivers, or the config path, not both"); + } + boneDrivers = [ + json + .decode(File(boneDriverConfigPath).readAsStringSync()) + .map(BoneDriver.fromJsonObject) + .toList() + ]; + } + // iterate over every bone driver + if (boneDrivers != null) { + for (var driver in boneDrivers) { // get all frames for the single the blendshape - var morphFrameData = - morphAnimationData.getData(driver.blendshape).toList(); + var morphData = driver.transformations + .map((String blendshape, Transformation transformation) { + return MapEntry( + blendshape, morphAnimationData.getData(blendshape).toList()); + }); // apply the driver to the blendshape weight - var transformedQ = driver.transform(morphFrameData).toList(); + var transformedQ = driver.transform(morphData).toList(); // transform the quaternion to a Float32List var transformedF = _quaternionToFloatList(transformedQ); // add to the list of boneAnimations boneAnimations.add(BoneAnimationData( - driver.bone, meshName, transformedF, 1000.0 / 60.0)); + driver.bone, meshName, transformedF, frameLengthInMs)); } } @@ -56,9 +68,11 @@ class DynamicAnimation { } static Float32List _quaternionToFloatList(List quats) { - var data = Float32List(quats.length * 4); + var data = Float32List(quats.length * 7); + int i = 0; for (var quat in quats) { - data.addAll([0, 0, 0, quat.w, quat.x, quat.y, quat.z]); + data.setRange(i, i + 7, [0, 0, 0, quat.w, quat.x, quat.y, quat.z]); + i += 7; } return data; } @@ -86,7 +100,7 @@ class DynamicAnimation { // CSVs may contain rows where the "BlendShapeCount" column is set to "0" and/or the weight columns are simply missing. // This can happen when something went wrong while recording via an app (e.g. LiveLinkFace) // Whenever we encounter this type of row, we consider that all weights should be set to zero for that frame. - if (numFrameWeights == int.parse(frame[1])) { + if (numFrameWeights != int.parse(frame[1])) { _data.addAll(List.filled(numBlendShapes, 0.0)); continue; } diff --git a/lib/animations/live_link_face_bone_driver.dart b/lib/animations/live_link_face_bone_driver.dart new file mode 100644 index 00000000..b5cc9c99 --- /dev/null +++ b/lib/animations/live_link_face_bone_driver.dart @@ -0,0 +1,12 @@ +import 'dart:math'; + +import 'package:polyvox_filament/animations/bone_driver.dart'; +import 'package:vector_math/vector_math.dart'; + +BoneDriver getLiveLinkFaceBoneDrivers(String bone) { + return BoneDriver(bone, { + "HeadPitch": Transformation(Quaternion.axisAngle(Vector3(1, 0, 0), pi / 2)), + "HeadRoll": Transformation(Quaternion.axisAngle(Vector3(0, 0, 1), pi / 2)), + "HeadYaw": Transformation(Quaternion.axisAngle(Vector3(0, 1, 0), pi / 2)), + }); +} diff --git a/lib/animations/morph_animation_data.dart b/lib/animations/morph_animation_data.dart index ac6ab4a2..ad7ce936 100644 --- a/lib/animations/morph_animation_data.dart +++ b/lib/animations/morph_animation_data.dart @@ -24,8 +24,9 @@ class MorphAnimationData { final double frameLengthInMs; Iterable getData(String morphName) sync* { + int index = morphNames.indexOf(morphName); for (int i = 0; i < numFrames; i++) { - yield data[i * numMorphWeights]; + yield data[(i * numMorphWeights) + index]; } } } diff --git a/lib/filament_controller.dart b/lib/filament_controller.dart index 1e6c6dad..52df302b 100644 --- a/lib/filament_controller.dart +++ b/lib/filament_controller.dart @@ -323,15 +323,19 @@ class FilamentController { /// void setBoneAnimation( FilamentEntity asset, List animations) async { + // for future compatibility, instances of BoneAnimationData can specify individual mesh targets + // however on the rendering side we currently only allow one set of frame data for one mesh target (though multiple bones are supported). + // this is a check that all animations are targeting the same mesh + assert(animations.map((e) => e.meshName).toSet().length == 1); + var data = calloc(animations.length * animations.first.frameData.length); int offset = 0; - var numFrames = animations.first.frameData.length; - var meshNames = calloc>(animations.length); + var numFrames = animations.first.frameData.length ~/ 7; var boneNames = calloc>(animations.length); int animIdx = 0; for (var animation in animations) { - if (animation.frameData.length != numFrames) { + if (animation.frameData.length ~/ 7 != numFrames) { throw Exception( "All bone animations must share the same animation frame data length."); } @@ -339,20 +343,19 @@ class FilamentController { data.elementAt(offset).value = animation.frameData[i]; offset += 1; } - meshNames.elementAt(animIdx).value = - animation.meshName.toNativeUtf8().cast(); boneNames.elementAt(animIdx).value = animation.boneName.toNativeUtf8().cast(); + animIdx++; } _nativeLibrary.set_bone_animation( _assetManager, asset, - animations.length, - boneNames, - meshNames, data, numFrames, + animations.length, + boneNames, + animations.first.meshName.toNativeUtf8().cast(), animations.first.frameLengthInMs); calloc.free(data); } @@ -379,7 +382,6 @@ class FilamentController { void playAnimation(FilamentEntity asset, int index, {bool loop = false, bool reverse = false}) async { - print("LOOP $loop"); _nativeLibrary.play_animation( _assetManager, asset, index, loop ? 1 : 0, reverse ? 1 : 0); } diff --git a/lib/generated_bindings.dart b/lib/generated_bindings.dart index fbba1e36..2197eb07 100644 --- a/lib/generated_bindings.dart +++ b/lib/generated_bindings.dart @@ -655,21 +655,21 @@ class NativeLibrary { void set_bone_animation( ffi.Pointer assetManager, int asset, - int length, - ffi.Pointer> boneNames, - ffi.Pointer> meshNames, ffi.Pointer frameData, int numFrames, + int numBones, + ffi.Pointer> boneNames, + ffi.Pointer meshName, double frameLengthInMs, ) { return _set_bone_animation( assetManager, asset, - length, - boneNames, - meshNames, frameData, numFrames, + numBones, + boneNames, + meshName, frameLengthInMs, ); } @@ -679,21 +679,21 @@ class NativeLibrary { ffi.Void Function( ffi.Pointer, EntityId, - ffi.Int, - ffi.Pointer>, - ffi.Pointer>, ffi.Pointer, ffi.Int, + ffi.Int, + ffi.Pointer>, + ffi.Pointer, ffi.Float)>>('set_bone_animation'); late final _set_bone_animation = _set_bone_animationPtr.asFunction< void Function( ffi.Pointer, int, - int, - ffi.Pointer>, - ffi.Pointer>, ffi.Pointer, int, + int, + ffi.Pointer>, + ffi.Pointer, double)>(); void play_animation( @@ -1122,96 +1122,9 @@ class NativeLibrary { late final _ios_dummy = _ios_dummyPtr.asFunction(); } -class __mbstate_t extends ffi.Union { - @ffi.Array.multi([128]) - external ffi.Array __mbstate8; - - @ffi.LongLong() - external int _mbstateL; -} - -class __darwin_pthread_handler_rec extends ffi.Struct { - external ffi - .Pointer)>> - __routine; - - external ffi.Pointer __arg; - - external ffi.Pointer<__darwin_pthread_handler_rec> __next; -} - -class _opaque_pthread_attr_t extends ffi.Struct { - @ffi.Long() - external int __sig; - - @ffi.Array.multi([56]) - external ffi.Array __opaque; -} - -class _opaque_pthread_cond_t extends ffi.Struct { - @ffi.Long() - external int __sig; - - @ffi.Array.multi([40]) - external ffi.Array __opaque; -} - -class _opaque_pthread_condattr_t extends ffi.Struct { - @ffi.Long() - external int __sig; - - @ffi.Array.multi([8]) - external ffi.Array __opaque; -} - -class _opaque_pthread_mutex_t extends ffi.Struct { - @ffi.Long() - external int __sig; - - @ffi.Array.multi([56]) - external ffi.Array __opaque; -} - -class _opaque_pthread_mutexattr_t extends ffi.Struct { - @ffi.Long() - external int __sig; - - @ffi.Array.multi([8]) - external ffi.Array __opaque; -} - -class _opaque_pthread_once_t extends ffi.Struct { - @ffi.Long() - external int __sig; - - @ffi.Array.multi([8]) - external ffi.Array __opaque; -} - -class _opaque_pthread_rwlock_t extends ffi.Struct { - @ffi.Long() - external int __sig; - - @ffi.Array.multi([192]) - external ffi.Array __opaque; -} - -class _opaque_pthread_rwlockattr_t extends ffi.Struct { - @ffi.Long() - external int __sig; - - @ffi.Array.multi([16]) - external ffi.Array __opaque; -} - -class _opaque_pthread_t extends ffi.Struct { - @ffi.Long() - external int __sig; - - external ffi.Pointer<__darwin_pthread_handler_rec> __cleanup_stack; - - @ffi.Array.multi([8176]) - external ffi.Array __opaque; +class __fsid_t extends ffi.Struct { + @ffi.Array.multi([2]) + external ffi.Array __val; } class ResourceBuffer extends ffi.Struct { @@ -1225,8 +1138,6 @@ class ResourceBuffer extends ffi.Struct { } class ResourceLoaderWrapper extends ffi.Struct { - external ffi.Pointer mOwner; - external LoadResource mLoadResource; external FreeResource mFreeResource; @@ -1234,6 +1145,8 @@ class ResourceLoaderWrapper extends ffi.Struct { external LoadResourceFromOwner mLoadResourceFromOwner; external FreeResourceFromOwner mFreeResourceFromOwner; + + external ffi.Pointer mOwner; } typedef LoadResource = ffi.Pointer< @@ -1248,75 +1161,127 @@ typedef FreeResourceFromOwner = ffi.Pointer< ffi.Void Function(ResourceBuffer, ffi.Pointer)>>; typedef EntityId = ffi.Int32; +const int _STDINT_H = 1; + +const int _FEATURES_H = 1; + +const int _DEFAULT_SOURCE = 1; + +const int __GLIBC_USE_ISOC2X = 1; + +const int __USE_ISOC11 = 1; + +const int __USE_ISOC99 = 1; + +const int __USE_ISOC95 = 1; + +const int _POSIX_SOURCE = 1; + +const int _POSIX_C_SOURCE = 200809; + +const int __USE_POSIX = 1; + +const int __USE_POSIX2 = 1; + +const int __USE_POSIX199309 = 1; + +const int __USE_POSIX199506 = 1; + +const int __USE_XOPEN2K = 1; + +const int __USE_XOPEN2K8 = 1; + +const int _ATFILE_SOURCE = 1; + const int __WORDSIZE = 64; -const int __DARWIN_ONLY_64_BIT_INO_T = 1; +const int __WORDSIZE_TIME64_COMPAT32 = 1; -const int __DARWIN_ONLY_UNIX_CONFORMANCE = 1; +const int __SYSCALL_WORDSIZE = 64; -const int __DARWIN_ONLY_VERS_1050 = 1; +const int __TIMESIZE = 64; -const int __DARWIN_UNIX03 = 1; +const int __USE_MISC = 1; -const int __DARWIN_64_BIT_INO_T = 1; +const int __USE_ATFILE = 1; -const int __DARWIN_VERS_1050 = 1; +const int __USE_FORTIFY_LEVEL = 0; -const int __DARWIN_NON_CANCELABLE = 0; +const int __GLIBC_USE_DEPRECATED_GETS = 0; -const String __DARWIN_SUF_EXTSN = '\$DARWIN_EXTSN'; +const int __GLIBC_USE_DEPRECATED_SCANF = 0; -const int __DARWIN_C_ANSI = 4096; +const int _STDC_PREDEF_H = 1; -const int __DARWIN_C_FULL = 900000; +const int __STDC_IEC_559__ = 1; -const int __DARWIN_C_LEVEL = 900000; +const int __STDC_IEC_60559_BFP__ = 201404; -const int __STDC_WANT_LIB_EXT1__ = 1; +const int __STDC_IEC_559_COMPLEX__ = 1; -const int __DARWIN_NO_LONG_LONG = 0; +const int __STDC_IEC_60559_COMPLEX__ = 201404; -const int _DARWIN_FEATURE_64_BIT_INODE = 1; +const int __STDC_ISO_10646__ = 201706; -const int _DARWIN_FEATURE_ONLY_64_BIT_INODE = 1; +const int __GNU_LIBRARY__ = 6; -const int _DARWIN_FEATURE_ONLY_VERS_1050 = 1; +const int __GLIBC__ = 2; -const int _DARWIN_FEATURE_ONLY_UNIX_CONFORMANCE = 1; +const int __GLIBC_MINOR__ = 37; -const int _DARWIN_FEATURE_UNIX_CONFORMANCE = 3; +const int _SYS_CDEFS_H = 1; -const int __has_ptrcheck = 0; +const int __THROW = 1; -const int __DARWIN_NULL = 0; +const int __THROWNL = 1; -const int __PTHREAD_SIZE__ = 8176; +const int __glibc_c99_flexarr_available = 1; -const int __PTHREAD_ATTR_SIZE__ = 56; +const int __LDOUBLE_REDIRECTS_TO_FLOAT128_ABI = 0; -const int __PTHREAD_MUTEXATTR_SIZE__ = 8; +const int __HAVE_GENERIC_SELECTION = 0; -const int __PTHREAD_MUTEX_SIZE__ = 56; +const int __GLIBC_USE_LIB_EXT2 = 1; -const int __PTHREAD_CONDATTR_SIZE__ = 8; +const int __GLIBC_USE_IEC_60559_BFP_EXT = 1; -const int __PTHREAD_COND_SIZE__ = 40; +const int __GLIBC_USE_IEC_60559_BFP_EXT_C2X = 1; -const int __PTHREAD_ONCE_SIZE__ = 8; +const int __GLIBC_USE_IEC_60559_EXT = 1; -const int __PTHREAD_RWLOCK_SIZE__ = 192; +const int __GLIBC_USE_IEC_60559_FUNCS_EXT = 1; -const int __PTHREAD_RWLOCKATTR_SIZE__ = 16; +const int __GLIBC_USE_IEC_60559_FUNCS_EXT_C2X = 1; -const int USER_ADDR_NULL = 0; +const int __GLIBC_USE_IEC_60559_TYPES_EXT = 1; -const int INT8_MAX = 127; +const int _BITS_TYPES_H = 1; -const int INT16_MAX = 32767; +const int _BITS_TYPESIZES_H = 1; -const int INT32_MAX = 2147483647; +const int __OFF_T_MATCHES_OFF64_T = 1; -const int INT64_MAX = 9223372036854775807; +const int __INO_T_MATCHES_INO64_T = 1; + +const int __RLIM_T_MATCHES_RLIM64_T = 1; + +const int __STATFS_MATCHES_STATFS64 = 1; + +const int __KERNEL_OLD_TIMEVAL_MATCHES_TIMEVAL64 = 1; + +const int __FD_SETSIZE = 1024; + +const int _BITS_TIME64_H = 1; + +const int _BITS_WCHAR_H = 1; + +const int __WCHAR_MAX = 2147483647; + +const int __WCHAR_MIN = -2147483648; + +const int _BITS_STDINT_INTN_H = 1; + +const int _BITS_STDINT_UINTN_H = 1; const int INT8_MIN = -128; @@ -1326,6 +1291,14 @@ const int INT32_MIN = -2147483648; const int INT64_MIN = -9223372036854775808; +const int INT8_MAX = 127; + +const int INT16_MAX = 32767; + +const int INT32_MAX = 2147483647; + +const int INT64_MAX = 9223372036854775807; + const int UINT8_MAX = 255; const int UINT16_MAX = 65535; @@ -1360,66 +1333,54 @@ const int UINT_LEAST64_MAX = -1; const int INT_FAST8_MIN = -128; -const int INT_FAST16_MIN = -32768; +const int INT_FAST16_MIN = -9223372036854775808; -const int INT_FAST32_MIN = -2147483648; +const int INT_FAST32_MIN = -9223372036854775808; const int INT_FAST64_MIN = -9223372036854775808; const int INT_FAST8_MAX = 127; -const int INT_FAST16_MAX = 32767; +const int INT_FAST16_MAX = 9223372036854775807; -const int INT_FAST32_MAX = 2147483647; +const int INT_FAST32_MAX = 9223372036854775807; const int INT_FAST64_MAX = 9223372036854775807; const int UINT_FAST8_MAX = 255; -const int UINT_FAST16_MAX = 65535; +const int UINT_FAST16_MAX = -1; -const int UINT_FAST32_MAX = 4294967295; +const int UINT_FAST32_MAX = -1; const int UINT_FAST64_MAX = -1; -const int INTPTR_MAX = 9223372036854775807; - const int INTPTR_MIN = -9223372036854775808; +const int INTPTR_MAX = 9223372036854775807; + const int UINTPTR_MAX = -1; +const int INTMAX_MIN = -9223372036854775808; + const int INTMAX_MAX = 9223372036854775807; const int UINTMAX_MAX = -1; -const int INTMAX_MIN = -9223372036854775808; - const int PTRDIFF_MIN = -9223372036854775808; const int PTRDIFF_MAX = 9223372036854775807; -const int SIZE_MAX = -1; - -const int RSIZE_MAX = 9223372036854775807; - -const int WCHAR_MAX = 2147483647; - -const int WCHAR_MIN = -2147483648; - -const int WINT_MIN = -2147483648; - -const int WINT_MAX = 2147483647; - const int SIG_ATOMIC_MIN = -2147483648; const int SIG_ATOMIC_MAX = 2147483647; -const int __DARWIN_WCHAR_MAX = 2147483647; +const int SIZE_MAX = -1; -const int __DARWIN_WCHAR_MIN = -2147483648; +const int WCHAR_MIN = -2147483648; -const int __DARWIN_WEOF = -1; +const int WCHAR_MAX = 2147483647; -const int _FORTIFY_SOURCE = 2; +const int WINT_MIN = 0; -const int NULL = 0; +const int WINT_MAX = 4294967295; diff --git a/test/bone_driver_test.dart b/test/bone_driver_test.dart index 09a90305..1119876f 100644 --- a/test/bone_driver_test.dart +++ b/test/bone_driver_test.dart @@ -1,73 +1,70 @@ -import 'dart:convert'; -import 'dart:math'; -import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; import 'package:polyvox_filament/animations/bone_driver.dart'; import 'package:vector_math/vector_math.dart'; void main() { group('BoneDriver', () { - test('constructor sets correct values', () { - Quaternion rotMin = Quaternion.identity(); - Quaternion rotMax = Quaternion.axisAngle(Vector3(1, 0, 0), 0.5); - Vector3 transMin = Vector3.zero(); - Vector3 transMax = Vector3(1, 1, 1); - - BoneDriver boneDriver = BoneDriver( - 'bone1', 'blendshape1', rotMin, rotMax, transMin, transMax); - - expect(boneDriver.bone, 'bone1'); - expect(boneDriver.blendshape, 'blendshape1'); - expect(boneDriver.rotMin, rotMin); - expect(boneDriver.rotMax, rotMax); - expect(boneDriver.transMin, transMin); - expect(boneDriver.transMax, transMax); - }); - - test('fromJsonObject creates BoneDriver instance correctly', () { - dynamic jsonObject = { - "bone": "bone1", - "blendshape": "blendshape1", - "rotMin": Quaternion.identity().storage, - "rotMax": Quaternion.axisAngle(Vector3(1, 0, 0), 0.5).storage, - "transMin": Vector3.zero().storage, - "transMax": Vector3(1, 1, 1).storage + test( + 'transform should yield correct Quaternions for given morphTargetFrameData', + () { + final bone = 'bone1'; + final transformations = { + 'blendshape1': Transformation(Quaternion(1, 0, 0, 1)), + 'blendshape2': Transformation(Quaternion(0, 1, 0, 1)), }; + final morphTargetFrameData = >{ + 'blendshape1': [0.5, -0.5], + 'blendshape2': [-1, 1], + }; + final boneDriver = BoneDriver(bone, transformations); - BoneDriver boneDriver = BoneDriver.fromJsonObject(jsonObject); + final result = boneDriver.transform(morphTargetFrameData).toList(); - expect(boneDriver.bone, 'bone1'); - expect(boneDriver.blendshape, 'blendshape1'); - expect(boneDriver.rotMin.absoluteError(Quaternion.identity()), 0); - expect( - boneDriver.rotMax - .absoluteError(Quaternion.axisAngle(Vector3(1, 0, 0), 0.5)), - 0); - expect(boneDriver.transMin.absoluteError(Vector3.zero()), 0); - expect(boneDriver.transMax.absoluteError(Vector3(1, 1, 1)), 0); + expect(result.length, 2); + expect(result[0].x, -0.5); + expect(result[0].y, 0); + expect(result[0].z, -0.5); + expect(result[0].w, 0); + expect(result[1].x, 0.5); + expect(result[1].y, 0); + expect(result[1].z, 0.5); + expect(result[1].w, 0); }); - test('transform generates correct Quaternions', () { - Quaternion rotMin = Quaternion.identity(); - Quaternion rotMax = Quaternion.axisAngle(Vector3(1, 0, 0), 0.5); - BoneDriver boneDriver = - BoneDriver('bone1', 'blendshape1', rotMin, rotMax, null, null); + test( + 'transform should throw AssertionError when morphTargetFrameData keys do not match transformations keys', + () { + final bone = 'bone1'; + final transformations = { + 'blendshape1': Transformation(Quaternion(1, 0, 0, 0)), + 'blendshape2': Transformation(Quaternion(0, 1, 0, 0)), + }; + final morphTargetFrameData = >{ + 'blendshape1': [0.5, -0.5], + 'blendshape3': [-1, 1], + }; + final boneDriver = BoneDriver(bone, transformations); - List morphTargetFrameData = [-1, 0, 1]; - List expectedResult = [ - Quaternion(rotMin.x, rotMin.y, rotMin.z, 1.0), - Quaternion((rotMin.x + rotMax.x) / 2, (rotMin.y + rotMax.y) / 2, - (rotMin.z + rotMax.z) / 2, 1.0), - Quaternion(rotMax.x, rotMax.y, rotMax.z, 1.0), - ]; + expect(() => boneDriver.transform(morphTargetFrameData), + throwsA(isA())); + }); - Iterable result = boneDriver.transform(morphTargetFrameData); - List resultAsList = result.toList(); - expect(resultAsList.length, expectedResult.length); + test( + 'transform should throw AssertionError when morphTargetFrameData values lengths do not match', + () { + final bone = 'bone1'; + final transformations = { + 'blendshape1': Transformation(Quaternion(1, 0, 0, 0)), + 'blendshape2': Transformation(Quaternion(0, 1, 0, 0)), + }; + final morphTargetFrameData = >{ + 'blendshape1': [0.5, -0.5], + 'blendshape2': [-1], + }; + final boneDriver = BoneDriver(bone, transformations); - for (int i = 0; i < expectedResult.length; i++) { - expect(resultAsList[i].absoluteError(expectedResult[i]), 0); - } + expect(() => boneDriver.transform(morphTargetFrameData), + throwsA(isA())); }); }); }