diff --git a/thermion_dart/lib/src/input/src/delegate_gesture_handler.dart b/thermion_dart/lib/src/input/src/delegate_gesture_handler.dart index f5138789..668d3ba2 100644 --- a/thermion_dart/lib/src/input/src/delegate_gesture_handler.dart +++ b/thermion_dart/lib/src/input/src/delegate_gesture_handler.dart @@ -69,11 +69,11 @@ class DelegateInputHandler implements InputHandler { }); factory DelegateInputHandler.flight(ThermionViewer viewer, - {PickDelegate? pickDelegate, bool freeLook=false}) => + {PickDelegate? pickDelegate, bool freeLook=false, double? clampY, ThermionEntity? entity}) => DelegateInputHandler( viewer: viewer, pickDelegate: pickDelegate, - transformDelegate: FreeFlightInputHandlerDelegate(viewer), + transformDelegate: FreeFlightInputHandlerDelegate(viewer, clampY:clampY, entity:entity), actions: { InputType.MMB_HOLD_AND_MOVE: InputAction.ROTATE, InputType.SCROLLWHEEL: InputAction.TRANSLATE, diff --git a/thermion_dart/lib/src/input/src/implementations/free_flight_camera_delegate.dart b/thermion_dart/lib/src/input/src/implementations/free_flight_camera_delegate.dart index 3a85aaaf..04e35c66 100644 --- a/thermion_dart/lib/src/input/src/implementations/free_flight_camera_delegate.dart +++ b/thermion_dart/lib/src/input/src/implementations/free_flight_camera_delegate.dart @@ -5,14 +5,15 @@ import '../delegates.dart'; import '../input_handler.dart'; class FreeFlightInputHandlerDelegate implements InputHandlerDelegate { - final ThermionViewer viewer; + late Future entity; final Vector3? minBounds; final Vector3? maxBounds; final double rotationSensitivity; final double movementSensitivity; final double zoomSensitivity; final double panSensitivity; + final double? clampY; static final _up = Vector3(0, 1, 0); static final _forward = Vector3(0, 0, -1); @@ -23,15 +24,21 @@ class FreeFlightInputHandlerDelegate implements InputHandlerDelegate { double _queuedZoomDelta = 0.0; Vector3 _queuedMoveDelta = Vector3.zero(); - FreeFlightInputHandlerDelegate( - this.viewer, { - this.minBounds, - this.maxBounds, - this.rotationSensitivity = 0.001, - this.movementSensitivity = 0.1, - this.zoomSensitivity = 0.1, - this.panSensitivity = 0.1, - }); + FreeFlightInputHandlerDelegate(this.viewer, + {this.minBounds, + this.maxBounds, + this.rotationSensitivity = 0.001, + this.movementSensitivity = 0.1, + this.zoomSensitivity = 0.1, + this.panSensitivity = 0.1, + this.clampY, + ThermionEntity? entity}) { + if (entity != null) { + this.entity = Future.value(entity); + } else { + this.entity = viewer.getMainCameraEntity(); + } + } @override Future queue(InputAction action, Vector3? delta) async { @@ -73,10 +80,14 @@ class FreeFlightInputHandlerDelegate implements InputHandlerDelegate { return; } - Matrix4 currentModelMatrix = await viewer.getCameraModelMatrix(); - Vector3 currentPosition = currentModelMatrix.getTranslation(); + final activeCamera = await viewer.getActiveCamera(); + Matrix4 currentViewMatrix = activeCamera.getViewMatrix(); + + + Matrix4 currentTransform = await viewer.getLocalTransform(await entity); + Vector3 currentPosition = currentTransform.getTranslation(); Quaternion currentRotation = - Quaternion.fromRotation(currentModelMatrix.getRotation()); + Quaternion.fromRotation(currentTransform.getRotation()); // Apply rotation if (_queuedRotationDelta.length2 > 0.0) { @@ -135,7 +146,7 @@ class FreeFlightInputHandlerDelegate implements InputHandlerDelegate { // Update camera Matrix4 newModelMatrix = Matrix4.compose(currentPosition, currentRotation, Vector3(1, 1, 1)); - await viewer.setCameraModelMatrix4(newModelMatrix); + await viewer.setTransform(await entity, newModelMatrix); _executing = false; } @@ -151,6 +162,10 @@ class FreeFlightInputHandlerDelegate implements InputHandlerDelegate { position.y = position.y.clamp(double.negativeInfinity, maxBounds!.y); position.z = position.z.clamp(double.negativeInfinity, maxBounds!.z); } + + if (clampY != null) { + position.y = clampY!; + } return position; } } diff --git a/thermion_dart/lib/src/viewer/src/ffi/src/camera_ffi.dart b/thermion_dart/lib/src/viewer/src/ffi/src/camera_ffi.dart index 457c5615..5b797cbc 100644 --- a/thermion_dart/lib/src/viewer/src/ffi/src/camera_ffi.dart +++ b/thermion_dart/lib/src/viewer/src/ffi/src/camera_ffi.dart @@ -7,7 +7,6 @@ import '../../thermion_viewer_base.dart'; import 'thermion_dart.g.dart'; class ThermionFFICamera extends Camera { - final Pointer camera; final Pointer engine; late ThermionEntity _entity; @@ -52,7 +51,7 @@ class ThermionFFICamera extends Camera { Camera_setModelMatrix(camera, matrix4ToDouble4x4(matrix)); } - @override + @override bool operator ==(Object other) => identical(this, other) || other is ThermionFFICamera && @@ -61,4 +60,19 @@ class ThermionFFICamera extends Camera { @override int get hashCode => camera.hashCode; + + @override + Future getCullingFar() async { + return Camera_getCullingFar(camera); + } + + @override + Future getNear() async { + return Camera_getNear(camera); + } + + @override + Future getFocalLength() async { + return Camera_getFocalLength(camera); + } } diff --git a/thermion_dart/lib/src/viewer/src/ffi/src/thermion_dart.g.dart b/thermion_dart/lib/src/viewer/src/ffi/src/thermion_dart.g.dart index 6429cd52..3436ea07 100644 --- a/thermion_dart/lib/src/viewer/src/ffi/src/thermion_dart.g.dart +++ b/thermion_dart/lib/src/viewer/src/ffi/src/thermion_dart.g.dart @@ -819,11 +819,6 @@ external ffi.Pointer get_camera( int entity, ); -@ffi.Native)>(isLeaf: true) -external double get_camera_focal_length( - ffi.Pointer camera, -); - @ffi.Native)>(isLeaf: true) external double4x4 get_camera_model_matrix( ffi.Pointer camera, @@ -872,6 +867,26 @@ external void set_camera_projection_from_fov( bool horizontal, ); +@ffi.Native)>(isLeaf: true) +external double get_camera_focal_length( + ffi.Pointer camera, +); + +@ffi.Native)>(isLeaf: true) +external double Camera_getFocalLength( + ffi.Pointer camera, +); + +@ffi.Native)>(isLeaf: true) +external double Camera_getNear( + ffi.Pointer camera, +); + +@ffi.Native)>(isLeaf: true) +external double Camera_getCullingFar( + ffi.Pointer camera, +); + @ffi.Native)>(isLeaf: true) external double get_camera_near( ffi.Pointer camera, @@ -983,6 +998,12 @@ external ffi.Pointer SceneManager_getCameraAt( int index, ); +@ffi.Native Function(ffi.Pointer)>( + isLeaf: true) +external ffi.Pointer SceneManager_getActiveCamera( + ffi.Pointer sceneManager, +); + @ffi.Native< ffi.Int Function(ffi.Pointer, EntityId, ffi.Pointer)>(isLeaf: true) diff --git a/thermion_dart/lib/src/viewer/src/ffi/src/thermion_viewer_ffi.dart b/thermion_dart/lib/src/viewer/src/ffi/src/thermion_viewer_ffi.dart index 21daa4a6..3b0f698a 100644 --- a/thermion_dart/lib/src/viewer/src/ffi/src/thermion_viewer_ffi.dart +++ b/thermion_dart/lib/src/viewer/src/ffi/src/thermion_viewer_ffi.dart @@ -105,22 +105,28 @@ class ThermionViewerFFI extends ThermionViewer { Future updateViewportAndCameraProjection(double width, double height) async { viewportDimensions = (width * pixelRatio, height * pixelRatio); update_viewport(_viewer!, width.toInt(), height.toInt()); - var mainCamera = await getMainCamera() as ThermionFFICamera; - var near = await getCameraCullingNear(); - if (near.abs() < 0.000001) { - near = kNear; - } - var far = await getCameraCullingFar(); - if (far.abs() < 0.000001) { - far = kFar; - } - var aspect = viewportDimensions.$1 / viewportDimensions.$2; - var focalLength = get_camera_focal_length(mainCamera.camera); - if (focalLength.abs() < 0.1) { - focalLength = kFocalLength; + final cameraCount = await getCameraCount(); + + for (int i = 0; i < cameraCount; i++) { + var camera = await getCameraAt(i); + var near = await camera.getNear(); + if (near.abs() < 0.000001) { + near = kNear; + } + var far = await camera.getCullingFar(); + if (far.abs() < 0.000001) { + far = kFar; + } + + var aspect = viewportDimensions.$1 / viewportDimensions.$2; + var focalLength = await camera.getFocalLength(); + if (focalLength.abs() < 0.1) { + focalLength = kFocalLength; + } + camera.setLensProjection( + near: near, far: far, aspect: aspect, focalLength: focalLength); } - Camera_setLensProjection(mainCamera.camera, near, far, aspect, focalLength); } Future createSwapChain(double width, double height, @@ -2174,9 +2180,10 @@ class ThermionViewerFFI extends ThermionViewer { } Future createCamera() async { - var camera = SceneManager_createCamera(_sceneManager!); + var cameraPtr = SceneManager_createCamera(_sceneManager!); var engine = Viewer_getEngine(_viewer!); - return ThermionFFICamera(camera, engine); + var camera = ThermionFFICamera(cameraPtr, engine); + return camera; } Future destroyCamera(ThermionFFICamera camera) async { @@ -2190,6 +2197,14 @@ class ThermionViewerFFI extends ThermionViewer { SceneManager_setCamera(_sceneManager!, camera.camera); } + /// + /// + /// + Future getActiveCamera() async { + final ptr = SceneManager_getActiveCamera(_sceneManager!); + return ThermionFFICamera(ptr, Viewer_getEngine(_viewer!)); + } + final _hooks = []; @override diff --git a/thermion_dart/lib/src/viewer/src/shared_types/camera.dart b/thermion_dart/lib/src/viewer/src/shared_types/camera.dart index e542d348..3b306036 100644 --- a/thermion_dart/lib/src/viewer/src/shared_types/camera.dart +++ b/thermion_dart/lib/src/viewer/src/shared_types/camera.dart @@ -18,4 +18,8 @@ abstract class Camera { ThermionEntity getEntity(); Future setTransform(Matrix4 transform); + + Future getNear(); + Future getCullingFar(); + Future getFocalLength(); } diff --git a/thermion_dart/lib/src/viewer/src/thermion_viewer_base.dart b/thermion_dart/lib/src/viewer/src/thermion_viewer_base.dart index 0e6ad543..c8fcff7a 100644 --- a/thermion_dart/lib/src/viewer/src/thermion_viewer_base.dart +++ b/thermion_dart/lib/src/viewer/src/thermion_viewer_base.dart @@ -970,6 +970,11 @@ abstract class ThermionViewer { /// Future setActiveCamera(covariant Camera camera); + /// + /// + /// + Future getActiveCamera(); + /// /// /// diff --git a/thermion_dart/native/include/SceneManager.hpp b/thermion_dart/native/include/SceneManager.hpp index 0ad2a748..b40a7eb7 100644 --- a/thermion_dart/native/include/SceneManager.hpp +++ b/thermion_dart/native/include/SceneManager.hpp @@ -310,6 +310,8 @@ namespace thermion_filament Camera* getCameraAt(size_t index); + Camera* getActiveCamera(); + private: gltfio::AssetLoader *_assetLoader = nullptr; const ResourceLoaderWrapperImpl *const _resourceLoaderWrapper; diff --git a/thermion_dart/native/include/ThermionDartApi.h b/thermion_dart/native/include/ThermionDartApi.h index 7b47ed31..e378d533 100644 --- a/thermion_dart/native/include/ThermionDartApi.h +++ b/thermion_dart/native/include/ThermionDartApi.h @@ -217,7 +217,6 @@ extern "C" EMSCRIPTEN_KEEPALIVE void set_camera_exposure(TCamera *camera, float aperture, float shutterSpeed, float sensitivity); EMSCRIPTEN_KEEPALIVE void set_camera_model_matrix(TCamera *camera, double4x4 matrix); EMSCRIPTEN_KEEPALIVE TCamera *get_camera(TViewer *viewer, EntityId entity); - EMSCRIPTEN_KEEPALIVE double get_camera_focal_length(TCamera *const camera); EMSCRIPTEN_KEEPALIVE double4x4 get_camera_model_matrix(TCamera *const camera); EMSCRIPTEN_KEEPALIVE double4x4 get_camera_view_matrix(TCamera *const camera); EMSCRIPTEN_KEEPALIVE double4x4 get_camera_projection_matrix(TCamera *const camera); @@ -225,6 +224,10 @@ extern "C" EMSCRIPTEN_KEEPALIVE const double *const get_camera_frustum(TCamera *const camera); EMSCRIPTEN_KEEPALIVE void set_camera_projection_matrix(TCamera *camera, double4x4 matrix, double near, double far); EMSCRIPTEN_KEEPALIVE void set_camera_projection_from_fov(TCamera *camera, double fovInDegrees, double aspect, double near, double far, bool horizontal); + EMSCRIPTEN_KEEPALIVE double get_camera_focal_length(TCamera *const camera); + EMSCRIPTEN_KEEPALIVE double Camera_getFocalLength(TCamera *const camera); + EMSCRIPTEN_KEEPALIVE double Camera_getNear(TCamera *const camera); + EMSCRIPTEN_KEEPALIVE double Camera_getCullingFar(TCamera *const camera); EMSCRIPTEN_KEEPALIVE double get_camera_near(TCamera *camera); EMSCRIPTEN_KEEPALIVE double get_camera_culling_far(TCamera *camera); EMSCRIPTEN_KEEPALIVE float get_camera_fov(TCamera *camera, bool horizontal); @@ -247,6 +250,8 @@ extern "C" EMSCRIPTEN_KEEPALIVE void SceneManager_setCamera(TSceneManager *sceneManager, TCamera* camera); EMSCRIPTEN_KEEPALIVE size_t SceneManager_getCameraCount(TSceneManager *sceneManager); EMSCRIPTEN_KEEPALIVE TCamera* SceneManager_getCameraAt(TSceneManager *sceneManager, size_t index); + EMSCRIPTEN_KEEPALIVE TCamera* SceneManager_getActiveCamera(TSceneManager *sceneManager); + EMSCRIPTEN_KEEPALIVE int hide_mesh(TSceneManager *sceneManager, EntityId entity, const char *meshName); EMSCRIPTEN_KEEPALIVE int reveal_mesh(TSceneManager *sceneManager, EntityId entity, const char *meshName); EMSCRIPTEN_KEEPALIVE void set_post_processing(TViewer *viewer, bool enabled); diff --git a/thermion_dart/native/src/SceneManager.cpp b/thermion_dart/native/src/SceneManager.cpp index 83c9e1e0..324a97e5 100644 --- a/thermion_dart/native/src/SceneManager.cpp +++ b/thermion_dart/native/src/SceneManager.cpp @@ -2678,6 +2678,11 @@ EntityId SceneManager::createGeometry( } return _cameras[index-1]; } + + Camera* SceneManager::getActiveCamera() { + auto& camera = _view->getCamera(); + return &camera; + } } // namespace thermion_filament diff --git a/thermion_dart/native/src/ThermionDartApi.cpp b/thermion_dart/native/src/ThermionDartApi.cpp index d24a2334..78ed51ce 100644 --- a/thermion_dart/native/src/ThermionDartApi.cpp +++ b/thermion_dart/native/src/ThermionDartApi.cpp @@ -1086,6 +1086,21 @@ EMSCRIPTEN_KEEPALIVE EntityId Camera_getEntity(TCamera* tCamera) { return Entity::smuggle(camera->getEntity()); } +EMSCRIPTEN_KEEPALIVE double Camera_getFocalLength(TCamera *const tCamera) { + auto * camera = reinterpret_cast(tCamera); + return camera->getFocalLength(); +} + +EMSCRIPTEN_KEEPALIVE double Camera_getNear(TCamera *const tCamera) { + auto * camera = reinterpret_cast(tCamera); + return camera->getNear(); +} + +EMSCRIPTEN_KEEPALIVE double Camera_getCullingFar(TCamera *const tCamera) { + auto * camera = reinterpret_cast(tCamera); + return camera->getCullingFar(); +} + EMSCRIPTEN_KEEPALIVE TCamera *Engine_getCameraComponent(TEngine* tEngine, EntityId entityId) { auto * engine = reinterpret_cast(tEngine); auto * camera = engine->getCameraComponent(utils::Entity::import(entityId)); @@ -1129,4 +1144,11 @@ EMSCRIPTEN_KEEPALIVE TCamera* SceneManager_getCameraAt(TSceneManager *tSceneMana auto * camera = sceneManager->getCameraAt(index); return reinterpret_cast(camera); } + +EMSCRIPTEN_KEEPALIVE TCamera* SceneManager_getActiveCamera(TSceneManager *tSceneManager) { + auto * sceneManager = reinterpret_cast(tSceneManager); + auto * camera = sceneManager->getActiveCamera(); + return reinterpret_cast(camera); +} + } diff --git a/thermion_dart/test/camera_tests.dart b/thermion_dart/test/camera_tests.dart index 75e340d9..1b64362e 100644 --- a/thermion_dart/test/camera_tests.dart +++ b/thermion_dart/test/camera_tests.dart @@ -142,13 +142,19 @@ void main() async { await viewer.setBackgroundColor(1.0, 0.0, 1.0, 1.0); await viewer.createGeometry(GeometryHelper.cube()); await testHelper.capture(viewer, "create_camera_main_camera"); + var newCamera = await viewer.createCamera(); await newCamera.setTransform(Matrix4.translation(Vector3(0, 0, 4))); newCamera.setLensProjection(); await viewer.setActiveCamera(newCamera); + + expect(await viewer.getActiveCamera(), newCamera); + await testHelper.capture(viewer, "create_camera_new_camera"); + final mainCamera = await viewer.getMainCamera(); await viewer.setActiveCamera(mainCamera); + expect(await viewer.getActiveCamera(), mainCamera); await testHelper.capture(viewer, "create_camera_back_to_main"); expect(viewer.getCameraCount(), 2); diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/camera_selector_widget.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/camera_selector_widget.dart new file mode 100644 index 00000000..756c3045 --- /dev/null +++ b/thermion_flutter/thermion_flutter/lib/src/widgets/src/camera/camera_selector_widget.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:thermion_dart/thermion_dart.dart'; + +class CameraSelectorWidget extends StatefulWidget { + final ThermionViewer viewer; + + const CameraSelectorWidget({Key? key, required this.viewer}) : super(key: key); + + @override + _CameraSelectorWidgetState createState() => _CameraSelectorWidgetState(); +} + +class _CameraSelectorWidgetState extends State { + int _activeIndex = 0; + + @override + Widget build(BuildContext context) { + int cameraCount = widget.viewer.getCameraCount(); + + return Container( + height:32, + margin: const EdgeInsets.all(8), + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + _buildCameraButton("Main", 0), + if (cameraCount > 1) const VerticalDivider(width: 16, thickness: 1), + ...List.generate(cameraCount - 1, (index) { + return _buildCameraButton("${index + 1}", index + 1); + }), + ], + ), + ); + } + + Widget _buildCameraButton(String label, int index) { + bool isActive = _activeIndex == index; + return Flexible(child:TextButton( + onPressed: () async { + if (index == 0) { + await widget.viewer.setMainCamera(); + } else { + Camera camera = widget.viewer.getCameraAt(index); + await widget.viewer.setActiveCamera(camera); + } + setState(() { + _activeIndex = index; + }); + }, + style: TextButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + minimumSize: Size.zero, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + backgroundColor: isActive ? Colors.blue.withOpacity(0.1) : null, + ), + child: Text( + label, + style: TextStyle( + fontSize: 10, + fontWeight: isActive ? FontWeight.bold : FontWeight.normal, + color: isActive ? Colors.blue : Colors.black87, + ), + ), + )); + } +} \ No newline at end of file diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/widgets.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/widgets.dart index 8c2cbf41..ea801eaa 100644 --- a/thermion_flutter/thermion_flutter/lib/src/widgets/widgets.dart +++ b/thermion_flutter/thermion_flutter/lib/src/widgets/widgets.dart @@ -2,3 +2,4 @@ library; export 'src/thermion_widget.dart'; export 'src/thermion_listener_widget.dart'; +export 'src/camera/camera_selector_widget.dart';