fix dynamic bone animations
This commit is contained in:
@@ -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<MyApp> 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<MyApp> with SingleTickerProviderStateMixin {
|
||||
_filamentController.clearLights();
|
||||
break;
|
||||
case 32:
|
||||
_filamentController.setCameraModelMatrix(List<double>.filled(16, 1.0));
|
||||
var frameData = Float32List.fromList(
|
||||
List<double>.generate(120, (i) => i / 120).expand((x) {
|
||||
var vals = List<double>.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<MyApp> 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')),
|
||||
|
||||
@@ -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<SceneAsset> _assets;
|
||||
tsl::robin_map<EntityId, int> _entityIdLookup;
|
||||
|
||||
void setBoneTransform(
|
||||
FilamentInstance* instance,
|
||||
vector<BoneAnimationData> 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);
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <filament/Renderer.h>
|
||||
#include <filament/Scene.h>
|
||||
#include <filament/Texture.h>
|
||||
#include <filament/TransformManager.h>
|
||||
|
||||
#include <math/vec3.h>
|
||||
#include <math/vec4.h>
|
||||
@@ -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<float> 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<uint8_t> mBones;
|
||||
size_t skinIndex = 0;
|
||||
int mNumFrames = -1;
|
||||
float mFrameLengthInMs = 0;
|
||||
vector<BoneAnimationData> mAnimations;
|
||||
vector<float> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<std::chrono::milliseconds>(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<std::chrono::milliseconds>(
|
||||
now - boneAnimation.mStart
|
||||
).count()) / 1000.0f;
|
||||
int lengthInFrames = static_cast<int>(
|
||||
boneAnimation.mDuration * 1000.0f /
|
||||
asset.mBoneAnimationBuffer.mFrameLengthInMs
|
||||
);
|
||||
|
||||
// lengthInFrames = static_cast<int>(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<int>(elapsed * 1000.0f / asset.mBoneAnimationBuffer.mFrameLengthInMs) % lengthInFrames;
|
||||
|
||||
// frameNumber = static_cast<int>(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<std::chrono::milliseconds>(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<int> 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<std::chrono::milliseconds>(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<BoneAnimationData> 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<std::chrono::milliseconds>(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);
|
||||
|
||||
@@ -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<void()> 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<void()> lambda([=]() mutable {
|
||||
std::cout << "Playing animation" << std::endl;
|
||||
((AssetManager*)assetManager)->playAnimation(asset, index, loop, reverse);
|
||||
//});
|
||||
// auto fut = _tp->add_task(lambda);
|
||||
|
||||
@@ -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<BoneAnimationData>? _BoneAnimationDatas = null;
|
||||
// List<BoneAnimationData>? _BoneAnimationDatas = null;
|
||||
|
||||
FilamentEntity asset;
|
||||
String meshName;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<String, Transformation>
|
||||
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<Quaternion> transform(List<double> morphTargetFrameData) sync* {
|
||||
for (int i = 0; i < morphTargetFrameData.length; i++) {
|
||||
var weight = (morphTargetFrameData[i] / 2) + 0.5;
|
||||
Iterable<Quaternion> transform(
|
||||
Map<String, List<double>> 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<String,Transformation>.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);
|
||||
@@ -15,40 +15,52 @@ class DynamicAnimation {
|
||||
final List<BoneAnimationData> boneAnimation;
|
||||
|
||||
factory DynamicAnimation.load(String meshName, String csvPath,
|
||||
{String? boneDriverConfigPath}) {
|
||||
{List<BoneDriver>? 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 = <BoneAnimationData>[];
|
||||
|
||||
// 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<Quaternion> 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<double>.filled(numBlendShapes, 0.0));
|
||||
continue;
|
||||
}
|
||||
|
||||
12
lib/animations/live_link_face_bone_driver.dart
Normal file
12
lib/animations/live_link_face_bone_driver.dart
Normal file
@@ -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)),
|
||||
});
|
||||
}
|
||||
@@ -24,8 +24,9 @@ class MorphAnimationData {
|
||||
final double frameLengthInMs;
|
||||
|
||||
Iterable<double> 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,15 +323,19 @@ class FilamentController {
|
||||
///
|
||||
void setBoneAnimation(
|
||||
FilamentEntity asset, List<BoneAnimationData> 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<Float>(animations.length * animations.first.frameData.length);
|
||||
int offset = 0;
|
||||
var numFrames = animations.first.frameData.length;
|
||||
var meshNames = calloc<Pointer<Char>>(animations.length);
|
||||
var numFrames = animations.first.frameData.length ~/ 7;
|
||||
var boneNames = calloc<Pointer<Char>>(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<Char>();
|
||||
boneNames.elementAt(animIdx).value =
|
||||
animation.boneName.toNativeUtf8().cast<Char>();
|
||||
animIdx++;
|
||||
}
|
||||
|
||||
_nativeLibrary.set_bone_animation(
|
||||
_assetManager,
|
||||
asset,
|
||||
animations.length,
|
||||
boneNames,
|
||||
meshNames,
|
||||
data,
|
||||
numFrames,
|
||||
animations.length,
|
||||
boneNames,
|
||||
animations.first.meshName.toNativeUtf8().cast<Char>(),
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -655,21 +655,21 @@ class NativeLibrary {
|
||||
void set_bone_animation(
|
||||
ffi.Pointer<ffi.Void> assetManager,
|
||||
int asset,
|
||||
int length,
|
||||
ffi.Pointer<ffi.Pointer<ffi.Char>> boneNames,
|
||||
ffi.Pointer<ffi.Pointer<ffi.Char>> meshNames,
|
||||
ffi.Pointer<ffi.Float> frameData,
|
||||
int numFrames,
|
||||
int numBones,
|
||||
ffi.Pointer<ffi.Pointer<ffi.Char>> boneNames,
|
||||
ffi.Pointer<ffi.Char> 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<ffi.Void>,
|
||||
EntityId,
|
||||
ffi.Int,
|
||||
ffi.Pointer<ffi.Pointer<ffi.Char>>,
|
||||
ffi.Pointer<ffi.Pointer<ffi.Char>>,
|
||||
ffi.Pointer<ffi.Float>,
|
||||
ffi.Int,
|
||||
ffi.Int,
|
||||
ffi.Pointer<ffi.Pointer<ffi.Char>>,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
ffi.Float)>>('set_bone_animation');
|
||||
late final _set_bone_animation = _set_bone_animationPtr.asFunction<
|
||||
void Function(
|
||||
ffi.Pointer<ffi.Void>,
|
||||
int,
|
||||
int,
|
||||
ffi.Pointer<ffi.Pointer<ffi.Char>>,
|
||||
ffi.Pointer<ffi.Pointer<ffi.Char>>,
|
||||
ffi.Pointer<ffi.Float>,
|
||||
int,
|
||||
int,
|
||||
ffi.Pointer<ffi.Pointer<ffi.Char>>,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
double)>();
|
||||
|
||||
void play_animation(
|
||||
@@ -1122,96 +1122,9 @@ class NativeLibrary {
|
||||
late final _ios_dummy = _ios_dummyPtr.asFunction<void Function()>();
|
||||
}
|
||||
|
||||
class __mbstate_t extends ffi.Union {
|
||||
@ffi.Array.multi([128])
|
||||
external ffi.Array<ffi.Char> __mbstate8;
|
||||
|
||||
@ffi.LongLong()
|
||||
external int _mbstateL;
|
||||
}
|
||||
|
||||
class __darwin_pthread_handler_rec extends ffi.Struct {
|
||||
external ffi
|
||||
.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>
|
||||
__routine;
|
||||
|
||||
external ffi.Pointer<ffi.Void> __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<ffi.Char> __opaque;
|
||||
}
|
||||
|
||||
class _opaque_pthread_cond_t extends ffi.Struct {
|
||||
@ffi.Long()
|
||||
external int __sig;
|
||||
|
||||
@ffi.Array.multi([40])
|
||||
external ffi.Array<ffi.Char> __opaque;
|
||||
}
|
||||
|
||||
class _opaque_pthread_condattr_t extends ffi.Struct {
|
||||
@ffi.Long()
|
||||
external int __sig;
|
||||
|
||||
@ffi.Array.multi([8])
|
||||
external ffi.Array<ffi.Char> __opaque;
|
||||
}
|
||||
|
||||
class _opaque_pthread_mutex_t extends ffi.Struct {
|
||||
@ffi.Long()
|
||||
external int __sig;
|
||||
|
||||
@ffi.Array.multi([56])
|
||||
external ffi.Array<ffi.Char> __opaque;
|
||||
}
|
||||
|
||||
class _opaque_pthread_mutexattr_t extends ffi.Struct {
|
||||
@ffi.Long()
|
||||
external int __sig;
|
||||
|
||||
@ffi.Array.multi([8])
|
||||
external ffi.Array<ffi.Char> __opaque;
|
||||
}
|
||||
|
||||
class _opaque_pthread_once_t extends ffi.Struct {
|
||||
@ffi.Long()
|
||||
external int __sig;
|
||||
|
||||
@ffi.Array.multi([8])
|
||||
external ffi.Array<ffi.Char> __opaque;
|
||||
}
|
||||
|
||||
class _opaque_pthread_rwlock_t extends ffi.Struct {
|
||||
@ffi.Long()
|
||||
external int __sig;
|
||||
|
||||
@ffi.Array.multi([192])
|
||||
external ffi.Array<ffi.Char> __opaque;
|
||||
}
|
||||
|
||||
class _opaque_pthread_rwlockattr_t extends ffi.Struct {
|
||||
@ffi.Long()
|
||||
external int __sig;
|
||||
|
||||
@ffi.Array.multi([16])
|
||||
external ffi.Array<ffi.Char> __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<ffi.Char> __opaque;
|
||||
class __fsid_t extends ffi.Struct {
|
||||
@ffi.Array.multi([2])
|
||||
external ffi.Array<ffi.Int> __val;
|
||||
}
|
||||
|
||||
class ResourceBuffer extends ffi.Struct {
|
||||
@@ -1225,8 +1138,6 @@ class ResourceBuffer extends ffi.Struct {
|
||||
}
|
||||
|
||||
class ResourceLoaderWrapper extends ffi.Struct {
|
||||
external ffi.Pointer<ffi.Void> 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<ffi.Void> mOwner;
|
||||
}
|
||||
|
||||
typedef LoadResource = ffi.Pointer<
|
||||
@@ -1248,75 +1161,127 @@ typedef FreeResourceFromOwner = ffi.Pointer<
|
||||
ffi.Void Function(ResourceBuffer, ffi.Pointer<ffi.Void>)>>;
|
||||
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;
|
||||
|
||||
@@ -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 = <String, List<double>>{
|
||||
'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 = <String, List<double>>{
|
||||
'blendshape1': [0.5, -0.5],
|
||||
'blendshape3': [-1, 1],
|
||||
};
|
||||
final boneDriver = BoneDriver(bone, transformations);
|
||||
|
||||
List<double> morphTargetFrameData = [-1, 0, 1];
|
||||
List<Quaternion> 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<AssertionError>()));
|
||||
});
|
||||
|
||||
Iterable<Quaternion> result = boneDriver.transform(morphTargetFrameData);
|
||||
List<Quaternion> 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 = <String, List<double>>{
|
||||
'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<AssertionError>()));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user