merge
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 50;
|
objectVersion = 54;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@@ -220,6 +220,7 @@
|
|||||||
};
|
};
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
@@ -234,6 +235,7 @@
|
|||||||
};
|
};
|
||||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
import 'dart:typed_data';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:vector_math/vector_math.dart' as v;
|
import 'package:vector_math/vector_math.dart' as v;
|
||||||
|
|
||||||
import 'package:polyvox_filament/filament_controller.dart';
|
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_gesture_detector.dart';
|
||||||
import 'package:polyvox_filament/filament_widget.dart';
|
import 'package:polyvox_filament/filament_widget.dart';
|
||||||
import 'package:polyvox_filament/animations/animation_builder.dart';
|
import 'package:polyvox_filament/animations/animation_builder.dart';
|
||||||
import 'package:polyvox_filament/animations/animations.dart';
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
@@ -103,6 +104,7 @@ class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
|
|||||||
break;
|
break;
|
||||||
case 9:
|
case 9:
|
||||||
for (int i = 0; i < _animationNames.length; i++) {
|
for (int i = 0; i < _animationNames.length; i++) {
|
||||||
|
print("Playing animation ${_animationNames[i]}");
|
||||||
_filamentController.playAnimation(_cube!, i, loop: _loop);
|
_filamentController.playAnimation(_cube!, i, loop: _loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,15 +206,26 @@ class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
|
|||||||
_filamentController.clearLights();
|
_filamentController.clearLights();
|
||||||
break;
|
break;
|
||||||
case 32:
|
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(
|
_filamentController.setBoneAnimation(
|
||||||
// _cube!,
|
_cube!,
|
||||||
// "Bone.001",
|
BoneAnimationData(
|
||||||
// "Cube.001",
|
"Bone.001", ["Cube.001"], frameData, 1000.0 / 60.0));
|
||||||
// BoneTransform([Vec3(x: 0, y: 0.0, z: 0.0)],
|
// ,
|
||||||
// [Quaternion(x: 1, y: 1, z: 1, w: 1)]));
|
// "Bone.001",
|
||||||
break;
|
// "Cube.001",
|
||||||
|
// BoneTransform([Vec3(x: 0, y: 0.0, z: 0.0)],
|
||||||
|
// [Quaternion(x: 1, y: 1, z: 1, w: 1)]));
|
||||||
|
// break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,9 +275,7 @@ class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
|
|||||||
_item(value: 21, child: Text('swap cube texture')),
|
_item(value: 21, child: Text('swap cube texture')),
|
||||||
_item(value: 22, child: Text('transform to unit cube')),
|
_item(value: 22, child: Text('transform to unit cube')),
|
||||||
_item(value: 23, child: Text('set position to 1, 1, -1')),
|
_item(value: 23, child: Text('set position to 1, 1, -1')),
|
||||||
_item(
|
_item(value: 32, child: Text('construct bone animation')),
|
||||||
value: 32,
|
|
||||||
child: Text('set bone transform to 1, 1, -1')),
|
|
||||||
_item(value: 24, child: Text('rotate by pi around Y axis')),
|
_item(value: 24, child: Text('rotate by pi around Y axis')),
|
||||||
_item(value: 5, child: Text('load flight helmet')),
|
_item(value: 5, child: Text('load flight helmet')),
|
||||||
_item(value: 6, child: Text('remove cube')),
|
_item(value: 6, child: Text('remove cube')),
|
||||||
|
|||||||
@@ -40,23 +40,25 @@ namespace polyvox {
|
|||||||
size_t getLightEntityCount(EntityId e) const noexcept;
|
size_t getLightEntityCount(EntityId e) const noexcept;
|
||||||
void updateAnimations();
|
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(
|
bool setMorphAnimationBuffer(
|
||||||
EntityId entity,
|
EntityId entityId,
|
||||||
const char* entityName,
|
const char* entityName,
|
||||||
const float* const morphData,
|
const float* const morphData,
|
||||||
int numMorphWeights,
|
int numMorphWeights,
|
||||||
int numFrames,
|
int numFrames,
|
||||||
float frameLengthInMs);
|
float frameLengthInMs);
|
||||||
void setMorphTargetWeights(EntityId entityId, const char* const entityName, const float* const weights, int count);
|
void setMorphTargetWeights(EntityId entityId, const char* const entityName, const float* const weights, int count);
|
||||||
|
|
||||||
|
bool setBoneAnimationBuffer(
|
||||||
|
EntityId entity,
|
||||||
|
const float* const frameData,
|
||||||
|
int numFrames,
|
||||||
|
int numBones,
|
||||||
|
const char** const boneNames,
|
||||||
|
const char** const meshName,
|
||||||
|
int numMeshTargets,
|
||||||
|
float frameLengthInMs);
|
||||||
void playAnimation(EntityId e, int index, bool loop, bool reverse);
|
void playAnimation(EntityId e, int index, bool loop, bool reverse);
|
||||||
void stopAnimation(EntityId e, int index);
|
void stopAnimation(EntityId e, int index);
|
||||||
void setMorphTargetWeights(const char* const entityName, float *weights, int count);
|
void setMorphTargetWeights(const char* const entityName, float *weights, int count);
|
||||||
@@ -76,18 +78,15 @@ namespace polyvox {
|
|||||||
vector<SceneAsset> _assets;
|
vector<SceneAsset> _assets;
|
||||||
tsl::robin_map<EntityId, int> _entityIdLookup;
|
tsl::robin_map<EntityId, int> _entityIdLookup;
|
||||||
|
|
||||||
void setBoneTransform(
|
|
||||||
FilamentInstance* instance,
|
|
||||||
vector<BoneAnimationData> animations,
|
|
||||||
int frameNumber
|
|
||||||
);
|
|
||||||
|
|
||||||
utils::Entity findEntityByName(
|
utils::Entity findEntityByName(
|
||||||
SceneAsset asset,
|
SceneAsset asset,
|
||||||
const char* entityName
|
const char* entityName
|
||||||
);
|
);
|
||||||
|
|
||||||
inline void updateTransform(SceneAsset asset);
|
inline void updateTransform(SceneAsset& asset);
|
||||||
|
|
||||||
|
inline void setBoneTransform(SceneAsset& asset, int frameNumber);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -65,29 +65,17 @@ bool set_morph_animation(
|
|||||||
int numFrames,
|
int numFrames,
|
||||||
float frameLengthInMs);
|
float frameLengthInMs);
|
||||||
|
|
||||||
void set_bone_animation(
|
void set_bone_animation(
|
||||||
void* assetManager,
|
void* assetManager,
|
||||||
EntityId asset,
|
EntityId asset,
|
||||||
int length,
|
|
||||||
const char** const boneNames,
|
|
||||||
const char** const meshNames,
|
|
||||||
const float* const frameData,
|
const float* const frameData,
|
||||||
int numFrames,
|
int numFrames,
|
||||||
|
int numBones,
|
||||||
|
const char** const boneNames,
|
||||||
|
const char** const meshName,
|
||||||
|
int numMeshTargets,
|
||||||
float frameLengthInMs);
|
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 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 set_animation_frame(void* assetManager, EntityId asset, int animationIndex, int animationFrame);
|
||||||
void stop_animation(void* assetManager, EntityId asset, int index);
|
void stop_animation(void* assetManager, EntityId asset, int index);
|
||||||
|
|||||||
@@ -27,17 +27,11 @@ extern "C" {
|
|||||||
//
|
//
|
||||||
struct ResourceBuffer {
|
struct ResourceBuffer {
|
||||||
#if defined(__cplusplus)
|
#if defined(__cplusplus)
|
||||||
ResourceBuffer(const void* data, const uint32_t size, const uint32_t id) : data(data), size(size), id(id) {};
|
ResourceBuffer(const void* const data, const uint32_t size, const uint32_t id) : data(data), size(size), id(id) {};
|
||||||
ResourceBuffer& operator=(ResourceBuffer other) {
|
|
||||||
data = other.data;
|
|
||||||
size = other.size;
|
|
||||||
id = other.id;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
const void* data;
|
const void * const data;
|
||||||
uint32_t size;
|
const uint32_t size;
|
||||||
uint32_t id;
|
const uint32_t id;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct ResourceBuffer ResourceBuffer;
|
typedef struct ResourceBuffer ResourceBuffer;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <filament/Renderer.h>
|
#include <filament/Renderer.h>
|
||||||
#include <filament/Scene.h>
|
#include <filament/Scene.h>
|
||||||
#include <filament/Texture.h>
|
#include <filament/Texture.h>
|
||||||
|
#include <filament/TransformManager.h>
|
||||||
|
|
||||||
#include <math/vec3.h>
|
#include <math/vec3.h>
|
||||||
#include <math/vec4.h>
|
#include <math/vec4.h>
|
||||||
@@ -21,7 +22,7 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#include "PolyvoxFilamentApi.h"
|
#include "PolyvoxFilamentApi.h"
|
||||||
}
|
}
|
||||||
|
template class std::vector<float>;
|
||||||
namespace polyvox {
|
namespace polyvox {
|
||||||
using namespace filament;
|
using namespace filament;
|
||||||
using namespace filament::gltfio;
|
using namespace filament::gltfio;
|
||||||
@@ -36,35 +37,6 @@ namespace polyvox {
|
|||||||
bool mReverse = false;
|
bool mReverse = false;
|
||||||
float mDuration = 0;
|
float mDuration = 0;
|
||||||
bool mAnimating = false;
|
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,22 @@ namespace polyvox {
|
|||||||
int mNumMorphWeights = 0;
|
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 {
|
struct BoneAnimationBuffer {
|
||||||
|
vector<utils::Entity> mMeshTargets;
|
||||||
|
vector<uint8_t> mBones;
|
||||||
|
vector<math::mat4f> mBaseTransforms;
|
||||||
|
// vector<math::float3> mBaseTranslations; // these are the base transforms for the bones we will animate; the translations/rotations in mFrameData will be relative to this.
|
||||||
|
// vector<math::quatf> mBaseRotations; // these are the base transforms for the bones we will animate; the translations/rotations in mFrameData will be relative to this.
|
||||||
|
// vector<math::float3> mBaseScales; // these are the base transforms for the bones we will animate; the translations/rotations in mFrameData will be relative to this.
|
||||||
|
size_t skinIndex = 0;
|
||||||
int mNumFrames = -1;
|
int mNumFrames = -1;
|
||||||
float mFrameLengthInMs = 0;
|
float mFrameLengthInMs = 0;
|
||||||
vector<BoneAnimationData> mAnimations;
|
vector<float> mFrameData;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SceneAsset {
|
struct SceneAsset {
|
||||||
@@ -129,12 +99,9 @@ namespace polyvox {
|
|||||||
|
|
||||||
mAnimations.resize(2 + mAnimator->getAnimationCount());
|
mAnimations.resize(2 + mAnimator->getAnimationCount());
|
||||||
|
|
||||||
for(int i=2; i < mAnimations.size(); i++) {
|
for(int i=0; i < mAnimations.size() - 2; i++) {
|
||||||
mAnimations[i].mDuration = mAnimator->getAnimationDuration(i-2);
|
mAnimations[i].mDuration = mAnimator->getAnimationDuration(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ namespace polyvox {
|
|||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FileMaterialProvider(Engine* engine, void* const data, size_t size) {
|
FileMaterialProvider(Engine* engine, const void* const data, const size_t size) {
|
||||||
_m = Material::Builder()
|
_m = Material::Builder()
|
||||||
.package(data, size)
|
.package(data, size)
|
||||||
.build(*engine);
|
.build(*engine);
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ A new flutter plugin project.
|
|||||||
'DEFINES_MODULE' => 'YES',
|
'DEFINES_MODULE' => 'YES',
|
||||||
'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386',
|
'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386',
|
||||||
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17",
|
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17",
|
||||||
'OTHER_CXXFLAGS' => '"--std=c++17" "-fmodules" "-fcxx-modules" "-stdlib=libc++" "-fvisibility=default" "$(inherited)"',
|
|
||||||
'OTHER_CFLAGS' => '"-fvisibility=default" "$(inherited)"',
|
'OTHER_CFLAGS' => '"-fvisibility=default" "$(inherited)"',
|
||||||
'USER_HEADER_SEARCH_PATHS' => '"${PODS_ROOT}/../.symlinks/plugins/polyvox_filament/ios/include" "${PODS_ROOT}/../.symlinks/plugins/polyvox_filament/ios/src" "${PODS_ROOT}/../.symlinks/plugins/polyvox_filament/ios/src/image" "${PODS_ROOT}/../.symlinks/plugins/polyvox_filament/ios/src/shaders" "$(inherited)"',
|
'USER_HEADER_SEARCH_PATHS' => '"${PODS_ROOT}/../.symlinks/plugins/polyvox_filament/ios/include" "${PODS_ROOT}/../.symlinks/plugins/polyvox_filament/ios/src" "${PODS_ROOT}/../.symlinks/plugins/polyvox_filament/ios/src/image" "${PODS_ROOT}/../.symlinks/plugins/polyvox_filament/ios/src/shaders" "$(inherited)"',
|
||||||
'ALWAYS_SEARCH_USER_PATHS' => 'YES',
|
'ALWAYS_SEARCH_USER_PATHS' => 'YES',
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
#include "AssetManager.hpp"
|
#include "AssetManager.hpp"
|
||||||
#include "Log.hpp"
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <filament/Engine.h>
|
#include <filament/Engine.h>
|
||||||
#include <filament/TransformManager.h>
|
#include <filament/TransformManager.h>
|
||||||
#include <filament/Texture.h>
|
#include <filament/Texture.h>
|
||||||
#include <filament/RenderableManager.h>
|
#include <filament/RenderableManager.h>
|
||||||
|
|
||||||
|
|
||||||
#include <gltfio/Animator.h>
|
#include <gltfio/Animator.h>
|
||||||
#include <gltfio/AssetLoader.h>
|
#include <gltfio/AssetLoader.h>
|
||||||
#include <gltfio/FilamentAsset.h>
|
#include <gltfio/FilamentAsset.h>
|
||||||
#include <gltfio/ResourceLoader.h>
|
#include <gltfio/ResourceLoader.h>
|
||||||
#include <gltfio/TextureProvider.h>
|
#include <gltfio/TextureProvider.h>
|
||||||
|
#include <gltfio/math.h>
|
||||||
|
|
||||||
#include <imageio/ImageDecoder.h>
|
#include <imageio/ImageDecoder.h>
|
||||||
|
|
||||||
@@ -52,8 +53,13 @@ AssetManager::AssetManager(ResourceLoaderWrapper* resourceLoaderWrapper,
|
|||||||
_ubershaderProvider = gltfio::createUbershaderProvider(
|
_ubershaderProvider = gltfio::createUbershaderProvider(
|
||||||
_engine, UBERARCHIVE_DEFAULT_DATA, UBERARCHIVE_DEFAULT_SIZE);
|
_engine, UBERARCHIVE_DEFAULT_DATA, UBERARCHIVE_DEFAULT_SIZE);
|
||||||
EntityManager &em = EntityManager::get();
|
EntityManager &em = EntityManager::get();
|
||||||
_assetLoader = AssetLoader::create({_engine, _ubershaderProvider, _ncm, &em });
|
|
||||||
_unlitProvider = new UnlitMaterialProvider(_engine);
|
_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/png", _stbDecoder);
|
||||||
_gltfResourceLoader->addTextureProvider("image/jpeg", _stbDecoder);
|
_gltfResourceLoader->addTextureProvider("image/jpeg", _stbDecoder);
|
||||||
@@ -213,13 +219,42 @@ void AssetManager::updateAnimations() {
|
|||||||
RenderableManager &rm = _engine->getRenderableManager();
|
RenderableManager &rm = _engine->getRenderableManager();
|
||||||
|
|
||||||
for (auto& asset : _assets) {
|
for (auto& asset : _assets) {
|
||||||
|
|
||||||
if(!asset.mAnimating) {
|
if(!asset.mAnimating) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
asset.mAnimating = false;
|
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;
|
||||||
|
} 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
|
// dynamically constructed morph animation
|
||||||
AnimationStatus& morphAnimation = asset.mAnimations[0];
|
AnimationStatus& morphAnimation = asset.mAnimations[asset.mAnimations.size() - 2];
|
||||||
|
|
||||||
if(morphAnimation.mAnimating) {
|
if(morphAnimation.mAnimating) {
|
||||||
|
|
||||||
@@ -254,74 +289,79 @@ void AssetManager::updateAnimations() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// // bone animation
|
// bone animation
|
||||||
// AnimationStatus boneAnimation = asset.mAnimations[1];
|
AnimationStatus boneAnimation = asset.mAnimations[asset.mAnimations.size() - 1];
|
||||||
// elapsed = (now - boneAnimation.mStart).count();
|
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;
|
||||||
|
} else {
|
||||||
|
|
||||||
// if(elapsed >= boneAnimation.mDuration) {
|
asset.mAnimating = true;
|
||||||
// if(boneAnimation.mLoop) {
|
int frameNumber = static_cast<int>(elapsed * 1000.0f / asset.mBoneAnimationBuffer.mFrameLengthInMs) % lengthInFrames;
|
||||||
// boneAnimation.mStart = now;
|
|
||||||
// if(boneAnimation.mReverse) {
|
|
||||||
// boneAnimation.mFrameNumber = lengthInFrames;
|
|
||||||
// }
|
|
||||||
// asset.mAnimating = true;
|
|
||||||
// } else {
|
|
||||||
// boneAnimation.mStart = time_point_t::max();
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// asset.mAnimating = true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// frameNumber = static_cast<int>(elapsed / asset.mBoneAnimationBuffer.mFrameLengthInMs);
|
// offset from the end if reverse
|
||||||
// if(frameNumber < lengthInFrames) {
|
if(boneAnimation.mReverse) {
|
||||||
// if(boneAnimation.mReverse) {
|
frameNumber = lengthInFrames - frameNumber;
|
||||||
// frameNumber = lengthInFrames - frameNumber;
|
}
|
||||||
// }
|
|
||||||
// boneAnimation.mFrameNumber = frameNumber;
|
|
||||||
// setBoneTransform(
|
|
||||||
// asset.mAsset->getInstance(),
|
|
||||||
// asset.mBoneAnimationBuffer.mAnimations,
|
|
||||||
// frameNumber
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// GLTF animations
|
setBoneTransform(
|
||||||
|
asset,
|
||||||
int j = -1;
|
frameNumber
|
||||||
for(AnimationStatus& anim : asset.mAnimations) {
|
);
|
||||||
j++;
|
}
|
||||||
if(j < 2) {
|
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;
|
||||||
|
|
||||||
|
for(int i = 0; i < asset.mBoneAnimationBuffer.mBones.size(); i++) {
|
||||||
|
auto mBoneIndex = asset.mBoneAnimationBuffer.mBones[i];
|
||||||
|
auto frameDataOffset = (frameNumber * asset.mBoneAnimationBuffer.mBones.size() * 7) + (i * 7);
|
||||||
|
|
||||||
|
utils::Entity joint = filamentInstance->getJointsAt(skinIndex)[mBoneIndex];
|
||||||
|
if(joint.isNull()) {
|
||||||
|
Log("ERROR : joint not found");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!anim.mAnimating) {
|
vector<float>& fd = asset.mBoneAnimationBuffer.mFrameData;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto elapsed = float(std::chrono::duration_cast<std::chrono::milliseconds>(now - anim.mStart).count()) / 1000.0f;
|
math::mat4f localTransform(math::quatf {
|
||||||
|
fd[frameDataOffset+3],
|
||||||
|
fd[frameDataOffset+4],
|
||||||
|
fd[frameDataOffset+5],
|
||||||
|
fd[frameDataOffset+6],
|
||||||
|
});
|
||||||
|
|
||||||
|
auto jointInstance = transformManager.getInstance(joint);
|
||||||
|
|
||||||
if(anim.mLoop || elapsed < anim.mDuration) {
|
auto xform = asset.mBoneAnimationBuffer.mBaseTransforms[i];
|
||||||
asset.mAnimator->applyAnimation(j-2, elapsed);
|
|
||||||
asset.mAnimating = true;
|
transformManager.setTransform(jointInstance, xform * localTransform);
|
||||||
} 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(asset.mAnimating) {
|
|
||||||
if(!asset.mAnimator) {
|
|
||||||
Log("WARNING");
|
|
||||||
} else {
|
|
||||||
asset.mAnimator->updateBoneMatrices();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,7 +459,7 @@ bool AssetManager::setMorphAnimationBuffer(
|
|||||||
asset.mMorphAnimationBuffer.mFrameLengthInMs = frameLengthInMs;
|
asset.mMorphAnimationBuffer.mFrameLengthInMs = frameLengthInMs;
|
||||||
asset.mMorphAnimationBuffer.mNumMorphWeights = numMorphWeights;
|
asset.mMorphAnimationBuffer.mNumMorphWeights = numMorphWeights;
|
||||||
|
|
||||||
AnimationStatus& animation = asset.mAnimations[0];
|
AnimationStatus& animation = asset.mAnimations[asset.mAnimations.size() - 2];
|
||||||
animation.mDuration = (frameLengthInMs * numFrames) / 1000.0f;
|
animation.mDuration = (frameLengthInMs * numFrames) / 1000.0f;
|
||||||
animation.mStart = high_resolution_clock::now();
|
animation.mStart = high_resolution_clock::now();
|
||||||
animation.mAnimating = true;
|
animation.mAnimating = true;
|
||||||
@@ -432,21 +472,23 @@ bool AssetManager::setMorphAnimationBuffer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool AssetManager::setBoneAnimationBuffer(
|
bool AssetManager::setBoneAnimationBuffer(
|
||||||
EntityId entity,
|
EntityId entityId,
|
||||||
int length,
|
|
||||||
const char** const boneNames,
|
|
||||||
const char** const meshNames,
|
|
||||||
const float* const frameData,
|
const float* const frameData,
|
||||||
int numFrames,
|
int numFrames,
|
||||||
|
int numBones,
|
||||||
|
const char** const boneNames,
|
||||||
|
const char** const meshNames,
|
||||||
|
int numMeshTargets,
|
||||||
float frameLengthInMs) {
|
float frameLengthInMs) {
|
||||||
|
|
||||||
const auto& pos = _entityIdLookup.find(entity);
|
|
||||||
|
|
||||||
|
const auto& pos = _entityIdLookup.find(entityId);
|
||||||
if(pos == _entityIdLookup.end()) {
|
if(pos == _entityIdLookup.end()) {
|
||||||
Log("ERROR: asset not found for entity.");
|
Log("ERROR: asset not found for entity.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto& asset = _assets[pos->second];
|
auto& asset = _assets[pos->second];
|
||||||
|
|
||||||
auto filamentInstance = asset.mAsset->getInstance();
|
auto filamentInstance = asset.mAsset->getInstance();
|
||||||
|
|
||||||
size_t skinCount = filamentInstance->getSkinCount();
|
size_t skinCount = filamentInstance->getSkinCount();
|
||||||
@@ -455,95 +497,82 @@ bool AssetManager::setBoneAnimationBuffer(
|
|||||||
Log("WARNING - skin count > 1 not currently implemented. This will probably not work");
|
Log("WARNING - skin count > 1 not currently implemented. This will probably not work");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TransformManager &transformManager = _engine->getTransformManager();
|
||||||
|
|
||||||
int skinIndex = 0;
|
int skinIndex = 0;
|
||||||
const utils::Entity* joints = filamentInstance->getJointsAt(skinIndex);
|
const utils::Entity* joints = filamentInstance->getJointsAt(skinIndex);
|
||||||
size_t numJoints = filamentInstance->getJointCountAt(skinIndex);
|
size_t numJoints = filamentInstance->getJointCountAt(skinIndex);
|
||||||
vector<int> boneIndices;
|
|
||||||
for(int i = 0; i < length; i++) {
|
BoneAnimationBuffer& animationBuffer = asset.mBoneAnimationBuffer;
|
||||||
|
|
||||||
|
// if an animation has already been set, reset the transform for the respective bones
|
||||||
|
for(int i = 0; i < animationBuffer.mBones.size(); i++) {
|
||||||
|
auto boneIndex = animationBuffer.mBones[i];
|
||||||
|
auto jointInstance = transformManager.getInstance(joints[boneIndex]);
|
||||||
|
transformManager.setTransform(jointInstance, animationBuffer.mBaseTransforms[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
asset.mAnimator->resetBoneMatrices();
|
||||||
|
|
||||||
|
animationBuffer.mBones.resize(numBones);
|
||||||
|
animationBuffer.mBaseTransforms.resize(numBones);
|
||||||
|
|
||||||
|
for(int i = 0; i < numBones; i++) {
|
||||||
|
Log("Bone %s", boneNames[i]);
|
||||||
for(int j = 0; j < numJoints; j++) {
|
for(int j = 0; j < numJoints; j++) {
|
||||||
const char* jointName = _ncm->getName(_ncm->getInstance(joints[j]));
|
const char* jointName = _ncm->getName(_ncm->getInstance(joints[j]));
|
||||||
if(strcmp(jointName, boneNames[i]) == 0) {
|
if(strcmp(jointName, boneNames[i]) == 0) {
|
||||||
boneIndices.push_back(j);
|
auto jointInstance = transformManager.getInstance(joints[j]);
|
||||||
|
// auto currentXform = ;
|
||||||
|
auto baseTransform = transformManager.getTransform(jointInstance); // inverse(filamentInstance->getInverseBindMatricesAt(skinIndex)[j]);
|
||||||
|
animationBuffer.mBaseTransforms[i] = baseTransform;
|
||||||
|
animationBuffer.mBones[i] = j;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(boneIndices.size() != length) {
|
if(animationBuffer.mBones.size() != numBones) {
|
||||||
Log("Failed to find one or more bone indices");
|
Log("Failed to find one or more bone indices");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
asset.mBoneAnimationBuffer.mAnimations.clear();
|
|
||||||
|
|
||||||
for(int i = 0; i < length; i++) {
|
animationBuffer.mFrameData.clear();
|
||||||
BoneAnimationData boneAnimationData;
|
// 7 == locX, locY, locZ, rotW, rotX, rotY, rotZ
|
||||||
boneAnimationData.mBoneIndex = boneIndices[i];
|
animationBuffer.mFrameData.resize(numFrames * numBones * 7);
|
||||||
|
animationBuffer.mFrameData.insert(
|
||||||
|
animationBuffer.mFrameData.begin(),
|
||||||
|
frameData,
|
||||||
|
frameData + numFrames * numBones * 7
|
||||||
|
);
|
||||||
|
|
||||||
|
Log("%d frames for %d bones", numFrames, numBones);
|
||||||
|
|
||||||
|
animationBuffer.mFrameLengthInMs = frameLengthInMs;
|
||||||
|
animationBuffer.mNumFrames = numFrames;
|
||||||
|
|
||||||
|
animationBuffer.mMeshTargets.clear();
|
||||||
|
for(int i = 0; i < numMeshTargets; i++) {
|
||||||
auto entity = findEntityByName(asset, meshNames[i]);
|
auto entity = findEntityByName(asset, meshNames[i]);
|
||||||
|
|
||||||
if(!entity) {
|
if(!entity) {
|
||||||
Log("Mesh target %s for bone animation could not be found", meshNames[i]);
|
Log("Mesh target %s for bone animation could not be found", meshNames[i]);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
Log("Added mesh target %s", meshNames[i]);
|
||||||
boneAnimationData.mMeshTarget = entity;
|
animationBuffer.mMeshTargets.push_back(entity);
|
||||||
|
|
||||||
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)]
|
|
||||||
);
|
|
||||||
|
|
||||||
asset.mBoneAnimationBuffer.mAnimations.push_back(boneAnimationData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
return true;
|
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) {
|
void AssetManager::playAnimation(EntityId e, int index, bool loop, bool reverse) {
|
||||||
const auto& pos = _entityIdLookup.find(e);
|
const auto& pos = _entityIdLookup.find(e);
|
||||||
@@ -552,11 +581,12 @@ void AssetManager::playAnimation(EntityId e, int index, bool loop, bool reverse)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto& asset = _assets[pos->second];
|
auto& asset = _assets[pos->second];
|
||||||
|
Log("Playing animation at %d", index);
|
||||||
asset.mAnimations[index+2].mAnimating = true;
|
|
||||||
asset.mAnimations[index+2].mStart = std::chrono::high_resolution_clock::now();
|
asset.mAnimations[index].mStart = std::chrono::high_resolution_clock::now();
|
||||||
asset.mAnimations[index+2].mLoop = loop;
|
asset.mAnimations[index].mLoop = loop;
|
||||||
asset.mAnimations[index+2].mReverse = reverse;
|
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);
|
// 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;
|
asset.mAnimating = true;
|
||||||
}
|
}
|
||||||
@@ -568,7 +598,7 @@ void AssetManager::stopAnimation(EntityId entityId, int index) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto& asset = _assets[pos->second];
|
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) {
|
void AssetManager::loadTexture(EntityId entity, const char* resourcePath, int renderableIndex) {
|
||||||
@@ -727,7 +757,7 @@ void AssetManager::transformToUnitCube(EntityId entity) {
|
|||||||
tm.setTransform(tm.getInstance(inst->getRoot()), transform);
|
tm.setTransform(tm.getInstance(inst->getRoot()), transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetManager::updateTransform(SceneAsset asset) {
|
void AssetManager::updateTransform(SceneAsset& asset) {
|
||||||
auto &tm = _engine->getTransformManager();
|
auto &tm = _engine->getTransformManager();
|
||||||
auto transform =
|
auto transform =
|
||||||
asset.mPosition * asset.mRotation * math::mat4f::scaling(asset.mScale);
|
asset.mPosition * asset.mRotation * math::mat4f::scaling(asset.mScale);
|
||||||
@@ -809,3 +839,40 @@ size_t AssetManager::getLightEntityCount(EntityId entity) const noexcept {
|
|||||||
|
|
||||||
|
|
||||||
} // namespace polyvox
|
} // namespace polyvox
|
||||||
|
|
||||||
|
|
||||||
|
// auto& inverseBindMatrix = filamentInstance->getInverseBindMatricesAt(skinIndex)[mBoneIndex];
|
||||||
|
|
||||||
|
// auto globalJointTransform = transformManager.getWorldTransform(jointInstance);
|
||||||
|
|
||||||
|
// for(auto& target : asset.mBoneAnimationBuffer.mMeshTargets) {
|
||||||
|
|
||||||
|
// auto inverseGlobalTransform = inverse(
|
||||||
|
// transformManager.getWorldTransform(
|
||||||
|
// transformManager.getInstance(target)
|
||||||
|
// )
|
||||||
|
// );
|
||||||
|
|
||||||
|
// auto boneTransform = inverseGlobalTransform * globalJointTransform * localTransform * inverseBindMatrix;
|
||||||
|
// auto renderable = rm.getInstance(target);
|
||||||
|
// rm.setBones(
|
||||||
|
// renderable,
|
||||||
|
// &boneTransform,
|
||||||
|
// 1,
|
||||||
|
// mBoneIndex
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 1.0f, 0.0f, 0.0f, 0.0f,
|
||||||
|
// 0.0f, 0.0f, 1.0f, 0.0f,
|
||||||
|
// 0.0f, -1.0f, 0.0f, 0.0f,
|
||||||
|
// 0.0f, 0.0f, 0.0f, 1.0f
|
||||||
|
// };
|
||||||
|
// Log("TRANSFORM");
|
||||||
|
// Log("%f %f %f %f", localTransform[0][0], localTransform[1][0], localTransform[2][0], localTransform[3][0] ) ;
|
||||||
|
// Log("%f %f %f %f", localTransform[0][1], localTransform[1][1], localTransform[2][1], localTransform[3][1] ) ;
|
||||||
|
// Log("%f %f %f %f", localTransform[0][2], localTransform[1][2], localTransform[2][2], localTransform[3][2] ) ;
|
||||||
|
// Log("%f %f %f %f", localTransform[0][3], localTransform[1][3], localTransform[2][3], localTransform[3][3] ) ;
|
||||||
|
// transformManager.getTransform(jointInstance);
|
||||||
@@ -788,7 +788,7 @@ void FilamentViewer::render(uint64_t frameTimeInNanos) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(_frameCount == 60) {
|
if(_frameCount == 60) {
|
||||||
//Log("1 sec average for asset animation update %f", _elapsed);
|
// Log("1 sec average for asset animation update %f", _elapsed / 60);
|
||||||
_elapsed = 0;
|
_elapsed = 0;
|
||||||
_frameCount = 0;
|
_frameCount = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -378,20 +378,22 @@ extern "C" {
|
|||||||
FLUTTER_PLUGIN_EXPORT void set_bone_animation(
|
FLUTTER_PLUGIN_EXPORT void set_bone_animation(
|
||||||
void* assetManager,
|
void* assetManager,
|
||||||
EntityId asset,
|
EntityId asset,
|
||||||
int length,
|
|
||||||
const char** const boneNames,
|
|
||||||
const char** const meshNames,
|
|
||||||
const float* const frameData,
|
const float* const frameData,
|
||||||
int numFrames,
|
int numFrames,
|
||||||
|
int numBones,
|
||||||
|
const char** const boneNames,
|
||||||
|
const char** const meshNames,
|
||||||
|
int numMeshTargets,
|
||||||
float frameLengthInMs) {
|
float frameLengthInMs) {
|
||||||
//std::packaged_task<void()> lambda([=]() mutable {
|
//std::packaged_task<void()> lambda([=]() mutable {
|
||||||
((AssetManager*)assetManager)->setBoneAnimationBuffer(
|
((AssetManager*)assetManager)->setBoneAnimationBuffer(
|
||||||
asset,
|
asset,
|
||||||
length,
|
frameData,
|
||||||
|
numFrames,
|
||||||
|
numBones,
|
||||||
boneNames,
|
boneNames,
|
||||||
meshNames,
|
meshNames,
|
||||||
frameData,
|
numMeshTargets,
|
||||||
numFrames,
|
|
||||||
frameLengthInMs
|
frameLengthInMs
|
||||||
);
|
);
|
||||||
//});
|
//});
|
||||||
@@ -437,7 +439,6 @@ extern "C" {
|
|||||||
bool reverse) {
|
bool reverse) {
|
||||||
|
|
||||||
//std::packaged_task<void()> lambda([=]() mutable {
|
//std::packaged_task<void()> lambda([=]() mutable {
|
||||||
std::cout << "Playing animation" << std::endl;
|
|
||||||
((AssetManager*)assetManager)->playAnimation(asset, index, loop, reverse);
|
((AssetManager*)assetManager)->playAnimation(asset, index, loop, reverse);
|
||||||
//});
|
//});
|
||||||
// auto fut = _tp->add_task(lambda);
|
// auto fut = _tp->add_task(lambda);
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import 'package:polyvox_filament/animations/animations.dart';
|
import 'package:polyvox_filament/animations/bone_animation_data.dart';
|
||||||
|
import 'package:polyvox_filament/animations/morph_animation_data.dart';
|
||||||
import 'package:polyvox_filament/filament_controller.dart';
|
import 'package:polyvox_filament/filament_controller.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:vector_math/vector_math.dart';
|
import 'package:vector_math/vector_math.dart';
|
||||||
|
|
||||||
class AnimationBuilder {
|
class AnimationBuilder {
|
||||||
final FilamentController controller;
|
final FilamentController controller;
|
||||||
DartBoneAnimation? dartBoneAnimation;
|
// BoneAnimationData? BoneAnimationData;
|
||||||
double _frameLengthInMs = 0;
|
double _frameLengthInMs = 0;
|
||||||
double _duration = 0;
|
double _duration = 0;
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ class AnimationBuilder {
|
|||||||
double? _interpMorphStartValue;
|
double? _interpMorphStartValue;
|
||||||
double? _interpMorphEndValue;
|
double? _interpMorphEndValue;
|
||||||
|
|
||||||
List<DartBoneAnimation>? _dartBoneAnimations = null;
|
// List<BoneAnimationData>? _BoneAnimationDatas = null;
|
||||||
|
|
||||||
FilamentEntity asset;
|
FilamentEntity asset;
|
||||||
String meshName;
|
String meshName;
|
||||||
@@ -53,11 +53,11 @@ class AnimationBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var morphAnimation =
|
var morphAnimation =
|
||||||
MorphAnimation(meshName, morphData, morphNames, _frameLengthInMs);
|
MorphAnimationData(meshName, morphData, morphNames, _frameLengthInMs);
|
||||||
print("SETTING!");
|
|
||||||
controller.setMorphAnimation(asset, morphAnimation);
|
controller.setMorphAnimationData(asset, morphAnimation);
|
||||||
// return Tuple2<MorphAnimation, List<DartBoneAnimation>>(
|
// return Tuple2<MorphAnimationData, List<BoneAnimationData>>(
|
||||||
// morphAnimation, _dartBoneAnimations!);
|
// morphAnimation, _BoneAnimationDatas!);
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimationBuilder setDuration(double secs) {
|
AnimationBuilder setDuration(double secs) {
|
||||||
@@ -118,15 +118,40 @@ class AnimationBuilder {
|
|||||||
|
|
||||||
// var boneFrameData = BoneTransformFrameData(translations, quats);
|
// var boneFrameData = BoneTransformFrameData(translations, quats);
|
||||||
|
|
||||||
// _DartBoneAnimations ??= <DartBoneAnimation>[];
|
// _BoneAnimationDatas ??= <BoneAnimationData>[];
|
||||||
|
|
||||||
// var frameData = List<List<double>>.generate(
|
// var frameData = List<List<double>>.generate(
|
||||||
// numFrames, (index) => boneFrameData.getFrameData(index).toList());
|
// numFrames, (index) => boneFrameData.getFrameData(index).toList());
|
||||||
|
|
||||||
// var animData = Float32List.fromList(frameData.expand((x) => x).toList());
|
// var animData = Float32List.fromList(frameData.expand((x) => x).toList());
|
||||||
|
|
||||||
// _DartBoneAnimations!.add(DartDartBoneAnimation([boneName], [meshName], animData));
|
// _BoneAnimationDatas!.add(DartBoneAnimationData([boneName], [meshName], animData));
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BoneTransformFrameData {
|
||||||
|
final List<Vector3> translations;
|
||||||
|
final List<Quaternion> quaternions;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// The length of [translations] and [quaternions] must be the same;
|
||||||
|
/// each entry represents the Vec3/Quaternion for the given frame.
|
||||||
|
///
|
||||||
|
BoneTransformFrameData(this.translations, this.quaternions) {
|
||||||
|
if (translations.length != quaternions.length) {
|
||||||
|
throw Exception("Length of translation/quaternion frames must match");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterable<double> getFrameData(int frame) sync* {
|
||||||
|
yield translations[frame].x;
|
||||||
|
yield translations[frame].y;
|
||||||
|
yield translations[frame].z;
|
||||||
|
yield quaternions[frame].x;
|
||||||
|
yield quaternions[frame].y;
|
||||||
|
yield quaternions[frame].z;
|
||||||
|
yield quaternions[frame].w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:vector_math/vector_math.dart';
|
|
||||||
|
|
||||||
class DartBoneAnimation {
|
|
||||||
final String boneName;
|
|
||||||
final String meshName;
|
|
||||||
final Float32List frameData;
|
|
||||||
double frameLengthInMs;
|
|
||||||
DartBoneAnimation(
|
|
||||||
this.boneName, this.meshName, this.frameData, this.frameLengthInMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Frame weights for the morph targets specified in [morphNames] attached to mesh [meshName].
|
|
||||||
// morphData is laid out as numFrames x numMorphTargets
|
|
||||||
// where the weights are in the same order as [morphNames].
|
|
||||||
// [morphNames] must be provided but is not used directly; this is only used to check that the eventual asset being animated contains the same morph targets in the same order.
|
|
||||||
//
|
|
||||||
class MorphAnimation {
|
|
||||||
final String meshName;
|
|
||||||
final List<String> morphNames;
|
|
||||||
|
|
||||||
final Float32List data;
|
|
||||||
|
|
||||||
MorphAnimation(
|
|
||||||
this.meshName, this.data, this.morphNames, this.frameLengthInMs) {
|
|
||||||
assert(data.length == morphNames.length * numFrames);
|
|
||||||
}
|
|
||||||
|
|
||||||
int get numMorphWeights => morphNames.length;
|
|
||||||
|
|
||||||
int get numFrames => data.length ~/ numMorphWeights;
|
|
||||||
|
|
||||||
final double frameLengthInMs;
|
|
||||||
}
|
|
||||||
|
|
||||||
class BoneTransformFrameData {
|
|
||||||
final List<Vector3> translations;
|
|
||||||
final List<Quaternion> quaternions;
|
|
||||||
|
|
||||||
///
|
|
||||||
/// The length of [translations] and [quaternions] must be the same;
|
|
||||||
/// each entry represents the Vec3/Quaternion for the given frame.
|
|
||||||
///
|
|
||||||
BoneTransformFrameData(this.translations, this.quaternions) {
|
|
||||||
if (translations.length != quaternions.length) {
|
|
||||||
throw Exception("Length of translation/quaternion frames must match");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterable<double> getFrameData(int frame) sync* {
|
|
||||||
yield translations[frame].x;
|
|
||||||
yield translations[frame].y;
|
|
||||||
yield translations[frame].z;
|
|
||||||
yield quaternions[frame].x;
|
|
||||||
yield quaternions[frame].y;
|
|
||||||
yield quaternions[frame].z;
|
|
||||||
yield quaternions[frame].w;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
17
lib/animations/bone_animation_data.dart
Normal file
17
lib/animations/bone_animation_data.dart
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
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;
|
||||||
|
final List<String> meshNames;
|
||||||
|
final Float32List frameData;
|
||||||
|
double frameLengthInMs;
|
||||||
|
BoneAnimationData(
|
||||||
|
this.boneName, this.meshNames, this.frameData, this.frameLengthInMs);
|
||||||
|
}
|
||||||
86
lib/animations/bone_driver.dart
Normal file
86
lib/animations/bone_driver.dart
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:vector_math/vector_math.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:vector_math/vector_math.dart';
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Some animation data may be specified as blendshape weights (say, between -1 and 1)
|
||||||
|
/// but at runtime we want to retarget this to drive a bone translation/rotation (say, between -pi/2 and pi/2).
|
||||||
|
/// A [BoneDriver] is our mechanism for translating the former to the latter, containing:
|
||||||
|
/// 1) a blendshape name
|
||||||
|
/// 2) a bone name
|
||||||
|
/// 3) min/max translation values (corresponding to -1/1 on the blendshape), and
|
||||||
|
/// 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 Map<String, Transformation>
|
||||||
|
transformations; // maps a blendshape key to a Transformation
|
||||||
|
|
||||||
|
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(
|
||||||
|
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;
|
||||||
|
rotation.w = 1;
|
||||||
|
|
||||||
|
return rotation;
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
if (frameNum == 0) {
|
||||||
|
print(rotations);
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = rotations.fold(
|
||||||
|
rotations.first, (Quaternion a, Quaternion b) => a + b);
|
||||||
|
result.w = 1;
|
||||||
|
print("RESULT $result");
|
||||||
|
yield result;
|
||||||
|
// .normalized();
|
||||||
|
// 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);
|
||||||
129
lib/animations/csv_animation.dart
Normal file
129
lib/animations/csv_animation.dart
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
import 'package:polyvox_filament/animations/bone_animation_data.dart';
|
||||||
|
import 'package:polyvox_filament/animations/bone_driver.dart';
|
||||||
|
import 'package:polyvox_filament/animations/morph_animation_data.dart';
|
||||||
|
import 'package:vector_math/vector_math.dart';
|
||||||
|
|
||||||
|
///
|
||||||
|
/// A class for loading animation data from a single CSV and allocating between morph/bone animation with help.
|
||||||
|
///
|
||||||
|
class DynamicAnimation {
|
||||||
|
final MorphAnimationData? morphAnimation;
|
||||||
|
final List<BoneAnimationData> boneAnimation;
|
||||||
|
|
||||||
|
factory DynamicAnimation.load(String? meshName, String csvPath,
|
||||||
|
{List<BoneDriver>? boneDrivers,
|
||||||
|
List<String>? boneMeshes,
|
||||||
|
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 ?? "NULL", llf.item2, morphNames, frameLengthInMs);
|
||||||
|
|
||||||
|
final boneAnimations = <BoneAnimationData>[];
|
||||||
|
|
||||||
|
// if applicable, load the bone driver config
|
||||||
|
if (boneDriverConfigPath != null) {
|
||||||
|
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) {
|
||||||
|
// collect the frame data for the blendshapes that this driver uses
|
||||||
|
var morphData = driver.transformations
|
||||||
|
.map((String blendshape, Transformation transformation) {
|
||||||
|
return MapEntry(
|
||||||
|
blendshape, morphAnimationData.getData(blendshape).toList());
|
||||||
|
});
|
||||||
|
|
||||||
|
// apply the driver to the frame data
|
||||||
|
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, boneMeshes!, transformedF, frameLengthInMs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DynamicAnimation(morphAnimationData, boneAnimations);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Float32List _quaternionToFloatList(List<Quaternion> quats) {
|
||||||
|
var data = Float32List(quats.length * 7);
|
||||||
|
int i = 0;
|
||||||
|
for (var quat in quats) {
|
||||||
|
data.setRange(i, i + 7, [0, 0, 0, quat.w, quat.x, quat.y, quat.z]);
|
||||||
|
i += 7;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
DynamicAnimation(this.morphAnimation, this.boneAnimation);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Load visemes fom a CSV file formatted according to the following header:
|
||||||
|
/// "Timecode,BlendShapeCount,EyeBlinkLeft,EyeLookDownLeft,EyeLookInLeft,EyeLookOutLeft,EyeLookUpLeft,EyeSquintLeft,EyeWideLeft,EyeBlinkRight,EyeLookDownRight,EyeLookInRight,EyeLookOutRight,EyeLookUpRight,EyeSquintRight,EyeWideRight,JawForward,JawRight,JawLeft,JawOpen,MouthClose,MouthFunnel,MouthPucker,MouthRight,MouthLeft,MouthSmileLeft,MouthSmileRight,MouthFrownLeft,MouthFrownRight,MouthDimpleLeft,MouthDimpleRight,MouthStretchLeft,MouthStretchRight,MouthRollLower,MouthRollUpper,MouthShrugLower,MouthShrugUpper,MouthPressLeft,MouthPressRight,MouthLowerDownLeft,MouthLowerDownRight,MouthUpperUpLeft,MouthUpperUpRight,BrowDownLeft,BrowDownRight,BrowInnerUp,BrowOuterUpLeft,BrowOuterUpRight,CheekPuff,CheekSquintLeft,CheekSquintRight,NoseSneerLeft,NoseSneerRight,TongueOut,HeadYaw,HeadPitch,HeadRoll,LeftEyeYaw,LeftEyePitch,LeftEyeRoll,RightEyeYaw,RightEyePitch,RightEyeRoll"
|
||||||
|
/// Returns only those specified by [targetNames].
|
||||||
|
///
|
||||||
|
static Tuple2<List<String>, Float32List> _loadLiveLinkFaceCSV(String path) {
|
||||||
|
final data = File(path)
|
||||||
|
.readAsLinesSync()
|
||||||
|
.where((l) => l.length > 1)
|
||||||
|
.map((l) => l.split(","));
|
||||||
|
|
||||||
|
final header = data.first;
|
||||||
|
final numBlendShapes = header.length - 2;
|
||||||
|
|
||||||
|
final _data = <double>[];
|
||||||
|
|
||||||
|
for (var frame in data.skip(1)) {
|
||||||
|
int numFrameWeights = frame.length - 2;
|
||||||
|
// 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])) {
|
||||||
|
_data.addAll(List<double>.filled(numBlendShapes, 0.0));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Now, we check that the actual number of weight columns matches the header
|
||||||
|
// we ignore the "BlendShapeCount" column (and just count the number of columns)
|
||||||
|
// This is due to some legacy issues where we generated CSVs that had 61 weight columns, but accidentally left the "BlendShapeCount" column at 55
|
||||||
|
// This is probably fine as we always have either zero weights (handled above), or all weights (handled below).
|
||||||
|
// In other words, if this throws, we have a serious problem.
|
||||||
|
if (numFrameWeights != numBlendShapes) {
|
||||||
|
throw Exception(
|
||||||
|
"Malformed CSV, header specifies ${numBlendShapes} columns but frame specified only $numFrameWeights weights");
|
||||||
|
}
|
||||||
|
|
||||||
|
_data.addAll(frame
|
||||||
|
.skip(2)
|
||||||
|
.map((weight) => double.parse(weight))
|
||||||
|
.cast<double>()
|
||||||
|
.toList());
|
||||||
|
}
|
||||||
|
return Tuple2(header.skip(2).toList(), Float32List.fromList(_data));
|
||||||
|
}
|
||||||
|
}
|
||||||
13
lib/animations/live_link_face_bone_driver.dart
Normal file
13
lib/animations/live_link_face_bone_driver.dart
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
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(0, 0, -1), pi / 3)),
|
||||||
|
"HeadRoll": Transformation(Quaternion.axisAngle(Vector3(1, 0, 0), pi / 2)),
|
||||||
|
"HeadYaw": Transformation(Quaternion.axisAngle(Vector3(0, 1, 0), pi / 2)),
|
||||||
|
});
|
||||||
|
}
|
||||||
32
lib/animations/morph_animation_data.dart
Normal file
32
lib/animations/morph_animation_data.dart
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// Frame weights for the morph targets specified in [morphNames] attached to mesh [meshName].
|
||||||
|
// morphData is laid out as numFrames x numMorphTargets
|
||||||
|
// where the weights are in the same order as [morphNames].
|
||||||
|
// [morphNames] must be provided but is not used directly; this is only used to check that the eventual asset being animated contains the same morph targets in the same order.
|
||||||
|
//
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
class MorphAnimationData {
|
||||||
|
final String meshName;
|
||||||
|
final List<String> morphNames;
|
||||||
|
|
||||||
|
final Float32List data;
|
||||||
|
|
||||||
|
MorphAnimationData(
|
||||||
|
this.meshName, this.data, this.morphNames, this.frameLengthInMs) {
|
||||||
|
assert(data.length == morphNames.length * numFrames);
|
||||||
|
}
|
||||||
|
|
||||||
|
int get numMorphWeights => morphNames.length;
|
||||||
|
|
||||||
|
int get numFrames => data.length ~/ numMorphWeights;
|
||||||
|
|
||||||
|
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) + index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,10 +9,10 @@ import 'package:flutter/animation.dart';
|
|||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:polyvox_filament/animations/bone_animation_data.dart';
|
||||||
|
import 'package:polyvox_filament/animations/morph_animation_data.dart';
|
||||||
import 'package:polyvox_filament/generated_bindings.dart';
|
import 'package:polyvox_filament/generated_bindings.dart';
|
||||||
|
|
||||||
import 'animations/animations.dart';
|
|
||||||
|
|
||||||
typedef AssetManager = Pointer<Void>;
|
typedef AssetManager = Pointer<Void>;
|
||||||
typedef FilamentViewer = Pointer<Void>;
|
typedef FilamentViewer = Pointer<Void>;
|
||||||
typedef FilamentEntity = int;
|
typedef FilamentEntity = int;
|
||||||
@@ -306,8 +306,8 @@ class FilamentController {
|
|||||||
/// [morphWeights] is a list of doubles in frame-major format.
|
/// [morphWeights] is a list of doubles in frame-major format.
|
||||||
/// Each frame is [numWeights] in length, and each entry is the weight to be applied to the morph target located at that index in the mesh primitive at that frame.
|
/// Each frame is [numWeights] in length, and each entry is the weight to be applied to the morph target located at that index in the mesh primitive at that frame.
|
||||||
///
|
///
|
||||||
void setMorphAnimation(FilamentEntity asset, MorphAnimation animation) async {
|
void setMorphAnimationData(
|
||||||
print("Setting morph animation");
|
FilamentEntity asset, MorphAnimationData animation) async {
|
||||||
var data = calloc<Float>(animation.data.length);
|
var data = calloc<Float>(animation.data.length);
|
||||||
for (int i = 0; i < animation.data.length; i++) {
|
for (int i = 0; i < animation.data.length; i++) {
|
||||||
data.elementAt(i).value = animation.data[i];
|
data.elementAt(i).value = animation.data[i];
|
||||||
@@ -327,40 +327,38 @@ class FilamentController {
|
|||||||
/// Animates morph target weights/bone transforms (where each frame requires a duration of [frameLengthInMs].
|
/// Animates morph target weights/bone transforms (where each frame requires a duration of [frameLengthInMs].
|
||||||
/// [morphWeights] is a list of doubles in frame-major format.
|
/// [morphWeights] is a list of doubles in frame-major format.
|
||||||
/// Each frame is [numWeights] in length, and each entry is the weight to be applied to the morph target located at that index in the mesh primitive at that frame.
|
/// Each frame is [numWeights] in length, and each entry is the weight to be applied to the morph target located at that index in the mesh primitive at that frame.
|
||||||
|
/// for now we only allow animating a single bone (though multiple skinned targets are supported)
|
||||||
///
|
///
|
||||||
void setBoneAnimation(
|
void setBoneAnimation(
|
||||||
FilamentEntity asset, List<DartBoneAnimation> animations) async {
|
FilamentEntity asset, BoneAnimationData animation) async {
|
||||||
var data =
|
var data = calloc<Float>(animation.frameData.length);
|
||||||
calloc<Float>(animations.length * animations.first.frameData.length);
|
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
var numFrames = animations.first.frameData.length;
|
var numFrames = animation.frameData.length ~/ 7;
|
||||||
var meshNames = calloc<Pointer<Char>>(animations.length);
|
var boneNames = calloc<Pointer<Char>>(1);
|
||||||
var boneNames = calloc<Pointer<Char>>(animations.length);
|
boneNames.elementAt(0).value =
|
||||||
int animIdx = 0;
|
animation.boneName.toNativeUtf8().cast<Char>();
|
||||||
for (var animation in animations) {
|
|
||||||
if (animation.frameData.length != numFrames) {
|
var meshNames = calloc<Pointer<Char>>(animation.meshNames.length);
|
||||||
throw Exception(
|
for (int i = 0; i < animation.meshNames.length; i++) {
|
||||||
"All bone animations must share the same animation frame data length.");
|
meshNames.elementAt(i).value =
|
||||||
}
|
animation.meshNames[i].toNativeUtf8().cast<Char>();
|
||||||
for (int i = 0; i < animation.frameData.length; i++) {
|
}
|
||||||
data.elementAt(offset).value = animation.frameData[i];
|
|
||||||
offset += 1;
|
for (int i = 0; i < animation.frameData.length; i++) {
|
||||||
}
|
data.elementAt(offset).value = animation.frameData[i];
|
||||||
meshNames.elementAt(animIdx).value =
|
offset += 1;
|
||||||
animation.meshName.toNativeUtf8().cast<Char>();
|
|
||||||
boneNames.elementAt(animIdx).value =
|
|
||||||
animation.boneName.toNativeUtf8().cast<Char>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_nativeLibrary.set_bone_animation(
|
_nativeLibrary.set_bone_animation(
|
||||||
_assetManager,
|
_assetManager,
|
||||||
asset,
|
asset,
|
||||||
animations.length,
|
|
||||||
boneNames,
|
|
||||||
meshNames,
|
|
||||||
data,
|
data,
|
||||||
numFrames,
|
numFrames,
|
||||||
animations.first.frameLengthInMs);
|
1,
|
||||||
|
boneNames,
|
||||||
|
meshNames,
|
||||||
|
animation.meshNames.length,
|
||||||
|
animation.frameLengthInMs);
|
||||||
calloc.free(data);
|
calloc.free(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,7 +384,6 @@ class FilamentController {
|
|||||||
|
|
||||||
void playAnimation(FilamentEntity asset, int index,
|
void playAnimation(FilamentEntity asset, int index,
|
||||||
{bool loop = false, bool reverse = false}) async {
|
{bool loop = false, bool reverse = false}) async {
|
||||||
print("LOOP $loop");
|
|
||||||
_nativeLibrary.play_animation(
|
_nativeLibrary.play_animation(
|
||||||
_assetManager, asset, index, loop ? 1 : 0, reverse ? 1 : 0);
|
_assetManager, asset, index, loop ? 1 : 0, reverse ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ class _FilamentGestureDetectorState extends State<FilamentGestureDetector> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool _rotating = false;
|
bool _rotating = false;
|
||||||
bool _scaling = false;
|
|
||||||
|
|
||||||
// to avoid duplicating code for pan/rotate (panStart, panUpdate, panEnd, rotateStart, rotateUpdate etc)
|
// to avoid duplicating code for pan/rotate (panStart, panUpdate, panEnd, rotateStart, rotateUpdate etc)
|
||||||
// we have only a single function for start/update/end.
|
// we have only a single function for start/update/end.
|
||||||
@@ -45,8 +44,6 @@ class _FilamentGestureDetectorState extends State<FilamentGestureDetector> {
|
|||||||
late Function(double x, double y) _functionUpdate;
|
late Function(double x, double y) _functionUpdate;
|
||||||
late Function() _functionEnd;
|
late Function() _functionEnd;
|
||||||
|
|
||||||
double _lastScale = 0;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_setFunction();
|
_setFunction();
|
||||||
@@ -85,101 +82,135 @@ class _FilamentGestureDetectorState extends State<FilamentGestureDetector> {
|
|||||||
|
|
||||||
Timer? _scrollTimer;
|
Timer? _scrollTimer;
|
||||||
|
|
||||||
|
Widget _desktop() {
|
||||||
|
return Listener(
|
||||||
|
onPointerSignal: !widget.enableControls
|
||||||
|
? null
|
||||||
|
: (pointerSignal) async {
|
||||||
|
// scroll-wheel zoom on desktop
|
||||||
|
if (pointerSignal is PointerScrollEvent) {
|
||||||
|
_scrollTimer?.cancel();
|
||||||
|
widget.controller.zoomBegin();
|
||||||
|
widget.controller
|
||||||
|
.zoomUpdate(pointerSignal.scrollDelta.dy > 0 ? 10 : -10);
|
||||||
|
_scrollTimer = Timer(Duration(milliseconds: 100), () {
|
||||||
|
widget.controller.zoomEnd();
|
||||||
|
_scrollTimer = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onPointerPanZoomStart: !widget.enableControls
|
||||||
|
? null
|
||||||
|
: (pzs) {
|
||||||
|
print("PAN ZOOM START");
|
||||||
|
},
|
||||||
|
onPointerDown: !widget.enableControls
|
||||||
|
? null
|
||||||
|
: (d) async {
|
||||||
|
print("a");
|
||||||
|
// if (d.buttons == kTertiaryButton || _rotating) {
|
||||||
|
// widget.controller
|
||||||
|
// .rotateStart(d.localPosition.dx, d.localPosition.dy);
|
||||||
|
// } else {
|
||||||
|
// widget.controller.panStart(d.focalPoint.dx, d.focalPoint.dy);
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
onPointerMove: !widget.enableControls
|
||||||
|
? null
|
||||||
|
: (PointerMoveEvent d) async {
|
||||||
|
// if (d.buttons == kTertiaryButton || _rotating) {
|
||||||
|
// widget.controller
|
||||||
|
// .rotateUpdate(d.localPosition.dx, d.localPosition.dy);
|
||||||
|
// } else {
|
||||||
|
// widget.controller.panUpdate(d.focalPoint.dx, d.focalPoint.dy);
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
onPointerUp: !widget.enableControls
|
||||||
|
? null
|
||||||
|
: (d) async {
|
||||||
|
// if (d.buttons == kTertiaryButton || _rotating) {
|
||||||
|
// widget.controller.rotateEnd();
|
||||||
|
// } else {
|
||||||
|
// widget.controller.panEnd(d.focalPoint.dx, d.focalPoint.dy);
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
child: widget.child);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _scaling = false;
|
||||||
|
double _lastScale = 0;
|
||||||
|
DateTime _lastUpdate = DateTime.now();
|
||||||
|
Widget _mobile() {
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onDoubleTap: () {
|
||||||
|
_rotating = !_rotating;
|
||||||
|
print("Set rotating to $_rotating");
|
||||||
|
},
|
||||||
|
onScaleStart: !widget.enableControls
|
||||||
|
? null
|
||||||
|
: (d) async {
|
||||||
|
if (d.pointerCount == 2) {
|
||||||
|
_lastScale = 0;
|
||||||
|
_scaling = true;
|
||||||
|
widget.controller.zoomBegin();
|
||||||
|
} else if (!_scaling) {
|
||||||
|
if (_rotating) {
|
||||||
|
widget.controller
|
||||||
|
.rotateStart(d.focalPoint.dx, d.focalPoint.dy);
|
||||||
|
} else {
|
||||||
|
widget.controller
|
||||||
|
.panStart(d.focalPoint.dx, d.focalPoint.dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onScaleEnd: !widget.enableControls
|
||||||
|
? null
|
||||||
|
: (d) async {
|
||||||
|
if (d.pointerCount == 2) {
|
||||||
|
widget.controller.zoomEnd();
|
||||||
|
_lastScale = 0;
|
||||||
|
_scaling = false;
|
||||||
|
} else if (!_scaling) {
|
||||||
|
if (_rotating) {
|
||||||
|
widget.controller.rotateEnd();
|
||||||
|
} else {
|
||||||
|
widget.controller.panEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onScaleUpdate: !widget.enableControls
|
||||||
|
? null
|
||||||
|
: (ScaleUpdateDetails d) async {
|
||||||
|
if (d.pointerCount == 2) {
|
||||||
|
// var scale = d.horizontalScale - _lastScale;
|
||||||
|
// print(scale);
|
||||||
|
widget.controller
|
||||||
|
.zoomUpdate(d.horizontalScale > 1 ? 0.1 : -0.1);
|
||||||
|
_lastScale = d.horizontalScale;
|
||||||
|
} else if (!_scaling) {
|
||||||
|
if (_rotating) {
|
||||||
|
widget.controller
|
||||||
|
.rotateUpdate(d.focalPoint.dx, d.focalPoint.dy);
|
||||||
|
} else {
|
||||||
|
widget.controller
|
||||||
|
.panUpdate(d.focalPoint.dx, d.focalPoint.dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: widget.child);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
print(widget.enableControls);
|
|
||||||
return Stack(children: [
|
return Stack(children: [
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
// pinch zoom on mobile
|
// pinch zoom on mobile
|
||||||
// couldn't find any equivalent for pointerCount in Listener so we use two widgets:
|
// couldn't find any equivalent for pointerCount in Listener so we use two widgets:
|
||||||
// - outer is a GestureDetector only for pinch zoom
|
// - outer is a GestureDetector only for pinch zoom
|
||||||
// - inner is a Listener for all other gestures (including scroll zoom on desktop)
|
// - inner is a Listener for all other gestures (including scroll zoom on desktop)
|
||||||
child: GestureDetector(
|
child:
|
||||||
onDoubleTap: () {
|
Platform.isLinux || Platform.isWindows ? _desktop() : _mobile()),
|
||||||
_rotating = !_rotating;
|
|
||||||
print("Set rotating to $_rotating");
|
|
||||||
},
|
|
||||||
onScaleStart: !widget.enableControls
|
|
||||||
? null
|
|
||||||
: (d) async {
|
|
||||||
_scaling = true;
|
|
||||||
if (d.pointerCount == 2) {
|
|
||||||
widget.controller.zoomEnd();
|
|
||||||
widget.controller.zoomBegin();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onScaleEnd: !widget.enableControls
|
|
||||||
? null
|
|
||||||
: (d) async {
|
|
||||||
_scaling = false;
|
|
||||||
if (d.pointerCount == 2) {
|
|
||||||
_lastScale = 0;
|
|
||||||
widget.controller.zoomEnd();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onScaleUpdate: !widget.enableControls
|
|
||||||
? null
|
|
||||||
: (d) async {
|
|
||||||
if (d.pointerCount == 2) {
|
|
||||||
if (_lastScale != 0) {
|
|
||||||
widget.controller.zoomUpdate(Platform.isIOS
|
|
||||||
? 1000 * (_lastScale - d.scale)
|
|
||||||
: 100 * (_lastScale - d.scale));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_lastScale = d.scale;
|
|
||||||
},
|
|
||||||
child: Listener(
|
|
||||||
onPointerSignal: !widget.enableControls
|
|
||||||
? null
|
|
||||||
: (pointerSignal) async {
|
|
||||||
// scroll-wheel zoom on desktop
|
|
||||||
if (pointerSignal is PointerScrollEvent) {
|
|
||||||
_scrollTimer?.cancel();
|
|
||||||
widget.controller.zoomBegin();
|
|
||||||
widget.controller.zoomUpdate(
|
|
||||||
pointerSignal.scrollDelta.dy > 0 ? 10 : -10);
|
|
||||||
_scrollTimer =
|
|
||||||
Timer(Duration(milliseconds: 100), () {
|
|
||||||
widget.controller.zoomEnd();
|
|
||||||
_scrollTimer = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onPointerPanZoomStart:
|
|
||||||
!widget.enableControls ? null : (pzs) {},
|
|
||||||
onPointerDown: !widget.enableControls
|
|
||||||
? null
|
|
||||||
: (d) async {
|
|
||||||
if (d.buttons == kTertiaryButton || _rotating) {
|
|
||||||
widget.controller.rotateStart(
|
|
||||||
d.localPosition.dx, d.localPosition.dy);
|
|
||||||
} else {
|
|
||||||
_functionStart(
|
|
||||||
d.localPosition.dx, d.localPosition.dy);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onPointerMove: !widget.enableControls
|
|
||||||
? null
|
|
||||||
: (d) async {
|
|
||||||
if (d.buttons == kTertiaryButton || _rotating) {
|
|
||||||
widget.controller.rotateUpdate(
|
|
||||||
d.localPosition.dx, d.localPosition.dy);
|
|
||||||
} else {
|
|
||||||
_functionUpdate(
|
|
||||||
d.localPosition.dx, d.localPosition.dy);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onPointerUp: !widget.enableControls
|
|
||||||
? null
|
|
||||||
: (d) async {
|
|
||||||
if (d.buttons == kTertiaryButton || _rotating) {
|
|
||||||
widget.controller.rotateEnd();
|
|
||||||
} else {
|
|
||||||
_functionEnd();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: widget.child))),
|
|
||||||
widget.showControlOverlay
|
widget.showControlOverlay
|
||||||
? Align(
|
? Align(
|
||||||
alignment: Alignment.bottomRight,
|
alignment: Alignment.bottomRight,
|
||||||
|
|||||||
@@ -118,7 +118,8 @@ class _FilamentWidgetState extends State<FilamentWidget> {
|
|||||||
? Container()
|
? Container()
|
||||||
: Transform(
|
: Transform(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
transform: Matrix4.rotationX(pi),
|
transform: Matrix4.rotationX(
|
||||||
|
pi), // TODO - this rotation is due to OpenGL texture coordinate working in a different space from Flutter, can we move this to the C++ side somewhere?
|
||||||
child: texture)
|
child: texture)
|
||||||
: texture))));
|
: texture))));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -684,21 +684,23 @@ class NativeLibrary {
|
|||||||
void set_bone_animation(
|
void set_bone_animation(
|
||||||
ffi.Pointer<ffi.Void> assetManager,
|
ffi.Pointer<ffi.Void> assetManager,
|
||||||
int asset,
|
int asset,
|
||||||
int length,
|
|
||||||
ffi.Pointer<ffi.Pointer<ffi.Char>> boneNames,
|
|
||||||
ffi.Pointer<ffi.Pointer<ffi.Char>> meshNames,
|
|
||||||
ffi.Pointer<ffi.Float> frameData,
|
ffi.Pointer<ffi.Float> frameData,
|
||||||
int numFrames,
|
int numFrames,
|
||||||
|
int numBones,
|
||||||
|
ffi.Pointer<ffi.Pointer<ffi.Char>> boneNames,
|
||||||
|
ffi.Pointer<ffi.Pointer<ffi.Char>> meshName,
|
||||||
|
int numMeshTargets,
|
||||||
double frameLengthInMs,
|
double frameLengthInMs,
|
||||||
) {
|
) {
|
||||||
return _set_bone_animation(
|
return _set_bone_animation(
|
||||||
assetManager,
|
assetManager,
|
||||||
asset,
|
asset,
|
||||||
length,
|
|
||||||
boneNames,
|
|
||||||
meshNames,
|
|
||||||
frameData,
|
frameData,
|
||||||
numFrames,
|
numFrames,
|
||||||
|
numBones,
|
||||||
|
boneNames,
|
||||||
|
meshName,
|
||||||
|
numMeshTargets,
|
||||||
frameLengthInMs,
|
frameLengthInMs,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -708,20 +710,22 @@ class NativeLibrary {
|
|||||||
ffi.Void Function(
|
ffi.Void Function(
|
||||||
ffi.Pointer<ffi.Void>,
|
ffi.Pointer<ffi.Void>,
|
||||||
EntityId,
|
EntityId,
|
||||||
|
ffi.Pointer<ffi.Float>,
|
||||||
|
ffi.Int,
|
||||||
ffi.Int,
|
ffi.Int,
|
||||||
ffi.Pointer<ffi.Pointer<ffi.Char>>,
|
ffi.Pointer<ffi.Pointer<ffi.Char>>,
|
||||||
ffi.Pointer<ffi.Pointer<ffi.Char>>,
|
ffi.Pointer<ffi.Pointer<ffi.Char>>,
|
||||||
ffi.Pointer<ffi.Float>,
|
|
||||||
ffi.Int,
|
ffi.Int,
|
||||||
ffi.Float)>>('set_bone_animation');
|
ffi.Float)>>('set_bone_animation');
|
||||||
late final _set_bone_animation = _set_bone_animationPtr.asFunction<
|
late final _set_bone_animation = _set_bone_animationPtr.asFunction<
|
||||||
void Function(
|
void Function(
|
||||||
ffi.Pointer<ffi.Void>,
|
ffi.Pointer<ffi.Void>,
|
||||||
int,
|
int,
|
||||||
|
ffi.Pointer<ffi.Float>,
|
||||||
|
int,
|
||||||
int,
|
int,
|
||||||
ffi.Pointer<ffi.Pointer<ffi.Char>>,
|
ffi.Pointer<ffi.Pointer<ffi.Char>>,
|
||||||
ffi.Pointer<ffi.Pointer<ffi.Char>>,
|
ffi.Pointer<ffi.Pointer<ffi.Char>>,
|
||||||
ffi.Pointer<ffi.Float>,
|
|
||||||
int,
|
int,
|
||||||
double)>();
|
double)>();
|
||||||
|
|
||||||
@@ -1151,96 +1155,9 @@ class NativeLibrary {
|
|||||||
late final _ios_dummy = _ios_dummyPtr.asFunction<void Function()>();
|
late final _ios_dummy = _ios_dummyPtr.asFunction<void Function()>();
|
||||||
}
|
}
|
||||||
|
|
||||||
class __mbstate_t extends ffi.Union {
|
class __fsid_t extends ffi.Struct {
|
||||||
@ffi.Array.multi([128])
|
@ffi.Array.multi([2])
|
||||||
external ffi.Array<ffi.Char> __mbstate8;
|
external ffi.Array<ffi.Int> __val;
|
||||||
|
|
||||||
@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 ResourceBuffer extends ffi.Struct {
|
class ResourceBuffer extends ffi.Struct {
|
||||||
@@ -1277,75 +1194,127 @@ typedef FreeResourceFromOwner = ffi.Pointer<
|
|||||||
ffi.Void Function(ResourceBuffer, ffi.Pointer<ffi.Void>)>>;
|
ffi.Void Function(ResourceBuffer, ffi.Pointer<ffi.Void>)>>;
|
||||||
typedef EntityId = ffi.Int32;
|
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 __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;
|
const int INT8_MIN = -128;
|
||||||
|
|
||||||
@@ -1355,6 +1324,14 @@ const int INT32_MIN = -2147483648;
|
|||||||
|
|
||||||
const int INT64_MIN = -9223372036854775808;
|
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 UINT8_MAX = 255;
|
||||||
|
|
||||||
const int UINT16_MAX = 65535;
|
const int UINT16_MAX = 65535;
|
||||||
@@ -1389,66 +1366,54 @@ const int UINT_LEAST64_MAX = -1;
|
|||||||
|
|
||||||
const int INT_FAST8_MIN = -128;
|
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_FAST64_MIN = -9223372036854775808;
|
||||||
|
|
||||||
const int INT_FAST8_MAX = 127;
|
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 INT_FAST64_MAX = 9223372036854775807;
|
||||||
|
|
||||||
const int UINT_FAST8_MAX = 255;
|
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 UINT_FAST64_MAX = -1;
|
||||||
|
|
||||||
const int INTPTR_MAX = 9223372036854775807;
|
|
||||||
|
|
||||||
const int INTPTR_MIN = -9223372036854775808;
|
const int INTPTR_MIN = -9223372036854775808;
|
||||||
|
|
||||||
|
const int INTPTR_MAX = 9223372036854775807;
|
||||||
|
|
||||||
const int UINTPTR_MAX = -1;
|
const int UINTPTR_MAX = -1;
|
||||||
|
|
||||||
|
const int INTMAX_MIN = -9223372036854775808;
|
||||||
|
|
||||||
const int INTMAX_MAX = 9223372036854775807;
|
const int INTMAX_MAX = 9223372036854775807;
|
||||||
|
|
||||||
const int UINTMAX_MAX = -1;
|
const int UINTMAX_MAX = -1;
|
||||||
|
|
||||||
const int INTMAX_MIN = -9223372036854775808;
|
|
||||||
|
|
||||||
const int PTRDIFF_MIN = -9223372036854775808;
|
const int PTRDIFF_MIN = -9223372036854775808;
|
||||||
|
|
||||||
const int PTRDIFF_MAX = 9223372036854775807;
|
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_MIN = -2147483648;
|
||||||
|
|
||||||
const int SIG_ATOMIC_MAX = 2147483647;
|
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,63 +0,0 @@
|
|||||||
|
|
||||||
// import 'package:flutter/widgets.dart';
|
|
||||||
// import 'package:polyvox_filament/filament_controller.dart';
|
|
||||||
// import 'package:polyvox_filament/filament_controller.dart';
|
|
||||||
// import 'package:vector_math/vector_math_64.dart';
|
|
||||||
|
|
||||||
// class Position {
|
|
||||||
// final double x;
|
|
||||||
// final double y;
|
|
||||||
// final double z;
|
|
||||||
// Position(this.x, this.y, this.z);
|
|
||||||
// Position copy({double? x, double? y, double? z}) {
|
|
||||||
// return Position(x ?? this.x, y ?? this.y, z ?? this.z);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// factory Position.zero() {
|
|
||||||
// return Position(0,0,0);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// class Rotation {
|
|
||||||
// final double rads;
|
|
||||||
// final double x;
|
|
||||||
// final double y;
|
|
||||||
// final double z;
|
|
||||||
// Rotation(this.x, this.y, this.z, this.rads);
|
|
||||||
// Rotation copy({double? rads, double? x, double? y, double? z}) {
|
|
||||||
// return Rotation(x ?? this.x, y ?? this.y, z ?? this.z, rads ?? this.rads);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// factory Rotation.zero() {
|
|
||||||
// return Rotation(0, 1,0,0);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ///
|
|
||||||
// /// Handles local transforms for assets/cameras.
|
|
||||||
// ///
|
|
||||||
// class TransformManager {
|
|
||||||
|
|
||||||
// final FilamentController _controller;
|
|
||||||
|
|
||||||
// Matrix4 transform = Matrix4.identity();
|
|
||||||
|
|
||||||
// TransformManager(this._controller);
|
|
||||||
|
|
||||||
// void scale(double scale) {
|
|
||||||
// transform.scale(scale, scale, scale);
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// void translate(double x, double y, double z) {
|
|
||||||
// transform.translate(x,y,z);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void rotate(double x, double y, double z) {
|
|
||||||
// transform.rotate(Vector3(x,y,z));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// }
|
|
||||||
70
test/bone_driver_test.dart
Normal file
70
test/bone_driver_test.dart
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
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(
|
||||||
|
'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);
|
||||||
|
|
||||||
|
final result = boneDriver.transform(morphTargetFrameData).toList();
|
||||||
|
|
||||||
|
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 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);
|
||||||
|
|
||||||
|
expect(() => boneDriver.transform(morphTargetFrameData),
|
||||||
|
throwsA(isA<AssertionError>()));
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
expect(() => boneDriver.transform(morphTargetFrameData),
|
||||||
|
throwsA(isA<AssertionError>()));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
const MethodChannel channel = MethodChannel('polyvox_filament');
|
|
||||||
|
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
|
||||||
|
|
||||||
test('getPlatformVersion', () async {});
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user