diff --git a/ios/include/AssetManager.hpp b/ios/include/AssetManager.hpp index e3edbec6..bff446a6 100644 --- a/ios/include/AssetManager.hpp +++ b/ios/include/AssetManager.hpp @@ -38,7 +38,7 @@ namespace polyvox void transformToUnitCube(EntityId e); inline void updateTransform(EntityId e); void setScale(EntityId e, float scale); - void setPosition(EntityId e, float x, float y, float z); + void setPosition(EntityId e, float x, float y, float z, bool relative); void setRotation(EntityId e, float rads, float x, float y, float z); const utils::Entity *getCameraEntities(EntityId e); size_t getCameraEntityCount(EntityId e); diff --git a/ios/include/FilamentViewer.hpp b/ios/include/FilamentViewer.hpp index 51d61e6d..845c4657 100644 --- a/ios/include/FilamentViewer.hpp +++ b/ios/include/FilamentViewer.hpp @@ -200,11 +200,15 @@ namespace polyvox void loadTextureFromPath(string path); void savePng(void* data, size_t size, int frameNumber); - time_point_t _startTime = std::chrono::high_resolution_clock::now(); - + time_point_t _recordingStartTime = std::chrono::high_resolution_clock::now(); + time_point_t _fpsCounterStartTime = std::chrono::high_resolution_clock::now(); + bool _recording = false; std::string _recordingOutputDirectory = std::string("/tmp"); std::mutex _recordingMutex; + double _cumulativeAnimationUpdateTime = 0; + int _frameCount = 0; + int _skippedFrames = 0; }; struct FrameCallbackData { diff --git a/ios/include/FlutterFilamentApi.h b/ios/include/FlutterFilamentApi.h index d8217f59..98ef8ff9 100644 --- a/ios/include/FlutterFilamentApi.h +++ b/ios/include/FlutterFilamentApi.h @@ -146,7 +146,7 @@ extern "C" FLUTTER_PLUGIN_EXPORT void clear_assets(const void *const viewer); 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); + 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_scale(void *assetManager, EntityId asset, float scale); @@ -183,6 +183,7 @@ extern "C" FLUTTER_PLUGIN_EXPORT void set_recording_output_directory(void *const viewer, const char* outputDirectory); FLUTTER_PLUGIN_EXPORT void ios_dummy(); FLUTTER_PLUGIN_EXPORT void flutter_filament_free(void *ptr); + #ifdef __cplusplus } #endif diff --git a/ios/src/AssetManager.cpp b/ios/src/AssetManager.cpp index 4a5168b4..417c6ced 100644 --- a/ios/src/AssetManager.cpp +++ b/ios/src/AssetManager.cpp @@ -1184,7 +1184,7 @@ namespace polyvox updateTransform(asset); } - void AssetManager::setPosition(EntityId entity, float x, float y, float z) + void AssetManager::setPosition(EntityId entity, float x, float y, float z, bool relative) { const auto &pos = _entityIdLookup.find(entity); if (pos == _entityIdLookup.end()) @@ -1193,7 +1193,14 @@ namespace polyvox return; } auto &asset = _assets[pos->second]; - asset.position = math::mat4f::translation(math::float3(x, y, z)); + 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); } diff --git a/ios/src/FilamentViewer.cpp b/ios/src/FilamentViewer.cpp index 48ccdd0f..92223064 100644 --- a/ios/src/FilamentViewer.cpp +++ b/ios/src/FilamentViewer.cpp @@ -1005,9 +1005,6 @@ namespace polyvox } } - double _elapsed = 0; - int _frameCount = 0; - int _skippedFrames = 0; void FilamentViewer::render( uint64_t frameTimeInNanos, @@ -1022,21 +1019,30 @@ namespace polyvox return; } - if (_frameCount == 60) - { - // Log("1 sec average for asset animation update %f", _elapsed / 60); - Log("Skipped frames : %d", _skippedFrames); - _elapsed = 0; + // if (_frameCount == 60) + // { + // Log("Skipped frames : %d", _skippedFrames); + // _elapsed = 0; + // _frameCount = 0; + // _skippedFrames = 0; + // } + auto now = std::chrono::high_resolution_clock::now(); + auto secsSinceLastFpsCheck = float(std::chrono::duration_cast(now - _fpsCounterStartTime).count()); + + if(secsSinceLastFpsCheck >= 1) { + auto fps = _frameCount / secsSinceLastFpsCheck; + Log("%ffps (_frameCount %d, secs since last check %f)", fps, _frameCount, secsSinceLastFpsCheck); + // Log("1 sec average for asset animation update %f", _elapsed / _frameCount); _frameCount = 0; _skippedFrames = 0; + _fpsCounterStartTime = now; } Timer tmr; _assetManager->updateAnimations(); - _elapsed += tmr.elapsed(); - _frameCount++; + _cumulativeAnimationUpdateTime += tmr.elapsed(); // if a manipulator is active, update the active camera orientation if(_manipulator) { @@ -1068,6 +1074,8 @@ namespace polyvox if (beginFrame) { _renderer->render(_view); + _frameCount++; + if(_recording) { Viewport const &vp = _view->getViewport(); @@ -1081,7 +1089,7 @@ namespace polyvox }; auto now = std::chrono::high_resolution_clock::now(); - auto elapsed = float(std::chrono::duration_cast(now - _startTime).count()); + auto elapsed = float(std::chrono::duration_cast(now - _recordingStartTime).count()); auto frameNumber = uint32_t(floor(elapsed / _frameInterval)); @@ -1161,7 +1169,7 @@ namespace polyvox this->_recording = recording; if(recording) { _tp = new flutter_filament::ThreadPool(8); - _startTime = std::chrono::high_resolution_clock::now(); + _recordingStartTime = std::chrono::high_resolution_clock::now(); } else { delete _tp; } diff --git a/ios/src/FlutterFilamentApi.cpp b/ios/src/FlutterFilamentApi.cpp index c58fbe8b..069f54f9 100644 --- a/ios/src/FlutterFilamentApi.cpp +++ b/ios/src/FlutterFilamentApi.cpp @@ -470,9 +470,9 @@ extern "C" ((AssetManager *)assetManager)->transformToUnitCube(asset); } - FLUTTER_PLUGIN_EXPORT void set_position(void *assetManager, EntityId asset, float x, float y, float z) + FLUTTER_PLUGIN_EXPORT void set_position(void *assetManager, EntityId asset, float x, float y, float z, bool relative) { - ((AssetManager *)assetManager)->setPosition(asset, x, y, z); + ((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) diff --git a/lib/entities/entity_transform_controller.dart b/lib/entities/entity_transform_controller.dart new file mode 100644 index 00000000..3731bc85 --- /dev/null +++ b/lib/entities/entity_transform_controller.dart @@ -0,0 +1,116 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:flutter_filament/filament_controller.dart'; +import 'package:vector_math/vector_math_64.dart' as v; + +class EntityTransformController { + final FilamentController controller; + final FilamentEntity _entity; + + late Timer _ticker; + + double translationSpeed; + double rotationRadsPerSecond; + + bool _forward = false; + bool _strafeLeft = false; + bool _strafeRight = false; + bool _back = false; + bool _rotateLeft = false; + bool _rotateRight = false; + + EntityTransformController(this.controller, this._entity, + {this.translationSpeed = 1, this.rotationRadsPerSecond = pi / 2}) { + var translationSpeedPerTick = translationSpeed / (1000 / 16.667); + _ticker = Timer.periodic(const Duration(milliseconds: 16), (timer) { + _update(translationSpeedPerTick); + }); + } + + void _update(double translationSpeedPerTick) async { + var _position = v.Vector3.zero(); + var _rotation = v.Quaternion.identity(); + bool requiresUpdate = false; + if (_forward) { + _position.add(v.Vector3(0, 0, -translationSpeedPerTick)); + requiresUpdate = true; + } + if (_back) { + _position.add(v.Vector3(0, 0, translationSpeedPerTick)); + requiresUpdate = true; + } + if (_strafeLeft) { + _position.add(v.Vector3(-translationSpeedPerTick, 0, 0)); + requiresUpdate = true; + } + if (_strafeRight) { + _position.add(v.Vector3(translationSpeedPerTick, 0, 0)); + requiresUpdate = true; + } + + // todo - better to use pitch/yaw/roll + if (_rotateLeft) {} + if (_rotateRight) {} + + if (requiresUpdate) { + await controller.setPosition( + _entity, _position.x, _position.y, _position.z, + relative: true); + } + } + + void dispose() { + _ticker.cancel(); + } + + void forwardPressed() { + print("forward"); + _forward = true; + } + + Timer? _forwardTimer; + Timer? _backwardsTimer; + Timer? _strafeLeftTimer; + Timer? _strafeRightTimer; + + void forwardReleased() async { + _forwardTimer?.cancel(); + _forwardTimer = Timer(Duration(milliseconds: 50), () { + _forward = false; + }); + } + + void backPressed() { + _back = true; + } + + void backReleased() async { + _backwardsTimer?.cancel(); + _backwardsTimer = Timer(Duration(milliseconds: 50), () { + _back = false; + }); + } + + void strafeLeftPressed() { + _strafeLeft = true; + } + + void strafeLeftReleased() async { + _strafeLeftTimer?.cancel(); + _strafeLeftTimer = Timer(Duration(milliseconds: 50), () { + _strafeLeft = false; + }); + } + + void strafeRightPressed() { + _strafeRight = true; + } + + void strafeRightReleased() async { + _strafeRightTimer?.cancel(); + _strafeRightTimer = Timer(Duration(milliseconds: 50), () { + _strafeRight = false; + }); + } +} diff --git a/lib/filament_controller.dart b/lib/filament_controller.dart index c2afaf04..327b71d4 100644 --- a/lib/filament_controller.dart +++ b/lib/filament_controller.dart @@ -450,7 +450,8 @@ abstract class FilamentController { /// /// Sets the world space position for [entity] to the given coordinates. /// - Future setPosition(FilamentEntity entity, double x, double y, double z); + Future setPosition(FilamentEntity entity, double x, double y, double z, + {bool relative = false}); /// /// Enable/disable postprocessing. @@ -523,4 +524,9 @@ abstract class FilamentController { /// Sets the output directory where recorded PNGs will be placed. /// Future setRecordingOutputDirectory(String outputDirectory); + + // Stream get keyboardFocusRequested; + // void requestKeyboardFocus(); + + void control(FilamentEntity entity, {double? translationSpeed}); } diff --git a/lib/filament_controller_ffi.dart b/lib/filament_controller_ffi.dart index 953cdabe..e1acb5e8 100644 --- a/lib/filament_controller_ffi.dart +++ b/lib/filament_controller_ffi.dart @@ -6,11 +6,13 @@ import 'dart:developer' as dev; import 'package:flutter/services.dart'; import 'package:ffi/ffi.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter_filament/entities/entity_transform_controller.dart'; import 'package:flutter_filament/filament_controller.dart'; import 'package:flutter_filament/animations/animation_data.dart'; import 'package:flutter_filament/generated_bindings.dart'; +import 'package:flutter_filament/hardware/hardware_keyboard_listener.dart'; import 'package:flutter_filament/rendering_surface.dart'; import 'package:vector_math/vector_math_64.dart'; @@ -1049,12 +1051,12 @@ class FilamentControllerFFI extends FilamentController { } @override - Future setPosition( - FilamentEntity entity, double x, double y, double z) async { + Future setPosition(FilamentEntity entity, double x, double y, double z, + {bool relative = false}) async { if (_viewer == null) { throw Exception("No viewer available, ignoring"); } - set_position(_assetManager!, entity, x, y, z); + set_position(_assetManager!, entity, x, y, z, relative); } @override @@ -1321,4 +1323,12 @@ class FilamentControllerFFI extends FilamentController { set_recording_output_directory(_viewer!, pathPtr.cast()); allocator.free(pathPtr); } + + HardwareKeyboardListener? _keyboardListener; + void control(FilamentEntity entity, {double? translationSpeed}) { + _keyboardListener?.dispose(); + _keyboardListener = HardwareKeyboardListener(EntityTransformController( + this, entity, + translationSpeed: translationSpeed ?? 1.0)); + } } diff --git a/lib/generated_bindings.dart b/lib/generated_bindings.dart index ee99d594..76917fcd 100644 --- a/lib/generated_bindings.dart +++ b/lib/generated_bindings.dart @@ -541,14 +541,20 @@ external void transform_to_unit_cube( ); @ffi.Native< - ffi.Void Function(ffi.Pointer, EntityId, ffi.Float, ffi.Float, - ffi.Float)>(symbol: 'set_position', assetId: 'flutter_filament_plugin') + ffi.Void Function( + ffi.Pointer, + EntityId, + ffi.Float, + ffi.Float, + ffi.Float, + ffi.Bool)>(symbol: 'set_position', assetId: 'flutter_filament_plugin') external void set_position( ffi.Pointer assetManager, int asset, double x, double y, double z, + bool relative, ); @ffi.Native< diff --git a/lib/hardware/hardware_keyboard_listener.dart b/lib/hardware/hardware_keyboard_listener.dart new file mode 100644 index 00000000..d72a1d1f --- /dev/null +++ b/lib/hardware/hardware_keyboard_listener.dart @@ -0,0 +1,54 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_filament/entities/entity_transform_controller.dart'; +import 'package:flutter_filament/filament_controller.dart'; + +class HardwareKeyboardListener { + final EntityTransformController _controller; + HardwareKeyboardListener(this._controller) { + // Get the global handler. + final KeyMessageHandler? existing = + ServicesBinding.instance.keyEventManager.keyMessageHandler; + // The handler is guaranteed non-null since + // `FallbackKeyEventRegistrar.instance` is only called during + // `Focus.onFocusChange`, at which time `ServicesBinding.instance` must + // have been called somewhere. + assert(existing != null); + // Assign the global handler with a patched handler. + ServicesBinding.instance.keyEventManager.keyMessageHandler = (keyMessage) { + if (keyMessage.rawEvent == null) { + return false; + } + var event = keyMessage.rawEvent!; + switch (event.logicalKey) { + case LogicalKeyboardKey.keyW: + (event is RawKeyDownEvent) + ? _controller.forwardPressed() + : _controller.forwardReleased(); + break; + case LogicalKeyboardKey.keyA: + event is RawKeyDownEvent + ? _controller.strafeLeftPressed() + : _controller.strafeLeftReleased(); + break; + case LogicalKeyboardKey.keyS: + event is RawKeyDownEvent + ? _controller.backPressed() + : _controller.backReleased(); + break; + case LogicalKeyboardKey.keyD: + event is RawKeyDownEvent + ? _controller.strafeRightPressed() + : _controller.strafeRightReleased(); + break; + default: + break; + } + return true; + }; + } + + void dispose() { + ServicesBinding.instance.keyEventManager.keyMessageHandler = null; + _controller.dispose(); + } +}