diff --git a/ios/include/AssetManager.hpp b/ios/include/AssetManager.hpp index bff446a6..3494b33d 100644 --- a/ios/include/AssetManager.hpp +++ b/ios/include/AssetManager.hpp @@ -39,7 +39,7 @@ namespace polyvox inline void updateTransform(EntityId e); void setScale(EntityId e, float scale); void setPosition(EntityId e, float x, float y, float z, bool relative); - void setRotation(EntityId e, float rads, float x, float y, float z); + void setRotation(EntityId e, float rads, float x, float y, float z, float w, bool relative); const utils::Entity *getCameraEntities(EntityId e); size_t getCameraEntityCount(EntityId e); const utils::Entity *getLightEntities(EntityId e) const noexcept; @@ -119,7 +119,5 @@ namespace polyvox SceneAsset asset, const char *entityName); - inline void updateTransform(SceneAsset &asset); - }; } diff --git a/ios/include/FlutterFilamentApi.h b/ios/include/FlutterFilamentApi.h index 98ef8ff9..0276441e 100644 --- a/ios/include/FlutterFilamentApi.h +++ b/ios/include/FlutterFilamentApi.h @@ -147,7 +147,7 @@ extern "C" FLUTTER_PLUGIN_EXPORT bool set_material_color(void *assetManager, EntityId asset, const char *meshName, int materialIndex, const float r, const float g, const float b, const float a); FLUTTER_PLUGIN_EXPORT void transform_to_unit_cube(void *assetManager, EntityId asset); FLUTTER_PLUGIN_EXPORT void set_position(void *assetManager, EntityId asset, float x, float y, float z, bool relative); - FLUTTER_PLUGIN_EXPORT void set_rotation(void *assetManager, EntityId asset, float rads, float x, float y, float z); + FLUTTER_PLUGIN_EXPORT void set_rotation(void *assetManager, EntityId asset, float rads, float x, float y, float z, float w, bool relative); FLUTTER_PLUGIN_EXPORT void set_scale(void *assetManager, EntityId asset, float scale); // Camera methods diff --git a/ios/include/SceneAsset.hpp b/ios/include/SceneAsset.hpp index f5fe84d2..3b34940e 100644 --- a/ios/include/SceneAsset.hpp +++ b/ios/include/SceneAsset.hpp @@ -86,14 +86,6 @@ namespace polyvox { // a slot to preload textures filament::Texture* texture = nullptr; - // initialized to identity - math::mat4f position; - - // initialized to identity - math::mat4f rotation; - - float mScale = 1; - SceneAsset( FilamentAsset* asset ) : asset(asset) {} diff --git a/ios/src/AssetManager.cpp b/ios/src/AssetManager.cpp index 417c6ced..6fd506e9 100644 --- a/ios/src/AssetManager.cpp +++ b/ios/src/AssetManager.cpp @@ -360,7 +360,7 @@ namespace polyvox asset.asset->getInstance()->getAnimator()->updateBoneMatrices(); } - for (int i = asset.morphAnimations.size() - 1; i >= 0; i--) { + for (int i = (int)asset.morphAnimations.size() - 1; i >= 0; i--) { auto animationStatus = asset.morphAnimations[i]; @@ -392,7 +392,7 @@ namespace polyvox } } - for (int i = asset.boneAnimations.size() - 1; i >= 0; i--) { + for (int i = (int)asset.boneAnimations.size() - 1; i >= 0; i--) { auto animationStatus = asset.boneAnimations[i]; auto elapsedInSecs = float(std::chrono::duration_cast(now - animationStatus.start).count()) / 1000.0f; @@ -493,7 +493,7 @@ namespace polyvox Log("WARNING - skin count > 1 not currently implemented. This will probably not work"); } - int numJoints = filamentInstance->getJointCountAt(skinIndex); + size_t numJoints = filamentInstance->getJointCountAt(skinIndex); auto joints = filamentInstance->getJointsAt(skinIndex); int boneIndex = -1; for (int i = 0; i < numJoints; i++) @@ -830,7 +830,7 @@ namespace polyvox } animation.frameData.clear(); - const auto& tm = _engine->getTransformManager(); + const auto& inverseBindMatrix = filamentInstance->getInverseBindMatricesAt(skinIndex)[animation.boneIndex]; const auto& bindMatrix = inverse(inverseBindMatrix); math::float3 trans; @@ -1163,15 +1163,7 @@ namespace polyvox tm.setTransform(tm.getInstance(inst->getRoot()), transform); } - void AssetManager::updateTransform(SceneAsset &asset) - { - auto &tm = _engine->getTransformManager(); - auto transform = - asset.position * asset.rotation * math::mat4f::scaling(asset.mScale); - tm.setTransform(tm.getInstance(asset.asset->getRoot()), transform); - } - - void AssetManager::setScale(EntityId entity, float scale) + void AssetManager::setScale(EntityId entity, float newScale) { const auto &pos = _entityIdLookup.find(entity); if (pos == _entityIdLookup.end()) @@ -1180,8 +1172,22 @@ namespace polyvox return; } auto &asset = _assets[pos->second]; - asset.mScale = scale; - updateTransform(asset); + + auto &tm = _engine->getTransformManager(); + auto transformInstance = tm.getInstance(asset.asset->getRoot()); + + auto transform = tm.getTransform(transformInstance); + + math::float3 translation; + math::quatf rotation; + math::float3 scale; + + decomposeMatrix(transform, &translation, &rotation, &scale); + scale = { newScale, newScale, newScale}; + + transform = composeMatrix(translation, rotation, scale); + tm.setTransform(transformInstance, transform); + } void AssetManager::setPosition(EntityId entity, float x, float y, float z, bool relative) @@ -1193,18 +1199,29 @@ namespace polyvox return; } auto &asset = _assets[pos->second]; - if(relative) { - asset.position[3][0] += x; - asset.position[3][1] += y; - asset.position[3][2] += z; - } else { - asset.position = math::mat4f::translation(math::float3(x, y, z)); - } - updateTransform(asset); + auto &tm = _engine->getTransformManager(); + auto transformInstance = tm.getInstance(asset.asset->getRoot()); + + auto transform = tm.getTransform(transformInstance); + + math::float3 translation; + math::quatf rotation; + math::float3 scale; + + decomposeMatrix(transform, &translation, &rotation, &scale); + if(relative) { + translation += math::float3( x, y, z ); + } else { + translation = math::float3(x,y,z); + } + + transform = composeMatrix(translation, rotation, scale); + tm.setTransform(transformInstance, transform); + } - void AssetManager::setRotation(EntityId entity, float rads, float x, float y, float z) + void AssetManager::setRotation(EntityId entity, float rads, float x, float y, float z, float w, bool relative) { const auto &pos = _entityIdLookup.find(entity); if (pos == _entityIdLookup.end()) @@ -1213,8 +1230,27 @@ namespace polyvox return; } auto &asset = _assets[pos->second]; - asset.rotation = math::mat4f::rotation(rads, math::float3(x, y, z)); - updateTransform(asset); + + auto &tm = _engine->getTransformManager(); + auto transformInstance = tm.getInstance(asset.asset->getRoot()); + + auto transform = tm.getTransform(transformInstance); + + math::float3 translation; + math::quatf rotation; + math::float3 scale; + + decomposeMatrix(transform, &translation, &rotation, &scale); + + if(relative) { + rotation = normalize(rotation * math::quatf(w,x,y,z)); + } else { + rotation = math::quatf(w,x,y,z); + } + + transform = composeMatrix(translation, rotation, scale); + tm.setTransform(transformInstance, transform); + } const utils::Entity *AssetManager::getCameraEntities(EntityId entity) diff --git a/ios/src/FlutterFilamentApi.cpp b/ios/src/FlutterFilamentApi.cpp index 069f54f9..48c3cdea 100644 --- a/ios/src/FlutterFilamentApi.cpp +++ b/ios/src/FlutterFilamentApi.cpp @@ -475,9 +475,9 @@ extern "C" ((AssetManager *)assetManager)->setPosition(asset, x, y, z, relative); } - FLUTTER_PLUGIN_EXPORT void set_rotation(void *assetManager, EntityId asset, float rads, float x, float y, float z) + FLUTTER_PLUGIN_EXPORT void set_rotation(void *assetManager, EntityId asset, float rads, float x, float y, float z, float w, bool relative) { - ((AssetManager *)assetManager)->setRotation(asset, rads, x, y, z); + ((AssetManager *)assetManager)->setRotation(asset, rads, x, y, z, w, relative); } FLUTTER_PLUGIN_EXPORT void set_scale(void *assetManager, EntityId asset, float scale) diff --git a/lib/entities/entity_transform_controller.dart b/lib/entities/entity_transform_controller.dart index 3731bc85..602f413b 100644 --- a/lib/entities/entity_transform_controller.dart +++ b/lib/entities/entity_transform_controller.dart @@ -19,54 +19,103 @@ class EntityTransformController { bool _back = false; bool _rotateLeft = false; bool _rotateRight = false; + double _rotY = 0; + + int? forwardAnimationIndex; + int? backwardAnimationIndex; + int? strafeLeftAnimationIndex; + int? strafeRightAnimationIndex; EntityTransformController(this.controller, this._entity, - {this.translationSpeed = 1, this.rotationRadsPerSecond = pi / 2}) { + {this.translationSpeed = 1, + this.rotationRadsPerSecond = pi / 2, + this.forwardAnimationIndex, + this.backwardAnimationIndex, + this.strafeLeftAnimationIndex, + this.strafeRightAnimationIndex}) { var translationSpeedPerTick = translationSpeed / (1000 / 16.667); + var rotationRadsPerTick = rotationRadsPerSecond / (1000 / 16.667); _ticker = Timer.periodic(const Duration(milliseconds: 16), (timer) { - _update(translationSpeedPerTick); + _update(translationSpeedPerTick, rotationRadsPerTick); }); } - void _update(double translationSpeedPerTick) async { + bool _enabled = true; + void enable() { + _enabled = true; + } + + void disable() { + _enabled = false; + } + + void _update( + double translationSpeedPerTick, double rotationRadsPerTick) async { + if (!_enabled) { + return; + } var _position = v.Vector3.zero(); - var _rotation = v.Quaternion.identity(); - bool requiresUpdate = false; + bool updateTranslation = false; if (_forward) { _position.add(v.Vector3(0, 0, -translationSpeedPerTick)); - requiresUpdate = true; + updateTranslation = true; } if (_back) { _position.add(v.Vector3(0, 0, translationSpeedPerTick)); - requiresUpdate = true; + updateTranslation = true; } if (_strafeLeft) { _position.add(v.Vector3(-translationSpeedPerTick, 0, 0)); - requiresUpdate = true; + updateTranslation = true; } if (_strafeRight) { _position.add(v.Vector3(translationSpeedPerTick, 0, 0)); - requiresUpdate = true; + updateTranslation = true; } // todo - better to use pitch/yaw/roll - if (_rotateLeft) {} - if (_rotateRight) {} + bool updateRotation = false; + var _rotation = v.Quaternion.identity(); - if (requiresUpdate) { + double rads = 0.0; + if (_rotY != 0) { + rads = _rotY! * pi / 1000; + var rotY = v.Quaternion.axisAngle(v.Vector3(0, 1, 0), rads).normalized(); + _rotation = rotY; + updateRotation = true; + _rotY = 0; + } + + if (updateTranslation) { await controller.setPosition( _entity, _position.x, _position.y, _position.z, relative: true); } + if (updateRotation) { + var axis = _rotation.axis; + await controller.setRotationQuat(_entity, _rotation, relative: true); + } + } + + void look(double deltaX) async { + _rotY -= deltaX; } void dispose() { _ticker.cancel(); } - void forwardPressed() { - print("forward"); + bool _playingForwardAnimation = false; + + void forwardPressed() async { _forward = true; + if (forwardAnimationIndex != null) { + if (!_playingForwardAnimation) { + await controller.playAnimation(_entity, forwardAnimationIndex!, + loop: true); + _playingForwardAnimation = true; + } + } } Timer? _forwardTimer; @@ -76,8 +125,12 @@ class EntityTransformController { void forwardReleased() async { _forwardTimer?.cancel(); - _forwardTimer = Timer(Duration(milliseconds: 50), () { + _forwardTimer = Timer(Duration(milliseconds: 50), () async { _forward = false; + _playingForwardAnimation = false; + if (forwardAnimationIndex != null) { + await controller.stopAnimation(_entity, forwardAnimationIndex!); + } }); } diff --git a/lib/filament_controller.dart b/lib/filament_controller.dart index 327b71d4..bd7cdcf9 100644 --- a/lib/filament_controller.dart +++ b/lib/filament_controller.dart @@ -5,6 +5,7 @@ import 'dart:ui' as ui; import 'package:flutter/widgets.dart'; import 'package:flutter_filament/animations/animation_data.dart'; +import 'package:flutter_filament/entities/entity_transform_controller.dart'; import 'package:vector_math/vector_math_64.dart'; // a handle that can be safely passed back to the rendering layer to manipulate an Entity @@ -467,7 +468,14 @@ abstract class FilamentController { /// Sets the rotation for [entity] to [rads] around the axis {x,y,z}. /// Future setRotation( - FilamentEntity entity, double rads, double x, double y, double z); + FilamentEntity entity, double rads, double x, double y, double z, + {bool relative = false}); + + /// + /// Sets the rotation for [entity] to the specified quaternion. + /// + Future setRotationQuat(FilamentEntity entity, Quaternion rotation, + {bool relative = false}); /// /// Reveal the node [meshName] under [entity]. Only applicable if [hide] had previously been called; this is a no-op otherwise. @@ -528,5 +536,6 @@ abstract class FilamentController { // Stream get keyboardFocusRequested; // void requestKeyboardFocus(); - void control(FilamentEntity entity, {double? translationSpeed}); + Future control(FilamentEntity entity, + {double? translationSpeed, String? forwardAnimation}); } diff --git a/lib/filament_controller_ffi.dart b/lib/filament_controller_ffi.dart index e1acb5e8..aac5b151 100644 --- a/lib/filament_controller_ffi.dart +++ b/lib/filament_controller_ffi.dart @@ -1069,11 +1069,23 @@ class FilamentControllerFFI extends FilamentController { @override Future setRotation( - FilamentEntity entity, double rads, double x, double y, double z) async { + FilamentEntity entity, double rads, double x, double y, double z, + {bool relative = false}) async { if (_viewer == null) { throw Exception("No viewer available, ignoring"); } - set_rotation(_assetManager!, entity, rads, x, y, z); + var quat = Quaternion.axisAngle(Vector3(x, y, z), rads); + await setRotationQuat(entity, quat, relative: relative); + } + + @override + Future setRotationQuat(FilamentEntity entity, Quaternion rotation, + {bool relative = false}) async { + if (_viewer == null) { + throw Exception("No viewer available, ignoring"); + } + set_rotation(_assetManager!, entity, rotation.radians, rotation.x, + rotation.y, rotation.z, rotation.w, relative); } @override @@ -1325,10 +1337,23 @@ class FilamentControllerFFI extends FilamentController { } HardwareKeyboardListener? _keyboardListener; - void control(FilamentEntity entity, {double? translationSpeed}) { + Future control(FilamentEntity entity, + {double? translationSpeed, String? forwardAnimation}) async { + int? forwardAnimationIndex; + if (forwardAnimation != null) { + final animationNames = await getAnimationNames(entity); + forwardAnimationIndex = animationNames.indexOf(forwardAnimation); + } + + if (forwardAnimationIndex == -1) { + throw Exception("Invalid animation : $forwardAnimation"); + } + _keyboardListener?.dispose(); - _keyboardListener = HardwareKeyboardListener(EntityTransformController( - this, entity, - translationSpeed: translationSpeed ?? 1.0)); + var transformController = EntityTransformController(this, entity, + translationSpeed: translationSpeed ?? 1.0, + forwardAnimationIndex: forwardAnimationIndex); + _keyboardListener = HardwareKeyboardListener(transformController); + return transformController; } } diff --git a/lib/generated_bindings.dart b/lib/generated_bindings.dart index 76917fcd..3b2bad38 100644 --- a/lib/generated_bindings.dart +++ b/lib/generated_bindings.dart @@ -564,7 +564,9 @@ external void set_position( ffi.Float, ffi.Float, ffi.Float, - ffi.Float)>(symbol: 'set_rotation', assetId: 'flutter_filament_plugin') + ffi.Float, + ffi.Float, + ffi.Bool)>(symbol: 'set_rotation', assetId: 'flutter_filament_plugin') external void set_rotation( ffi.Pointer assetManager, int asset, @@ -572,6 +574,8 @@ external void set_rotation( double x, double y, double z, + double w, + bool relative, ); @ffi.Native, EntityId, ffi.Float)>( diff --git a/lib/widgets/entity_controller_mouse_widget.dart b/lib/widgets/entity_controller_mouse_widget.dart new file mode 100644 index 00000000..bd692976 --- /dev/null +++ b/lib/widgets/entity_controller_mouse_widget.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_filament/entities/entity_transform_controller.dart'; + +/// +/// A widget that translates mouse gestures to zoom/pan/rotate actions. +/// +class EntityTransformMouseControllerWidget extends StatelessWidget { + final EntityTransformController? transformController; + final Widget? child; + + const EntityTransformMouseControllerWidget( + {Key? key, required this.transformController, this.child}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Listener( + onPointerHover: (event) { + transformController?.look(event.delta.dx); + }, + child: child); + } +}