From f2a458b9ca9a30abdf5f659178af40796e4a3adf Mon Sep 17 00:00:00 2001 From: Nick Fisher Date: Thu, 9 Nov 2023 15:08:34 +0800 Subject: [PATCH] add setCameraCulling method to set near/far culling plane and return vector_math:Frustum from planes returned by get_camera_frustum --- example/lib/camera_matrix_overlay.dart | 68 +++++++++++++++++----- example/lib/main.dart | 7 ++- example/lib/menus/camera_submenu.dart | 79 +++++++++++++++++++++++++- ios/include/FilamentViewer.hpp | 5 +- ios/include/FlutterFilamentApi.h | 1 + ios/src/FilamentViewer.cpp | 29 ++++++---- ios/src/FlutterFilamentApi.cpp | 5 ++ lib/filament_controller.dart | 10 +++- lib/filament_controller_ffi.dart | 41 ++++++++++++- lib/generated_bindings.dart | 8 +++ pubspec.lock | 78 +++---------------------- 11 files changed, 227 insertions(+), 104 deletions(-) diff --git a/example/lib/camera_matrix_overlay.dart b/example/lib/camera_matrix_overlay.dart index aab50022..2b3cb3ee 100644 --- a/example/lib/camera_matrix_overlay.dart +++ b/example/lib/camera_matrix_overlay.dart @@ -6,8 +6,12 @@ import 'package:flutter_filament/filament_controller.dart'; class CameraMatrixOverlay extends StatefulWidget { final FilamentController controller; + final bool showProjectionMatrices; - const CameraMatrixOverlay({super.key, required this.controller}); + const CameraMatrixOverlay( + {super.key, + required this.controller, + required this.showProjectionMatrices}); @override State createState() => _CameraMatrixOverlayState(); @@ -18,21 +22,36 @@ class _CameraMatrixOverlayState extends State { String? _cameraPosition; String? _cameraRotation; + String? _cameraProjectionMatrix; + String? _cameraCullingProjectionMatrix; + + void _tick(Timer timer) async { + var cameraPosition = await widget.controller.getCameraPosition(); + var cameraRotation = await widget.controller.getCameraRotation(); + + _cameraPosition = + "${cameraPosition.storage.map((v) => v.toStringAsFixed(2))}"; + _cameraRotation = + "${cameraRotation.storage.map((v) => v.toStringAsFixed(2))}"; + + if (widget.showProjectionMatrices) { + var projMatrix = await widget.controller.getCameraProjectionMatrix(); + var cullingMatrix = + await widget.controller.getCameraCullingProjectionMatrix(); + + _cameraProjectionMatrix = + projMatrix.storage.map((v) => v.toStringAsFixed(2)).join(","); + _cameraCullingProjectionMatrix = + cullingMatrix.storage.map((v) => v.toStringAsFixed(2)).join(","); + } + + setState(() {}); + } + void _updateTimer() { _cameraTimer?.cancel(); if (widget.controller.hasViewer.value) { - _cameraTimer = - Timer.periodic(const Duration(milliseconds: 50), (timer) async { - var cameraPosition = await widget.controller.getCameraPosition(); - var cameraRotation = await widget.controller.getCameraRotation(); - - _cameraPosition = - "${cameraPosition.storage.map((v) => v.toStringAsFixed(2))}"; - _cameraRotation = - "${cameraRotation.storage.map((v) => v.toStringAsFixed(2))}"; - - setState(() {}); - }); + _cameraTimer = Timer.periodic(const Duration(milliseconds: 50), _tick); } } @@ -45,6 +64,12 @@ class _CameraMatrixOverlayState extends State { widget.controller.hasViewer.addListener(_updateTimer); } + @override + void didUpdateWidget(CameraMatrixOverlay oldWidget) { + super.didUpdateWidget(oldWidget); + setState(() {}); + } + @override void dispose() { super.dispose(); @@ -59,7 +84,20 @@ class _CameraMatrixOverlayState extends State { color: Colors.black.withOpacity(0.5), borderRadius: BorderRadius.circular(29)), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), - child: Text("Camera position : $_cameraPosition $_cameraRotation", - style: const TextStyle(color: Colors.white, fontSize: 12))); + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text("Camera position : $_cameraPosition $_cameraRotation", + style: const TextStyle(color: Colors.white, fontSize: 12)), + widget.showProjectionMatrices + ? Text("Projection matrix : $_cameraProjectionMatrix", + style: const TextStyle(color: Colors.white, fontSize: 12)) + : Container(), + widget.showProjectionMatrices + ? Text("Culling matrix : $_cameraCullingProjectionMatrix", + style: const TextStyle(color: Colors.white, fontSize: 12)) + : Container(), + ])); } } diff --git a/example/lib/main.dart b/example/lib/main.dart index ab30eea3..249cede9 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -76,6 +76,7 @@ class ExampleWidgetState extends State { static FilamentEntity? directionalLight; static bool loop = false; + static final showProjectionMatrices = ValueNotifier(false); late StreamSubscription _listener; @@ -158,7 +159,11 @@ class ExampleWidgetState extends State { ? Container() : Padding( padding: const EdgeInsets.only(top: 40, left: 20, right: 20), - child: CameraMatrixOverlay(controller: _filamentController!), + child: ValueListenableBuilder( + valueListenable: showProjectionMatrices, + builder: (ctx, value, child) => CameraMatrixOverlay( + controller: _filamentController!, + showProjectionMatrices: value)), ), _filamentController == null ? Container() diff --git a/example/lib/menus/camera_submenu.dart b/example/lib/menus/camera_submenu.dart index 88822133..8ce199b8 100644 --- a/example/lib/menus/camera_submenu.dart +++ b/example/lib/menus/camera_submenu.dart @@ -15,8 +15,66 @@ class CameraSubmenu extends StatefulWidget { } class _CameraSubmenuState extends State { + double _near = 0.05; + double _far = 1000.0; + + final _menuController = MenuController(); + List _cameraMenu() { return [ + MenuItemButton( + closeOnActivate: false, + onPressed: () async { + ExampleWidgetState.showProjectionMatrices.value = + !ExampleWidgetState.showProjectionMatrices.value; + print("Set to ${ExampleWidgetState.showProjectionMatrices}"); + }, + child: Text( + '${ExampleWidgetState.showProjectionMatrices.value ? "Hide" : "Display"} camera projection/culling projection matrices', + style: TextStyle( + fontWeight: ExampleWidgetState.showProjectionMatrices.value + ? FontWeight.bold + : FontWeight.normal), + ), + ), + SubmenuButton( + menuChildren: [1.0, 7.0, 14.0, 28.0, 56.0] + .map((v) => MenuItemButton( + onPressed: () { + widget.controller.setCameraFocalLength(v); + }, + child: Text( + v.toStringAsFixed(2), + ), + )) + .toList(), + child: const Text("Set camera focal length")), + SubmenuButton( + menuChildren: [0.05, 0.1, 1.0, 10.0, 100.0] + .map((v) => MenuItemButton( + onPressed: () { + _near = v; + widget.controller.setCameraCulling(_near, _far); + }, + child: Text( + v.toStringAsFixed(2), + ), + )) + .toList(), + child: const Text("Set near")), + SubmenuButton( + menuChildren: [5.0, 50.0, 500.0, 1000.0, 100000.0] + .map((v) => MenuItemButton( + onPressed: () { + _far = v; + widget.controller.setCameraCulling(_near, _far); + }, + child: Text( + v.toStringAsFixed(2), + ), + )) + .toList(), + child: const Text("Set far")), MenuItemButton( onPressed: () async { widget.controller.setCameraPosition(1.0, 1.0, -1.0); @@ -80,7 +138,21 @@ class _CameraSubmenuState extends State { closeOnActivate: false, onPressed: () async { var frustum = await widget.controller.getCameraFrustum(); - await showDialog( + var normalString = [ + frustum.plane0, + frustum.plane1, + frustum.plane2, + frustum.plane3, + frustum.plane4, + frustum.plane5 + ] + .map((plane) => + plane.normal.storage + .map((v) => v.toStringAsFixed(2)) + .join(",") + + ",${plane.constant}") + .join("\n"); + showDialog( context: context, builder: (ctx) { return Center( @@ -88,8 +160,10 @@ class _CameraSubmenuState extends State { height: 300, width: 300, color: Colors.white, - child: Text(frustum.toString()))); + child: + Text("Frustum plane normals : $normalString "))); }); + _menuController.close(); }, child: const Text("Get frustum")), SubmenuButton( @@ -164,6 +238,7 @@ class _CameraSubmenuState extends State { @override Widget build(BuildContext context) { return SubmenuButton( + controller: _menuController, menuChildren: _cameraMenu(), child: const Text("Camera"), ); diff --git a/ios/include/FilamentViewer.hpp b/ios/include/FilamentViewer.hpp index 74fa1d48..c1881389 100644 --- a/ios/include/FilamentViewer.hpp +++ b/ios/include/FilamentViewer.hpp @@ -105,7 +105,8 @@ namespace polyvox const filament::Frustum getCameraFrustum(); void setCameraModelMatrix(const float *const matrix); void setCameraProjectionMatrix(const double *const matrix, double near, double far); - void setCameraFocalLength(float fl); + void setCameraFocalLength(float focalLength); + void setCameraCulling(double near, double far); void setCameraFocusDistance(float focusDistance); void setCameraManipulatorOptions(filament::camutils::Mode mode, double orbitSpeedX, double orbitSpeedY, double zoomSpeed); void grabBegin(float x, float y, bool pan); @@ -169,6 +170,8 @@ namespace polyvox math::mat4f _cameraPosition; math::mat4f _cameraRotation; void _createManipulator(); + double _near = 0.05; + double _far = 1000.0; ColorGrading *colorGrading = nullptr; diff --git a/ios/include/FlutterFilamentApi.h b/ios/include/FlutterFilamentApi.h index d53a0a71..2e2629f6 100644 --- a/ios/include/FlutterFilamentApi.h +++ b/ios/include/FlutterFilamentApi.h @@ -153,6 +153,7 @@ FLUTTER_PLUGIN_EXPORT const double* const get_camera_model_matrix(const void* co FLUTTER_PLUGIN_EXPORT const double* const get_camera_view_matrix(const void* const viewer); FLUTTER_PLUGIN_EXPORT const double* const get_camera_projection_matrix(const void* const viewer); FLUTTER_PLUGIN_EXPORT void set_camera_projection_matrix(const void* const viewer, const double *const matrix, double near, double far); +FLUTTER_PLUGIN_EXPORT void set_camera_culling(const void* const viewer, double near, double far); FLUTTER_PLUGIN_EXPORT const double* const get_camera_culling_projection_matrix(const void* const viewer); FLUTTER_PLUGIN_EXPORT const double* const get_camera_frustum(const void* const viewer); FLUTTER_PLUGIN_EXPORT void set_camera_focal_length(const void* const viewer, float focalLength); diff --git a/ios/src/FilamentViewer.cpp b/ios/src/FilamentViewer.cpp index 13bb7302..d25a8a99 100644 --- a/ios/src/FilamentViewer.cpp +++ b/ios/src/FilamentViewer.cpp @@ -96,9 +96,6 @@ namespace filament namespace polyvox { - const double kNearPlane = 0.05; // 5 cm - const double kFarPlane = 1000.0; // 1 km - // const float kAperture = 1.0f; // const float kShutterSpeed = 1.0f; // const float kSensitivity = 50.0f; @@ -163,8 +160,8 @@ namespace polyvox _view->setCamera(_mainCamera); _cameraFocalLength = 28.0f; - _mainCamera->setLensProjection(_cameraFocalLength, 1.0f, kNearPlane, - kFarPlane); + _mainCamera->setLensProjection(_cameraFocalLength, 1.0f, _near, + _far); // _mainCamera->setExposure(kAperture, kShutterSpeed, kSensitivity); Log("View created"); const float aperture = _mainCamera->getAperture(); @@ -743,8 +740,20 @@ namespace polyvox { Camera &cam = _view->getCamera(); _cameraFocalLength = focalLength; - cam.setLensProjection(_cameraFocalLength, 1.0f, kNearPlane, - kFarPlane); + cam.setLensProjection(_cameraFocalLength, 1.0f, _near, + _far); + } + + /// + /// Set the focal length of the active camera. + /// + void FilamentViewer::setCameraCulling(double near, double far) + { + Camera &cam = _view->getCamera(); + _near = near; + _far = far; + cam.setLensProjection(_cameraFocalLength, 1.0f, _near, + _far); } /// @@ -1036,8 +1045,8 @@ namespace polyvox const double aspect = (double)width / height; Camera &cam = _view->getCamera(); - cam.setLensProjection(_cameraFocalLength, 1.0f, kNearPlane, - kFarPlane); + cam.setLensProjection(_cameraFocalLength, 1.0f, _near, + _far); cam.setScaling({1.0 / aspect, 1.0}); @@ -1128,7 +1137,7 @@ namespace polyvox matrix[13], matrix[14], matrix[15]); - cam.setCustomProjection(projectionMatrix, near, far); + cam.setCustomProjection(projectionMatrix,projectionMatrix, near, far); } const math::mat4 FilamentViewer::getCameraModelMatrix() diff --git a/ios/src/FlutterFilamentApi.cpp b/ios/src/FlutterFilamentApi.cpp index edecde8c..3d9f3cbf 100644 --- a/ios/src/FlutterFilamentApi.cpp +++ b/ios/src/FlutterFilamentApi.cpp @@ -152,6 +152,11 @@ extern "C" { ((FilamentViewer *)viewer)->setCameraProjectionMatrix(matrix, near, far); } + + void set_camera_culling(const void *const viewer, double near, double far) + { + ((FilamentViewer *)viewer)->setCameraCulling(near, far); + } const double *const get_camera_frustum(const void *const viewer) { diff --git a/lib/filament_controller.dart b/lib/filament_controller.dart index e2b5c3f2..7f159eb8 100644 --- a/lib/filament_controller.dart +++ b/lib/filament_controller.dart @@ -335,10 +335,15 @@ abstract class FilamentController { Future setBloom(double bloom); /// - /// Sets the focal length of the camera. + /// Sets the focal length of the camera. Default value is 28.0. /// Future setCameraFocalLength(double focalLength); + /// + /// Sets the near/far culling planes for the active camera. Default values are 0.05/1000.0. See Camera.h for details. + /// + Future setCameraCulling(double near, double far); + /// /// Sets the focus distance for the camera. /// @@ -370,7 +375,8 @@ abstract class FilamentController { Future getCameraCullingProjectionMatrix(); /// - /// Get the camera's culling frustum in world space. Returns six Vector4s defining the left, right, bottom, top, far and near planes respectively. See Camera.h and Frustum.h for more details. + /// Get the camera's culling frustum in world space. Returns a (vector_math) [Frustum] instance where plane0-plane6 define the left, right, bottom, top, far and near planes respectively. + /// See Camera.h and (filament) Frustum.h for more details. /// Future getCameraFrustum(); diff --git a/lib/filament_controller_ffi.dart b/lib/filament_controller_ffi.dart index fe90c4d3..710dc470 100644 --- a/lib/filament_controller_ffi.dart +++ b/lib/filament_controller_ffi.dart @@ -846,6 +846,14 @@ class FilamentControllerFFI extends FilamentController { set_camera_focal_length(_viewer!, focalLength); } + @override + Future setCameraCulling(double near, double far) async { + if (_viewer == null) { + throw Exception("No viewer available, ignoring"); + } + set_camera_culling(_viewer!, near, far); + } + @override Future setCameraFocusDistance(double focusDistance) async { if (_viewer == null) { @@ -1084,11 +1092,20 @@ class FilamentControllerFFI extends FilamentController { _viewer!, mode.index, orbitSpeedX, orbitSpeedX, zoomSpeed); } + /// + /// I don't think these two methods are accurate - don't rely on them, use the Frustum values instead. + /// I think because we use [setLensProjection] and [setScaling] together, this projection matrix doesn't accurately reflect the field of view (because it's using an additional scaling matrix). + /// Also, the near/far planes never seem to get updated (which is what I would expect to see when calling [getCameraCullingProjectionMatrix]) + /// @override Future getCameraProjectionMatrix() async { if (_viewer == null) { throw Exception("No viewer available"); } + + print( + "WARNING: getCameraProjectionMatrix and getCameraCullingProjectionMatrix are not reliable. Consider these broken"); + var arrayPtr = get_camera_projection_matrix(_viewer!); var doubleList = arrayPtr.asTypedList(16); var projectionMatrix = Matrix4.fromList(doubleList); @@ -1101,6 +1118,8 @@ class FilamentControllerFFI extends FilamentController { if (_viewer == null) { throw Exception("No viewer available"); } + print( + "WARNING: getCameraProjectionMatrix and getCameraCullingProjectionMatrix are not reliable. Consider these broken"); var arrayPtr = get_camera_culling_projection_matrix(_viewer!); var doubleList = arrayPtr.asTypedList(16); var projectionMatrix = Matrix4.fromList(doubleList); @@ -1113,9 +1132,27 @@ class FilamentControllerFFI extends FilamentController { if (_viewer == null) { throw Exception("No viewer available"); } + var arrayPtr = get_camera_frustum(_viewer!); + var doubleList = arrayPtr.asTypedList(24); + var planeNormals = []; + for (int i = 0; i < 6; i++) { + planeNormals.add(Vector3.array(doubleList.sublist(i * 3, (i + 1) * 3))); + } - var projectionMatrix = await getCameraProjectionMatrix(); + var frustum = Frustum(); + frustum.plane0.setFromComponents( + doubleList[0], doubleList[1], doubleList[2], doubleList[3]); + frustum.plane1.setFromComponents( + doubleList[4], doubleList[5], doubleList[6], doubleList[7]); + frustum.plane2.setFromComponents( + doubleList[8], doubleList[9], doubleList[10], doubleList[11]); + frustum.plane3.setFromComponents( + doubleList[12], doubleList[13], doubleList[14], doubleList[15]); + frustum.plane4.setFromComponents( + doubleList[16], doubleList[17], doubleList[18], doubleList[19]); + frustum.plane5.setFromComponents( + doubleList[20], doubleList[21], doubleList[22], doubleList[23]); - return Frustum.matrix(projectionMatrix); + return frustum; } } diff --git a/lib/generated_bindings.dart b/lib/generated_bindings.dart index d033becc..d961686d 100644 --- a/lib/generated_bindings.dart +++ b/lib/generated_bindings.dart @@ -629,6 +629,14 @@ external void set_camera_projection_matrix( double far, ); +@ffi.Native, ffi.Double, ffi.Double)>( + symbol: 'set_camera_culling', assetId: 'flutter_filament_plugin') +external void set_camera_culling( + ffi.Pointer viewer, + double near, + double far, +); + @ffi.Native Function(ffi.Pointer)>( symbol: 'get_camera_culling_projection_matrix', assetId: 'flutter_filament_plugin') diff --git a/pubspec.lock b/pubspec.lock index 47d022a1..73757d23 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -57,14 +57,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" - crypto: - dependency: transitive - description: - name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab - url: "https://pub.dev" - source: hosted - version: "3.0.3" fake_async: dependency: transitive description: @@ -128,38 +120,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" - intl: - dependency: transitive - description: - name: intl - sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" - url: "https://pub.dev" - source: hosted - version: "0.18.1" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: f38a2c91c12f31726ca13015fbab3d2e9440edcb7c17b8b36ed9b85ed6eee6a2 - url: "https://pub.dev" - source: hosted - version: "9.0.11" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: "23770c69594f5260a79fe9d84e29f8b175d1b05d128e751c904b3cdf910e5dfc" - url: "https://pub.dev" - source: hosted - version: "1.0.9" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: b06739349ec2477e943055aea30172c5c7000225f79dad4702e2ec0eda79a6ff - url: "https://pub.dev" - source: hosted - version: "1.0.5" lints: dependency: transitive description: @@ -188,18 +148,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.5.0" meta: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.10.0" package_config: dependency: transitive description: @@ -293,14 +253,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c - url: "https://pub.dev" - source: hosted - version: "1.3.2" vector_math: dependency: "direct main" description: @@ -309,30 +261,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: a13d5503b4facefc515c8c587ce3cf69577a7b064a9f1220e005449cf1f64aad - url: "https://pub.dev" - source: hosted - version: "12.0.0" web: dependency: transitive description: name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: "14f1f70c51119012600c5f1f60ca68efda5a9b6077748163c6af2893ec5df8fc" url: "https://pub.dev" source: hosted - version: "0.3.0" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b - url: "https://pub.dev" - source: hosted - version: "2.4.0" + version: "0.2.1-beta" yaml: dependency: transitive description: @@ -350,5 +286,5 @@ packages: source: hosted version: "2.1.0" sdks: - dart: ">=3.2.0-194.0.dev <4.0.0" + dart: ">=3.2.0-157.0.dev <4.0.0" flutter: ">=3.16.0-0.2.pre"