diff --git a/.gitignore b/.gitignore index 12aef55c..dad2f3af 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ .DS_Store **/*/build **/*/pubspec.lock +pubspec_overrides.yaml +/pubspec.lock +*.iml diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..d0395fe9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,231 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## 2024-06-22 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`thermion_dart` - `v0.1.1+1`](#thermion_dart---v0111) + - [`thermion_flutter` - `v0.1.1+6`](#thermion_flutter---v0116) + - [`thermion_flutter_web` - `v0.0.1+5`](#thermion_flutter_web---v0015) + - [`thermion_flutter_platform_interface` - `v0.1.0+5`](#thermion_flutter_platform_interface---v0105) + - [`thermion_flutter_ffi` - `v0.1.0+5`](#thermion_flutter_ffi---v0105) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `thermion_flutter_web` - `v0.0.1+5` + - `thermion_flutter_platform_interface` - `v0.1.0+5` + - `thermion_flutter_ffi` - `v0.1.0+5` + +--- + +#### `thermion_dart` - `v0.1.1+1` + + - **DOCS**: update with links to playground. + +#### `thermion_flutter` - `v0.1.1+6` + + - **DOCS**: update with links to playground. + + +## 2024-06-21 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`thermion_dart` - `v0.1.1`](#thermion_dart---v011) + +--- + +#### `thermion_dart` - `v0.1.1` + + - Bump "thermion_dart" to `0.1.1`. + + +## 2024-06-21 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`thermion_dart` - `v0.1.0+4`](#thermion_dart---v0104) + - [`thermion_flutter_web` - `v0.0.1+4`](#thermion_flutter_web---v0014) + - [`thermion_flutter_platform_interface` - `v0.1.0+4`](#thermion_flutter_platform_interface---v0104) + - [`thermion_flutter` - `v0.1.1+5`](#thermion_flutter---v0115) + - [`thermion_flutter_ffi` - `v0.1.0+4`](#thermion_flutter_ffi---v0104) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `thermion_flutter_web` - `v0.0.1+4` + - `thermion_flutter_platform_interface` - `v0.1.0+4` + - `thermion_flutter` - `v0.1.1+5` + - `thermion_flutter_ffi` - `v0.1.0+4` + +--- + +#### `thermion_dart` - `v0.1.0+4` + + - **FIX**: add dummy asset to build.dart on Linux builds so we can use the package on a Linux host. + + +## 2024-06-21 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`thermion_dart` - `v0.1.0+3`](#thermion_dart---v0103) + - [`thermion_flutter_web` - `v0.0.1+3`](#thermion_flutter_web---v0013) + - [`thermion_flutter` - `v0.1.1+4`](#thermion_flutter---v0114) + - [`thermion_flutter_platform_interface` - `v0.1.0+3`](#thermion_flutter_platform_interface---v0103) + - [`thermion_flutter_ffi` - `v0.1.0+3`](#thermion_flutter_ffi---v0103) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `thermion_flutter_web` - `v0.0.1+3` + - `thermion_flutter` - `v0.1.1+4` + - `thermion_flutter_platform_interface` - `v0.1.0+3` + - `thermion_flutter_ffi` - `v0.1.0+3` + +--- + +#### `thermion_dart` - `v0.1.0+3` + + - **FIX**: exit build.dart early on Linux builds so we can use the package on a Linux host. + + +## 2024-06-21 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`thermion_dart` - `v0.1.0+2`](#thermion_dart---v0102) + - [`thermion_flutter_ffi` - `v0.1.0+2`](#thermion_flutter_ffi---v0102) + - [`thermion_flutter_web` - `v0.0.1+2`](#thermion_flutter_web---v0012) + - [`thermion_flutter` - `v0.1.1+3`](#thermion_flutter---v0113) + - [`thermion_flutter_platform_interface` - `v0.1.0+2`](#thermion_flutter_platform_interface---v0102) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `thermion_flutter_web` - `v0.0.1+2` + - `thermion_flutter` - `v0.1.1+3` + - `thermion_flutter_platform_interface` - `v0.1.0+2` + +--- + +#### `thermion_dart` - `v0.1.0+2` + + - **REFACTOR**: rearrange some stubs/imports for easier web WASM deployment. + +#### `thermion_flutter_ffi` - `v0.1.0+2` + + - **REFACTOR**: rearrange some stubs/imports for easier web WASM deployment. + + +## 2024-06-21 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`thermion_flutter` - `v0.1.1+2`](#thermion_flutter---v0112) + +--- + +#### `thermion_flutter` - `v0.1.1+2` + + - **FIX**: update Flutter example project to use new API. + - **FIX**: add logging dependency to thermion_flutter. + + +## 2024-06-21 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`thermion_dart` - `v0.1.0+1`](#thermion_dart---v0101) + - [`thermion_flutter` - `v0.1.1+1`](#thermion_flutter---v0111) + - [`thermion_flutter_web` - `v0.0.1+1`](#thermion_flutter_web---v0011) + - [`thermion_flutter_platform_interface` - `v0.1.0+1`](#thermion_flutter_platform_interface---v0101) + - [`thermion_flutter_ffi` - `v0.1.0+1`](#thermion_flutter_ffi---v0101) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `thermion_flutter_platform_interface` - `v0.1.0+1` + - `thermion_flutter_ffi` - `v0.1.0+1` + +--- + +#### `thermion_dart` - `v0.1.0+1` + + - **REFACTOR**: export ThermionViewerWasm for web and hide FFI/WASM version. + - **FIX**: use preserveDrawingBuffer=true on web. + +#### `thermion_flutter` - `v0.1.1+1` + + - **REFACTOR**: export ThermionViewerWasm for web and hide FFI/WASM version. + - **FIX**: catch exception if gizmo unavailable in ThermionGestureDestectorDesktop. + +#### `thermion_flutter_web` - `v0.0.1+1` + + - **REFACTOR**: export ThermionViewerWasm for web and hide FFI/WASM version. + diff --git a/README.md b/README.md index 2cc67dfc..00d364a3 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ Quickstart (Flutter)DocumentationShowcase • + PlaygroundDiscord

diff --git a/docs.json b/docs.json index f177d25e..bb7ab58a 100644 --- a/docs.json +++ b/docs.json @@ -7,10 +7,15 @@ "Getting Started", [ ["Overview", "/"], - ["Quick Start", "/quickstart"] + ["Quick Start", "/quickstart"], + ["Playground", "https://dartpad.thermion.dev"] ] ], - ["Misc.", [["Contributing", "/contributing"]]] + ["Misc.", [ + ["Windows", "/windows"], + ["Android", "/android"], + ["Contributing", "/contributing"] + ]] ] } diff --git a/docs/contributing.mdx b/docs/contributing.mdx index a4466ca5..3f3758f3 100644 --- a/docs/contributing.mdx +++ b/docs/contributing.mdx @@ -4,3 +4,7 @@ Thermion is an open source project and we welcome all contributions from every l Please [join us on Discord](https://discord.gg/h2VdDK3EAQ) if you'd like some guidance or just want to chat. +## + +We are now using [Melos](https://melos.invertase.dev/) to manage the repository. This lets us auto-generate changelogs & versioning from commit messages, so if you wish to submit a PR, please use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). + diff --git a/docs/images/drawing.png b/docs/images/drawing.png index 87e1931d..57684da7 100644 Binary files a/docs/images/drawing.png and b/docs/images/drawing.png differ diff --git a/examples/flutter/example/lib/example_viewport.dart b/examples/flutter/example/lib/example_viewport.dart index 4b48ea6c..4be74ea2 100644 --- a/examples/flutter/example/lib/example_viewport.dart +++ b/examples/flutter/example/lib/example_viewport.dart @@ -1,36 +1,34 @@ import 'package:flutter/widgets.dart'; -import 'package:thermion_flutter/filament/widgets/camera/entity_controller_mouse_widget.dart'; -import 'package:thermion_flutter/filament/widgets/camera/gestures/filament_gesture_detector.dart'; -import 'package:thermion_flutter/filament/widgets/filament_widget.dart'; +import 'package:thermion_flutter/thermion/widgets/camera/entity_controller_mouse_widget.dart'; import 'package:thermion_flutter/thermion_flutter.dart'; import 'package:thermion_dart/thermion_dart/entities/entity_transform_controller.dart'; class ExampleViewport extends StatelessWidget { - final ThermionFlutterPlugin? controller; + final ThermionViewer? viewer; final EntityTransformController? entityTransformController; final EdgeInsets padding; final FocusNode keyboardFocusNode; const ExampleViewport( {super.key, - required this.controller, + required this.viewer, required this.padding, required this.keyboardFocusNode, this.entityTransformController}); @override Widget build(BuildContext context) { - return controller != null + return viewer != null ? Padding( padding: padding, child: EntityTransformMouseControllerWidget( transformController: entityTransformController, - child: FilamentGestureDetector( + child: ThermionGestureDetector( showControlOverlay: true, - controller: controller!.viewer, + controller: viewer!, child: ThermionWidget( - plugin: controller!, + viewer: viewer!, )))) : Container(); } diff --git a/examples/flutter/example/lib/main.dart b/examples/flutter/example/lib/main.dart index b5cbf62c..4ab80781 100644 --- a/examples/flutter/example/lib/main.dart +++ b/examples/flutter/example/lib/main.dart @@ -1,14 +1,10 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:thermion_flutter/filament/widgets/debug/entity_list_widget.dart'; -import 'package:thermion_flutter_example/camera_matrix_overlay.dart'; +import 'package:thermion_flutter/thermion/widgets/debug/entity_list_widget.dart'; import 'package:thermion_flutter_example/menus/controller_menu.dart'; import 'package:thermion_flutter_example/example_viewport.dart'; -import 'package:thermion_dart/thermion_dart/entities/entity_transform_controller.dart'; import 'package:thermion_flutter_example/menus/scene_menu.dart'; import 'package:thermion_flutter/thermion_flutter.dart'; -import 'package:thermion_flutter_example/picker_result_widget.dart'; - const loadDefaultScene = bool.hasEnvironment('--load-default-scene'); @@ -38,8 +34,7 @@ class _MyAppState extends State with SingleTickerProviderStateMixin { bodyMedium: TextStyle(fontSize: 12))), // showPerformanceOverlay: true, home: const Scaffold( - backgroundColor: Color(0x00000000), - body: ExampleWidget())); + backgroundColor: Color(0x00000000), body: ExampleWidget())); } } @@ -55,7 +50,7 @@ class ExampleWidget extends StatefulWidget { enum MenuType { controller, assets, camera, misc } class ExampleWidgetState extends State { - final _plugin = ThermionFlutterPlugin(); + ThermionViewer? _viewer; EdgeInsets _viewportMargin = EdgeInsets.zero; @@ -99,105 +94,103 @@ class ExampleWidgetState extends State { @override Widget build(BuildContext context) { - return FutureBuilder( - future: _plugin.initialized, - builder: (_, AsyncSnapshot initialized) { - var isInitialized = initialized.data == true; - - return Stack( - fit: StackFit.expand, - children: [ - if (isInitialized) - Positioned.fill( - child: ExampleViewport( - controller: isInitialized ? _plugin : null, - entityTransformController: _transformController, - padding: _viewportMargin, - keyboardFocusNode: _sharedFocusNode), + return Stack(fit: StackFit.expand, children: [ + if (_viewer != null) + Positioned.fill( + child: ExampleViewport( + viewer: _viewer!, + entityTransformController: _transformController, + padding: _viewportMargin, + keyboardFocusNode: _sharedFocusNode), + ), + Positioned( + bottom: 30, + left: 0, + right: 10, + height: 30, + child: Container( + height: 30, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + color: Colors.white.withOpacity(0.25), ), - Positioned( - bottom: 30, - left: 0, - right: 10, - height: 30, - child: Container( - height: 30, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(30), - color: Colors.white.withOpacity(0.25), - ), - padding: const EdgeInsets.symmetric(horizontal: 10), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - ControllerMenu( - sharedFocusNode: _sharedFocusNode, - controller: _plugin, - onToggleViewport: () { - setState(() { - _viewportMargin = - (_viewportMargin == EdgeInsets.zero) - ? const EdgeInsets.all(30) - : EdgeInsets.zero; - }); - }, - onControllerDestroyed: () {}, - onControllerCreated: () {}), - SceneMenu( - sharedFocusNode: _sharedFocusNode, - controller: _plugin, - ), - GestureDetector( - onTap: () async { - await _plugin.viewer.loadGlb( - 'assets/shapes/shapes.glb', - numInstances: 1); - }, - child: Container( - color: Colors.transparent, - child: const Text("shapes.glb"))), - const SizedBox(width: 5), - GestureDetector( - onTap: () async { - await _plugin.viewer.loadGlb('assets/1.glb'); - }, - child: Container( - color: Colors.transparent, - child: const Text("1.glb"))), - const SizedBox(width: 5), - GestureDetector( - onTap: () async { - await _plugin.viewer.loadGlb('assets/2.glb'); - }, - child: Container( - color: Colors.transparent, - child: const Text("2.glb"))), - const SizedBox(width: 5), - GestureDetector( - onTap: () async { - await _plugin.viewer.loadGlb('assets/3.glb'); - }, - child: Container( - color: Colors.transparent, - child: const Text("3.glb"))), - Expanded(child: Container()), - ]))), - if (isInitialized) ...[ - Positioned(top:10, left:10, width:200, height:200, child:Container( - child:EntityListWidget(controller: _plugin.viewer))), - // Padding( - // padding: const EdgeInsets.only(top: 10, left: 20, right: 20), - // child: ValueListenableBuilder( - // valueListenable: showProjectionMatrices, - // builder: (ctx, value, child) => CameraMatrixOverlay( - // controller: _plugin.viewer, showProjectionMatrices: value)), - // ), - // Align( - // alignment: Alignment.topRight, - // child: PickerResultWidget(controller: _plugin.viewer), - // ) - ] - ]); - }); + padding: const EdgeInsets.symmetric(horizontal: 10), + child: + Row(crossAxisAlignment: CrossAxisAlignment.center, children: [ + ViewerMenu( + sharedFocusNode: _sharedFocusNode, + viewer: _viewer, + onToggleViewport: () { + setState(() { + _viewportMargin = (_viewportMargin == EdgeInsets.zero) + ? const EdgeInsets.all(30) + : EdgeInsets.zero; + }); + }, + onViewerDestroyed: () { + setState(() { + _viewer = null; + }); + }, + onViewerCreated: (v) { + setState(() { + _viewer = v; + }); + }), + SceneMenu( + sharedFocusNode: _sharedFocusNode, + controller: _viewer, + ), + GestureDetector( + onTap: () async { + await _viewer! + .loadGlb('assets/shapes/shapes.glb', numInstances: 1); + }, + child: Container( + color: Colors.transparent, + child: const Text("shapes.glb"))), + const SizedBox(width: 5), + GestureDetector( + onTap: () async { + await _viewer!.loadGlb('assets/1.glb'); + }, + child: Container( + color: Colors.transparent, child: const Text("1.glb"))), + const SizedBox(width: 5), + GestureDetector( + onTap: () async { + await _viewer!.loadGlb('assets/2.glb'); + }, + child: Container( + color: Colors.transparent, child: const Text("2.glb"))), + const SizedBox(width: 5), + GestureDetector( + onTap: () async { + await _viewer!.loadGlb('assets/3.glb'); + }, + child: Container( + color: Colors.transparent, child: const Text("3.glb"))), + Expanded(child: Container()), + ]))), + if (_viewer != null) ...[ + Positioned( + top: 10, + left: 10, + width: 200, + height: 200, + child: Container(child: EntityListWidget(controller: _viewer!))), + // Padding( + // padding: const EdgeInsets.only(top: 10, left: 20, right: 20), + // child: ValueListenableBuilder( + // valueListenable: showProjectionMatrices, + // builder: (ctx, value, child) => CameraMatrixOverlay( + // controller: _viewer!, showProjectionMatrices: value)), + // ), + // Align( + // alignment: Alignment.topRight, + // child: PickerResultWidget(controller: _viewer!), + // ) + ] + ]); } } diff --git a/examples/flutter/example/lib/menus/asset_submenu.dart b/examples/flutter/example/lib/menus/asset_submenu.dart index 3ddc3d08..78705f9c 100644 --- a/examples/flutter/example/lib/menus/asset_submenu.dart +++ b/examples/flutter/example/lib/menus/asset_submenu.dart @@ -8,8 +8,8 @@ import 'package:vector_math/vector_math_64.dart' as v; import 'package:thermion_dart/thermion_dart/thermion_viewer.dart'; class AssetSubmenu extends StatefulWidget { - final ThermionFlutterPlugin controller; - const AssetSubmenu({super.key, required this.controller}); + final ThermionViewer viewer; + const AssetSubmenu({super.key, required this.viewer}); @override State createState() => _AssetSubmenuState(); @@ -26,8 +26,8 @@ class _AssetSubmenuState extends State { MenuItemButton( closeOnActivate: false, onPressed: () async { - var entity = await widget.controller.viewer.getChildEntity( - widget.controller.viewer.scene.listEntities().last, "Cylinder"); + var entity = await widget.viewer.getChildEntity( + widget.viewer.scene.listEntities().last, "Cylinder"); await showDialog( context: context, builder: (BuildContext context) { @@ -39,8 +39,8 @@ class _AssetSubmenuState extends State { child: const Text('Find Cylinder entity by name')), MenuItemButton( onPressed: () async { - widget.controller.viewer.setPosition( - widget.controller.viewer.scene.listEntities().last, + widget.viewer.setPosition( + widget.viewer.scene.listEntities().last, 1.0, 1.0, -1.0); @@ -50,8 +50,8 @@ class _AssetSubmenuState extends State { MenuItemButton( onPressed: () async { final color = Colors.purple; - widget.controller.viewer.setMaterialColor( - widget.controller.viewer.scene.listEntities().last, + widget.viewer.setMaterialColor( + widget.viewer.scene.listEntities().last, "Cone", 0, color.red / 255.0, @@ -85,14 +85,14 @@ class _AssetSubmenuState extends State { -1.0, ]; var indices = [0, 1, 2, 2, 3, 0]; - var geom = await widget.controller.viewer.createGeometry( + var geom = await widget.viewer.createGeometry( verts, indices, materialPath: "asset://assets/solidcolor.filamat"); }, child: const Text("Quad")), MenuItemButton( onPressed: () async { - await widget.controller.viewer.createGeometry([ + await widget.viewer.createGeometry([ 0, 0, 0, @@ -118,14 +118,14 @@ class _AssetSubmenuState extends State { _geometrySubmenu(), MenuItemButton( onPressed: () async { - await widget.controller.viewer.addLight( + await widget.viewer.addLight( LightType.DIRECTIONAL, 6500, 100000, 0, 1, 0, 0, -1, 0); }, child: const Text("Add directional light"), ), MenuItemButton( onPressed: () async { - await widget.controller.viewer.addLight( + await widget.viewer.addLight( LightType.POINT, 6500, 100000, 0, 1, 0, 0, -1, 0, falloffRadius: 1.0); }, @@ -133,7 +133,7 @@ class _AssetSubmenuState extends State { ), MenuItemButton( onPressed: () async { - await widget.controller.viewer.addLight( + await widget.viewer.addLight( LightType.SPOT, 6500, 1000000, 0, 0, 0, 0, 1, 0, spotLightConeInner: 0.1, spotLightConeOuter: 0.4, @@ -143,26 +143,26 @@ class _AssetSubmenuState extends State { ), MenuItemButton( onPressed: () async { - await widget.controller.viewer.clearLights(); + await widget.viewer.clearLights(); }, child: const Text("Clear lights"), ), MenuItemButton( onPressed: () { final color = const Color(0xAA73C9FA); - widget.controller.viewer.setBackgroundColor(color.red / 255.0, + widget.viewer.setBackgroundColor(color.red / 255.0, color.green / 255.0, color.blue / 255.0, 1.0); }, child: const Text("Set background color")), MenuItemButton( onPressed: () { - widget.controller.viewer + widget.viewer .setBackgroundImage('assets/background.ktx'); }, child: const Text("Load background image")), MenuItemButton( onPressed: () { - widget.controller.viewer.setBackgroundImage( + widget.viewer.setBackgroundImage( 'assets/background.ktx', fillHeight: true); }, @@ -170,9 +170,9 @@ class _AssetSubmenuState extends State { MenuItemButton( onPressed: () { if (ExampleWidgetState.hasSkybox) { - widget.controller.viewer.removeSkybox(); + widget.viewer.removeSkybox(); } else { - widget.controller.viewer + widget.viewer .loadSkybox('assets/default_env/default_env_skybox.ktx'); } ExampleWidgetState.hasSkybox = !ExampleWidgetState.hasSkybox; @@ -182,18 +182,18 @@ class _AssetSubmenuState extends State { : 'Load skybox')), MenuItemButton( onPressed: () { - widget.controller.viewer + widget.viewer .loadIbl('assets/default_env/default_env_ibl.ktx'); }, child: const Text('Load IBL')), MenuItemButton( onPressed: () { - widget.controller.viewer.removeIbl(); + widget.viewer.removeIbl(); }, child: const Text('Remove IBL')), MenuItemButton( onPressed: () async { - await widget.controller.viewer.clearEntities(); + await widget.viewer.clearEntities(); }, child: const Text('Clear assets')), ], diff --git a/examples/flutter/example/lib/menus/camera_submenu.dart b/examples/flutter/example/lib/menus/camera_submenu.dart index 5113264e..30033684 100644 --- a/examples/flutter/example/lib/menus/camera_submenu.dart +++ b/examples/flutter/example/lib/menus/camera_submenu.dart @@ -8,8 +8,8 @@ import 'package:thermion_dart/thermion_dart/thermion_viewer.dart'; import 'package:thermion_flutter_example/main.dart'; class CameraSubmenu extends StatefulWidget { - final ThermionFlutterPlugin controller; - const CameraSubmenu({super.key, required this.controller}); + final ThermionViewer viewer; + const CameraSubmenu({super.key, required this.viewer}); @override State createState() => _CameraSubmenuState(); @@ -22,10 +22,10 @@ class _CameraSubmenuState extends State { @override void initState() { super.initState(); - widget.controller.viewer.initialized.then((_) { - widget.controller.viewer.getCameraCullingNear().then((v) { + widget.viewer.initialized.then((_) { + widget.viewer.getCameraCullingNear().then((v) { _near = v; - widget.controller.viewer.getCameraCullingFar().then((v) { + widget.viewer.getCameraCullingFar().then((v) { _far = v; setState(() {}); }); @@ -56,7 +56,7 @@ class _CameraSubmenuState extends State { menuChildren: [1.0, 7.0, 14.0, 28.0, 56.0] .map((v) => MenuItemButton( onPressed: () { - widget.controller.viewer.setCameraFocalLength(v); + widget.viewer.setCameraFocalLength(v); }, child: Text( v.toStringAsFixed(2), @@ -71,7 +71,7 @@ class _CameraSubmenuState extends State { _near = v; print("Setting camera culling to $_near $_far!"); - widget.controller.viewer.setCameraCulling(_near!, _far!); + widget.viewer.setCameraCulling(_near!, _far!); }, child: Text( v.toStringAsFixed(2), @@ -85,7 +85,7 @@ class _CameraSubmenuState extends State { onPressed: () { _far = v; print("Setting camera culling to $_near! $_far"); - widget.controller.viewer.setCameraCulling(_near!, _far!); + widget.viewer.setCameraCulling(_near!, _far!); }, child: Text( v.toStringAsFixed(2), @@ -95,21 +95,21 @@ class _CameraSubmenuState extends State { child: const Text("Set far")), MenuItemButton( onPressed: () async { - widget.controller.viewer.setCameraPosition(1.0, 1.0, -1.0); + widget.viewer.setCameraPosition(1.0, 1.0, -1.0); }, child: const Text('Set position to 1, 1, -1 (leave rotation as-is)'), ), MenuItemButton( onPressed: () async { - widget.controller.viewer.setCameraPosition(0.0, 0.0, 0.0); - widget.controller.viewer.setCameraRotation( + widget.viewer.setCameraPosition(0.0, 0.0, 0.0); + widget.viewer.setCameraRotation( v.Quaternion.axisAngle(v.Vector3(0, 0.0, 1.0), 0.0)); }, child: const Text('Move to 0,0,0, facing towards 0,0,-1'), ), MenuItemButton( onPressed: () { - widget.controller.viewer.setCameraRotation( + widget.viewer.setCameraRotation( v.Quaternion.axisAngle(v.Vector3(0, 1, 0), pi / 4)); }, child: const Text("Rotate camera 45 degrees around y axis"), @@ -118,7 +118,7 @@ class _CameraSubmenuState extends State { onPressed: () { ExampleWidgetState.frustumCulling = !ExampleWidgetState.frustumCulling; - widget.controller.viewer + widget.viewer .setViewFrustumCulling(ExampleWidgetState.frustumCulling); }, child: Text( @@ -128,7 +128,7 @@ class _CameraSubmenuState extends State { closeOnActivate: false, onPressed: () async { var projMatrix = - await widget.controller.viewer.getCameraProjectionMatrix(); + await widget.viewer.getCameraProjectionMatrix(); await showDialog( context: context, builder: (ctx) { @@ -147,7 +147,7 @@ class _CameraSubmenuState extends State { menuChildren: ManipulatorMode.values.map((mm) { return MenuItemButton( onPressed: () { - widget.controller.viewer.setCameraManipulatorOptions( + widget.viewer.setCameraManipulatorOptions( mode: mm, orbitSpeedX: ExampleWidgetState.orbitSpeedX, orbitSpeedY: ExampleWidgetState.orbitSpeedY, @@ -169,7 +169,7 @@ class _CameraSubmenuState extends State { return MenuItemButton( onPressed: () { ExampleWidgetState.zoomSpeed = speed; - widget.controller.viewer.setCameraManipulatorOptions( + widget.viewer.setCameraManipulatorOptions( orbitSpeedX: ExampleWidgetState.orbitSpeedX, orbitSpeedY: ExampleWidgetState.orbitSpeedY, zoomSpeed: ExampleWidgetState.zoomSpeed); @@ -191,7 +191,7 @@ class _CameraSubmenuState extends State { onPressed: () { ExampleWidgetState.orbitSpeedX = speed; ExampleWidgetState.orbitSpeedY = speed; - widget.controller.viewer.setCameraManipulatorOptions( + widget.viewer.setCameraManipulatorOptions( orbitSpeedX: ExampleWidgetState.orbitSpeedX, orbitSpeedY: ExampleWidgetState.orbitSpeedY, zoomSpeed: ExampleWidgetState.zoomSpeed); diff --git a/examples/flutter/example/lib/menus/controller_menu.dart b/examples/flutter/example/lib/menus/controller_menu.dart index 2ba71624..08bd89d4 100644 --- a/examples/flutter/example/lib/menus/controller_menu.dart +++ b/examples/flutter/example/lib/menus/controller_menu.dart @@ -5,29 +5,31 @@ import 'package:flutter/widgets.dart'; import 'package:thermion_flutter/thermion_flutter.dart'; -class ControllerMenu extends StatefulWidget { - final ThermionFlutterPlugin controller; +class ViewerMenu extends StatefulWidget { + final ThermionViewer? viewer; final void Function() onToggleViewport; - final void Function() onControllerCreated; - final void Function() onControllerDestroyed; + final void Function(ThermionViewer viewer) onViewerCreated; + final void Function() onViewerDestroyed; final FocusNode sharedFocusNode; - - ControllerMenu( - {required this.controller, - required this.onControllerCreated, - required this.onControllerDestroyed, + ViewerMenu( + { + required this.viewer, + required this.onViewerCreated, + required this.onViewerDestroyed, required this.sharedFocusNode, required this.onToggleViewport}); @override - State createState() => _ControllerMenuState(); + State createState() => _ViewerMenuState(); } -class _ControllerMenuState extends State { - void _createController({String? uberArchivePath}) async { - widget.controller.initialize(uberArchivePath: uberArchivePath); - widget.onControllerCreated(); +class _ViewerMenuState extends State { + void _createViewer({String? uberArchivePath}) async { + var viewer = await ThermionFlutterPlugin.createViewer( + uberArchivePath: uberArchivePath); + await viewer.initialized; + widget.onViewerCreated(viewer); } @override @@ -36,7 +38,7 @@ class _ControllerMenuState extends State { } @override - void didUpdateWidget(ControllerMenu oldWidget) { + void didUpdateWidget(ViewerMenu oldWidget) { super.didUpdateWidget(oldWidget); } @@ -50,14 +52,14 @@ class _ControllerMenuState extends State { child: const Text("Create ThermionFlutterPlugin (default ubershader)"), onPressed: () { - _createController(); + _createViewer(); }, ), MenuItemButton( child: const Text( "Create ThermionFlutterPlugin (custom ubershader - lit opaque only)"), onPressed: () { - _createController( + _createViewer( uberArchivePath: Platform.isWindows ? "assets/lit_opaque_32.uberz" : Platform.isMacOS @@ -73,8 +75,8 @@ class _ControllerMenuState extends State { MenuItemButton( child: const Text("Destroy viewer"), onPressed: () async { - widget.controller.dispose(); - widget.onControllerDestroyed(); + widget.viewer!.dispose(); + widget.onViewerDestroyed(); setState(() {}); }, ) @@ -89,8 +91,7 @@ class _ControllerMenuState extends State { onPressed: widget.onToggleViewport, ) ], - builder: - (BuildContext context, MenuController controller, Widget? child) { + builder: (BuildContext context, MenuController controller, Widget? child) { return TextButton( onPressed: () { if (controller.isOpen) { @@ -99,7 +100,7 @@ class _ControllerMenuState extends State { controller.open(); } }, - child: const Text("Controller / Viewer"), + child: const Text("Viewer / Viewer"), ); }); } diff --git a/examples/flutter/example/lib/menus/rendering_submenu.dart b/examples/flutter/example/lib/menus/rendering_submenu.dart index 0a6ced87..c075c0ad 100644 --- a/examples/flutter/example/lib/menus/rendering_submenu.dart +++ b/examples/flutter/example/lib/menus/rendering_submenu.dart @@ -4,9 +4,9 @@ import 'package:thermion_flutter_example/main.dart'; import 'package:thermion_dart/thermion_dart/thermion_viewer.dart'; class RenderingSubmenu extends StatefulWidget { - final ThermionFlutterPlugin controller; + final ThermionViewer viewer; - const RenderingSubmenu({super.key, required this.controller}); + const RenderingSubmenu({super.key, required this.viewer}); @override State createState() => _RenderingSubmenuState(); @@ -19,14 +19,14 @@ class _RenderingSubmenuState extends State { menuChildren: [ MenuItemButton( onPressed: () { - widget.controller.viewer.render(); + widget.viewer.render(); }, child: const Text("Render single frame"), ), MenuItemButton( onPressed: () { ExampleWidgetState.rendering = !ExampleWidgetState.rendering; - widget.controller.viewer.setRendering(ExampleWidgetState.rendering); + widget.viewer.setRendering(ExampleWidgetState.rendering); }, child: Text( "Set continuous rendering to ${!ExampleWidgetState.rendering}"), @@ -35,14 +35,14 @@ class _RenderingSubmenuState extends State { onPressed: () { ExampleWidgetState.framerate = ExampleWidgetState.framerate == 60 ? 30 : 60; - widget.controller.viewer.setFrameRate(ExampleWidgetState.framerate); + widget.viewer.setFrameRate(ExampleWidgetState.framerate); }, child: Text( "Toggle framerate (currently ${ExampleWidgetState.framerate}) "), ), MenuItemButton( onPressed: () { - widget.controller.viewer.setToneMapping(ToneMapper.LINEAR); + widget.viewer.setToneMapping(ToneMapper.LINEAR); }, child: const Text("Set tone mapping to linear"), ), @@ -50,7 +50,7 @@ class _RenderingSubmenuState extends State { onPressed: () { ExampleWidgetState.postProcessing = !ExampleWidgetState.postProcessing; - widget.controller.viewer + widget.viewer .setPostProcessing(ExampleWidgetState.postProcessing); }, child: Text( @@ -60,7 +60,7 @@ class _RenderingSubmenuState extends State { onPressed: () { ExampleWidgetState.antiAliasingMsaa = !ExampleWidgetState.antiAliasingMsaa; - widget.controller.viewer.setAntiAliasing( + widget.viewer.setAntiAliasing( ExampleWidgetState.antiAliasingMsaa, ExampleWidgetState.antiAliasingFxaa, ExampleWidgetState.antiAliasingTaa); @@ -72,7 +72,7 @@ class _RenderingSubmenuState extends State { onPressed: () { ExampleWidgetState.antiAliasingFxaa = !ExampleWidgetState.antiAliasingFxaa; - widget.controller.viewer.setAntiAliasing( + widget.viewer.setAntiAliasing( ExampleWidgetState.antiAliasingMsaa, ExampleWidgetState.antiAliasingFxaa, ExampleWidgetState.antiAliasingTaa); @@ -83,7 +83,7 @@ class _RenderingSubmenuState extends State { MenuItemButton( onPressed: () { ExampleWidgetState.recording = !ExampleWidgetState.recording; - widget.controller.viewer.setRecording(ExampleWidgetState.recording); + widget.viewer.setRecording(ExampleWidgetState.recording); }, child: Text( "Turn recording ${ExampleWidgetState.recording ? "OFF" : "ON"}) "), diff --git a/examples/flutter/example/lib/menus/scene_menu.dart b/examples/flutter/example/lib/menus/scene_menu.dart index fbd1bf71..3a5e1d9b 100644 --- a/examples/flutter/example/lib/menus/scene_menu.dart +++ b/examples/flutter/example/lib/menus/scene_menu.dart @@ -6,7 +6,7 @@ import 'package:thermion_flutter_example/menus/camera_submenu.dart'; import 'package:thermion_flutter_example/menus/rendering_submenu.dart'; class SceneMenu extends StatefulWidget { - final ThermionFlutterPlugin? controller; + final ThermionViewer? controller; final FocusNode sharedFocusNode; const SceneMenu( @@ -37,11 +37,11 @@ class _SceneMenuState extends State { ? [] : [ RenderingSubmenu( - controller: widget.controller!, + viewer: widget.controller!, ), - AssetSubmenu(controller: widget.controller!), + AssetSubmenu(viewer: widget.controller!), CameraSubmenu( - controller: widget.controller!, + viewer: widget.controller!, ), ], builder: diff --git a/examples/flutter/example/macos/Podfile.lock b/examples/flutter/example/macos/Podfile.lock deleted file mode 100644 index 94fb2021..00000000 --- a/examples/flutter/example/macos/Podfile.lock +++ /dev/null @@ -1,29 +0,0 @@ -PODS: - - FlutterMacOS (1.0.0) - - path_provider_foundation (0.0.1): - - Flutter - - FlutterMacOS - - thermion_flutter (0.0.1): - - FlutterMacOS - -DEPENDENCIES: - - FlutterMacOS (from `Flutter/ephemeral`) - - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - - thermion_flutter (from `Flutter/ephemeral/.symlinks/plugins/thermion_flutter/macos`) - -EXTERNAL SOURCES: - FlutterMacOS: - :path: Flutter/ephemeral - path_provider_foundation: - :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin - thermion_flutter: - :path: Flutter/ephemeral/.symlinks/plugins/thermion_flutter/macos - -SPEC CHECKSUMS: - FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - thermion_flutter: e4895ade6b14d9efb6e17ed2436e89dbb87fa998 - -PODFILE CHECKSUM: 1888651be91a8ad58692c1add9ce24279fd4e950 - -COCOAPODS: 1.15.2 diff --git a/examples/flutter/example/pubspec.yaml b/examples/flutter/example/pubspec.yaml index 1985ba79..2f10f80c 100644 --- a/examples/flutter/example/pubspec.yaml +++ b/examples/flutter/example/pubspec.yaml @@ -17,6 +17,18 @@ dependencies: cupertino_icons: ^1.0.2 web: +dependency_overrides: + thermion_flutter: + path: ../../../thermion_flutter/thermion_flutter + thermion_flutter_platform_interface: + path: ../../../thermion_flutter/thermion_flutter_platform_interface + thermion_flutter_ffi: + path: ../../../thermion_flutter/thermion_flutter_ffi + thermion_flutter_web: + path: ../../../thermion_flutter/thermion_flutter_web + thermion_dart: + path: ../../../thermion_dart + dev_dependencies: flutter_test: sdk: flutter @@ -36,4 +48,4 @@ flutter: - assets/BusterDrone/ - assets/BusterDrone/textures/ - assets/FlightHelmet/ - \ No newline at end of file + diff --git a/examples/flutter/quickstart/ios/Podfile b/examples/flutter/quickstart/ios/Podfile index d97f17e2..3e44f9c6 100644 --- a/examples/flutter/quickstart/ios/Podfile +++ b/examples/flutter/quickstart/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '12.0' +platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/examples/flutter/quickstart/ios/Podfile.lock b/examples/flutter/quickstart/ios/Podfile.lock new file mode 100644 index 00000000..81dd9d63 --- /dev/null +++ b/examples/flutter/quickstart/ios/Podfile.lock @@ -0,0 +1,22 @@ +PODS: + - Flutter (1.0.0) + - thermion_flutter (0.0.1): + - Flutter + +DEPENDENCIES: + - Flutter (from `Flutter`) + - thermion_flutter (from `.symlinks/plugins/thermion_flutter/ios`) + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + thermion_flutter: + :path: ".symlinks/plugins/thermion_flutter/ios" + +SPEC CHECKSUMS: + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + thermion_flutter: c965e09831858465a1a8df59ff97e40a4d002773 + +PODFILE CHECKSUM: a57f30d18f102dd3ce366b1d62a55ecbef2158e5 + +COCOAPODS: 1.15.2 diff --git a/examples/flutter/quickstart/ios/Runner.xcodeproj/project.pbxproj b/examples/flutter/quickstart/ios/Runner.xcodeproj/project.pbxproj index 8f6ac5fd..313884be 100644 --- a/examples/flutter/quickstart/ios/Runner.xcodeproj/project.pbxproj +++ b/examples/flutter/quickstart/ios/Runner.xcodeproj/project.pbxproj @@ -14,6 +14,8 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + F657CCCFB062AF56C7F1E15C /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D988E66CA93AB1163F3B4F2 /* Pods_RunnerTests.framework */; }; + F8DC37C0013EE3DC8802435E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B0831D015898C7047513698C /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -42,12 +44,17 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 31D54E0731320192F7631461 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3680A9DFE5EC3E59C051B544 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3D988E66CA93AB1163F3B4F2 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 704E1C99CAABFB056933D88F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 8C0FE5C77038897D844C5D42 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -55,6 +62,9 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9CD72080C199DAE3518D4C3D /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + B0831D015898C7047513698C /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BA68F2C1572D8BC92CC4C4FF /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -62,12 +72,30 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + F8DC37C0013EE3DC8802435E /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FA72758808529BDB8770545F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F657CCCFB062AF56C7F1E15C /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 0DB20859A61D425430A5D097 /* Frameworks */ = { + isa = PBXGroup; + children = ( + B0831D015898C7047513698C /* Pods_Runner.framework */, + 3D988E66CA93AB1163F3B4F2 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 331C8082294A63A400263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( @@ -76,6 +104,20 @@ path = RunnerTests; sourceTree = ""; }; + 4A129A4D3EB41F9DE0778AAC /* Pods */ = { + isa = PBXGroup; + children = ( + 704E1C99CAABFB056933D88F /* Pods-Runner.debug.xcconfig */, + 3680A9DFE5EC3E59C051B544 /* Pods-Runner.release.xcconfig */, + 8C0FE5C77038897D844C5D42 /* Pods-Runner.profile.xcconfig */, + 31D54E0731320192F7631461 /* Pods-RunnerTests.debug.xcconfig */, + BA68F2C1572D8BC92CC4C4FF /* Pods-RunnerTests.release.xcconfig */, + 9CD72080C199DAE3518D4C3D /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -94,6 +136,8 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, + 4A129A4D3EB41F9DE0778AAC /* Pods */, + 0DB20859A61D425430A5D097 /* Frameworks */, ); sourceTree = ""; }; @@ -128,8 +172,10 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + 78F05AF37D9E628BE6E89ABD /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, + FA72758808529BDB8770545F /* Frameworks */, ); buildRules = ( ); @@ -145,6 +191,7 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + C7C2718364AC4E2451407065 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, @@ -238,6 +285,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 78F05AF37D9E628BE6E89ABD /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -253,6 +322,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + C7C2718364AC4E2451407065 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -365,6 +456,7 @@ DEVELOPMENT_TEAM = TM2B4SJXNJ; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -379,6 +471,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 31D54E0731320192F7631461 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -396,6 +489,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = BA68F2C1572D8BC92CC4C4FF /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -411,6 +505,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 9CD72080C199DAE3518D4C3D /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -545,6 +640,7 @@ DEVELOPMENT_TEAM = TM2B4SJXNJ; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -568,6 +664,7 @@ DEVELOPMENT_TEAM = TM2B4SJXNJ; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/examples/flutter/quickstart/ios/Runner.xcworkspace/contents.xcworkspacedata b/examples/flutter/quickstart/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a16..21a3cc14 100644 --- a/examples/flutter/quickstart/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/examples/flutter/quickstart/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/examples/flutter/quickstart/lib/main.dart b/examples/flutter/quickstart/lib/main.dart index 9c8f5473..58bb4c45 100644 --- a/examples/flutter/quickstart/lib/main.dart +++ b/examples/flutter/quickstart/lib/main.dart @@ -4,7 +4,6 @@ import 'package:thermion_flutter/thermion_flutter.dart'; import 'package:vector_math/vector_math_64.dart' as v; import 'dart:math'; - void main() { runApp(const MyApp()); } @@ -35,40 +34,59 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { bool _loaded = false; - late ThermionFlutterPlugin _thermionFlutterPlugin; - late Future _thermionViewer; + ThermionViewer? _thermionViewer; @override void initState() { super.initState(); - _thermionFlutterPlugin = ThermionFlutterPlugin(); - _thermionViewer = _thermionFlutterPlugin.initialize(); + } + + ThermionFlutterTexture? _texture; + + Future _load() async { + var viewer = await ThermionFlutterPlugin.createViewer(); + _thermionViewer = viewer; + _thermionViewer!.loadSkybox("assets/default_env_skybox.ktx"); + _thermionViewer!.loadGlb("assets/cube.glb"); + + _thermionViewer!.setCameraPosition(0, 1, 10); + _thermionViewer!.setCameraRotation( + v.Quaternion.axisAngle(v.Vector3(1, 0, 0), -30 / 180 * pi) * + v.Quaternion.axisAngle(v.Vector3(0, 1, 0), 15 / 180 * pi)); + _thermionViewer!.addLight(LightType.SUN, 7500, 50000, 0, 0, 0, 1, -1, -1); + _thermionViewer!.setRendering(true); + _loaded = true; + + setState(() {}); + } + + Future _unload() async { + await ThermionFlutterPlugin.destroyTexture(_texture!); + var viewer = _thermionViewer!; + _thermionViewer = null; + setState(() {}); + await viewer.dispose(); + _loaded = false; + setState(() {}); + } + + Widget _loadButton() { + return Center( + child: ElevatedButton(child: const Text("Load"), onPressed: _load)); + } + + Widget _unloadButton() { + return Center( + child: ElevatedButton(child: const Text("Unload"), onPressed: _unload)); } @override Widget build(BuildContext context) { return Stack(children: [ - Positioned.fill(child: ThermionWidget(plugin: _thermionFlutterPlugin)), - if (!_loaded) - Center( - child: ElevatedButton( - child: const Text("Load"), - onPressed: () async { - var viewer = await _thermionViewer; - await viewer.loadSkybox("assets/default_env_skybox.ktx"); - await viewer.loadGlb("assets/cube.glb"); - - await viewer.setCameraPosition(0, 1, 10); - await viewer.setCameraRotation(v.Quaternion.axisAngle( - v.Vector3(1, 0, 0), -30 / 180 * pi) * - v.Quaternion.axisAngle( - v.Vector3(0, 1, 0), 15 / 180 * pi)); - await viewer.addLight( - LightType.SUN, 7500, 50000, 0, 0, 0, 1, -1, -1); - await viewer.setRendering(true); - _loaded = true; - setState(() {}); - })) + if (_thermionViewer != null) + Positioned.fill(child: ThermionWidget(viewer: _thermionViewer!)), + if (!_loaded) _loadButton(), + if (_loaded) _unloadButton(), ]); } } diff --git a/examples/flutter/quickstart/pubspec.yaml b/examples/flutter/quickstart/pubspec.yaml index 73825cd9..a0db16e2 100644 --- a/examples/flutter/quickstart/pubspec.yaml +++ b/examples/flutter/quickstart/pubspec.yaml @@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: '>=3.5.0-250.0.dev <4.0.0' + sdk: '>=3.3.0 <4.0.0' # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -30,11 +30,7 @@ environment: dependencies: flutter: sdk: flutter - thermion_flutter: - path: ../../../thermion_flutter/thermion_flutter - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. + thermion_flutter: cupertino_icons: ^1.0.8 vector_math: ^2.1.4 diff --git a/melos.yaml b/melos.yaml new file mode 100644 index 00000000..213832ce --- /dev/null +++ b/melos.yaml @@ -0,0 +1,4 @@ +name: thermion_workspace +packages: + - thermion_dart + - thermion_flutter/** diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 00000000..3ab56ff1 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,6 @@ +name: thermion_workspace + +environment: + sdk: '>=3.0.0 <4.0.0' +dev_dependencies: + melos: ^6.1.0 diff --git a/thermion_dart/CHANGELOG.md b/thermion_dart/CHANGELOG.md index feb7a4bf..0e468c1b 100644 --- a/thermion_dart/CHANGELOG.md +++ b/thermion_dart/CHANGELOG.md @@ -1,2 +1,27 @@ +## 0.1.1+1 + + - **DOCS**: update with links to playground. + +## 0.1.1 + + - Bump "thermion_dart" to `0.1.1`. + +## 0.1.0+4 + + - **FIX**: add dummy asset to build.dart on Linux builds so we can use the package on a Linux host. + +## 0.1.0+3 + + - **FIX**: exit build.dart early on Linux builds so we can use the package on a Linux host. + +## 0.1.0+2 + + - **REFACTOR**: rearrange some stubs/imports for easier web WASM deployment. + +## 0.1.0+1 + + - **REFACTOR**: export ThermionViewerWasm for web and hide FFI/WASM version. + - **FIX**: use preserveDrawingBuffer=true on web. + ## 0.0.1 * First release of Dart-only package diff --git a/thermion_dart/README.md b/thermion_dart/README.md index 12053abd..0f4dd7ba 100644 --- a/thermion_dart/README.md +++ b/thermion_dart/README.md @@ -4,6 +4,7 @@ Quickstart (Flutter)DocumentationShowcase • + PlaygroundDiscord

diff --git a/thermion_dart/hook/build.dart b/thermion_dart/hook/build.dart index 70615995..da71bf8b 100644 --- a/thermion_dart/hook/build.dart +++ b/thermion_dart/hook/build.dart @@ -6,12 +6,48 @@ import 'package:native_toolchain_c/native_toolchain_c.dart'; void main(List args) async { await build(args, (config, output) async { + var logDir = Directory( + "${config.packageRoot.toFilePath()}.dart_tool/thermion_dart/log/"); + if (!logDir.existsSync()) { + logDir.createSync(recursive: true); + } + var logFile = File(logDir.path + "/build.log"); - final logger = Logger("")..level = Level.ALL - ..onRecord.listen((record) => print(record.message)); + final logger = Logger("") + ..level = Level.ALL + ..onRecord.listen((record) => logFile.writeAsStringSync( + record.message + "\n", + mode: FileMode.append, + flush: true)); var platform = config.targetOS.toString().toLowerCase(); + // We don't support Linux (yet), so the native/Filament libraries won't be + // compiled/available. However, we still want to be able to run the Dart + // package itself on a Linux host(e.g. for dart_services backed), so if + // we detect that we're running on Linux, add some dummy native code + // assets and exit early. + if (platform == "linux") { + final linkMode = DynamicLoadingBundled(); + final name = "thermion_dart.dart"; + final libUri = config.outputDirectory + .resolve(config.targetOS.libraryFileName(name, linkMode)); + output.addAssets( + [ + NativeCodeAsset( + package: config.packageName, + name: name, + file: libUri, + linkMode: linkMode, + os: config.targetOS, + architecture: config.dryRun ? null : config.targetArchitecture, + ) + ], + linkInPackage: null, + ); + return; + } + var libDir = config.dryRun ? "" : (await getLibDir(config, logger)).path; final packageName = config.packageName; @@ -71,7 +107,7 @@ void main(List args) async { } else { libs.add("stdc++"); } - final flags = []; + final flags = []; //"-fsanitize=address"]; final defines = {}; var frameworks = []; @@ -85,7 +121,15 @@ void main(List args) async { } if (platform == "ios") { - frameworks.addAll(['Foundation', 'CoreGraphics', 'QuartzCore', 'GLKit', "Metal", 'CoreVideo', 'OpenGLES']); + frameworks.addAll([ + 'Foundation', + 'CoreGraphics', + 'QuartzCore', + 'GLKit', + "Metal", + 'CoreVideo', + 'OpenGLES' + ]); } else if (platform == "macos") { frameworks.addAll([ 'Foundation', @@ -107,7 +151,7 @@ void main(List args) async { sources: sources, includes: ['native/include', 'native/include/filament'], defines: defines, - // UNCOMMENT THIS IF YOU ARE BUILDING WITH THE CUSTOM native_toolchain_c FORK FOR WINDOWS + // UNCOMMENT THIS IF YOU ARE BUILDING WITH THE CUSTOM native_toolchain_c FORK FOR WINDOWS // linkWith: linkWith, flags: [ if (platform == "macos") '-mmacosx-version-min=13.0', @@ -162,87 +206,90 @@ void main(List args) async { }); } -String _FILAMENT_VERSION ="v1.51.2"; +String _FILAMENT_VERSION = "v1.51.2"; String _getLibraryUrl(String platform, String mode) { - return "https://pub-c8b6266320924116aaddce03b5313c0a.r2.dev/filament-${_FILAMENT_VERSION}-${platform}-${mode}.zip"; + return "https://pub-c8b6266320924116aaddce03b5313c0a.r2.dev/filament-${_FILAMENT_VERSION}-${platform}-${mode}.zip"; } // // Download precompiled Filament libraries for the target platform from Cloudflare. // Future getLibDir(BuildConfig config, Logger logger) async { + var platform = config.targetOS.toString().toLowerCase(); - var platform = config.targetOS.toString().toLowerCase(); - - // Except on Windows, most users will only need release builds of Filament. - // Debug builds are probably only relevant if you're a package developer debugging an internal Filament issue - // or if you're working on Flutter+Windows (which requires the CRT debug DLLs). - // Also note that there are known driver issues with Android debug builds, e.g.: - // https://github.com/google/filament/issues/7162 - // (these aren't present in Filament release builds). - // However, if you know what you're doing, you can change "release" to "debug" below. - // TODO - check if we can pass this as a CLI compiler flag - var mode = "release"; - if(platform == "windows") { - mode = config.buildMode == BuildMode.debug ? "debug" : "release"; - } + // Except on Windows, most users will only need release builds of Filament. + // Debug builds are probably only relevant if you're a package developer debugging an internal Filament issue + // or if you're working on Flutter+Windows (which requires the CRT debug DLLs). + // Also note that there are known driver issues with Android debug builds, e.g.: + // https://github.com/google/filament/issues/7162 + // (these aren't present in Filament release builds). + // However, if you know what you're doing, you can change "release" to "debug" below. + // TODO - check if we can pass this as a CLI compiler flag + var mode = "release"; + if (platform == "windows") { + mode = config.buildMode == BuildMode.debug ? "debug" : "release"; + } - var libDir = Directory("${config.packageRoot.toFilePath()}/.dart_tool/thermion_dart/lib/${_FILAMENT_VERSION}/$platform/$mode/"); + var libDir = Directory( + "${config.packageRoot.toFilePath()}/.dart_tool/thermion_dart/lib/${_FILAMENT_VERSION}/$platform/$mode/"); - if (platform == "android") { - final archExtension = switch (config.targetArchitecture) { - Architecture.arm => "armeabi-v7a", - Architecture.arm64 => "arm64-v8a", - Architecture.x64 => "x86_64", - Architecture.ia32 => "x86", - _ => throw FormatException('Invalid') - }; - libDir = Directory("${libDir.path}/$archExtension/"); - } else if(platform == "windows") { - if(config.targetArchitecture != Architecture.x64) { - throw Exception("Unsupported architecture : ${config.targetArchitecture}"); + if (platform == "android") { + final archExtension = switch (config.targetArchitecture) { + Architecture.arm => "armeabi-v7a", + Architecture.arm64 => "arm64-v8a", + Architecture.x64 => "x86_64", + Architecture.ia32 => "x86", + _ => throw FormatException('Invalid') + }; + libDir = Directory("${libDir.path}/$archExtension/"); + } else if (platform == "windows") { + if (config.targetArchitecture != Architecture.x64) { + throw Exception( + "Unsupported architecture : ${config.targetArchitecture}"); + } + } + + final url = _getLibraryUrl(platform, mode); + + final filename = url.split("/").last; + + // We will write an empty file called success to the unzip directory after successfully downloading/extracting the prebuilt libraries. + // If this file already exists, we assume everything has been successfully extracted and skip + final unzipDir = platform == "android" ? libDir.parent.path : libDir.path; + final successToken = File("$unzipDir/success"); + final libraryZip = File("$unzipDir/$filename"); + + if (!successToken.existsSync()) { + if (libraryZip.existsSync()) { + libraryZip.deleteSync(); + } + + if (!libraryZip.parent.existsSync()) { + libraryZip.parent.createSync(recursive: true); + } + + logger.info( + "Downloading prebuilt libraries for $platform/$mode from $url to ${libraryZip}, files will be unzipped to ${unzipDir}"); + final request = await HttpClient().getUrl(Uri.parse(url)); + final response = await request.close(); + + await response.pipe(libraryZip.openWrite()); + + final archive = ZipDecoder().decodeBytes(await libraryZip.readAsBytes()); + + for (final file in archive) { + final filename = file.name; + if (file.isFile) { + final data = file.content as List; + final f = File('${unzipDir}/$filename'); + await f.create(recursive: true); + await f.writeAsBytes(data); + } else { + final d = Directory('${unzipDir}/$filename'); + await d.create(recursive: true); } } - - final url = _getLibraryUrl(platform, mode); - - final filename = url.split("/").last; - - // Assume that the libraries exist if the directory containing them exists. - if (!libDir.existsSync()) { - - final unzipDir = platform == "android" ? libDir.parent.path : libDir.path; - - final libraryZip = File("$unzipDir/$filename"); - - if(libraryZip.existsSync()) { - libraryZip.deleteSync(); - } - - if(!libraryZip.parent.existsSync()) { - libraryZip.parent.createSync(recursive: true); - } - - logger.info("Downloading prebuilt libraries for $platform/$mode from $url to ${libraryZip}, files will be unzipped to ${unzipDir}"); - final request = await HttpClient().getUrl(Uri.parse(url)); - final response = await request.close(); - - await response.pipe(libraryZip.openWrite()); - - final archive = ZipDecoder().decodeBytes(await libraryZip.readAsBytes()); - - for (final file in archive) { - final filename = file.name; - if (file.isFile) { - final data = file.content as List; - final f = File('${unzipDir}/$filename'); - await f.create(recursive: true); - await f.writeAsBytes(data); - } else { - final d = Directory('${unzipDir}/$filename'); - await d.create(recursive: true); - } - } - } - return libDir; + successToken.writeAsStringSync("SUCCESS"); + } + return libDir; } diff --git a/thermion_dart/lib/thermion_dart.dart b/thermion_dart/lib/thermion_dart.dart index 0d69812a..c283d51a 100644 --- a/thermion_dart/lib/thermion_dart.dart +++ b/thermion_dart/lib/thermion_dart.dart @@ -1,5 +1,8 @@ library filament_dart; export 'thermion_dart/thermion_viewer.dart'; -export 'thermion_dart/thermion_viewer_ffi.dart'; +export 'thermion_dart/thermion_viewer_stub.dart' + if (dart.library.io) 'thermion_dart/thermion_viewer_ffi.dart' + if (dart.library.js_interop)'thermion_dart/compatibility/web/interop/thermion_viewer_wasm.dart'; + export 'thermion_dart/entities/entity_transform_controller.dart'; diff --git a/thermion_dart/lib/thermion_dart/compatibility/compatibility.dart b/thermion_dart/lib/thermion_dart/compatibility/compatibility.dart index 55e1f5e0..a8435873 100644 --- a/thermion_dart/lib/thermion_dart/compatibility/compatibility.dart +++ b/thermion_dart/lib/thermion_dart/compatibility/compatibility.dart @@ -1,3 +1 @@ -export 'web/compatibility.dart' if (dart.library.io) 'native/compatibility.dart'; - - +export 'native/compatibility.dart'; diff --git a/thermion_dart/lib/thermion_dart/compatibility/web/allocator.dart b/thermion_dart/lib/thermion_dart/compatibility/web/ffi/allocator.dart similarity index 98% rename from thermion_dart/lib/thermion_dart/compatibility/web/allocator.dart rename to thermion_dart/lib/thermion_dart/compatibility/web/ffi/allocator.dart index c19ace21..41d8f6d8 100644 --- a/thermion_dart/lib/thermion_dart/compatibility/web/allocator.dart +++ b/thermion_dart/lib/thermion_dart/compatibility/web/ffi/allocator.dart @@ -6,7 +6,7 @@ import 'dart:convert'; import 'dart:ffi' as ffi hide Uint8Pointer, FloatPointer; import 'dart:typed_data'; -import 'package:thermion_dart/thermion_dart/compatibility/web/thermion_dart.g.dart'; +import 'package:thermion_dart/thermion_dart/compatibility/web/ffi/thermion_dart.g.dart'; import 'package:ffi/ffi.dart'; export 'package:ffi/ffi.dart' hide StringUtf8Pointer, Utf8Pointer; diff --git a/thermion_dart/lib/thermion_dart/compatibility/web/compatibility.dart b/thermion_dart/lib/thermion_dart/compatibility/web/ffi/compatibility_ffi.dart similarity index 97% rename from thermion_dart/lib/thermion_dart/compatibility/web/compatibility.dart rename to thermion_dart/lib/thermion_dart/compatibility/web/ffi/compatibility_ffi.dart index f8da247c..e9242cc2 100644 --- a/thermion_dart/lib/thermion_dart/compatibility/web/compatibility.dart +++ b/thermion_dart/lib/thermion_dart/compatibility/web/ffi/compatibility_ffi.dart @@ -1,6 +1,6 @@ import 'dart:async'; import 'dart:js_interop'; -import 'package:thermion_dart/thermion_dart/compatibility/web/interop.dart'; +import 'package:thermion_dart/thermion_dart/compatibility/web/ffi/interop.dart'; import "allocator.dart"; diff --git a/thermion_dart/lib/thermion_dart/compatibility/web/interop.dart b/thermion_dart/lib/thermion_dart/compatibility/web/ffi/interop.dart similarity index 100% rename from thermion_dart/lib/thermion_dart/compatibility/web/interop.dart rename to thermion_dart/lib/thermion_dart/compatibility/web/ffi/interop.dart diff --git a/thermion_dart/lib/thermion_dart/compatibility/web/thermion_dart.g.dart b/thermion_dart/lib/thermion_dart/compatibility/web/ffi/thermion_dart.g.dart similarity index 100% rename from thermion_dart/lib/thermion_dart/compatibility/web/thermion_dart.g.dart rename to thermion_dart/lib/thermion_dart/compatibility/web/ffi/thermion_dart.g.dart diff --git a/thermion_dart/lib/thermion_dart/compatibility/web/interop/shims/thermion_dart_api_js_shim.dart b/thermion_dart/lib/thermion_dart/compatibility/web/interop/shims/thermion_dart_api_js_shim.dart deleted file mode 100644 index dab1daed..00000000 --- a/thermion_dart/lib/thermion_dart/compatibility/web/interop/shims/thermion_dart_api_js_shim.dart +++ /dev/null @@ -1,403 +0,0 @@ -@JS() -library thermion_flutter_js; - -import 'dart:js_interop'; - -import 'package:thermion_dart/thermion_dart/thermion_viewer.dart'; - -/// -/// An extension type on [JSObject] that represents a -/// Javascript shim implementation for the [ThermionViewer] interface. -/// -extension type ThermionDartAPIJSShim(JSObject _) implements JSObject { - - @JS('wasm_test') - external JSPromise wasm_test(String str); - - @JS('set_rendering') - external JSPromise set_rendering(bool render); - - @JS('render') - external JSPromise render(); - - @JS('setFrameRate') - external JSPromise setFrameRate(int framerate); - - @JS('setBackgroundImage') - external JSPromise setBackgroundImage(String path, bool fillHeight); - - @JS('setBackgroundImagePosition') - external JSPromise setBackgroundImagePosition(double x, double y, bool clamp); - - @JS('clearBackgroundImage') - external JSPromise clearBackgroundImage(); - - @JS('setBackgroundColor') - external JSPromise setBackgroundColor( - double r, double g, double b, double alpha); - - @JS('loadSkybox') - external JSPromise loadSkybox(String skyboxPath); - - @JS('removeSkybox') - external JSPromise removeSkybox(); - - @JS('loadIbl') - external JSPromise loadIbl(String lightingPath, double intensity); - - @JS('rotateIbl') - external JSPromise rotateIbl(JSArray rotationMatrix); - - @JS('removeIbl') - external JSPromise removeIbl(); - - @JS('addLight') - external JSPromise addLight( - int type, - double colour, - double intensity, - double posX, - double posY, - double posZ, - double dirX, - double dirY, - double dirZ, - double falloffRadius, - double spotLightConeInner, - double spotLightConeOuter, - double sunAngularRadius, - double sunHaloSize, - double sunHaloFallof, - bool castShadows); - - @JS('removeLight') - external JSPromise removeLight(ThermionEntity light); - - @JS('clearLights') - external JSPromise clearLights(); - - @JS('loadGlb') - external JSPromise loadGlb(String path, int numInstances); - - @JS('createInstance') - external JSPromise createInstance(ThermionEntity entity); - - @JS('getInstanceCount') - external JSPromise getInstanceCount(ThermionEntity entity); - - @JS('getInstances') - external JSPromise> getInstances(ThermionEntity entity); - - @JS('loadGltf') - external JSPromise loadGltf( - String path, String relativeResourcePath); - - @JS('panStart') - external JSPromise panStart(double x, double y); - - @JS('panUpdate') - external JSPromise panUpdate(double x, double y); - - @JS('panEnd') - external JSPromise panEnd(); - - @JS('rotateStart') - external JSPromise rotateStart(double x, double y); - - @JS('rotateUpdate') - external JSPromise rotateUpdate(double x, double y); - - @JS('rotateEnd') - external JSPromise rotateEnd(); - - @JS('setMorphTargetWeights') - external JSPromise setMorphTargetWeights( - ThermionEntity entity, JSArray weights); - - @JS('getMorphTargetNames') - external JSPromise> getMorphTargetNames( - ThermionEntity entity, ThermionEntity childEntity); - - @JS('getBoneNames') - external JSPromise> getBoneNames( - ThermionEntity entity, int skinIndex); - - @JS('getAnimationNames') - external JSPromise> getAnimationNames( - ThermionEntity entity); - - @JS('getAnimationDuration') - external JSPromise getAnimationDuration( - ThermionEntity entity, int animationIndex); - - @JS('setMorphAnimationData') - external JSPromise setMorphAnimationData( - ThermionEntity entity, - JSArray> animation, - JSArray morphTargets, - JSArray? targetMeshNames, - double frameLengthInMs); - - @JS('resetBones') - external JSPromise resetBones(ThermionEntity entity); - - @JS('addBoneAnimation') - external JSPromise addBoneAnimation( - ThermionEntity entity, - JSArray bones, - JSArray>> frameData, - JSNumber frameLengthInMs, - JSNumber spaceEnum, - JSNumber skinIndex, - JSNumber fadeInInSecs, - JSNumber fadeOutInSecs, - JSNumber maxDelta); - - @JS('removeEntity') - external JSPromise removeEntity(ThermionEntity entity); - - @JS('clearEntities') - external JSPromise clearEntities(); - - @JS('zoomBegin') - external JSPromise zoomBegin(); - - @JS('zoomUpdate') - external JSPromise zoomUpdate(double x, double y, double z); - - @JS('zoomEnd') - external JSPromise zoomEnd(); - - @JS('playAnimation') - external JSPromise playAnimation( - ThermionEntity entity, - int index, - bool loop, - bool reverse, - bool replaceActive, - double crossfade, - ); - - @JS('playAnimationByName') - external JSPromise playAnimationByName( - ThermionEntity entity, - String name, - bool loop, - bool reverse, - bool replaceActive, - double crossfade, - ); - - @JS('setAnimationFrame') - external JSPromise setAnimationFrame( - ThermionEntity entity, int index, int animationFrame); - - @JS('stopAnimation') - external JSPromise stopAnimation(ThermionEntity entity, int animationIndex); - - @JS('stopAnimationByName') - external JSPromise stopAnimationByName(ThermionEntity entity, String name); - - @JS('setCamera') - external JSPromise setCamera(ThermionEntity entity, String? name); - - @JS('setMainCamera') - external JSPromise setMainCamera(); - - @JS('getMainCamera') - external JSPromise getMainCamera(); - - @JS('setCameraFov') - external JSPromise setCameraFov(double degrees, double width, double height); - - @JS('setToneMapping') - external JSPromise setToneMapping(int mapper); - - @JS('setBloom') - external JSPromise setBloom(double bloom); - - @JS('setCameraFocalLength') - external JSPromise setCameraFocalLength(double focalLength); - - @JS('setCameraCulling') - external JSPromise setCameraCulling(double near, double far); - - @JS('getCameraCullingNear') - external JSPromise getCameraCullingNear(); - - @JS('getCameraCullingFar') - external JSPromise getCameraCullingFar(); - - @JS('setCameraFocusDistance') - external JSPromise setCameraFocusDistance(double focusDistance); - - @JS('getCameraPosition') - external JSPromise> getCameraPosition(); - - @JS('getCameraModelMatrix') - external JSPromise> getCameraModelMatrix(); - - @JS('getCameraViewMatrix') - external JSPromise> getCameraViewMatrix(); - - @JS('getCameraProjectionMatrix') - external JSPromise> getCameraProjectionMatrix(); - - @JS('getCameraCullingProjectionMatrix') - external JSPromise> getCameraCullingProjectionMatrix(); - - @JS('getCameraFrustum') - external JSPromise getCameraFrustum(); - - @JS('setCameraPosition') - external JSPromise setCameraPosition(double x, double y, double z); - - @JS('getCameraRotation') - external JSPromise> getCameraRotation(); - - @JS('moveCameraToAsset') - external JSPromise moveCameraToAsset(ThermionEntity entity); - - @JS('setViewFrustumCulling') - external JSPromise setViewFrustumCulling(JSBoolean enabled); - - @JS('setCameraExposure') - external JSPromise setCameraExposure( - double aperture, double shutterSpeed, double sensitivity); - - @JS('setCameraRotation') - external JSPromise setCameraRotation(JSArray quaternion); - - @JS('setCameraModelMatrix') - external JSPromise setCameraModelMatrix(JSArray matrix); - - @JS('setMaterialColor') - external JSPromise setMaterialColor(ThermionEntity entity, String meshName, - int materialIndex, double r, double g, double b, double a); - - @JS('transformToUnitCube') - external JSPromise transformToUnitCube(ThermionEntity entity); - - @JS('setPosition') - external JSPromise setPosition( - ThermionEntity entity, double x, double y, double z); - - @JS('setScale') - external JSPromise setScale(ThermionEntity entity, double scale); - - @JS('setRotation') - external JSPromise setRotation( - ThermionEntity entity, double rads, double x, double y, double z); - - @JS('queuePositionUpdate') - external JSPromise queuePositionUpdate( - ThermionEntity entity, double x, double y, double z, bool relative); - - @JS('queueRotationUpdate') - external JSPromise queueRotationUpdate(ThermionEntity entity, double rads, - double x, double y, double z, bool relative); - - @JS('queueRotationUpdateQuat') - external JSPromise queueRotationUpdateQuat( - ThermionEntity entity, JSArray quat, bool relative); - - @JS('setPostProcessing') - external JSPromise setPostProcessing(bool enabled); - - @JS('setAntiAliasing') - external JSPromise setAntiAliasing(bool msaa, bool fxaa, bool taa); - - @JS('setRotationQuat') - external JSPromise setRotationQuat( - ThermionEntity entity, JSArray rotation); - - @JS('reveal') - external JSPromise reveal(ThermionEntity entity, String? meshName); - - @JS('hide') - external JSPromise hide(ThermionEntity entity, String? meshName); - - @JS('pick') - external void pick(int x, int y); - - @JS('getNameForEntity') - external String? getNameForEntity(ThermionEntity entity); - - @JS('setCameraManipulatorOptions') - external JSPromise setCameraManipulatorOptions( - int mode, - double orbitSpeedX, - double orbitSpeedY, - double zoomSpeed, - ); - - @JS('getChildEntities') - external JSPromise> getChildEntities( - ThermionEntity parent, bool renderableOnly); - - @JS('getChildEntity') - external JSPromise getChildEntity( - ThermionEntity parent, String childName); - - @JS('getChildEntityNames') - external JSPromise> getChildEntityNames( - ThermionEntity entity, bool renderableOnly); - - @JS('setRecording') - external JSPromise setRecording(JSBoolean recording); - - @JS('setRecordingOutputDirectory') - external JSPromise setRecordingOutputDirectory(String outputDirectory); - - @JS('addAnimationComponent') - external JSPromise addAnimationComponent(ThermionEntity entity); - - @JS('removeAnimationComponent') - external JSPromise removeAnimationComponent(ThermionEntity entity); - - @JS('addCollisionComponent') - external JSPromise addCollisionComponent(ThermionEntity entity); - - @JS('removeCollisionComponent') - external JSPromise removeCollisionComponent(ThermionEntity entity); - - @JS('createGeometry') - external JSPromise createGeometry(JSArray vertices, - JSArray indices, String? materialPath, int primitiveType); - - @JS('setParent') - external JSPromise setParent(ThermionEntity child, ThermionEntity parent); - - @JS('getParent') - external JSPromise getParent(ThermionEntity child); - - @JS('getParent') - external JSPromise getBone( - ThermionEntity child, int boneIndex, int skinIndex); - - @JS('testCollisions') - external JSPromise testCollisions(ThermionEntity entity); - - @JS('setPriority') - external JSPromise setPriority(ThermionEntity entityId, int priority); - - @JS('getLocalTransform') - external JSPromise> getLocalTransform( - ThermionEntity entity); - - @JS('getWorldTransform') - external JSPromise> getWorldTransform( - ThermionEntity entity); - - @JS('updateBoneMatrices') - external JSPromise updateBoneMatrices(ThermionEntity entity); - - @JS('setTransform') - external JSPromise setTransform( - ThermionEntity entity, JSArray transform); - - @JS('setBoneTransform') - external JSPromise setBoneTransform( - ThermionEntity entity, int boneIndex, JSArray transform, int skinIndex); -} - diff --git a/thermion_dart/lib/thermion_dart/compatibility/web/interop/filament_viewer_js_dart_bridge.dart b/thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_dart_bridge.dart similarity index 98% rename from thermion_dart/lib/thermion_dart/compatibility/web/interop/filament_viewer_js_dart_bridge.dart rename to thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_dart_bridge.dart index edffe64c..85db072c 100644 --- a/thermion_dart/lib/thermion_dart/compatibility/web/interop/filament_viewer_js_dart_bridge.dart +++ b/thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_dart_bridge.dart @@ -2,7 +2,7 @@ library thermion_flutter_js; import 'dart:js_interop'; -import 'package:thermion_dart/thermion_dart/compatibility/web/interop/shims/thermion_viewer_js_shim.dart'; +import 'package:thermion_dart/thermion_dart/compatibility/web/interop/thermion_viewer_js_shim.dart'; import 'package:vector_math/vector_math_64.dart' as v64; import 'package:animation_tools_dart/animation_tools_dart.dart'; @@ -15,19 +15,18 @@ import 'package:vector_math/vector_math_64.dart'; /// A (Dart) class that wraps a (Dart) instance of [ThermionViewer], /// but exported to JS by binding to a global property. /// This is effectively an implementation of [ThermionViewerJSShim]; -/// allowing users to interact with an instance of [ThermionViewer] -/// (presumably compiled to WASM) from any Javascript context (including +/// allowing users to interact with an instance of [ThermionViewer] +/// (presumably compiled to WASM) from any Javascript context (including /// the browser console). /// @JSExport() -class ThermionViewerFFIJSDartBridge { +class ThermionViewerJSDartBridge { final ThermionViewer viewer; - ThermionViewerFFIJSDartBridge(this.viewer); - - void bind( - {String globalPropertyName = "filamentViewer"}) { - var wrapper = createJSInteropWrapper(this) + ThermionViewerJSDartBridge(this.viewer); + + void bind({String globalPropertyName = "thermionViewer"}) { + var wrapper = createJSInteropWrapper(this) as ThermionViewerJSShim; globalContext.setProperty(globalPropertyName.toJS, wrapper); } @@ -131,10 +130,13 @@ class ThermionViewerFFIJSDartBridge { @JSExport() JSPromise loadGlb(String path, {int numInstances = 1}) { + print("Loading GLB from path $path with numInstances $numInstances"); return viewer .loadGlb(path, numInstances: numInstances) .then((entity) => entity.toJS) - .toJS; + .catchError((err) { + print("Error: $err"); + }).toJS; } @JSExport() diff --git a/thermion_dart/lib/thermion_dart/compatibility/web/interop/filament_viewer_js.dart b/thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_js.dart similarity index 98% rename from thermion_dart/lib/thermion_dart/compatibility/web/interop/filament_viewer_js.dart rename to thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_js.dart index 7abe2477..ab325097 100644 --- a/thermion_dart/lib/thermion_dart/compatibility/web/interop/filament_viewer_js.dart +++ b/thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_js.dart @@ -3,25 +3,26 @@ import 'dart:js_interop_unsafe'; import 'dart:math'; import 'package:animation_tools_dart/animation_tools_dart.dart'; +import 'package:thermion_dart/thermion_dart/scene.dart'; import 'package:thermion_dart/thermion_dart/thermion_viewer.dart'; -import 'package:thermion_dart/thermion_dart/scene.dart'; +import 'package:thermion_dart/thermion_dart/scene_impl.dart'; import 'package:vector_math/vector_math_64.dart'; -import 'shims/thermion_viewer_js_shim.dart'; +import 'thermion_viewer_js_shim.dart'; /// /// An [ThermionViewer] implementation that forwards calls to /// a corresponding Javascript shim implementation (see [ThermionViewerJSShim]). /// -class ThermionViewerFFIJS implements ThermionViewer { +class ThermionViewerJS implements ThermionViewer { late final ThermionViewerJSShim _shim; - ThermionViewerFFIJS.fromGlobalProperty(String globalPropertyName) { + ThermionViewerJS.fromGlobalProperty(String globalPropertyName) { this._shim = globalContext.getProperty(globalPropertyName.toJS) as ThermionViewerJSShim; } - ThermionViewerFFIJS(this._shim); + ThermionViewerJS(this._shim); @override Future get initialized async { @@ -56,6 +57,9 @@ class ThermionViewerFFIJS implements ThermionViewer { @override Future dispose() async { await _shim.dispose().toDart; + for (final callback in _onDispose) { + callback.call(); + } } @override @@ -813,4 +817,13 @@ class ThermionViewerFFIJS implements ThermionViewer { Future updateBoneMatrices(ThermionEntity entity) { return _shim.updateBoneMatrices(entity).toDart; } + + final _onDispose = []; + + /// + /// + /// + void onDispose(Future Function() callback) { + _onDispose.add(callback); + } } diff --git a/thermion_dart/lib/thermion_dart/compatibility/web/interop/shims/thermion_viewer_js_shim.dart b/thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_js_shim.dart similarity index 100% rename from thermion_dart/lib/thermion_dart/compatibility/web/interop/shims/thermion_viewer_js_shim.dart rename to thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_js_shim.dart diff --git a/thermion_dart/lib/thermion_dart/compatibility/web/interop/filament_viewer_wasm.dart b/thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_wasm.dart similarity index 58% rename from thermion_dart/lib/thermion_dart/compatibility/web/interop/filament_viewer_wasm.dart rename to thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_wasm.dart index cbca9454..b881443f 100644 --- a/thermion_dart/lib/thermion_dart/compatibility/web/interop/filament_viewer_wasm.dart +++ b/thermion_dart/lib/thermion_dart/compatibility/web/interop/thermion_viewer_wasm.dart @@ -1,13 +1,17 @@ +import 'dart:async'; import 'dart:js_interop'; import 'dart:js_interop_unsafe'; import 'dart:math'; import 'dart:typed_data' as td; import 'dart:typed_data'; +import 'package:thermion_dart/thermion_dart/scene.dart'; import 'package:web/web.dart'; import 'package:animation_tools_dart/animation_tools_dart.dart'; import 'package:thermion_dart/thermion_dart/thermion_viewer.dart'; import 'package:vector_math/vector_math_64.dart'; +export 'thermion_viewer_dart_bridge.dart'; + extension type _EmscriptenModule(JSObject _) implements JSObject { external JSAny? ccall(String name, String returnType, JSArray argTypes, JSArray args, JSAny? opts); @@ -29,24 +33,26 @@ extension type _EmscriptenModule(JSObject _) implements JSObject { external JSAny get HEAP32; } +typedef ThermionViewerImpl = ThermionViewerWasm; + + /// -/// An [ThermionViewer] implementation that forwards calls to +/// A [ThermionViewer] implementation that forwards calls to /// the (Emscripten-generated) ThermionDart JS module. /// -class ThermionViewerFFIWasm implements ThermionViewer { +class ThermionViewerWasm implements ThermionViewer { late _EmscriptenModule _module; bool _initialized = false; bool _rendering = false; - ThermionViewerFFIWasm() { - _module = window.getProperty<_EmscriptenModule>("df".toJS); + ThermionViewerWasm({JSObject? module, String moduleName = "thermion_dart"}) { + _module = module as _EmscriptenModule? ?? window.getProperty<_EmscriptenModule>(moduleName.toJS); } - JSBigInt? _viewer; - JSBigInt? _sceneManager; + JSNumber? _viewer; + JSNumber? _sceneManager; - @override Future initialize(int width, int height, {String? uberArchivePath}) async { final context = _module.ccall("thermion_dart_web_create_gl_context", "int", [].toJS, [].toJS, null); @@ -61,12 +67,11 @@ class ThermionViewerFFIWasm implements ThermionViewer { "void*", ["void*".toJS, "void*".toJS, "void*".toJS, "string".toJS].toJS, [context, loader, null, uberArchivePath?.toJS].toJS, - null) as JSBigInt; - print("Created viewer"); + null) as JSNumber; await createSwapChain(width, height); _updateViewportAndCameraProjection(width, height, 1.0); _sceneManager = _module.ccall("get_scene_manager", "void*", - ["void*".toJS].toJS, [_viewer!].toJS, null) as JSBigInt; + ["void*".toJS].toJS, [_viewer!].toJS, null) as JSNumber; _initialized = true; } @@ -84,7 +89,6 @@ class ThermionViewerFFIWasm implements ThermionViewer { [_viewer!].toJS, null); } - @override void _updateViewportAndCameraProjection( int width, int height, double scaleFactor) { _module.ccall( @@ -110,10 +114,27 @@ class ThermionViewerFFIWasm implements ThermionViewer { @override Future dispose() async { + if (_viewer == null) { + // we've already cleaned everything up, ignore the call to dispose + return; + } + await setRendering(false); + await clearEntities(); + await clearLights(); + _destroyViewer(); + + _sceneManager = null; + _viewer = null; + + for (final callback in _onDispose) { + await callback.call(); + } + _onDispose.clear(); + } + + void _destroyViewer() { _module.ccall("destroy_filament_viewer", "void", ["void*".toJS].toJS, [_viewer].toJS, null); - _initialized = false; - _viewer = null; } @override @@ -359,51 +380,6 @@ class ThermionViewerFFIWasm implements ThermionViewer { return entityId.toDartInt; } - @override - Future clearBackgroundImage() { - // TODO: implement clearBackgroundImage - throw UnimplementedError(); - } - - @override - Future clearEntities() { - // TODO: implement clearEntities - throw UnimplementedError(); - } - - @override - Future clearLights() { - // TODO: implement clearLights - throw UnimplementedError(); - } - - @override - Future createGeometry(List vertices, List indices, - {String? materialPath, - PrimitiveType primitiveType = PrimitiveType.TRIANGLES}) { - // TODO: implement createGeometry - throw UnimplementedError(); - } - - @override - Future createInstance(ThermionEntity entity) { - // TODO: implement createInstance - throw UnimplementedError(); - } - - @override - Future getAnimationDuration( - ThermionEntity entity, int animationIndex) { - // TODO: implement getAnimationDuration - throw UnimplementedError(); - } - - @override - Future> getAnimationNames(ThermionEntity entity) { - // TODO: implement getAnimationNames - throw UnimplementedError(); - } - @override Future> getBoneNames(ThermionEntity entity, {int skinIndex = 0}) async { @@ -439,60 +415,6 @@ class ThermionViewerFFIWasm implements ThermionViewer { return names; } - @override - Future getCameraCullingFar() { - // TODO: implement getCameraCullingFar - throw UnimplementedError(); - } - - @override - Future getCameraCullingNear() { - // TODO: implement getCameraCullingNear - throw UnimplementedError(); - } - - @override - Future getCameraCullingProjectionMatrix() { - // TODO: implement getCameraCullingProjectionMatrix - throw UnimplementedError(); - } - - @override - Future getCameraFrustum() { - // TODO: implement getCameraFrustum - throw UnimplementedError(); - } - - @override - Future getCameraModelMatrix() { - // TODO: implement getCameraModelMatrix - throw UnimplementedError(); - } - - @override - Future getCameraPosition() { - // TODO: implement getCameraPosition - throw UnimplementedError(); - } - - @override - Future getCameraProjectionMatrix() { - // TODO: implement getCameraProjectionMatrix - throw UnimplementedError(); - } - - @override - Future getCameraRotation() { - // TODO: implement getCameraRotation - throw UnimplementedError(); - } - - @override - Future getCameraViewMatrix() { - // TODO: implement getCameraViewMatrix - throw UnimplementedError(); - } - @override Future> getChildEntities( ThermionEntity parent, bool renderableOnly) async { @@ -559,31 +481,6 @@ class ThermionViewerFFIWasm implements ThermionViewer { return names; } - @override - Future getInstanceCount(ThermionEntity entity) { - // TODO: implement getInstanceCount - throw UnimplementedError(); - } - - @override - Future> getInstances(ThermionEntity entity) { - // TODO: implement getInstances - throw UnimplementedError(); - } - - @override - Future getInverseBindMatrix(ThermionEntity parent, int boneIndex, - {int skinIndex = 0}) { - // TODO: implement getInverseBindMatrix - throw UnimplementedError(); - } - - @override - Future getLocalTransform(ThermionEntity entity) { - // TODO: implement getLocalTransform - throw UnimplementedError(); - } - @override Future getMainCamera() async { final entityId = _module.ccall( @@ -766,35 +663,6 @@ class ThermionViewerFFIWasm implements ThermionViewer { await promise.toDart; } - @override - Future moveCameraToAsset(ThermionEntity entity) { - // TODO: implement moveCameraToAsset - throw UnimplementedError(); - } - - @override - Future panEnd() { - // TODO: implement panEnd - throw UnimplementedError(); - } - - @override - Future panStart(double x, double y) { - // TODO: implement panStart - throw UnimplementedError(); - } - - @override - Future panUpdate(double x, double y) { - // TODO: implement panUpdate - throw UnimplementedError(); - } - - @override - void pick(int x, int y) { - // TODO: implement pick - } - @override Future playAnimation(ThermionEntity entity, int index, {bool loop = false, @@ -825,75 +693,6 @@ class ThermionViewerFFIWasm implements ThermionViewer { null); } - @override - Future playAnimationByName(ThermionEntity entity, String name, - {bool loop = false, - bool reverse = false, - bool replaceActive = true, - double crossfade = 0.0}) { - // TODO: implement playAnimationByName - throw UnimplementedError(); - } - - @override - Future queuePositionUpdate( - ThermionEntity entity, double x, double y, double z, - {bool relative = false}) { - // TODO: implement queuePositionUpdate - throw UnimplementedError(); - } - - @override - Future queueRotationUpdate( - ThermionEntity entity, double rads, double x, double y, double z, - {bool relative = false}) { - // TODO: implement queueRotationUpdate - throw UnimplementedError(); - } - - @override - Future queueRotationUpdateQuat(ThermionEntity entity, Quaternion quat, - {bool relative = false}) { - // TODO: implement queueRotationUpdateQuat - throw UnimplementedError(); - } - - @override - Future removeAnimationComponent(ThermionEntity entity) { - // TODO: implement removeAnimationComponent - throw UnimplementedError(); - } - - @override - Future removeCollisionComponent(ThermionEntity entity) { - // TODO: implement removeCollisionComponent - throw UnimplementedError(); - } - - @override - Future removeEntity(ThermionEntity entity) { - // TODO: implement removeEntity - throw UnimplementedError(); - } - - @override - Future removeIbl() { - // TODO: implement removeIbl - throw UnimplementedError(); - } - - @override - Future removeLight(ThermionEntity light) { - // TODO: implement removeLight - throw UnimplementedError(); - } - - @override - Future removeSkybox() { - // TODO: implement removeSkybox - throw UnimplementedError(); - } - int _last = 0; @override @@ -920,52 +719,8 @@ class ThermionViewerFFIWasm implements ThermionViewer { } @override - Future resetBones(ThermionEntity entity) { - // TODO: implement resetBones - throw UnimplementedError(); - } - - @override - Future reveal(ThermionEntity entity, String? meshName) { - // TODO: implement reveal - throw UnimplementedError(); - } - - @override - Future rotateEnd() { - // TODO: implement rotateEnd - throw UnimplementedError(); - } - - @override - Future rotateIbl(Matrix3 rotation) { - // TODO: implement rotateIbl - throw UnimplementedError(); - } - - @override - Future rotateStart(double x, double y) { - // TODO: implement rotateStart - throw UnimplementedError(); - } - - @override - Future rotateUpdate(double x, double y) { - // TODO: implement rotateUpdate - throw UnimplementedError(); - } - - @override - // TODO: implement scene Scene get scene => throw UnimplementedError(); - @override - Future setAnimationFrame( - ThermionEntity entity, int index, int animationFrame) { - // TODO: implement setAnimationFrame - throw UnimplementedError(); - } - @override Future setAntiAliasing(bool msaa, bool fxaa, bool taa) async { _module.ccall( @@ -976,85 +731,6 @@ class ThermionViewerFFIWasm implements ThermionViewer { null); } - @override - Future setBackgroundImage(String path, {bool fillHeight = false}) { - // TODO: implement setBackgroundImage - throw UnimplementedError(); - } - - @override - Future setBackgroundImagePosition(double x, double y, {bool clamp = false}) { - // TODO: implement setBackgroundImagePosition - throw UnimplementedError(); - } - - @override - Future setBloom(double bloom) { - // TODO: implement setBloom - throw UnimplementedError(); - } - - @override - Future setBoneTransform( - ThermionEntity entity, int boneIndex, Matrix4 transform, - {int skinIndex = 0}) { - // TODO: implement setBoneTransform - throw UnimplementedError(); - } - - @override - Future setCamera(ThermionEntity entity, String? name) { - // TODO: implement setCamera - throw UnimplementedError(); - } - - @override - Future setCameraCulling(double near, double far) { - // TODO: implement setCameraCulling - throw UnimplementedError(); - } - - @override - Future setCameraExposure( - double aperture, double shutterSpeed, double sensitivity) { - // TODO: implement setCameraExposure - throw UnimplementedError(); - } - - @override - Future setCameraFocalLength(double focalLength) { - // TODO: implement setCameraFocalLength - throw UnimplementedError(); - } - - @override - Future setCameraFocusDistance(double focusDistance) { - // TODO: implement setCameraFocusDistance - throw UnimplementedError(); - } - - @override - Future setCameraFov(double degrees, double width, double height) { - // TODO: implement setCameraFov - throw UnimplementedError(); - } - - @override - Future setCameraManipulatorOptions( - {ManipulatorMode mode = ManipulatorMode.ORBIT, - double orbitSpeedX = 0.01, - double orbitSpeedY = 0.01, - double zoomSpeed = 0.01}) { - // TODO: implement setCameraManipulatorOptions - throw UnimplementedError(); - } - - @override - Future setCameraModelMatrix(List matrix) { - // TODO: implement setCameraModelMatrix - throw UnimplementedError(); - } - @override Future setCameraPosition(double x, double y, double z) async { _module.ccall( @@ -1082,80 +758,6 @@ class ThermionViewerFFIWasm implements ThermionViewer { null); } - @override - Future setFrameRate(int framerate) { - // TODO: implement setFrameRate - throw UnimplementedError(); - } - - @override - Future setMainCamera() { - // TODO: implement setMainCamera - throw UnimplementedError(); - } - - @override - Future setMaterialColor(ThermionEntity entity, String meshName, - int materialIndex, double r, double g, double b, double a) { - // TODO: implement setMaterialColor - throw UnimplementedError(); - } - - // @override - // Future setMorphAnimationData( - // ThermionEntity entity, MorphAnimationData animation, - // {List? targetMeshNames}) async { - // final morphTargetNames = await getMorphTargetNames(entity, entity); - - // // We need to create a JS array for the morph indices and morph data - // final numFrames = animation.numFrames; - // final numMorphTargets = morphTargetNames.length; - // final numBytes = numFrames * numMorphTargets * 4; - // final floatPtr = _module._malloc(numBytes); - // final morphIndicesPtr = _module._malloc(numFrames * 4); - - // // Extract the morph data for the target morph targets - // final morphData = animation.extract(morphTargets: targetMeshNames); - - // // Create a list of morph indices based on the target morph targets - // final morphIndices = targetMeshNames != null - // ? animation._getMorphTargetIndices(targetMeshNames) - // : List.generate(morphTargetNames.length, (i) => i); - // final morphIndicesList = td.Int32List.fromList(morphIndices); - - // // Set the morph data and indices into the JS arrays - // _module.writeArrayToMemory(morphData.buffer.asUint8List(morphData.offsetInBytes).toJS, floatPtr); - // _module.writeArrayToMemory(morphIndicesList.buffer.asUint8List(morphData.offsetInBytes).toJS, morphIndicesPtr); - - // // Set the morph animation data - // _module.ccall( - // "set_morph_animation", - // "bool", - // [ - // "void*".toJS, - // "int".toJS, - // "float*".toJS, - // "int*".toJS, - // "int".toJS, - // "int".toJS, - // "float".toJS - // ].toJS, - // [ - // _sceneManager!, - // entity.toJS, - // floatPtr, - // morphIndicesPtr, - // numMorphTargets.toJS, - // numFrames.toJS, - // animation.frameLengthInMs.toJS - // ].toJS, - // null); - - // // Free the memory allocated for the JS arrays - // _module._free(floatPtr); - // _module._free(morphIndicesPtr); - // } - @override Future setMorphAnimationData( ThermionEntity entity, MorphAnimationData animation, @@ -1267,18 +869,6 @@ class ThermionViewerFFIWasm implements ThermionViewer { } } - @override - Future setMorphTargetWeights(ThermionEntity entity, List weights) { - // TODO: implement setMorphTargetWeights - throw UnimplementedError(); - } - - @override - Future setParent(ThermionEntity child, ThermionEntity parent) { - // TODO: implement setParent - throw UnimplementedError(); - } - @override Future setPosition( ThermionEntity entity, double x, double y, double z) async { @@ -1297,30 +887,6 @@ class ThermionViewerFFIWasm implements ThermionViewer { ["void*".toJS, "bool".toJS].toJS, [_viewer!, enabled.toJS].toJS, null); } - @override - Future setPriority(ThermionEntity entityId, int priority) { - // TODO: implement setPriority - throw UnimplementedError(); - } - - @override - Future setRecording(bool recording) { - // TODO: implement setRecording - throw UnimplementedError(); - } - - @override - Future setRecordingOutputDirectory(String outputDirectory) { - // TODO: implement setRecordingOutputDirectory - throw UnimplementedError(); - } - - @override - Future setRendering(bool render) { - // TODO: implement setRendering - throw UnimplementedError(); - } - @override Future setRotation( ThermionEntity entity, double rads, double x, double y, double z) async { @@ -1373,36 +939,873 @@ class ThermionViewerFFIWasm implements ThermionViewer { null); } + final _onDispose = []; + + /// + /// + /// + void onDispose(Future Function() callback) { + _onDispose.add(callback); + } + @override - Future setScale(ThermionEntity entity, double scale) { - // TODO: implement setScale + Future clearBackgroundImage() async { + _module.ccall("clear_background_image", "void", ["void*".toJS].toJS, + [_viewer!].toJS, null); + } + + @override + Future clearEntities() async { + _module.ccall( + "clear_entities", "void", ["void*".toJS].toJS, [_viewer!].toJS, null); + } + + @override + Future clearLights() async { + _module.ccall( + "clear_lights", "void", ["void*".toJS].toJS, [_viewer!].toJS, null); + } + + @override + Future createGeometry(List vertices, List indices, + {String? materialPath, + PrimitiveType primitiveType = PrimitiveType.TRIANGLES}) async { + final verticesData = td.Float32List.fromList(vertices); + final indicesData = Uint16List.fromList(indices); + final verticesPtr = _module._malloc(verticesData.lengthInBytes); + final indicesPtr = _module._malloc(indicesData.lengthInBytes); + _module.writeArrayToMemory( + verticesData.buffer.asUint8List().toJS, verticesPtr); + _module.writeArrayToMemory( + indicesData.buffer.asUint8List().toJS, indicesPtr); + + final entityId = _module.ccall( + "create_geometry", + "int", + [ + "void*".toJS, + "float*".toJS, + "int".toJS, + "uint16_t*".toJS, + "int".toJS, + "int".toJS, + "string".toJS + ].toJS, + [ + _viewer!, + verticesPtr, + vertices.length.toJS, + indicesPtr, + indices.length.toJS, + primitiveType.index.toJS, + materialPath?.toJS ?? "".toJS, + ].toJS, + null) as JSNumber; + + _module._free(verticesPtr); + _module._free(indicesPtr); + + if (entityId.toDartInt == -1) { + throw Exception("Failed to create geometry"); + } + return entityId.toDartInt; + } + + @override + Future createInstance(ThermionEntity entity) async { + final result = _module.ccall( + "create_instance", + "int", + ["void*".toJS, "int".toJS].toJS, + [_sceneManager!, entity.toJS].toJS, + null) as JSNumber; + if (result.toDartInt == -1) { + throw Exception("Failed to create instance of entity ${entity}"); + } + return result.toDartInt; + } + + @override + Future getAnimationDuration( + ThermionEntity entity, int animationIndex) async { + final result = _module.ccall( + "get_animation_duration", + "float", + ["void*".toJS, "int".toJS, "int".toJS].toJS, + [_sceneManager!, entity.toJS, animationIndex.toJS].toJS, + null) as JSNumber; + return result.toDartDouble; + } + + @override + Future getAnimationCount(ThermionEntity entity) async { + final animationCount = _module.ccall( + "get_animation_count", + "int", + ["void*".toJS, "int".toJS].toJS, + [_sceneManager!, entity.toJS].toJS, + null) as JSNumber; + return animationCount.toDartInt; + } + + @override + Future> getAnimationNames(ThermionEntity entity) async { + final animationCount = await getAnimationCount(entity); + final names = []; + for (int i = 0; i < animationCount; i++) { + final namePtr = _module._malloc(256) as JSNumber; + _module.ccall( + "get_animation_name", + "void", + ["void*".toJS, "int".toJS, "char*".toJS, "int".toJS].toJS, + [_sceneManager!, entity.toJS, namePtr, i.toJS].toJS, + null); + names.add(_module.UTF8ToString(namePtr).toDart); + _module._free(namePtr); + } + return names; + } + + @override + Future getCameraCullingFar() async { + final result = _module.ccall("get_camera_culling_far", "double", + ["void*".toJS].toJS, [_viewer!].toJS, null) as JSNumber; + return result.toDartDouble; + } + + @override + Future getCameraCullingNear() async { + final result = _module.ccall("get_camera_culling_near", "double", + ["void*".toJS].toJS, [_viewer!].toJS, null) as JSNumber; + return result.toDartDouble; + } + + @override + Future getCameraCullingProjectionMatrix() async { + final ptr = _module._malloc(16 * 8) as JSNumber; + _module.ccall("get_camera_culling_projection_matrix", "void", + ["void*".toJS, "double*".toJS].toJS, [_viewer!, ptr].toJS, null); + final matrix = Matrix4.zero(); + for (int i = 0; i < 16; i++) { + matrix[i] = (_module.getValue((ptr.toDartInt + (i * 8)).toJS, "double") + as JSNumber) + .toDartDouble; + } + _module._free(ptr); + return matrix; + } + + @override + Future getCameraFrustum() async { + final ptr = _module._malloc(24 * 8) as JSNumber; + _module.ccall("get_camera_frustum", "void", + ["void*".toJS, "double*".toJS].toJS, [_viewer!, ptr].toJS, null); + final planes = List.generate(6, (i) { + final offset = i * 4; + return Plane() + ..setFromComponents( + (_module.getValue((ptr.toDartInt + (offset * 8)).toJS, "double") + as JSNumber) + .toDartDouble, + (_module.getValue( + (ptr.toDartInt + ((offset + 1) * 8)).toJS, "double") + as JSNumber) + .toDartDouble, + (_module.getValue( + (ptr.toDartInt + ((offset + 2) * 8)).toJS, "double") + as JSNumber) + .toDartDouble, + (_module.getValue( + (ptr.toDartInt + ((offset + 3) * 8)).toJS, "double") + as JSNumber) + .toDartDouble); + }); + _module._free(ptr); + throw UnimplementedError(); + // return Frustum()..plane0 = planes[0]..plane1 =planes[1]..plane2 =planes[2]..plane3 =planes[3], planes[4], planes[5]); + } + + @override + Future getCameraModelMatrix() async { + final ptr = _module._malloc(16 * 8) as JSNumber; + _module.ccall("get_camera_model_matrix", "void", + ["void*".toJS, "double*".toJS].toJS, [_viewer!, ptr].toJS, null); + final matrix = _matrixFromPtr(ptr); + _module._free(ptr); + return matrix; + } + + @override + Future getCameraPosition() async { + final ptr = _module._malloc(3 * 8) as JSNumber; + _module.ccall("get_camera_position", "void", + ["void*".toJS, "void*".toJS].toJS, [_viewer!, ptr].toJS, null); + final pos = Vector3( + (_module.getValue(ptr.toDartInt.toJS, "double") as JSNumber) + .toDartDouble, + (_module.getValue((ptr.toDartInt + 8).toJS, "double") as JSNumber) + .toDartDouble, + (_module.getValue((ptr.toDartInt + 16).toJS, "double") as JSNumber) + .toDartDouble); + _module._free(ptr); + return pos; + } + + @override + Future getCameraProjectionMatrix() async { + final ptr = _module._malloc(16 * 8) as JSNumber; + _module.ccall("get_camera_projection_matrix", "void", + ["void*".toJS, "double*".toJS].toJS, [_viewer!, ptr].toJS, null); + final matrix = _matrixFromPtr(ptr); + _module._free(ptr); + return matrix; + } + + @override + Future getCameraRotation() async { + final model = await getCameraModelMatrix(); + final rotation = model.getRotation(); + return rotation; + } + + @override + Future getCameraViewMatrix() async { + final ptr = _module._malloc(16 * 8) as JSNumber; + _module.ccall("get_camera_view_matrix", "void", + ["void*".toJS, "double*".toJS].toJS, [_viewer!, ptr].toJS, null); + final matrix = Matrix4.zero(); + for (int i = 0; i < 16; i++) { + matrix[i] = (_module.getValue((ptr.toDartInt + (i * 8)).toJS, "double") + as JSNumber) + .toDartDouble; + } + _module._free(ptr); + return matrix; + } + + @override + Future getInstanceCount(ThermionEntity entity) async { + final result = _module.ccall( + "get_instance_count", + "int", + ["void*".toJS, "int".toJS].toJS, + [_sceneManager!, entity.toJS].toJS, + null) as JSNumber; + return result.toDartInt; + } + + @override + Future> getInstances(ThermionEntity entity) async { + final instanceCount = await getInstanceCount(entity); + final buf = _module._malloc(instanceCount * 4) as JSNumber; + _module.ccall( + "get_instances", + "void", + ["void*".toJS, "int".toJS, "int*".toJS].toJS, + [_sceneManager!, entity.toJS, buf].toJS, + null); + final instances = []; + for (int i = 0; i < instanceCount; i++) { + final instanceId = + _module.getValue((buf.toDartInt + (i * 4)).toJS, "i32") as JSNumber; + instances.add(instanceId.toDartInt); + } + _module._free(buf); + return instances; + } + + @override + Future getInverseBindMatrix(ThermionEntity parent, int boneIndex, + {int skinIndex = 0}) async { + final ptr = _module._malloc(16 * 4) as JSNumber; + _module.ccall( + "get_inverse_bind_matrix", + "void", + ["void*".toJS, "int".toJS, "int".toJS, "int".toJS, "float*".toJS].toJS, + [_sceneManager!, parent.toJS, skinIndex.toJS, boneIndex.toJS, ptr].toJS, + null); + final matrix = _matrixFromPtr(ptr); + _module._free(ptr); + return matrix; + } + + @override + Future getLocalTransform(ThermionEntity entity) async { + final ptr = _module._malloc(16 * 4) as JSNumber; + _module.ccall( + "get_local_transform", + "void", + ["void*".toJS, "int".toJS, "float*".toJS].toJS, + [_sceneManager!, entity.toJS, ptr].toJS, + null); + final matrix = _matrixFromPtr(ptr); + _module._free(ptr); + return matrix; + } + + @override + Future moveCameraToAsset(ThermionEntity entity) async { + _module.ccall("move_camera_to_asset", "void", + ["void*".toJS, "int".toJS].toJS, [_viewer!, entity.toJS].toJS, null); + } + + @override + Future panEnd() { + // TODO: implement panEnd throw UnimplementedError(); } @override - Future setToneMapping(ToneMapper mapper) { - // TODO: implement setToneMapping + Future panStart(double x, double y) { + // TODO: implement panStart throw UnimplementedError(); } @override - Future setTransform(ThermionEntity entity, Matrix4 transform) { - // TODO: implement setTransform + Future panUpdate(double x, double y) { + // TODO: implement panUpdate throw UnimplementedError(); } @override - Future setViewFrustumCulling(bool enabled) { - // TODO: implement setViewFrustumCulling + void pick(int x, int y) { + throw UnimplementedError(); + // _module.ccall("filament_pick", "void", + // ["void*".toJS, "int".toJS, "int".toJS, "void*".toJS].toJS, [ + // _viewer!, + // x.toJS, + // y.toJS, + // (entityId, x, y) {}.toJS + // ]); + } + + @override + Future playAnimationByName(ThermionEntity entity, String name, + {bool loop = false, + bool reverse = false, + bool replaceActive = true, + double crossfade = 0.0}) async { + final animationNames = await getAnimationNames(entity); + final index = animationNames.indexOf(name); + if (index == -1) { + throw Exception("Animation ${name} not found."); + } + return playAnimation(entity, index, + loop: loop, + reverse: reverse, + replaceActive: replaceActive, + crossfade: crossfade); + } + + @override + Future queuePositionUpdate( + ThermionEntity entity, double x, double y, double z, + {bool relative = false}) async { + _module.ccall( + "queue_position_update", + "void", + [ + "void*".toJS, + "int".toJS, + "float".toJS, + "float".toJS, + "float".toJS, + "bool".toJS + ].toJS, + [_sceneManager!, entity.toJS, x.toJS, y.toJS, z.toJS, relative.toJS] + .toJS, + null); + } + + @override + Future queueRotationUpdate( + ThermionEntity entity, double rads, double x, double y, double z, + {bool relative = false}) async { + _module.ccall( + "queue_rotation_update", + "void", + [ + "void*".toJS, + "int".toJS, + "float".toJS, + "float".toJS, + "float".toJS, + "float".toJS, + "bool".toJS + ].toJS, + [ + _sceneManager!, + entity.toJS, + rads.toJS, + x.toJS, + y.toJS, + z.toJS, + relative.toJS + ].toJS, + null); + } + + @override + Future queueRotationUpdateQuat(ThermionEntity entity, Quaternion quat, + {bool relative = false}) async { + _module.ccall( + "queue_rotation_update", + "void", + [ + "void*".toJS, + "int".toJS, + "float".toJS, + "float".toJS, + "float".toJS, + "float".toJS, + "bool".toJS + ].toJS, + [ + _sceneManager!, + entity.toJS, + quat.radians.toJS, + quat.x.toJS, + quat.y.toJS, + quat.z.toJS, + relative.toJS + ].toJS, + null); + } + + @override + Future removeAnimationComponent(ThermionEntity entity) async { + _module.ccall( + "remove_animation_component", + "void", + ["void*".toJS, "int".toJS].toJS, + [_sceneManager!, entity.toJS].toJS, + null); + } + + @override + Future removeCollisionComponent(ThermionEntity entity) async { + _module.ccall( + "remove_collision_component", + "void", + ["void*".toJS, "int".toJS].toJS, + [_sceneManager!, entity.toJS].toJS, + null); + } + + @override + Future removeEntity(ThermionEntity entity) async { + _module.ccall("remove_entity", "void", ["void*".toJS, "int".toJS].toJS, + [_viewer!, entity.toJS].toJS, null); + } + + @override + Future removeIbl() async { + _module.ccall( + "remove_ibl", "void", ["void*".toJS].toJS, [_viewer!].toJS, null); + } + + @override + Future removeLight(ThermionEntity light) async { + _module.ccall("remove_light", "void", ["void*".toJS, "int".toJS].toJS, + [_viewer!, light.toJS].toJS, null); + } + + @override + Future removeSkybox() async { + _module.ccall( + "remove_skybox", "void", ["void*".toJS].toJS, [_viewer!].toJS, null); + } + + @override + Future resetBones(ThermionEntity entity) async { + _module.ccall("reset_to_rest_pose", "void", ["void*".toJS, "int".toJS].toJS, + [_sceneManager!, entity.toJS].toJS, null); + } + + @override + Future reveal(ThermionEntity entity, String? meshName) async { + if (meshName != null) { + final result = _module.ccall( + "reveal_mesh", + "int", + ["void*".toJS, "int".toJS, "string".toJS].toJS, + [_sceneManager!, entity.toJS, meshName.toJS].toJS, + null) as JSNumber; + if (result.toDartInt == -1) { + throw Exception( + "Failed to reveal mesh ${meshName} on entity ${entity.toJS}"); + } + } else { + throw Exception( + "Cannot reveal mesh, meshName must be specified when invoking this method"); + } + } + + @override + Future rotateEnd() { + // TODO: implement rotateEnd throw UnimplementedError(); } @override - Future stopAnimation(ThermionEntity entity, int animationIndex) { - // TODO: implement stopAnimation + Future rotateIbl(Matrix3 rotation) async { + final ptr = _module._malloc(9 * 4) as JSNumber; + for (int i = 0; i < 9; i++) { + _module.setValue( + (ptr.toDartInt + (i * 4)).toJS, rotation.storage[i].toJS, "float"); + } + _module.ccall("rotate_ibl", "void", ["void*".toJS, "float*".toJS].toJS, + [_viewer!, ptr].toJS, null); + _module._free(ptr); + } + + @override + Future rotateStart(double x, double y) { + // TODO: implement rotateStart throw UnimplementedError(); } + @override + Future rotateUpdate(double x, double y) { + // TODO: implement rotateUpdate + throw UnimplementedError(); + } + + @override + Future setAnimationFrame( + ThermionEntity entity, int index, int animationFrame) async { + _module.ccall( + "set_animation_frame", + "void", + ["void*".toJS, "int".toJS, "int".toJS, "int".toJS].toJS, + [ + _sceneManager!, + entity.toJS, + index.toJS, + animationFrame.toJS, + ].toJS, + null); + } + + @override + Future setBackgroundImage(String path, {bool fillHeight = false}) async { + _module.ccall( + "set_background_image", + "void", + ["void*".toJS, "string".toJS, "bool".toJS].toJS, + [_viewer!, path.toJS, fillHeight.toJS].toJS, + null); + } + + @override + Future setBackgroundImagePosition(double x, double y, + {bool clamp = false}) async { + _module.ccall( + "set_background_image_position", + "void", + ["void*".toJS, "float".toJS, "float".toJS, "bool".toJS].toJS, + [_viewer!, x.toJS, y.toJS, clamp.toJS].toJS, + null); + } + + @override + Future setBloom(double bloom) async { + _module.ccall("set_bloom", "void", ["void*".toJS, "float".toJS].toJS, + [_viewer!, bloom.toJS].toJS, null); + } + + @override + Future setBoneTransform( + ThermionEntity entity, int boneIndex, Matrix4 transform, + {int skinIndex = 0}) async { + final ptr = _module._malloc(16 * 4) as JSNumber; + for (int i = 0; i < 16; i++) { + _module.setValue( + (ptr.toDartInt + (i * 4)).toJS, transform.storage[i].toJS, "float"); + } + final result = _module.ccall( + "set_bone_transform", + "bool", + ["void*".toJS, "int".toJS, "int".toJS, "int".toJS, "float*".toJS].toJS, + [_sceneManager!, entity.toJS, skinIndex.toJS, boneIndex.toJS, ptr].toJS, + null) as JSBoolean; + _module._free(ptr); + if (!result.toDart) { + throw Exception("Failed to set bone transform"); + } + } + + @override + Future setCamera(ThermionEntity entity, String? name) async { + final result = _module.ccall( + "set_camera", + "bool", + ["void*".toJS, "int".toJS, "string".toJS].toJS, + [_viewer!, entity.toJS, (name ?? "").toJS].toJS, + null) as JSBoolean; + if (!result.toDart) { + throw Exception("Failed to set camera to entity ${entity}"); + } + } + + @override + Future setCameraCulling(double near, double far) async { + _module.ccall( + "set_camera_culling", + "void", + ["void*".toJS, "double".toJS, "double".toJS].toJS, + [_viewer!, near.toJS, far.toJS].toJS, + null); + } + + @override + Future setCameraExposure( + double aperture, double shutterSpeed, double sensitivity) async { + _module.ccall( + "set_camera_exposure", + "void", + ["void*".toJS, "float".toJS, "float".toJS, "float".toJS].toJS, + [ + _viewer!, + aperture.toJS, + shutterSpeed.toJS, + sensitivity.toJS, + ].toJS, + null); + } + + @override + Future setCameraFocalLength(double focalLength) async { + _module.ccall( + "set_camera_focal_length", + "void", + ["void*".toJS, "float".toJS].toJS, + [_viewer!, focalLength.toJS].toJS, + null); + } + + @override + Future setCameraFocusDistance(double focusDistance) async { + _module.ccall( + "set_camera_focus_distance", + "void", + ["void*".toJS, "float".toJS].toJS, + [_viewer!, focusDistance.toJS].toJS, + null); + } + + @override + Future setCameraFov(double degrees, double width, double height) async { + _module.ccall( + "set_camera_fov", + "void", + ["void*".toJS, "float".toJS, "float".toJS].toJS, + [_viewer!, degrees.toJS, (width / height).toJS].toJS, + null); + } + + @override + Future setCameraManipulatorOptions( + {ManipulatorMode mode = ManipulatorMode.ORBIT, + double orbitSpeedX = 0.01, + double orbitSpeedY = 0.01, + double zoomSpeed = 0.01}) async { + _module.ccall( + "set_camera_manipulator_options", + "void", + ["void*".toJS, "int".toJS, "double".toJS, "double".toJS, "double".toJS] + .toJS, + [ + _viewer!, + mode.index.toJS, + orbitSpeedX.toJS, + orbitSpeedY.toJS, + zoomSpeed.toJS + ].toJS, + null); + } + + @override + Future setCameraModelMatrix(List matrix) async { + assert(matrix.length == 16, "Matrix must have 16 elements"); + final ptr = _module._malloc(16 * 8) as JSNumber; + for (int i = 0; i < 16; i++) { + _module.setValue( + (ptr.toDartInt + (i * 8)).toJS, matrix[i].toJS, "double"); + } + _module.ccall("set_camera_model_matrix", "void", + ["void*".toJS, "float*".toJS].toJS, [_viewer!, ptr].toJS, null); + _module._free(ptr); + } + + @override + Future setFrameRate(int framerate) async { + _module.ccall( + "set_frame_interval", + "void", + ["void*".toJS, "float".toJS].toJS, + [_viewer!, (1 / framerate).toJS].toJS, + null); + } + + @override + Future setMainCamera() async { + _module.ccall( + "set_main_camera", "void", ["void*".toJS].toJS, [_viewer!].toJS, null); + } + + @override + Future setMaterialColor(ThermionEntity entity, String meshName, + int materialIndex, double r, double g, double b, double a) async { + final result = _module.ccall( + "set_material_color", + "bool", + [ + "void*".toJS, + "int".toJS, + "string".toJS, + "int".toJS, + "float".toJS, + "float".toJS, + "float".toJS, + "float".toJS + ].toJS, + [ + _sceneManager!, + entity.toJS, + meshName.toJS, + materialIndex.toJS, + r.toJS, + g.toJS, + b.toJS, + a.toJS + ].toJS, + null) as JSBoolean; + if (!result.toDart) { + throw Exception("Failed to set material color"); + } + } + + @override + Future setMorphTargetWeights( + ThermionEntity entity, List weights) async { + final numWeights = weights.length; + final ptr = _module._malloc(numWeights * 4); + for (int i = 0; i < numWeights; i++) { + _module.setValue( + (ptr.toDartInt + (i * 4)).toJS, weights[i].toJS, "float"); + } + final result = _module.ccall( + "set_morph_target_weights", + "bool", + ["void*".toJS, "int".toJS, "float*".toJS, "int".toJS].toJS, + [_sceneManager!, entity.toJS, ptr, numWeights.toJS].toJS, + null) as JSBoolean; + _module._free(ptr); + if (!result.toDart) { + throw Exception("Failed to set morph target weights"); + } + } + + @override + Future setParent(ThermionEntity child, ThermionEntity parent) async { + _module.ccall( + "set_parent", + "void", + ["void*".toJS, "int".toJS, "int".toJS].toJS, + [_sceneManager!, child.toJS, parent.toJS].toJS, + null); + } + + @override + Future setPriority(ThermionEntity entityId, int priority) { + // TODO: implement setPriority + throw UnimplementedError(); + } + + @override + Future setRecording(bool recording) async { + _module.ccall("set_recording", "void", ["void*".toJS, "bool".toJS].toJS, + [_viewer!, recording.toJS].toJS, null); + } + + @override + Future setRecordingOutputDirectory(String outputDirectory) async { + _module.ccall( + "set_recording_output_directory", + "void", + ["void*".toJS, "string".toJS].toJS, + [_viewer!, outputDirectory.toJS].toJS, + null); + } + + Timer? _renderLoop; + + @override + Future setRendering(bool render) async { + if (render && !_rendering) { + _rendering = true; + _renderLoop = Timer.periodic(Duration(microseconds: 16667), (_) { + this.render(); + }); + } else if (!render && _rendering) { + _rendering = false; + _renderLoop?.cancel(); + _renderLoop = null; + } + } + + @override + Future setScale(ThermionEntity entity, double scale) async { + _module.ccall( + "set_scale", + "void", + ["void*".toJS, "int".toJS, "float".toJS].toJS, + [_sceneManager!, entity.toJS, scale.toJS].toJS, + null); + } + + @override + Future setToneMapping(ToneMapper mapper) async { + _module.ccall("set_tone_mapping", "void", ["void*".toJS, "int".toJS].toJS, + [_viewer!, mapper.index.toJS].toJS, null); + } + + @override + Future setTransform(ThermionEntity entity, Matrix4 transform) async { + final ptr = _module._malloc(16 * 4) as JSNumber; + for (int i = 0; i < 16; i++) { + _module.setValue( + (ptr.toDartInt + (i * 4)).toJS, transform.storage[i].toJS, "float"); + } + final result = _module.ccall( + "set_transform", + "bool", + ["void*".toJS, "int".toJS, "float*".toJS].toJS, + [_sceneManager!, entity.toJS, ptr].toJS, + null) as JSBoolean; + _module._free(ptr); + if (!result.toDart) { + throw Exception("Failed to set transform"); + } + } + + @override + Future setViewFrustumCulling(bool enabled) async { + _module.ccall("set_view_frustum_culling", "void", + ["void*".toJS, "bool".toJS].toJS, [_viewer!, enabled.toJS].toJS, null); + } + + @override + Future stopAnimation(ThermionEntity entity, int animationIndex) async { + _module.ccall( + "stop_animation", + "void", + ["void*".toJS, "int".toJS, "int".toJS].toJS, + [_sceneManager!, entity.toJS, animationIndex.toJS].toJS, + null); + } + @override Future stopAnimationByName(ThermionEntity entity, String name) { // TODO: implement stopAnimationByName diff --git a/thermion_dart/lib/thermion_dart/scene.dart b/thermion_dart/lib/thermion_dart/scene.dart index 2bb820b4..76d86d43 100644 --- a/thermion_dart/lib/thermion_dart/scene.dart +++ b/thermion_dart/lib/thermion_dart/scene.dart @@ -1,120 +1,48 @@ +import 'package:thermion_dart/thermion_dart/thermion_viewer.dart'; import 'dart:async'; -import 'thermion_viewer.dart'; - /// /// For now, this class just holds the entities that have been loaded (though not necessarily visible in the Filament Scene). /// -class SceneImpl extends Scene { - - ThermionViewer controller; - - SceneImpl(this.controller); - - @override +abstract class Scene { + /// + /// The last entity clicked/tapped in the viewport (internally, the result of calling pick); ThermionEntity? selected; - final _onUpdatedController = StreamController.broadcast(); - @override - Stream get onUpdated => _onUpdatedController.stream; - - final _onLoadController = StreamController.broadcast(); - @override - Stream get onLoad => _onLoadController.stream; - - final _onUnloadController = StreamController.broadcast(); - @override - Stream get onUnload => _onUnloadController.stream; - - final _lights = {}; - final _entities = {}; - - void registerLight(ThermionEntity entity) { - _lights.add(entity); - _onLoadController.sink.add(entity); - _onUpdatedController.add(true); - } - - void unregisterLight(ThermionEntity entity) async { - var children = await controller.getChildEntities(entity, true); - if (selected == entity || children.contains(selected)) { - selected = null; - controller.gizmo?.detach(); - } - _lights.remove(entity); - _onUnloadController.add(entity); - _onUpdatedController.add(true); - } - - void unregisterEntity(ThermionEntity entity) async { - var children = await controller.getChildEntities(entity, true); - if (selected == entity || children.contains(selected)) { - selected = null; - - controller.gizmo?.detach(); - } - _entities.remove(entity); - _onUnloadController.add(entity); - _onUpdatedController.add(true); - } - - void registerEntity(ThermionEntity entity) { - _entities.add(entity); - _onLoadController.sink.add(entity); - _onUpdatedController.add(true); - } - - void clearLights() { - for (final light in _lights) { - if (selected == light) { - selected = null; - controller.gizmo?.detach(); - } - _onUnloadController.add(light); - } - - _lights.clear(); - _onUpdatedController.add(true); - } - - void clearEntities() { - for (final entity in _entities) { - if (selected == entity) { - selected = null; - controller.gizmo?.detach(); - } - _onUnloadController.add(entity); - } - _entities.clear(); - _onUpdatedController.add(true); - } + /// + /// A Stream updated whenever an entity is added/removed from the scene. + /// + Stream get onUpdated; /// - /// Lists all entities currently loaded (not necessarily active in the scene). + /// A Stream containing every ThermionEntity added to the scene (i.e. via [loadGlb], [loadGltf] or [addLight]). + /// This is provided for convenience so you can set listeners in front-end widgets that can respond to entity loads without manually passing around the ThermionEntity returned from those methods. /// - Iterable listLights() { - return _lights; - } + Stream get onLoad; - @override - Iterable listEntities() { - return _entities; - } + /// + /// A Stream containing every ThermionEntity removed from the scene (i.e. via [removeEntity], [clearEntities], [removeLight] or [clearLights]). - void registerSelected(ThermionEntity entity) { - selected = entity; - _onUpdatedController.add(true); - } + Stream get onUnload; - void unregisterSelected() { - selected = null; - _onUpdatedController.add(true); - } + /// + /// Lists all light entities currently loaded (not necessarily active in the scene). Does not account for instances. + /// + Iterable listLights(); - @override - void select(ThermionEntity entity) { - selected = entity; - controller.gizmo?.attach(entity); - _onUpdatedController.add(true); - } -} + /// + /// Lists all entities currently loaded (not necessarily active in the scene). Does not account for instances. + /// + Iterable listEntities(); + + /// + /// Attach the gizmo to the specified entity. + /// + void select(ThermionEntity entity); + + /// + /// + /// + void registerEntity(ThermionEntity entity); + +} \ No newline at end of file diff --git a/thermion_dart/lib/thermion_dart/scene_impl.dart b/thermion_dart/lib/thermion_dart/scene_impl.dart new file mode 100644 index 00000000..a31c8848 --- /dev/null +++ b/thermion_dart/lib/thermion_dart/scene_impl.dart @@ -0,0 +1,125 @@ +import 'dart:async'; +import 'package:thermion_dart/thermion_dart/scene.dart'; +import 'thermion_viewer.dart'; + +/// +/// For now, this class just holds the entities that have been loaded (though not necessarily visible in the Filament Scene). +/// +class SceneImpl extends Scene { + ThermionViewer controller; + + SceneImpl(this.controller); + + @override + ThermionEntity? selected; + + final _onUpdatedController = StreamController.broadcast(); + @override + Stream get onUpdated => _onUpdatedController.stream; + + final _onLoadController = StreamController.broadcast(); + @override + Stream get onLoad => _onLoadController.stream; + + final _onUnloadController = StreamController.broadcast(); + @override + Stream get onUnload => _onUnloadController.stream; + + final _lights = {}; + final _entities = {}; + + void registerLight(ThermionEntity entity) { + _lights.add(entity); + _onLoadController.sink.add(entity); + _onUpdatedController.add(true); + } + + void unregisterLight(ThermionEntity entity) async { + var children = await controller.getChildEntities(entity, true); + if (selected == entity || children.contains(selected)) { + selected = null; + controller.gizmo?.detach(); + } + _lights.remove(entity); + _onUnloadController.add(entity); + _onUpdatedController.add(true); + } + + void unregisterEntity(ThermionEntity entity) async { + var children = await controller.getChildEntities(entity, true); + if (selected == entity || children.contains(selected)) { + selected = null; + + controller.gizmo?.detach(); + } + _entities.remove(entity); + _onUnloadController.add(entity); + _onUpdatedController.add(true); + } + + void registerEntity(ThermionEntity entity) { + _entities.add(entity); + _onLoadController.sink.add(entity); + _onUpdatedController.add(true); + } + + void clearLights() { + for (final light in _lights) { + if (selected == light) { + selected = null; + controller.gizmo?.detach(); + } + _onUnloadController.add(light); + } + + _lights.clear(); + _onUpdatedController.add(true); + } + + void clearEntities() { + for (final entity in _entities) { + if (selected == entity) { + selected = null; + controller.gizmo?.detach(); + } + _onUnloadController.add(entity); + } + _entities.clear(); + _onUpdatedController.add(true); + } + + /// + /// Lists all entities currently loaded (not necessarily active in the scene). + /// + Iterable listLights() { + return _lights; + } + + @override + Iterable listEntities() { + return _entities; + } + + void registerSelected(ThermionEntity entity) { + selected = entity; + _onUpdatedController.add(true); + } + + void unregisterSelected() { + selected = null; + _onUpdatedController.add(true); + } + + @override + void select(ThermionEntity entity) { + selected = entity; + controller.gizmo?.attach(entity); + _onUpdatedController.add(true); + } + + Future dispose() async { + await _onLoadController.close(); + await _onUnloadController.close(); + await _onUpdatedController.close(); + } +} diff --git a/thermion_dart/lib/thermion_dart/thermion_viewer.dart b/thermion_dart/lib/thermion_dart/thermion_viewer.dart index 93cf4995..332f5a83 100644 --- a/thermion_dart/lib/thermion_dart/thermion_viewer.dart +++ b/thermion_dart/lib/thermion_dart/thermion_viewer.dart @@ -1,5 +1,6 @@ import 'dart:math'; +import 'package:thermion_dart/thermion_dart/scene.dart'; import 'package:vector_math/vector_math_64.dart'; import 'dart:async'; import 'package:animation_tools_dart/animation_tools_dart.dart'; @@ -279,11 +280,11 @@ abstract class ThermionViewer { /// [fadeInInSecs]/[fadeOutInSecs]/[maxDelta] are used to cross-fade between /// the current active glTF animation ("animation1") and the animation you /// set via this method ("animation2"). The bone orientations will be - /// linearly interpolated between animation1 and animation2; at time 0, - /// the orientation will be 100% animation1, at time [fadeInInSecs], the + /// linearly interpolated between animation1 and animation2; at time 0, + /// the orientation will be 100% animation1, at time [fadeInInSecs], the /// animation will be ((1 - maxDelta) * animation1) + (maxDelta * animation2). - /// This will be applied in reverse after [fadeOutInSecs]. - /// + /// This will be applied in reverse after [fadeOutInSecs]. + /// /// Future addBoneAnimation(ThermionEntity entity, BoneAnimationData animation, {int skinIndex = 0, @@ -701,51 +702,11 @@ abstract class ThermionViewer { /// /// AbstractGizmo? get gizmo; -} - -/// -/// For now, this class just holds the entities that have been loaded (though not necessarily visible in the Filament Scene). -/// -abstract class Scene { - /// - /// The last entity clicked/tapped in the viewport (internally, the result of calling pick); - ThermionEntity? selected; /// - /// A Stream updated whenever an entity is added/removed from the scene. + /// Register a callback to be invoked when this viewer is disposed. /// - Stream get onUpdated; - - /// - /// A Stream containing every ThermionEntity added to the scene (i.e. via [loadGlb], [loadGltf] or [addLight]). - /// This is provided for convenience so you can set listeners in front-end widgets that can respond to entity loads without manually passing around the ThermionEntity returned from those methods. - /// - Stream get onLoad; - - /// - /// A Stream containing every ThermionEntity removed from the scene (i.e. via [removeEntity], [clearEntities], [removeLight] or [clearLights]). - - Stream get onUnload; - - /// - /// Lists all light entities currently loaded (not necessarily active in the scene). Does not account for instances. - /// - Iterable listLights(); - - /// - /// Lists all entities currently loaded (not necessarily active in the scene). Does not account for instances. - /// - Iterable listEntities(); - - /// - /// Attach the gizmo to the specified entity. - /// - void select(ThermionEntity entity); - - /// - /// - /// - void registerEntity(ThermionEntity entity); + void onDispose(Future Function() callback); } abstract class AbstractGizmo { diff --git a/thermion_dart/lib/thermion_dart/thermion_viewer_ffi.dart b/thermion_dart/lib/thermion_dart/thermion_viewer_ffi.dart index ab83957e..fba9b24c 100644 --- a/thermion_dart/lib/thermion_dart/thermion_viewer_ffi.dart +++ b/thermion_dart/lib/thermion_dart/thermion_viewer_ffi.dart @@ -1,15 +1,15 @@ import 'dart:async'; -import 'dart:io'; import 'dart:math'; import 'package:animation_tools_dart/animation_tools_dart.dart'; import 'package:thermion_dart/thermion_dart/compatibility/compatibility.dart'; - import 'package:thermion_dart/thermion_dart/entities/gizmo.dart'; - +import 'package:thermion_dart/thermion_dart/scene.dart'; import 'package:vector_math/vector_math_64.dart'; import 'thermion_viewer.dart'; -import 'scene.dart'; -import 'compatibility/compatibility.dart'; +import 'scene_impl.dart'; +import 'package:logging/logging.dart'; + +typedef ThermionViewerImpl = ThermionViewerFFI; // ignore: constant_identifier_names const ThermionEntity _FILAMENT_ASSET_ERROR = 0; @@ -17,10 +17,10 @@ const ThermionEntity _FILAMENT_ASSET_ERROR = 0; typedef RenderCallback = Pointer)>>; class ThermionViewerFFI extends ThermionViewer { - final _compat = Compatibility(); + final _logger = Logger("ThermionViewerFFI"); - late SceneImpl _scene; - Scene get scene => _scene; + SceneImpl? _scene; + Scene get scene => _scene!; double _pixelRatio = 1.0; @@ -72,7 +72,7 @@ class ThermionViewerFFI extends ThermionViewer { NativeCallable.listener( _onPickResult); } catch (err) { - print( + _logger.severe( "Failed to set pick result callback. This is expected if running on web/wasm"); } _initialize(); @@ -172,14 +172,39 @@ class ThermionViewerFFI extends ThermionViewer { set_frame_interval_ffi(_viewer!, interval); } + final _onDispose = []; + /// /// /// @override Future dispose() async { + if (_viewer == null) { + // we've already cleaned everything up, ignore the call to dispose + return; + } + await setRendering(false); + await clearEntities(); + await clearLights(); + await _scene!.dispose(); + _scene = null; destroy_filament_viewer_ffi(_viewer!); + _sceneManager = null; _viewer = null; + await _pickResultController.close(); + + for (final callback in _onDispose) { + await callback.call(); + } + _onDispose.clear(); + } + + /// + /// + /// + void onDispose(Future Function() callback) { + _onDispose.add(callback); } /// @@ -317,7 +342,7 @@ class ThermionViewerFFI extends ThermionViewer { throw Exception("Failed to add light to scene"); } - _scene.registerLight(entity); + _scene!.registerLight(entity); return entity; } @@ -326,7 +351,7 @@ class ThermionViewerFFI extends ThermionViewer { /// @override Future removeLight(ThermionEntity entity) async { - _scene.unregisterLight(entity); + _scene!.unregisterLight(entity); remove_light_ffi(_viewer!, entity); } @@ -337,7 +362,7 @@ class ThermionViewerFFI extends ThermionViewer { Future clearLights() async { clear_lights_ffi(_viewer!); - _scene.clearLights(); + _scene!.clearLights(); } /// @@ -393,7 +418,7 @@ class ThermionViewerFFI extends ThermionViewer { if (entity == _FILAMENT_ASSET_ERROR) { throw Exception("An error occurred loading the asset at $path"); } - _scene.registerEntity(entity); + _scene!.registerEntity(entity); return entity; } @@ -404,11 +429,6 @@ class ThermionViewerFFI extends ThermionViewer { @override Future loadGltf(String path, String relativeResourcePath, {bool force = false}) async { - // if (Platform.isWindows && !force) { - // throw Exception( - // "loadGltf has a race condition on Windows which is likely to crash your program. If you really want to try, pass force=true to loadGltf"); - // } - final pathPtr = path.toNativeUtf8(allocator: allocator).cast(); final relativeResourcePathPtr = relativeResourcePath.toNativeUtf8(allocator: allocator).cast(); @@ -419,7 +439,7 @@ class ThermionViewerFFI extends ThermionViewer { if (entity == _FILAMENT_ASSET_ERROR) { throw Exception("An error occurred loading the asset at $path"); } - _scene.registerEntity(entity); + _scene!.registerEntity(entity); return entity; } @@ -608,14 +628,12 @@ class ThermionViewerFFI extends ThermionViewer { var meshEntity = meshEntities[i]; if (targetMeshNames?.contains(meshName) == false) { - print("Skipping $meshName, not contained in target"); + _logger.info("Skipping $meshName, not contained in target"); continue; } var meshMorphTargets = await getMorphTargetNames(entity, meshEntity); - print("Got mesh morph targets ${meshMorphTargets}"); - var intersection = animation.morphTargets .toSet() .intersection(meshMorphTargets.toSet()) @@ -676,8 +694,8 @@ class ThermionViewerFFI extends ThermionViewer { Future addBoneAnimation(ThermionEntity entity, BoneAnimationData animation, {int skinIndex = 0, double fadeOutInSecs = 0.0, - double fadeInInSecs = 0.0, - double maxDelta=1.0}) async { + double fadeInInSecs = 0.0, + double maxDelta = 1.0}) async { if (animation.space != Space.Bone && animation.space != Space.ParentWorldRotation) { throw UnimplementedError("TODO - support ${animation.space}"); @@ -710,7 +728,7 @@ class ThermionViewerFFI extends ThermionViewer { var boneName = animation.bones[i]; var entityBoneIndex = boneNames.indexOf(boneName); if (entityBoneIndex == -1) { - print("Warning : bone $boneName not found, skipping"); + _logger.warning("Bone $boneName not found, skipping"); continue; } var boneEntity = bones[entityBoneIndex]; @@ -720,7 +738,7 @@ class ThermionViewerFFI extends ThermionViewer { var world = Matrix4.identity(); // this odd use of ! is intentional, without it, the WASM optimizer gets in trouble var parentBoneEntity = (await getParent(boneEntity))!; - while(true) { + while (true) { if (!bones.contains(parentBoneEntity!)) { break; } @@ -758,7 +776,7 @@ class ThermionViewerFFI extends ThermionViewer { numFrames, animation.frameLengthInMs, fadeOutInSecs, - fadeInInSecs, + fadeInInSecs, maxDelta); } allocator.free(data); @@ -894,7 +912,7 @@ class ThermionViewerFFI extends ThermionViewer { /// @override Future removeEntity(ThermionEntity entity) async { - _scene.unregisterEntity(entity); + _scene!.unregisterEntity(entity); await withVoidCallback( (callback) => remove_entity_ffi(_viewer!, entity, callback)); @@ -911,7 +929,7 @@ class ThermionViewerFFI extends ThermionViewer { await withVoidCallback((callback) { clear_entities_ffi(_viewer!, callback); }); - _scene.clearEntities(); + _scene!.clearEntities(); } /// @@ -984,7 +1002,6 @@ class ThermionViewerFFI extends ThermionViewer { var animations = await getAnimationNames(entity); var index = animations.indexOf(name); var duration = await getAnimationDuration(entity, index); - print("Duration for $name : $duration"); await playAnimation(entity, index, loop: loop, reverse: reverse, @@ -1296,7 +1313,7 @@ class ThermionViewerFFI extends ThermionViewer { x: (x / _pixelRatio).toDouble(), y: (viewportDimensions.$2 - y) / _pixelRatio )); - _scene.registerSelected(entityId); + _scene!.registerSelected(entityId); } late NativeCallable @@ -1307,7 +1324,7 @@ class ThermionViewerFFI extends ThermionViewer { /// @override void pick(int x, int y) async { - _scene.unregisterSelected(); + _scene!.unregisterSelected(); filament_pick( _viewer!, @@ -1414,9 +1431,6 @@ class ThermionViewerFFI extends ThermionViewer { 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); @@ -1432,7 +1446,7 @@ class ThermionViewerFFI extends ThermionViewer { if (_viewer == null) { throw Exception("No viewer available"); } - print( + throw Exception( "WARNING: getCameraProjectionMatrix and getCameraCullingProjectionMatrix are not reliable. Consider these broken"); var arrayPtr = get_camera_culling_projection_matrix(_viewer!); var doubleList = arrayPtr.asTypedList(16); @@ -1451,7 +1465,6 @@ class ThermionViewerFFI extends ThermionViewer { } var arrayPtr = get_camera_frustum(_viewer!); var doubleList = arrayPtr.asTypedList(24); - print(doubleList); var frustum = Frustum(); frustum.plane0.setFromComponents( @@ -1624,7 +1637,7 @@ class ThermionViewerFFI extends ThermionViewer { throw Exception("Failed to create geometry"); } - _scene.registerEntity(entity); + _scene!.registerEntity(entity); allocator.free(materialPathPtr); allocator.free(vertexPtr); diff --git a/thermion_dart/lib/thermion_dart/thermion_viewer_stub.dart b/thermion_dart/lib/thermion_dart/thermion_viewer_stub.dart new file mode 100644 index 00000000..2fff9d97 --- /dev/null +++ b/thermion_dart/lib/thermion_dart/thermion_viewer_stub.dart @@ -0,0 +1,729 @@ +import 'dart:math'; + +import 'package:thermion_dart/thermion_dart/scene.dart'; +import 'package:thermion_dart/thermion_dart/thermion_viewer.dart'; +import 'package:vector_math/vector_math_64.dart'; +import 'dart:async'; +import 'package:animation_tools_dart/animation_tools_dart.dart'; + +typedef ThermionViewerImpl = ThermionViewerStub; + +class ThermionViewerStub extends ThermionViewer { + @override + Future addAnimationComponent(ThermionEntity entity) { + // TODO: implement addAnimationComponent + throw UnimplementedError(); + } + + @override + Future addBoneAnimation(ThermionEntity entity, BoneAnimationData animation, + {int skinIndex = 0, + double fadeInInSecs = 0.0, + double fadeOutInSecs = 0.0, + double maxDelta = 1.0}) { + // TODO: implement addBoneAnimation + throw UnimplementedError(); + } + + @override + Future addCollisionComponent(ThermionEntity entity, + {void Function(int entityId1, int entityId2)? callback, + bool affectsTransform = false}) { + // TODO: implement addCollisionComponent + throw UnimplementedError(); + } + + @override + Future addLight( + LightType type, + double colour, + double intensity, + double posX, + double posY, + double posZ, + double dirX, + double dirY, + double dirZ, + {double falloffRadius = 1.0, + double spotLightConeInner = pi / 8, + double spotLightConeOuter = pi / 4, + double sunAngularRadius = 0.545, + double sunHaloSize = 10.0, + double sunHaloFallof = 80.0, + bool castShadows = true}) { + // TODO: implement addLight + throw UnimplementedError(); + } + + @override + Future clearBackgroundImage() { + // TODO: implement clearBackgroundImage + throw UnimplementedError(); + } + + @override + Future clearEntities() { + // TODO: implement clearEntities + throw UnimplementedError(); + } + + @override + Future clearLights() { + // TODO: implement clearLights + throw UnimplementedError(); + } + + @override + Future createGeometry(List vertices, List indices, + {String? materialPath, + PrimitiveType primitiveType = PrimitiveType.TRIANGLES}) { + // TODO: implement createGeometry + throw UnimplementedError(); + } + + @override + Future createInstance(ThermionEntity entity) { + // TODO: implement createInstance + throw UnimplementedError(); + } + + @override + Future dispose() { + // TODO: implement dispose + throw UnimplementedError(); + } + + @override + Future getAnimationDuration( + ThermionEntity entity, int animationIndex) { + // TODO: implement getAnimationDuration + throw UnimplementedError(); + } + + @override + Future> getAnimationNames(ThermionEntity entity) { + // TODO: implement getAnimationNames + throw UnimplementedError(); + } + + @override + Future getBone(ThermionEntity parent, int boneIndex, + {int skinIndex = 0}) { + // TODO: implement getBone + throw UnimplementedError(); + } + + @override + Future> getBoneNames(ThermionEntity entity, + {int skinIndex = 0}) { + // TODO: implement getBoneNames + throw UnimplementedError(); + } + + @override + Future getCameraCullingFar() { + // TODO: implement getCameraCullingFar + throw UnimplementedError(); + } + + @override + Future getCameraCullingNear() { + // TODO: implement getCameraCullingNear + throw UnimplementedError(); + } + + @override + Future getCameraCullingProjectionMatrix() { + // TODO: implement getCameraCullingProjectionMatrix + throw UnimplementedError(); + } + + @override + Future getCameraFrustum() { + // TODO: implement getCameraFrustum + throw UnimplementedError(); + } + + @override + Future getCameraModelMatrix() { + // TODO: implement getCameraModelMatrix + throw UnimplementedError(); + } + + @override + Future getCameraPosition() { + // TODO: implement getCameraPosition + throw UnimplementedError(); + } + + @override + Future getCameraProjectionMatrix() { + // TODO: implement getCameraProjectionMatrix + throw UnimplementedError(); + } + + @override + Future getCameraRotation() { + // TODO: implement getCameraRotation + throw UnimplementedError(); + } + + @override + Future getCameraViewMatrix() { + // TODO: implement getCameraViewMatrix + throw UnimplementedError(); + } + + @override + Future> getChildEntities( + ThermionEntity parent, bool renderableOnly) { + // TODO: implement getChildEntities + throw UnimplementedError(); + } + + @override + Future getChildEntity( + ThermionEntity parent, String childName) { + // TODO: implement getChildEntity + throw UnimplementedError(); + } + + @override + Future> getChildEntityNames(ThermionEntity entity, + {bool renderableOnly = true}) { + // TODO: implement getChildEntityNames + throw UnimplementedError(); + } + + @override + Future getInstanceCount(ThermionEntity entity) { + // TODO: implement getInstanceCount + throw UnimplementedError(); + } + + @override + Future> getInstances(ThermionEntity entity) { + // TODO: implement getInstances + throw UnimplementedError(); + } + + @override + Future getInverseBindMatrix(ThermionEntity parent, int boneIndex, + {int skinIndex = 0}) { + // TODO: implement getInverseBindMatrix + throw UnimplementedError(); + } + + @override + Future getLocalTransform(ThermionEntity entity) { + // TODO: implement getLocalTransform + throw UnimplementedError(); + } + + @override + Future getMainCamera() { + // TODO: implement getMainCamera + throw UnimplementedError(); + } + + @override + Future> getMorphTargetNames( + ThermionEntity entity, ThermionEntity childEntity) { + // TODO: implement getMorphTargetNames + throw UnimplementedError(); + } + + @override + String? getNameForEntity(ThermionEntity entity) { + // TODO: implement getNameForEntity + throw UnimplementedError(); + } + + @override + Future getParent(ThermionEntity child) { + // TODO: implement getParent + throw UnimplementedError(); + } + + @override + Future getWorldTransform(ThermionEntity entity) { + // TODO: implement getWorldTransform + throw UnimplementedError(); + } + + @override + // TODO: implement gizmo + AbstractGizmo? get gizmo => throw UnimplementedError(); + + @override + Future hide(ThermionEntity entity, String? meshName) { + // TODO: implement hide + throw UnimplementedError(); + } + + @override + // TODO: implement initialized + Future get initialized => throw UnimplementedError(); + + @override + Future loadGlb(String path, {int numInstances = 1}) { + // TODO: implement loadGlb + throw UnimplementedError(); + } + + @override + Future loadGltf(String path, String relativeResourcePath, + {bool force = false}) { + // TODO: implement loadGltf + throw UnimplementedError(); + } + + @override + Future loadIbl(String lightingPath, {double intensity = 30000}) { + // TODO: implement loadIbl + throw UnimplementedError(); + } + + @override + Future loadSkybox(String skyboxPath) { + // TODO: implement loadSkybox + throw UnimplementedError(); + } + + @override + Future moveCameraToAsset(ThermionEntity entity) { + // TODO: implement moveCameraToAsset + throw UnimplementedError(); + } + + @override + void onDispose(Future Function() callback) { + // TODO: implement onDispose + } + + @override + Future panEnd() { + // TODO: implement panEnd + throw UnimplementedError(); + } + + @override + Future panStart(double x, double y) { + // TODO: implement panStart + throw UnimplementedError(); + } + + @override + Future panUpdate(double x, double y) { + // TODO: implement panUpdate + throw UnimplementedError(); + } + + @override + void pick(int x, int y) { + // TODO: implement pick + } + + @override + // TODO: implement pickResult + Stream get pickResult => throw UnimplementedError(); + + @override + Future playAnimation(ThermionEntity entity, int index, + {bool loop = false, + bool reverse = false, + bool replaceActive = true, + double crossfade = 0.0}) { + // TODO: implement playAnimation + throw UnimplementedError(); + } + + @override + Future playAnimationByName(ThermionEntity entity, String name, + {bool loop = false, + bool reverse = false, + bool replaceActive = true, + double crossfade = 0.0}) { + // TODO: implement playAnimationByName + throw UnimplementedError(); + } + + @override + Future queuePositionUpdate( + ThermionEntity entity, double x, double y, double z, + {bool relative = false}) { + // TODO: implement queuePositionUpdate + throw UnimplementedError(); + } + + @override + Future queueRotationUpdate( + ThermionEntity entity, double rads, double x, double y, double z, + {bool relative = false}) { + // TODO: implement queueRotationUpdate + throw UnimplementedError(); + } + + @override + Future queueRotationUpdateQuat(ThermionEntity entity, Quaternion quat, + {bool relative = false}) { + // TODO: implement queueRotationUpdateQuat + throw UnimplementedError(); + } + + @override + Future removeAnimationComponent(ThermionEntity entity) { + // TODO: implement removeAnimationComponent + throw UnimplementedError(); + } + + @override + Future removeCollisionComponent(ThermionEntity entity) { + // TODO: implement removeCollisionComponent + throw UnimplementedError(); + } + + @override + Future removeEntity(ThermionEntity entity) { + // TODO: implement removeEntity + throw UnimplementedError(); + } + + @override + Future removeIbl() { + // TODO: implement removeIbl + throw UnimplementedError(); + } + + @override + Future removeLight(ThermionEntity light) { + // TODO: implement removeLight + throw UnimplementedError(); + } + + @override + Future removeSkybox() { + // TODO: implement removeSkybox + throw UnimplementedError(); + } + + @override + Future render() { + // TODO: implement render + throw UnimplementedError(); + } + + @override + // TODO: implement rendering + bool get rendering => throw UnimplementedError(); + + @override + Future resetBones(ThermionEntity entity) { + // TODO: implement resetBones + throw UnimplementedError(); + } + + @override + Future reveal(ThermionEntity entity, String? meshName) { + // TODO: implement reveal + throw UnimplementedError(); + } + + @override + Future rotateEnd() { + // TODO: implement rotateEnd + throw UnimplementedError(); + } + + @override + Future rotateIbl(Matrix3 rotation) { + // TODO: implement rotateIbl + throw UnimplementedError(); + } + + @override + Future rotateStart(double x, double y) { + // TODO: implement rotateStart + throw UnimplementedError(); + } + + @override + Future rotateUpdate(double x, double y) { + // TODO: implement rotateUpdate + throw UnimplementedError(); + } + + @override + // TODO: implement scene + Scene get scene => throw UnimplementedError(); + + @override + Future setAnimationFrame( + ThermionEntity entity, int index, int animationFrame) { + // TODO: implement setAnimationFrame + throw UnimplementedError(); + } + + @override + Future setAntiAliasing(bool msaa, bool fxaa, bool taa) { + // TODO: implement setAntiAliasing + throw UnimplementedError(); + } + + @override + Future setBackgroundColor(double r, double g, double b, double alpha) { + // TODO: implement setBackgroundColor + throw UnimplementedError(); + } + + @override + Future setBackgroundImage(String path, {bool fillHeight = false}) { + // TODO: implement setBackgroundImage + throw UnimplementedError(); + } + + @override + Future setBackgroundImagePosition(double x, double y, {bool clamp = false}) { + // TODO: implement setBackgroundImagePosition + throw UnimplementedError(); + } + + @override + Future setBloom(double bloom) { + // TODO: implement setBloom + throw UnimplementedError(); + } + + @override + Future setBoneTransform( + ThermionEntity entity, int boneIndex, Matrix4 transform, + {int skinIndex = 0}) { + // TODO: implement setBoneTransform + throw UnimplementedError(); + } + + @override + Future setCamera(ThermionEntity entity, String? name) { + // TODO: implement setCamera + throw UnimplementedError(); + } + + @override + Future setCameraCulling(double near, double far) { + // TODO: implement setCameraCulling + throw UnimplementedError(); + } + + @override + Future setCameraExposure( + double aperture, double shutterSpeed, double sensitivity) { + // TODO: implement setCameraExposure + throw UnimplementedError(); + } + + @override + Future setCameraFocalLength(double focalLength) { + // TODO: implement setCameraFocalLength + throw UnimplementedError(); + } + + @override + Future setCameraFocusDistance(double focusDistance) { + // TODO: implement setCameraFocusDistance + throw UnimplementedError(); + } + + @override + Future setCameraFov(double degrees, double width, double height) { + // TODO: implement setCameraFov + throw UnimplementedError(); + } + + @override + Future setCameraManipulatorOptions( + {ManipulatorMode mode = ManipulatorMode.ORBIT, + double orbitSpeedX = 0.01, + double orbitSpeedY = 0.01, + double zoomSpeed = 0.01}) { + // TODO: implement setCameraManipulatorOptions + throw UnimplementedError(); + } + + @override + Future setCameraModelMatrix(List matrix) { + // TODO: implement setCameraModelMatrix + throw UnimplementedError(); + } + + @override + Future setCameraPosition(double x, double y, double z) { + // TODO: implement setCameraPosition + throw UnimplementedError(); + } + + @override + Future setCameraRotation(Quaternion quaternion) { + // TODO: implement setCameraRotation + throw UnimplementedError(); + } + + @override + Future setFrameRate(int framerate) { + // TODO: implement setFrameRate + throw UnimplementedError(); + } + + @override + Future setMainCamera() { + // TODO: implement setMainCamera + throw UnimplementedError(); + } + + @override + Future setMaterialColor(ThermionEntity entity, String meshName, + int materialIndex, double r, double g, double b, double a) { + // TODO: implement setMaterialColor + throw UnimplementedError(); + } + + @override + Future setMorphAnimationData( + ThermionEntity entity, MorphAnimationData animation, + {List? targetMeshNames}) { + // TODO: implement setMorphAnimationData + throw UnimplementedError(); + } + + @override + Future setMorphTargetWeights(ThermionEntity entity, List weights) { + // TODO: implement setMorphTargetWeights + throw UnimplementedError(); + } + + @override + Future setParent(ThermionEntity child, ThermionEntity parent) { + // TODO: implement setParent + throw UnimplementedError(); + } + + @override + Future setPosition(ThermionEntity entity, double x, double y, double z) { + // TODO: implement setPosition + throw UnimplementedError(); + } + + @override + Future setPostProcessing(bool enabled) { + // TODO: implement setPostProcessing + throw UnimplementedError(); + } + + @override + Future setPriority(ThermionEntity entityId, int priority) { + // TODO: implement setPriority + throw UnimplementedError(); + } + + @override + Future setRecording(bool recording) { + // TODO: implement setRecording + throw UnimplementedError(); + } + + @override + Future setRecordingOutputDirectory(String outputDirectory) { + // TODO: implement setRecordingOutputDirectory + throw UnimplementedError(); + } + + @override + Future setRendering(bool render) { + // TODO: implement setRendering + throw UnimplementedError(); + } + + @override + Future setRotation( + ThermionEntity entity, double rads, double x, double y, double z) { + // TODO: implement setRotation + throw UnimplementedError(); + } + + @override + Future setRotationQuat(ThermionEntity entity, Quaternion rotation) { + // TODO: implement setRotationQuat + throw UnimplementedError(); + } + + @override + Future setScale(ThermionEntity entity, double scale) { + // TODO: implement setScale + throw UnimplementedError(); + } + + @override + Future setToneMapping(ToneMapper mapper) { + // TODO: implement setToneMapping + throw UnimplementedError(); + } + + @override + Future setTransform(ThermionEntity entity, Matrix4 transform) { + // TODO: implement setTransform + throw UnimplementedError(); + } + + @override + Future setViewFrustumCulling(bool enabled) { + // TODO: implement setViewFrustumCulling + throw UnimplementedError(); + } + + @override + Future stopAnimation(ThermionEntity entity, int animationIndex) { + // TODO: implement stopAnimation + throw UnimplementedError(); + } + + @override + Future stopAnimationByName(ThermionEntity entity, String name) { + // TODO: implement stopAnimationByName + throw UnimplementedError(); + } + + @override + Future testCollisions(ThermionEntity entity) { + // TODO: implement testCollisions + throw UnimplementedError(); + } + + @override + Future transformToUnitCube(ThermionEntity entity) { + // TODO: implement transformToUnitCube + throw UnimplementedError(); + } + + @override + Future updateBoneMatrices(ThermionEntity entity) { + // TODO: implement updateBoneMatrices + throw UnimplementedError(); + } + + @override + Future zoomBegin() { + // TODO: implement zoomBegin + throw UnimplementedError(); + } + + @override + Future zoomEnd() { + // TODO: implement zoomEnd + throw UnimplementedError(); + } + + @override + Future zoomUpdate(double x, double y, double z) { + // TODO: implement zoomUpdate + throw UnimplementedError(); + } +} diff --git a/thermion_dart/native/include/FilamentViewer.hpp b/thermion_dart/native/include/FilamentViewer.hpp index 52cc7ff6..32f59744 100644 --- a/thermion_dart/native/include/FilamentViewer.hpp +++ b/thermion_dart/native/include/FilamentViewer.hpp @@ -211,6 +211,7 @@ namespace thermion_filament uint32_t _imageWidth = 0; mat4f _imageScale; Texture *_imageTexture = nullptr; + Texture *_dummyImageTexture = nullptr; utils::Entity _imageEntity; VertexBuffer *_imageVb = nullptr; IndexBuffer *_imageIb = nullptr; diff --git a/thermion_dart/native/include/ResourceBuffer.hpp b/thermion_dart/native/include/ResourceBuffer.hpp index 57c68bda..94f64c05 100644 --- a/thermion_dart/native/include/ResourceBuffer.hpp +++ b/thermion_dart/native/include/ResourceBuffer.hpp @@ -3,7 +3,6 @@ #include "ResourceBuffer.h" -#if defined(__cplusplus) #ifndef __EMSCRIPTEN__ #include using namespace std::chrono_literals; @@ -15,6 +14,15 @@ namespace thermion_filament struct ResourceLoaderWrapperImpl : public ResourceLoaderWrapper { + ResourceLoaderWrapperImpl(ResourceLoaderWrapper* wrapper) { + loadFromOwner = wrapper->loadFromOwner; + freeFromOwner = wrapper->freeFromOwner; + loadResource = wrapper->loadResource; + freeResource = wrapper->freeResource; + owner = wrapper->owner; + loadToOut = wrapper->loadToOut; + } + ResourceLoaderWrapperImpl(LoadFilamentResource loader, FreeFilamentResource freeResource) { loadFromOwner = nullptr; @@ -73,4 +81,3 @@ namespace thermion_filament } #endif -#endif diff --git a/thermion_dart/native/include/SceneManager.hpp b/thermion_dart/native/include/SceneManager.hpp index f9f6efaa..484b75d2 100644 --- a/thermion_dart/native/include/SceneManager.hpp +++ b/thermion_dart/native/include/SceneManager.hpp @@ -179,7 +179,6 @@ namespace thermion_filament gltfio::TextureProvider *_stbDecoder = nullptr; gltfio::TextureProvider *_ktxDecoder = nullptr; std::mutex _mutex; - Material *_gizmoMaterial; utils::NameComponentManager *_ncm; @@ -201,8 +200,9 @@ namespace thermion_filament const char *entityName); EntityId addGizmo(); - utils::Entity _gizmoX; - utils::Entity _gizmoY; - utils::Entity _gizmoZ; + utils::Entity _gizmo[3]; + Material* _gizmoMaterial; + MaterialInstance* _gizmoMaterialInstances[3]; + }; } diff --git a/thermion_dart/native/src/FilamentViewer.cpp b/thermion_dart/native/src/FilamentViewer.cpp index 653d009b..fbc091c0 100644 --- a/thermion_dart/native/src/FilamentViewer.cpp +++ b/thermion_dart/native/src/FilamentViewer.cpp @@ -126,11 +126,10 @@ namespace thermion_filament static const uint16_t sFullScreenTriangleIndices[3] = {0, 1, 2}; - FilamentViewer::FilamentViewer(const void *sharedContext, const ResourceLoaderWrapperImpl *const ResourceLoaderWrapperImpl, void *const platform, const char *uberArchivePath) - : _resourceLoaderWrapper(ResourceLoaderWrapperImpl) + FilamentViewer::FilamentViewer(const void *sharedContext, const ResourceLoaderWrapperImpl *const resourceLoader, void *const platform, const char *uberArchivePath) + : _resourceLoaderWrapper(resourceLoader) { _context = (void*) sharedContext; - ASSERT_POSTCONDITION(_resourceLoaderWrapper != nullptr, "Resource loader must be non-null"); #if TARGET_OS_IPHONE @@ -215,7 +214,7 @@ namespace thermion_filament Log("Created scene maager"); - _imageTexture = Texture::Builder() + _dummyImageTexture = Texture::Builder() .width(1) .height(1) .levels(0x01) @@ -230,7 +229,7 @@ namespace thermion_filament .build(*_engine); _imageMaterial->setDefaultParameter("showImage", 0); _imageMaterial->setDefaultParameter("backgroundColor", RgbaType::sRGB, float4(1.0f, 1.0f, 1.0f, 0.0f)); - _imageMaterial->setDefaultParameter("image", _imageTexture, _imageSampler); + _imageMaterial->setDefaultParameter("image", _dummyImageTexture, _imageSampler); } catch (...) { @@ -269,7 +268,6 @@ namespace thermion_filament .culling(false) .build(*_engine, _imageEntity); _scene->addEntity(_imageEntity); - Log("Added imageEntity %d", _imageEntity); } void FilamentViewer::setAntiAliasing(bool msaa, bool fxaa, bool taa) @@ -444,7 +442,8 @@ namespace thermion_filament new ktxreader::Ktx1Bundle(static_cast(rb.data), static_cast(rb.size)); - // because the ResourceBuffer will go out of scope before the texture callback is invoked, we need to make a copy to the heap + // the ResourceBuffer will go out of scope before the texture callback is invoked + // make a copy to the heap ResourceBuffer *rbCopy = new ResourceBuffer(rb); std::vector *callbackData = new std::vector{(void *)_resourceLoaderWrapper, rbCopy}; @@ -550,6 +549,7 @@ namespace thermion_filament void FilamentViewer::clearBackgroundImage() { + _imageMaterial->setDefaultParameter("image", _dummyImageTexture, _imageSampler); _imageMaterial->setDefaultParameter("showImage", 0); if (_imageTexture) { @@ -680,22 +680,21 @@ namespace thermion_filament FilamentViewer::~FilamentViewer() { - clearEntities(); + clearLights(); + destroySwapChain(); + _engine->destroy(_imageEntity); + _engine->destroy(_imageTexture); + _engine->destroy(_imageVb); + _engine->destroy(_imageIb); + _engine->destroy(_imageMaterial); delete _sceneManager; - - for (auto it : _lights) - { - _engine->destroy(it); - } - _engine->destroyCameraComponent(_mainCamera->getEntity()); _mainCamera = nullptr; _engine->destroy(_view); _engine->destroy(_scene); _engine->destroy(_renderer); - _engine->destroy(_swapChain); - - Engine::destroy(&_engine); // clears engine* + Engine::destroy(&_engine); + delete _resourceLoaderWrapper; } Renderer *FilamentViewer::getRenderer() { return _renderer; } @@ -930,7 +929,6 @@ namespace thermion_filament } Log("Loading skybox from path %s", skyboxPath); - ResourceBuffer skyboxBuffer = _resourceLoaderWrapper->load(skyboxPath); // because this will go out of scope before the texture callback is invoked, we need to make a copy of the variable itself (not its contents) diff --git a/thermion_dart/native/src/SceneManager.cpp b/thermion_dart/native/src/SceneManager.cpp index c75a6f4d..2329492f 100644 --- a/thermion_dart/native/src/SceneManager.cpp +++ b/thermion_dart/native/src/SceneManager.cpp @@ -93,10 +93,28 @@ namespace thermion_filament SceneManager::~SceneManager() { + + destroyAll(); + + for(int i =0; i < 3; i++) { + _engine->destroy(_gizmo[i]); + _engine->destroy(_gizmoMaterialInstances[i]); + } + + _engine->destroy(_gizmoMaterial); _gltfResourceLoader->asyncCancelLoad(); _ubershaderProvider->destroyMaterials(); - destroyAll(); + + delete _animationComponentManager; + delete _collisionComponentManager; + delete _ncm; + + delete _gltfResourceLoader; + delete _stbDecoder; + delete _ktxDecoder; + delete _ubershaderProvider; AssetLoader::destroy(&_assetLoader); + } int SceneManager::getInstanceCount(EntityId entityId) @@ -2045,72 +2063,57 @@ namespace thermion_filament auto &entityManager = EntityManager::get(); - _gizmoY = entityManager.create(); - auto materialY = _gizmoMaterial->createInstance(); - materialY->setParameter("color", math::float3{1.0f, 0.0f, 0.0f}); + _gizmo[1] = entityManager.create(); + _gizmoMaterialInstances[1] = _gizmoMaterial->createInstance(); + _gizmoMaterialInstances[1]->setParameter("color", math::float3{1.0f, 0.0f, 0.0f}); RenderableManager::Builder(1) .boundingBox({{}, {1.0f, 1.0f, 1.0f}}) - .material(0, materialY) + .material(0, _gizmoMaterialInstances[1]) .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, vb, ib, 0, indexCount) .culling(false) - .build(*_engine, _gizmoY); + .build(*_engine, _gizmo[1]); - _gizmoX = entityManager.create(); - auto materialX = _gizmoMaterial->createInstance(); - materialX->setParameter("color", math::float3{0.0f, 1.0f, 0.0f}); + _gizmo[0] = entityManager.create(); + _gizmoMaterialInstances[0] = _gizmoMaterial->createInstance(); + _gizmoMaterialInstances[0]->setParameter("color", math::float3{0.0f, 1.0f, 0.0f}); auto xTransform = math::mat4f::translation(math::float3{0.0f, 0.05f, -0.05f}) * math::mat4f::rotation(-math::F_PI_2, math::float3{0, 0, 1}); auto *instanceBufferX = InstanceBuffer::Builder(1).localTransforms(&xTransform).build(*_engine); RenderableManager::Builder(1) .boundingBox({{}, {1.0f, 1.0f, 1.0f}}) .instances(1, instanceBufferX) - .material(0, materialX) + .material(0, _gizmoMaterialInstances[0]) .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, vb, ib, 0, indexCount) .culling(false) - .build(*_engine, _gizmoX); + .build(*_engine, _gizmo[0]); - _gizmoZ = entityManager.create(); - auto materialZ = _gizmoMaterial->createInstance(); - materialZ->setParameter("color", math::float3{0.0f, 0.0f, 1.0f}); + _gizmo[2] = entityManager.create(); + _gizmoMaterialInstances[2] = _gizmoMaterial->createInstance(); + _gizmoMaterialInstances[2]->setParameter("color", math::float3{0.0f, 0.0f, 1.0f}); auto zTransform = math::mat4f::translation(math::float3{0.0f, 0.05f, -0.05f}) * math::mat4f::rotation(3 * math::F_PI_2, math::float3{1, 0, 0}); auto *instanceBufferZ = InstanceBuffer::Builder(1).localTransforms(&zTransform).build(*_engine); RenderableManager::Builder(1) .boundingBox({{}, {1.0f, 1.0f, 1.0f}}) .instances(1, instanceBufferZ) - .material(0, materialZ) + .material(0, _gizmoMaterialInstances[2]) .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, vb, ib, 0, indexCount) .culling(false) - .build(*_engine, _gizmoZ); - - // auto localTransforms = math::mat4f[3] { - // math::mat4f(), - // math::mat4f::translation(math::float3 { 0.0f, 0.05f, -0.05f}) * math::mat4f::rotation(3 * math::F_PI_2, math::float3 { 1, 0, 0 }) , - // math::mat4f::translation(math::float3 { 0.0f, 0.05f, -0.05f}) * math::mat4f::rotation(math::F_PI_2, math::float3 { 0, 0, 1 }) - // }; - - // RenderableManager::Builder(1) - // .boundingBox({{}, {1.0f, 1.0f, 1.0f}}) - // .instances(3, instanceBuffer) - // .material(0, _gizmoMaterial->getDefaultInstance()) - // .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, vb, - // ib, 0, indexCount) - // .culling(false) - // .build(*_engine, _gizmo); + .build(*_engine, _gizmo[2]); auto &rm = _engine->getRenderableManager(); - rm.setPriority(rm.getInstance(_gizmoX), 7); - rm.setPriority(rm.getInstance(_gizmoY), 7); - rm.setPriority(rm.getInstance(_gizmoZ), 7); - return Entity::smuggle(_gizmoX); + rm.setPriority(rm.getInstance(_gizmo[0]), 7); + rm.setPriority(rm.getInstance(_gizmo[1]), 7); + rm.setPriority(rm.getInstance(_gizmo[2]), 7); + return Entity::smuggle(_gizmo[0]); } void SceneManager::getGizmo(EntityId *out) { - out[0] = Entity::smuggle(_gizmoX); - out[1] = Entity::smuggle(_gizmoY); - out[2] = Entity::smuggle(_gizmoZ); + out[0] = Entity::smuggle(_gizmo[0]); + out[1] = Entity::smuggle(_gizmo[1]); + out[2] = Entity::smuggle(_gizmo[2]); } } // namespace thermion_filament diff --git a/thermion_dart/native/src/ThermionDartApi.cpp b/thermion_dart/native/src/ThermionDartApi.cpp index a11cde46..bee3ab48 100644 --- a/thermion_dart/native/src/ThermionDartApi.cpp +++ b/thermion_dart/native/src/ThermionDartApi.cpp @@ -27,7 +27,8 @@ extern "C" EMSCRIPTEN_KEEPALIVE const void *create_filament_viewer(const void *context, const void *const loader, void *const platform, const char *uberArchivePath) { - auto viewer = (const void *)new FilamentViewer(context, (const ResourceLoaderWrapperImpl *const)loader, platform, uberArchivePath); + const auto * loaderImpl = new ResourceLoaderWrapperImpl((ResourceLoaderWrapper*)loader); + auto viewer = (const void *)new FilamentViewer(context, loaderImpl, platform, uberArchivePath); return viewer; } diff --git a/thermion_dart/native/src/ThermionDartFFIApi.cpp b/thermion_dart/native/src/ThermionDartFFIApi.cpp index 5791738a..f763830d 100644 --- a/thermion_dart/native/src/ThermionDartFFIApi.cpp +++ b/thermion_dart/native/src/ThermionDartFFIApi.cpp @@ -15,7 +15,6 @@ extern "C" } #include - #endif #include "ThermionDartFFIApi.h" @@ -52,6 +51,7 @@ public: { _stop = true; pthread_join(t, NULL); + Log("Render loop killed"); } static void mainLoop(void* arg) { @@ -103,48 +103,47 @@ public: } } - void createViewer(void *const context, void *const platform, + void createViewer(void *const context, + void *const platform, const char *uberArchivePath, - const ResourceLoaderWrapperImpl *const loader, + const ResourceLoaderWrapper *const loader, void (*renderCallback)(void *), void *const owner, void (*callback)(void *const)) { _renderCallback = renderCallback; _renderCallbackOwner = owner; - std::packaged_task lambda([=]() mutable + std::packaged_task lambda([=]() mutable { - FilamentViewer* viewer = nullptr; #ifdef __EMSCRIPTEN__ _context = thermion_dart_web_create_gl_context(); auto success = emscripten_webgl_make_context_current((EMSCRIPTEN_WEBGL_CONTEXT_HANDLE)_context); if(success != EMSCRIPTEN_RESULT_SUCCESS) { std::cout << "Failed to make context current." << std::endl; - return viewer; + return; } glClearColor(0.0, 0.5, 0.5, 1.0); glClear(GL_COLOR_BUFFER_BIT); // emscripten_webgl_commit_frame(); - viewer = (FilamentViewer*) create_filament_viewer((void* const) _context, loader, platform, uberArchivePath); + _viewer = (FilamentViewer*) create_filament_viewer((void* const) _context, loader, platform, uberArchivePath); MAIN_THREAD_EM_ASM({ moduleArg.dartFilamentResolveCallback($0, $1); - }, callback, viewer); + }, callback, _viewer); #else - viewer = (FilamentViewer*)create_filament_viewer(context, loader, platform, uberArchivePath); - callback(viewer); + _viewer = (FilamentViewer*)create_filament_viewer(context, loader, platform, uberArchivePath); + callback(_viewer); #endif - _viewer = viewer; - return viewer; }); + }); auto fut = add_task(lambda); } void destroyViewer(FilamentViewer* viewer) { - std::packaged_task lambda([=]() mutable - { + std::packaged_task lambda([=]() mutable { _rendering = false; + _viewer = nullptr; destroy_filament_viewer(viewer); }); auto fut = add_task(lambda); @@ -237,13 +236,15 @@ extern "C" { _rl = new RenderLoop(); } - _rl->createViewer(context, platform, uberArchivePath, (const ResourceLoaderWrapperImpl *const)loader, + _rl->createViewer(context, platform, uberArchivePath, (const ResourceLoaderWrapper *const)loader, renderCallback, renderCallbackOwner, callback); } EMSCRIPTEN_KEEPALIVE void destroy_filament_viewer_ffi(void *const viewer) { _rl->destroyViewer((FilamentViewer*)viewer); + delete _rl; + _rl = nullptr; } EMSCRIPTEN_KEEPALIVE void create_swap_chain_ffi(void *const viewer, diff --git a/thermion_dart/native/web/CMakeLists.txt b/thermion_dart/native/web/CMakeLists.txt index f9587ffe..42fdf646 100644 --- a/thermion_dart/native/web/CMakeLists.txt +++ b/thermion_dart/native/web/CMakeLists.txt @@ -49,7 +49,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY build/out) add_executable(${MODULE_NAME} "${CMAKE_CURRENT_SOURCE_DIR}/../src/SceneManager.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/../src/ThermionViewerFFI.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/../src/FilamentViewer.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/../src/ThermionDartApi.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/../src/ThermionDartFFIApi.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/../src/StreamBufferAdapter.cpp" @@ -221,10 +221,10 @@ target_link_libraries(${MODULE_NAME} tinyexr ) -add_custom_command(TARGET ${MODULE_NAME} POST_BUILD - COMMAND dart --enable-experiment=native-assets run ffigen --config ffigen/web.yaml WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../../ - COMMAND ${CMAKE_COMMAND} -DINPUTFILE=${CMAKE_CURRENT_SOURCE_DIR}/../../lib/thermion_dart/compatibility/web/thermion_dart.g.dart -DOUTPUTFILE=${CMAKE_CURRENT_SOURCE_DIR}/../../lib/thermion_dart/compatibility/web/thermion_dart.g.dart "-DTO_REPLACE=symbol: '" "-DREPLACEMENT=symbol: '_" -P ${CMAKE_CURRENT_SOURCE_DIR}/replace_in_file.cmake - # COMMAND ${CMAKE_COMMAND} -DINPUTFILE=${CMAKE_CURRENT_SOURCE_DIR}/build/build/out/thermion_dart.js -DOUTPUTFILE=${CMAKE_CURRENT_SOURCE_DIR}/build/build/out/thermion_dart.js "-DTO_REPLACE=var moduleRtn" "-DREPLACEMENT=var moduleRtn\;GLctx=moduleArg.ctx" -P ${CMAKE_CURRENT_SOURCE_DIR}/replace_in_file.cmake - VERBATIM -) +# add_custom_command(TARGET ${MODULE_NAME} POST_BUILD +# COMMAND dart --enable-experiment=native-assets run ffigen --config ffigen/web.yaml WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../../ +# COMMAND ${CMAKE_COMMAND} -DINPUTFILE=${CMAKE_CURRENT_SOURCE_DIR}/../../lib/thermion_dart/compatibility/web/thermion_dart.g.dart -DOUTPUTFILE=${CMAKE_CURRENT_SOURCE_DIR}/../../lib/thermion_dart/compatibility/web/thermion_dart.g.dart "-DTO_REPLACE=symbol: '" "-DREPLACEMENT=symbol: '_" -P ${CMAKE_CURRENT_SOURCE_DIR}/replace_in_file.cmake +# # COMMAND ${CMAKE_COMMAND} -DINPUTFILE=${CMAKE_CURRENT_SOURCE_DIR}/build/build/out/thermion_dart.js -DOUTPUTFILE=${CMAKE_CURRENT_SOURCE_DIR}/build/build/out/thermion_dart.js "-DTO_REPLACE=var moduleRtn" "-DREPLACEMENT=var moduleRtn\;GLctx=moduleArg.ctx" -P ${CMAKE_CURRENT_SOURCE_DIR}/replace_in_file.cmake +# VERBATIM +# ) diff --git a/thermion_dart/native/web/src/cpp/ThermionDartWebApi.cpp b/thermion_dart/native/web/src/cpp/ThermionDartWebApi.cpp index a55270ca..0c441841 100644 --- a/thermion_dart/native/web/src/cpp/ThermionDartWebApi.cpp +++ b/thermion_dart/native/web/src/cpp/ThermionDartWebApi.cpp @@ -125,7 +125,7 @@ extern "C" attr.stencil = EM_FALSE; attr.antialias = EM_FALSE; attr.explicitSwapControl = EM_FALSE; - attr.preserveDrawingBuffer = EM_FALSE; + attr.preserveDrawingBuffer = EM_TRUE; attr.proxyContextToMainThread = EMSCRIPTEN_WEBGL_CONTEXT_PROXY_DISALLOW; attr.enableExtensionsByDefault = EM_TRUE; attr.renderViaOffscreenBackBuffer = EM_FALSE; diff --git a/thermion_dart/pubspec.yaml b/thermion_dart/pubspec.yaml index cde4cec3..1e2a9b72 100644 --- a/thermion_dart/pubspec.yaml +++ b/thermion_dart/pubspec.yaml @@ -1,6 +1,6 @@ name: thermion_dart description: 3D rendering toolkit for Dart. -version: 0.0.4 +version: 0.1.1+1 homepage: https://docs.page/nmfisher/thermion repository: https://github.com/nmfisher/thermion @@ -15,6 +15,7 @@ dependencies: native_toolchain_c: ^0.4.2 archive: ^3.6.1 web: ^0.5.1 + logging: ^1.2.0 dev_dependencies: ffigen: ^11.0.0 diff --git a/thermion_dart/test/integration_test.dart b/thermion_dart/test/integration_test.dart index db155186..eaab19fb 100644 --- a/thermion_dart/test/integration_test.dart +++ b/thermion_dart/test/integration_test.dart @@ -1,9 +1,7 @@ -import 'dart:ffi'; import 'dart:io'; import 'package:thermion_dart/thermion_dart/swift/swift_bindings.g.dart'; +import 'package:thermion_dart/thermion_dart/thermion_viewer_ffi.dart'; import 'package:thermion_dart/thermion_dart/utils/dart_resources.dart'; -import 'package:ffi/ffi.dart'; -import 'package:thermion_dart/thermion_dart.dart'; import 'package:thermion_dart/thermion_dart/compatibility/compatibility.dart'; import 'package:test/test.dart'; import 'package:animation_tools_dart/animation_tools_dart.dart'; diff --git a/thermion_flutter/thermion_dart b/thermion_flutter/thermion_dart deleted file mode 120000 index da4db14f..00000000 --- a/thermion_flutter/thermion_dart +++ /dev/null @@ -1 +0,0 @@ -../thermion_dart \ No newline at end of file diff --git a/thermion_flutter/thermion_flutter/CHANGELOG.md b/thermion_flutter/thermion_flutter/CHANGELOG.md index e69de29b..924bceb3 100644 --- a/thermion_flutter/thermion_flutter/CHANGELOG.md +++ b/thermion_flutter/thermion_flutter/CHANGELOG.md @@ -0,0 +1,34 @@ +## 0.1.1+6 + + - **DOCS**: update with links to playground. + +## 0.1.1+5 + + - Update a dependency to the latest release. + +## 0.1.1+4 + + - Update a dependency to the latest release. + +## 0.1.1+3 + + - Update a dependency to the latest release. + +## 0.1.1+2 + + - **FIX**: update Flutter example project to use new API. + - **FIX**: add logging dependency to thermion_flutter. + +## 0.1.1+1 + + - **REFACTOR**: export ThermionViewerWasm for web and hide FFI/WASM version. + - **FIX**: catch exception if gizmo unavailable in ThermionGestureDestectorDesktop. + +## 0.1.0 +* [ThermionFlutterPlugin] is now static and [dispose] has been removed. Call [createViewer] to obtain an instance of [ThermionViewer]. If you need to release all resources, call [dispose] on [ThermionViewer] +* Fixed memory leaks + +## 0.0.4 +* First release of Dart-only package + + diff --git a/thermion_flutter/thermion_flutter/README.md b/thermion_flutter/thermion_flutter/README.md index 1c9b88a3..af32f1b4 100644 --- a/thermion_flutter/thermion_flutter/README.md +++ b/thermion_flutter/thermion_flutter/README.md @@ -3,7 +3,8 @@

Quickstart (Flutter)Documentation • - Showcase • + Showcase • + PlaygroundDiscord

diff --git a/thermion_flutter/thermion_flutter/android/src/main/kotlin/app/polyvox/filament/ThermionFlutterPlugin.kt b/thermion_flutter/thermion_flutter/android/src/main/kotlin/app/polyvox/filament/ThermionFlutterPlugin.kt index 52a5c7cb..ec922948 100644 --- a/thermion_flutter/thermion_flutter/android/src/main/kotlin/app/polyvox/filament/ThermionFlutterPlugin.kt +++ b/thermion_flutter/thermion_flutter/android/src/main/kotlin/app/polyvox/filament/ThermionFlutterPlugin.kt @@ -55,7 +55,7 @@ class RenderCallbackImpl(plugin:ThermionFlutterPlugin) : RenderCallback { class ThermionFlutterPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, LoadFilamentResourceFromOwner, FreeFilamentResourceFromOwner { companion object { - const val CHANNEL_NAME = "app.polyvox.filament/event" + const val CHANNEL_NAME = "dev.thermion.flutter/event" const val TAG = "FilamentPlugin" } diff --git a/thermion_flutter/thermion_flutter/ios/Classes/ResourceBuffer.c b/thermion_flutter/thermion_flutter/ios/Classes/ResourceBuffer.c index caf1ca6f..293215ee 100644 --- a/thermion_flutter/thermion_flutter/ios/Classes/ResourceBuffer.c +++ b/thermion_flutter/thermion_flutter/ios/Classes/ResourceBuffer.c @@ -1,8 +1,11 @@ -#include "ResourceBuffer.hpp" +#include "ResourceBuffer.h" -ResourceLoaderWrapper *make_resource_loader(LoadFilamentResourceFromOwner loadFn, FreeFilamentResourceFromOwner freeFn, void *const owner) +void *make_resource_loader(LoadFilamentResourceFromOwner loadFn, FreeFilamentResourceFromOwner freeFn, void *const owner) { ResourceLoaderWrapper *rlw = (ResourceLoaderWrapper *)malloc(sizeof(ResourceLoaderWrapper)); + rlw->loadResource = NULL; + rlw->freeResource = NULL; + rlw->loadToOut = NULL; rlw->loadFromOwner = loadFn; rlw->freeFromOwner = freeFn; rlw->owner = owner; diff --git a/thermion_flutter/thermion_flutter/ios/Classes/SwiftThermionFlutterPlugin.swift b/thermion_flutter/thermion_flutter/ios/Classes/SwiftThermionFlutterPlugin.swift index d950a950..473e76e8 100644 --- a/thermion_flutter/thermion_flutter/ios/Classes/SwiftThermionFlutterPlugin.swift +++ b/thermion_flutter/thermion_flutter/ios/Classes/SwiftThermionFlutterPlugin.swift @@ -127,7 +127,7 @@ public class SwiftThermionFlutterPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { let _messenger = registrar.messenger(); messenger = _messenger; - let channel = FlutterMethodChannel(name: "app.polyvox.filament/event", binaryMessenger: _messenger) + let channel = FlutterMethodChannel(name: "dev.thermion.flutter/event", binaryMessenger: _messenger) let instance = SwiftThermionFlutterPlugin(textureRegistry: registrar.textures(), registrar:registrar) registrar.addMethodCallDelegate(instance, channel: channel) } diff --git a/thermion_flutter/thermion_flutter/ios/include/ResourceBuffer.h b/thermion_flutter/thermion_flutter/ios/include/ResourceBuffer.h new file mode 100644 index 00000000..eab0ee0d --- /dev/null +++ b/thermion_flutter/thermion_flutter/ios/include/ResourceBuffer.h @@ -0,0 +1,44 @@ +#ifndef RESOURCE_BUFFER_H +#define RESOURCE_BUFFER_H + +#include +#include + +// +// A ResourceBuffer is a unified interface for working with +// binary assets across various platforms. +// This is simply: +// 1) a pointer to some data +// 2) the length of the data +// 3) an ID that can be passed back to the native platform to release the underlying asset when needed. +// +typedef struct ResourceBuffer +{ + const void *const data; + const int32_t size; + const int32_t id; + +#if defined(__cplusplus) + ResourceBuffer(void *const data, int32_t size, int32_t id) : data(data), size(size), id(id) {} +#endif +} ResourceBuffer; + +typedef void (*LoadFilamentResourceIntoOutPointer)(const char *uri, ResourceBuffer *out); +typedef ResourceBuffer (*LoadFilamentResource)(const char *uri); +typedef ResourceBuffer (*LoadFilamentResourceFromOwner)(const char *const, void *const owner); +typedef void (*FreeFilamentResource)(ResourceBuffer); +typedef void (*FreeFilamentResourceFromOwner)(ResourceBuffer, void *const owner); + +typedef struct ResourceLoaderWrapper +{ + LoadFilamentResource loadResource; + FreeFilamentResource freeResource; + LoadFilamentResourceFromOwner loadFromOwner; + FreeFilamentResourceFromOwner freeFromOwner; + void *owner; + LoadFilamentResourceIntoOutPointer loadToOut; +} ResourceLoaderWrapper; + +void *make_resource_loader(LoadFilamentResourceFromOwner loadFn, FreeFilamentResourceFromOwner freeFn, void *const owner); + +#endif diff --git a/thermion_flutter/thermion_flutter/ios/include/ResourceBuffer.hpp b/thermion_flutter/thermion_flutter/ios/include/ResourceBuffer.hpp deleted file mode 100644 index 97822546..00000000 --- a/thermion_flutter/thermion_flutter/ios/include/ResourceBuffer.hpp +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef RESOURCE_BUFFER_H -#define RESOURCE_BUFFER_H - -#include -#include - -// -// A ResourceBuffer is a unified interface for working with -// binary assets across various platforms. -// This is simply: -// 1) a pointer to some data -// 2) the length of the data -// 3) an ID that can be passed back to the native platform to release the underlying asset when needed. -// -struct ResourceBuffer -{ - const void *const data; - const int32_t size; - const int32_t id; -}; - -typedef struct ResourceBuffer ResourceBuffer; -typedef ResourceBuffer (*LoadFilamentResource)(const char *uri); -typedef ResourceBuffer (*LoadFilamentResourceFromOwner)(const char *const, void *const owner); -typedef void (*FreeFilamentResource)(ResourceBuffer); -typedef void (*FreeFilamentResourceFromOwner)(ResourceBuffer, void *const owner); - -struct ResourceLoaderWrapper -{ - LoadFilamentResource loadResource; - FreeFilamentResource freeResource; - LoadFilamentResourceFromOwner loadFromOwner; - FreeFilamentResourceFromOwner freeFromOwner; - void *owner; -}; -typedef struct ResourceLoaderWrapper ResourceLoaderWrapper; - - -#if defined(__cplusplus) - -namespace thermion_filament { - -struct ResourceLoaderWrapperImpl : public ResourceLoaderWrapper -{ - - ResourceLoaderWrapperImpl(LoadFilamentResource loader, FreeFilamentResource freeResource) - { - loadFromOwner = nullptr; - freeFromOwner = nullptr; - loadResource = loader; - freeResource = freeResource; - owner = nullptr; - } - - ResourceLoaderWrapperImpl(LoadFilamentResourceFromOwner loader, FreeFilamentResourceFromOwner freeResource, void * owner) - { - loadResource = nullptr; - freeResource = nullptr; - loadFromOwner = loader; - freeFromOwner = freeResource; - owner = owner; - } - - ResourceBuffer load(const char *uri) const - { - if (loadFromOwner) - { - auto rb = loadFromOwner(uri, owner); - return rb; - } - auto rb = loadResource(uri); - return rb; - } - - void free(ResourceBuffer rb) const - { - if (freeFromOwner) - { - freeFromOwner(rb, owner); - } - else - { - freeResource(rb); - } - } -}; - -} -#endif - -#endif diff --git a/thermion_flutter/thermion_flutter/ios/include/SwiftThermionFlutterPlugin-Bridging-Header.h b/thermion_flutter/thermion_flutter/ios/include/SwiftThermionFlutterPlugin-Bridging-Header.h index 96ec1fc8..2c229776 100644 --- a/thermion_flutter/thermion_flutter/ios/include/SwiftThermionFlutterPlugin-Bridging-Header.h +++ b/thermion_flutter/thermion_flutter/ios/include/SwiftThermionFlutterPlugin-Bridging-Header.h @@ -3,9 +3,9 @@ #include -#include "ResourceBuffer.hpp" +#include "ResourceBuffer.h" -ResourceLoaderWrapper *make_resource_loader(LoadFilamentResourceFromOwner loadFn, FreeFilamentResourceFromOwner freeFn, void *const owner); +void *make_resource_loader(LoadFilamentResourceFromOwner loadFn, FreeFilamentResourceFromOwner freeFn, void *const owner); // ResourceLoaderWrapper *make_resource_loader(LoadFilamentResourceFromOwner loadFn, FreeFilamentResourceFromOwner freeFn, void *const owner) // { diff --git a/thermion_flutter/thermion_flutter/ios/thermion_flutter.podspec b/thermion_flutter/thermion_flutter/ios/thermion_flutter.podspec index 549abac6..47a52e93 100644 --- a/thermion_flutter/thermion_flutter/ios/thermion_flutter.podspec +++ b/thermion_flutter/thermion_flutter/ios/thermion_flutter.podspec @@ -13,8 +13,8 @@ A new flutter plugin project. s.license = { :file => '../LICENSE' } s.author = { 'Your Company' => 'email@example.com' } s.source = { :path => '.' } - s.source_files = 'Classes/*', 'include/SwiftThermionFlutterPlugin-Bridging-Header.h','include/ResourceBuffer.hpp', 'src/ResourceBuffer.c' - s.public_header_files = 'include/SwiftThermionFlutterPlugin-Bridging-Header.h', 'include/ResourceBuffer.hpp' + s.source_files = 'Classes/*', 'include/SwiftThermionFlutterPlugin-Bridging-Header.h','include/ResourceBuffer.h', 'src/ResourceBuffer.c' + s.public_header_files = 'include/SwiftThermionFlutterPlugin-Bridging-Header.h', 'include/ResourceBuffer.h' s.dependency 'Flutter' s.platform = :ios, '13.0' s.static_framework = true diff --git a/thermion_flutter/thermion_flutter/lib/thermion/thermion_flutter_plugin.dart b/thermion_flutter/thermion_flutter/lib/thermion/thermion_flutter_plugin.dart index 9ff1e327..3c784cc9 100644 --- a/thermion_flutter/thermion_flutter/lib/thermion/thermion_flutter_plugin.dart +++ b/thermion_flutter/thermion_flutter/lib/thermion/thermion_flutter_plugin.dart @@ -1,103 +1,100 @@ import 'dart:async'; -import 'dart:ui'; import 'package:thermion_dart/thermion_dart.dart'; import 'package:flutter/widgets.dart'; import 'package:thermion_flutter_platform_interface/thermion_flutter_platform_interface.dart'; import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart'; /// -/// Handles all platform-specific initialization work necessary to create a -/// backing rendering surface in a Flutter application. -/// Instantiates/wraps a [ThermionViewer], +/// Handles all platform-specific initialization to create a backing rendering +/// surface in a Flutter application and lifecycle listeners to pause rendering +/// when the app is inactive or in the background. +/// Call [createViewer] to create an instance of [ThermionViewer]. +/// This is a lightweight singleton that /// class ThermionFlutterPlugin { - ThermionViewer get _viewer => ThermionFlutterPlatform.instance.viewer; + + ThermionFlutterPlugin._(); - bool _wasRenderingOnInactive = false; + static AppLifecycleListener? _appLifecycleListener; - void _handleStateChange(AppLifecycleState state) async { - await initialized; + static bool _initializing = false; + + static ThermionViewer? _viewer; + + static bool _wasRenderingOnInactive = false; + + static void _handleStateChange(AppLifecycleState state) async { + if (_viewer == null) { + return; + } + + await _viewer!.initialized; switch (state) { case AppLifecycleState.detached: - print("Detached"); if (!_wasRenderingOnInactive) { - _wasRenderingOnInactive = _viewer.rendering; + _wasRenderingOnInactive = _viewer!.rendering; } - await _viewer.setRendering(false); + await _viewer!.setRendering(false); break; case AppLifecycleState.hidden: - print("Hidden"); if (!_wasRenderingOnInactive) { - _wasRenderingOnInactive = _viewer.rendering; + _wasRenderingOnInactive = _viewer!.rendering; } - await _viewer.setRendering(false); + await _viewer!.setRendering(false); break; case AppLifecycleState.inactive: - print("Inactive"); if (!_wasRenderingOnInactive) { - _wasRenderingOnInactive = _viewer.rendering; + _wasRenderingOnInactive = _viewer!.rendering; } // on Windows in particular, restoring a window after minimizing stalls the renderer (and the whole application) for a considerable length of time. // disabling rendering on minimize seems to fix the issue (so I wonder if there's some kind of command buffer that's filling up while the window is minimized). - await _viewer.setRendering(false); + await _viewer!.setRendering(false); break; case AppLifecycleState.paused: - print("Paused"); if (!_wasRenderingOnInactive) { - _wasRenderingOnInactive = _viewer.rendering; + _wasRenderingOnInactive = _viewer!.rendering; } - await _viewer.setRendering(false); + await _viewer!.setRendering(false); break; case AppLifecycleState.resumed: - print("Resumed"); - await _viewer.setRendering(_wasRenderingOnInactive); + await _viewer!.setRendering(_wasRenderingOnInactive); break; } } - AppLifecycleListener? _appLifecycleListener; - - final _initialized = Completer(); - Future get initialized => _initialized.future; - - bool _initializing = false; - - Future initialize({String? uberArchivePath}) async { - _initializing = true; - if (_initialized.isCompleted) { - return ThermionFlutterPlatform.instance.viewer; + static Future createViewer({String? uberArchivePath}) async { + if (_initializing) { + throw Exception("Existing call to createViewer has not completed."); } - await ThermionFlutterPlatform.instance - .initialize(uberArchivePath: uberArchivePath); - + _initializing = true; + _viewer = await ThermionFlutterPlatform.instance + .createViewer(uberArchivePath: uberArchivePath); _appLifecycleListener = AppLifecycleListener( onStateChange: _handleStateChange, ); - _viewer.initialized; - _initialized.complete(true); + _viewer!.onDispose(() async { + _viewer = null; + _appLifecycleListener?.dispose(); + _appLifecycleListener = null; + }); _initializing = false; - return ThermionFlutterPlatform.instance.viewer; + return _viewer!; } - Future createTexture( + static Future createTexture( int width, int height, int offsetLeft, int offsetRight) async { return ThermionFlutterPlatform.instance .createTexture(width, height, offsetLeft, offsetRight); } - Future destroyTexture(ThermionFlutterTexture texture) async { + static Future destroyTexture(ThermionFlutterTexture texture) async { return ThermionFlutterPlatform.instance.destroyTexture(texture); } @override - Future resizeTexture(ThermionFlutterTexture texture, + static Future resizeTexture(ThermionFlutterTexture texture, int width, int height, int offsetLeft, int offsetRight) async { return ThermionFlutterPlatform.instance .resizeTexture(texture, width, height, offsetLeft, offsetRight); } - - void dispose() { - ThermionFlutterPlatform.instance.dispose(); - _appLifecycleListener?.dispose(); - } } diff --git a/thermion_flutter/thermion_flutter/lib/thermion/widgets/camera/gestures/thermion_gesture_detector_desktop.dart b/thermion_flutter/thermion_flutter/lib/thermion/widgets/camera/gestures/thermion_gesture_detector_desktop.dart index 38af660b..e01e96b3 100644 --- a/thermion_flutter/thermion_flutter/lib/thermion/widgets/camera/gestures/thermion_gesture_detector_desktop.dart +++ b/thermion_flutter/thermion_flutter/lib/thermion/widgets/camera/gestures/thermion_gesture_detector_desktop.dart @@ -1,5 +1,5 @@ import 'dart:async'; - +import 'package:logging/logging.dart'; import 'package:thermion_dart/thermion_dart/thermion_viewer.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; @@ -52,7 +52,8 @@ class ThermionGestureDetectorDesktop extends StatefulWidget { class _ThermionGestureDetectorDesktopState extends State { - /// + final _logger = Logger("_ThermionGestureDetectorDesktopState"); + /// /// // ignore: unused_field @@ -65,7 +66,12 @@ class _ThermionGestureDetectorDesktopState @override void initState() { super.initState(); - _gizmo = widget.controller.gizmo; + try { + _gizmo = widget.controller.gizmo; + } catch (err) { + _logger.warning( + "Failed to get gizmo. If you are running on WASM, this is expected"); + } } @override diff --git a/thermion_flutter/thermion_flutter/lib/thermion/widgets/thermion_widget.dart b/thermion_flutter/thermion_flutter/lib/thermion/widgets/thermion_widget.dart index ed8fc6cf..4cd26d4c 100644 --- a/thermion_flutter/thermion_flutter/lib/thermion/widgets/thermion_widget.dart +++ b/thermion_flutter/thermion_flutter/lib/thermion/widgets/thermion_widget.dart @@ -9,7 +9,7 @@ import 'package:thermion_flutter/thermion_flutter.dart'; import 'resize_observer.dart'; class ThermionWidget extends StatefulWidget { - final ThermionFlutterPlugin plugin; + final ThermionViewer viewer; /// /// The content to render before the texture widget is available. @@ -17,7 +17,7 @@ class ThermionWidget extends StatefulWidget { /// final Widget? initial; - const ThermionWidget({Key? key, this.initial, required this.plugin}) + const ThermionWidget({Key? key, this.initial, required this.viewer}) : super(key: key); @override @@ -30,12 +30,22 @@ class _ThermionWidgetState extends State { @override void initState() { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { - await widget.plugin.initialized; + await widget.viewer.initialized; + widget.viewer.onDispose(() async { + if (_texture != null) { + var texture = _texture; + _texture = null; + if (mounted) { + setState(() {}); + } + await ThermionFlutterPlugin.destroyTexture(texture!); + } + }); var dpr = MediaQuery.of(context).devicePixelRatio; var size = ((context.findRenderObject()) as RenderBox).size; var width = (dpr * size.width).ceil(); var height = (dpr * size.height).ceil(); - _texture = await widget.plugin.createTexture(width, height, 0, 0); + _texture = await ThermionFlutterPlugin.createTexture(width, height, 0, 0); if (mounted) { setState(() {}); @@ -50,18 +60,20 @@ class _ThermionWidgetState extends State { Future _resizeTexture(Size newSize) async { _resizeTimer?.cancel(); _resizeTimer = Timer(Duration(milliseconds: 500), () async { - if (_resizing) { + if (_resizing || !mounted) { return; } _resizeTimer!.cancel(); _resizing = true; var oldTexture = _texture; _texture = null; - setState(() {}); + if (!mounted) { + return; + } var dpr = MediaQuery.of(context).devicePixelRatio; - _texture = await widget.plugin.resizeTexture(oldTexture!, + _texture = await ThermionFlutterPlugin.resizeTexture(oldTexture!, (dpr * newSize.width).ceil(), (dpr * newSize.height).ceil(), 0, 0); setState(() {}); _resizing = false; diff --git a/thermion_flutter/thermion_flutter/linux/thermion_flutter_plugin.cc b/thermion_flutter/thermion_flutter/linux/thermion_flutter_plugin.cc index aa786fee..ac0e64f3 100644 --- a/thermion_flutter/thermion_flutter/linux/thermion_flutter_plugin.cc +++ b/thermion_flutter/thermion_flutter/linux/thermion_flutter_plugin.cc @@ -871,7 +871,7 @@ void thermion_flutter_plugin_register_with_registrar(FlPluginRegistrar* registra g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); g_autoptr(FlMethodChannel) channel = fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar), - "app.polyvox.filament/event", + "dev.thermion.flutter/event", FL_METHOD_CODEC(codec)); fl_method_channel_set_method_call_handler(channel, method_call_cb, g_object_ref(plugin), diff --git a/thermion_flutter/thermion_flutter/macos/Classes/SwiftThermionFlutterPlugin.swift b/thermion_flutter/thermion_flutter/macos/Classes/SwiftThermionFlutterPlugin.swift index 55918f12..ba07fcc0 100644 --- a/thermion_flutter/thermion_flutter/macos/Classes/SwiftThermionFlutterPlugin.swift +++ b/thermion_flutter/thermion_flutter/macos/Classes/SwiftThermionFlutterPlugin.swift @@ -8,6 +8,8 @@ public class SwiftThermionFlutterPlugin: NSObject, FlutterPlugin { var texture: ThermionFlutterTexture? var createdAt = Date() + + var destroying = false var resources:[UInt32:NSData] = [:] @@ -65,48 +67,59 @@ public class SwiftThermionFlutterPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { let _messenger = registrar.messenger; messenger = _messenger; - let channel = FlutterMethodChannel(name: "app.polyvox.filament/event", binaryMessenger: _messenger) + let channel = FlutterMethodChannel(name: "dev.thermion.flutter/event", binaryMessenger: _messenger) let instance = SwiftThermionFlutterPlugin(textureRegistry: registrar.textures, registrar:registrar) registrar.addMethodCallDelegate(instance, channel: channel) } + var resourceLoaderWrapper:UnsafeMutablePointer? = nil + var renderCallbackHolder:[Any] = [] + init(textureRegistry: FlutterTextureRegistry, registrar:FlutterPluginRegistrar) { self.registry = textureRegistry; self.registrar = registrar } - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { let methodName = call.method; switch methodName { case "getResourceLoaderWrapper": - var resourceLoaderWrapper = make_resource_loader(loadResource, freeResource, Unmanaged.passUnretained(self).toOpaque()) - result(unsafeBitCast(resourceLoaderWrapper, to:Int64.self)) + if(resourceLoaderWrapper == nil) { + resourceLoaderWrapper = make_resource_loader(loadResource, freeResource, Unmanaged.passUnretained(self).toOpaque()) + } + result(Int64(Int(bitPattern: resourceLoaderWrapper!))) case "getRenderCallback": - let renderCallback = markTextureFrameAvailable - let resultArray:[Any] = [ - unsafeBitCast(renderCallback, to:Int64.self), unsafeBitCast(Unmanaged.passUnretained(self), to:UInt64.self)] - result(resultArray) + if(renderCallbackHolder.isEmpty) { + renderCallbackHolder.append(unsafeBitCast(markTextureFrameAvailable, to:Int64.self)) + renderCallbackHolder.append(unsafeBitCast(Unmanaged.passUnretained(self), to:UInt64.self)) + } + result(renderCallbackHolder) case "getDriverPlatform": result(nil) case "getSharedContext": result(nil) case "createTexture": + if(destroying) { + result(nil) + return + } let args = call.arguments as! [Any] let width = args[0] as! Int64 let height = args[1] as! Int64 self.texture = ThermionFlutterTexture(registry: registry, width: width, height: height) - if(self.texture?.metalTextureAddress == -1) { + if(self.texture!.texture.metalTextureAddress == -1) { result(nil) } else { - result([self.texture!.flutterTextureId as Any, self.texture?.metalTextureAddress, nil]) + result([self.texture!.flutterTextureId as Any, self.texture!.texture.metalTextureAddress, nil]) } case "destroyTexture": + self.destroying = true self.texture?.destroy() self.texture = nil result(true) + self.destroying = false default: result(FlutterMethodNotImplemented) } diff --git a/thermion_flutter/thermion_flutter/macos/Classes/ThermionDartTexture.swift b/thermion_flutter/thermion_flutter/macos/Classes/ThermionDartTexture.swift index c17a7aab..8e7e7c98 100644 --- a/thermion_flutter/thermion_flutter/macos/Classes/ThermionDartTexture.swift +++ b/thermion_flutter/thermion_flutter/macos/Classes/ThermionDartTexture.swift @@ -11,18 +11,20 @@ import GLKit ] as [CFString : Any] as CFDictionary @objc public var cvMetalTextureCache:CVMetalTextureCache? + @objc public var metalDevice:MTLDevice? + @objc public var cvMetalTexture:CVMetalTexture? @objc public var metalTexture:MTLTexture? - @objc public var metalDevice:MTLDevice? @objc public var metalTextureAddress:Int = -1 @objc override public init() { } - @objc public init(width:Int64, height:Int64) { - - self.metalDevice = MTLCreateSystemDefaultDevice()! + @objc public init(width:Int64, height:Int64) { + if(self.metalDevice == nil) { + self.metalDevice = MTLCreateSystemDefaultDevice()! + } // create pixel buffer if(CVPixelBufferCreate(kCFAllocatorDefault, Int(width), Int(height), @@ -31,37 +33,36 @@ import GLKit metalTextureAddress = -1; return } - - var cvret = CVMetalTextureCacheCreate( - kCFAllocatorDefault, - nil, - metalDevice!, - nil, - &cvMetalTextureCache); - if(cvret != 0) { - print("Error creating Metal texture cache") - metalTextureAddress = -1 - return + if self.cvMetalTextureCache == nil { + let cacheCreationResult = CVMetalTextureCacheCreate( + kCFAllocatorDefault, + nil, + self.metalDevice!, + nil, + &self.cvMetalTextureCache) + if(cacheCreationResult != kCVReturnSuccess) { + print("Error creating Metal texture cache") + metalTextureAddress = -1 + return + } } - cvret = CVMetalTextureCacheCreateTextureFromImage( + let cvret = CVMetalTextureCacheCreateTextureFromImage( kCFAllocatorDefault, - cvMetalTextureCache!, + self.cvMetalTextureCache!, pixelBuffer!, nil, MTLPixelFormat.bgra8Unorm, Int(width), Int(height), 0, &cvMetalTexture) - if(cvret != 0) { + if(cvret != kCVReturnSuccess) { print("Error creating texture from image") metalTextureAddress = -1 return } metalTexture = CVMetalTextureGetTexture(cvMetalTexture!) - let metalTexturePtr = Unmanaged.passUnretained(metalTexture!).toOpaque() + let metalTexturePtr = Unmanaged.passRetained(metalTexture!).toOpaque() metalTextureAddress = Int(bitPattern:metalTexturePtr) - - print("Created metal texture @ \(metalTextureAddress)") - + // CVPixelBufferLockBaseAddress(pixelBuffer!, CVPixelBufferLockFlags(rawValue: 0)) // let bufferWidth = Int(CVPixelBufferGetWidth(pixelBuffer!)) // let bufferHeight = Int(CVPixelBufferGetHeight(pixelBuffer!)) @@ -95,7 +96,12 @@ import GLKit } @objc public func destroyTexture() { - metalTexture = nil + CVMetalTextureCacheFlush(self.cvMetalTextureCache!, 0) + self.metalTexture = nil + self.cvMetalTexture = nil + self.pixelBuffer = nil + self.metalDevice = nil + self.cvMetalTextureCache = nil } diff --git a/thermion_flutter/thermion_flutter/macos/Classes/ThermionFlutterTexture.swift b/thermion_flutter/thermion_flutter/macos/Classes/ThermionFlutterTexture.swift index 1a2f4c1d..57eea716 100644 --- a/thermion_flutter/thermion_flutter/macos/Classes/ThermionFlutterTexture.swift +++ b/thermion_flutter/thermion_flutter/macos/Classes/ThermionFlutterTexture.swift @@ -2,20 +2,24 @@ import Foundation import GLKit import FlutterMacOS -public class ThermionFlutterTexture : ThermionDartTexture, FlutterTexture { +public class ThermionFlutterTexture : NSObject, FlutterTexture { var flutterTextureId: Int64 = -1 var registry: FlutterTextureRegistry + var texture: ThermionDartTexture init(registry:FlutterTextureRegistry, width:Int64, height:Int64) { self.registry = registry - super.init(width:width, height:height) + self.texture = ThermionDartTexture(width:width, height: height) + super.init() self.flutterTextureId = registry.register(self) - } public func copyPixelBuffer() -> Unmanaged? { - return Unmanaged.passRetained(pixelBuffer!); + if(self.texture.pixelBuffer == nil) { + return nil + } + return Unmanaged.passRetained(self.texture.pixelBuffer!); } public func onTextureUnregistered(_ texture:FlutterTexture) { @@ -24,7 +28,7 @@ public class ThermionFlutterTexture : ThermionDartTexture, FlutterTexture { public func destroy() { self.registry.unregisterTexture(self.flutterTextureId) - self.destroyTexture() + self.texture.destroyTexture() } -} \ No newline at end of file +} diff --git a/thermion_flutter/thermion_flutter/macos/include/ResourceBuffer.h b/thermion_flutter/thermion_flutter/macos/include/ResourceBuffer.h new file mode 100644 index 00000000..0e159c30 --- /dev/null +++ b/thermion_flutter/thermion_flutter/macos/include/ResourceBuffer.h @@ -0,0 +1,44 @@ +#ifndef RESOURCE_BUFFER_H +#define RESOURCE_BUFFER_H + +#include +#include + +// +// A ResourceBuffer is a unified interface for working with +// binary assets across various platforms. +// This is simply: +// 1) a pointer to some data +// 2) the length of the data +// 3) an ID that can be passed back to the native platform to release the underlying asset when needed. +// +typedef struct ResourceBuffer +{ + const void *const data; + const int32_t size; + const int32_t id; + +#if defined(__cplusplus) + ResourceBuffer(void *const data, int32_t size, int32_t id) : data(data), size(size), id(id) {} +#endif +} ResourceBuffer; + +typedef void (*LoadFilamentResourceIntoOutPointer)(const char *uri, ResourceBuffer *out); +typedef ResourceBuffer (*LoadFilamentResource)(const char *uri); +typedef ResourceBuffer (*LoadFilamentResourceFromOwner)(const char *const, void *const owner); +typedef void (*FreeFilamentResource)(ResourceBuffer); +typedef void (*FreeFilamentResourceFromOwner)(ResourceBuffer, void *const owner); + +typedef struct ResourceLoaderWrapper +{ + LoadFilamentResource loadResource; + FreeFilamentResource freeResource; + LoadFilamentResourceFromOwner loadFromOwner; + FreeFilamentResourceFromOwner freeFromOwner; + void *owner; + LoadFilamentResourceIntoOutPointer loadToOut; +} ResourceLoaderWrapper; + +ResourceLoaderWrapper *make_resource_loader(LoadFilamentResourceFromOwner loadFn, FreeFilamentResourceFromOwner freeFn, void *const owner); + +#endif diff --git a/thermion_flutter/thermion_flutter/macos/include/ResourceBuffer.hpp b/thermion_flutter/thermion_flutter/macos/include/ResourceBuffer.hpp deleted file mode 100644 index 97822546..00000000 --- a/thermion_flutter/thermion_flutter/macos/include/ResourceBuffer.hpp +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef RESOURCE_BUFFER_H -#define RESOURCE_BUFFER_H - -#include -#include - -// -// A ResourceBuffer is a unified interface for working with -// binary assets across various platforms. -// This is simply: -// 1) a pointer to some data -// 2) the length of the data -// 3) an ID that can be passed back to the native platform to release the underlying asset when needed. -// -struct ResourceBuffer -{ - const void *const data; - const int32_t size; - const int32_t id; -}; - -typedef struct ResourceBuffer ResourceBuffer; -typedef ResourceBuffer (*LoadFilamentResource)(const char *uri); -typedef ResourceBuffer (*LoadFilamentResourceFromOwner)(const char *const, void *const owner); -typedef void (*FreeFilamentResource)(ResourceBuffer); -typedef void (*FreeFilamentResourceFromOwner)(ResourceBuffer, void *const owner); - -struct ResourceLoaderWrapper -{ - LoadFilamentResource loadResource; - FreeFilamentResource freeResource; - LoadFilamentResourceFromOwner loadFromOwner; - FreeFilamentResourceFromOwner freeFromOwner; - void *owner; -}; -typedef struct ResourceLoaderWrapper ResourceLoaderWrapper; - - -#if defined(__cplusplus) - -namespace thermion_filament { - -struct ResourceLoaderWrapperImpl : public ResourceLoaderWrapper -{ - - ResourceLoaderWrapperImpl(LoadFilamentResource loader, FreeFilamentResource freeResource) - { - loadFromOwner = nullptr; - freeFromOwner = nullptr; - loadResource = loader; - freeResource = freeResource; - owner = nullptr; - } - - ResourceLoaderWrapperImpl(LoadFilamentResourceFromOwner loader, FreeFilamentResourceFromOwner freeResource, void * owner) - { - loadResource = nullptr; - freeResource = nullptr; - loadFromOwner = loader; - freeFromOwner = freeResource; - owner = owner; - } - - ResourceBuffer load(const char *uri) const - { - if (loadFromOwner) - { - auto rb = loadFromOwner(uri, owner); - return rb; - } - auto rb = loadResource(uri); - return rb; - } - - void free(ResourceBuffer rb) const - { - if (freeFromOwner) - { - freeFromOwner(rb, owner); - } - else - { - freeResource(rb); - } - } -}; - -} -#endif - -#endif diff --git a/thermion_flutter/thermion_flutter/macos/include/SwiftThermionFlutterPlugin-Bridging-Header.h b/thermion_flutter/thermion_flutter/macos/include/SwiftThermionFlutterPlugin-Bridging-Header.h index 0a005191..3e8b248f 100644 --- a/thermion_flutter/thermion_flutter/macos/include/SwiftThermionFlutterPlugin-Bridging-Header.h +++ b/thermion_flutter/thermion_flutter/macos/include/SwiftThermionFlutterPlugin-Bridging-Header.h @@ -3,11 +3,14 @@ #include -#include "ResourceBuffer.hpp" +#include "ResourceBuffer.h" ResourceLoaderWrapper *make_resource_loader(LoadFilamentResourceFromOwner loadFn, FreeFilamentResourceFromOwner freeFn, void *const owner) { ResourceLoaderWrapper *rlw = (ResourceLoaderWrapper *)malloc(sizeof(ResourceLoaderWrapper)); + rlw->loadResource = NULL; + rlw->freeResource = NULL; + rlw->loadToOut = NULL; rlw->loadFromOwner = loadFn; rlw->freeFromOwner = freeFn; rlw->owner = owner; diff --git a/thermion_flutter/thermion_flutter/macos/thermion_flutter.podspec b/thermion_flutter/thermion_flutter/macos/thermion_flutter.podspec index 632360f6..b11c286d 100644 --- a/thermion_flutter/thermion_flutter/macos/thermion_flutter.podspec +++ b/thermion_flutter/thermion_flutter/macos/thermion_flutter.podspec @@ -13,8 +13,8 @@ A new Flutter plugin project. s.license = { :file => '../LICENSE' } s.author = { 'Your Company' => 'email@example.com' } s.source = { :path => '.' } - s.source_files = 'Classes/*', 'include/ResourceBuffer.hpp','include/SwiftThermionFlutterPlugin-Bridging-Header.h' - s.public_header_files = 'include/SwiftThermionFlutterPlugin-Bridging-Header.h', 'include/ResourceBuffer.hpp' + s.source_files = 'Classes/*', 'include/ResourceBuffer.h','include/SwiftThermionFlutterPlugin-Bridging-Header.h' + s.public_header_files = 'include/SwiftThermionFlutterPlugin-Bridging-Header.h', 'include/ResourceBuffer.h' s.dependency 'FlutterMacOS' s.platform = :osx, '13' diff --git a/thermion_flutter/thermion_flutter/pubspec.yaml b/thermion_flutter/thermion_flutter/pubspec.yaml index cf1b22f2..1145a139 100644 --- a/thermion_flutter/thermion_flutter/pubspec.yaml +++ b/thermion_flutter/thermion_flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: thermion_flutter description: Flutter plugin for 3D rendering with the Thermion toolkit. -version: 0.0.3 +version: 0.1.1+6 homepage: https://docs.page/nmfisher/thermion repository: https://github.com/nmfisher/thermion @@ -17,10 +17,11 @@ dependencies: plugin_platform_interface: ^2.0.0 ffi: ^2.1.2 animation_tools_dart: ^0.0.4 - thermion_dart: ^0.0.4 - thermion_flutter_platform_interface: ^0.0.1 - thermion_flutter_ffi: ^0.0.1 - thermion_flutter_web: ^0.0.1 + thermion_dart: ^0.1.1+1 + thermion_flutter_platform_interface: ^0.1.0+5 + thermion_flutter_ffi: ^0.1.0+5 + thermion_flutter_web: ^0.0.1+5 + logging: ^1.2.0 dev_dependencies: flutter_test: diff --git a/thermion_flutter/thermion_flutter/windows/thermion_flutter_plugin.cpp b/thermion_flutter/thermion_flutter/windows/thermion_flutter_plugin.cpp index 6e870225..b5c0f261 100644 --- a/thermion_flutter/thermion_flutter/windows/thermion_flutter_plugin.cpp +++ b/thermion_flutter/thermion_flutter/windows/thermion_flutter_plugin.cpp @@ -43,7 +43,7 @@ void ThermionFlutterPlugin::RegisterWithRegistrar( flutter::PluginRegistrarWindows *registrar) { auto channel = std::make_unique>( - registrar->messenger(), "app.polyvox.filament/event", + registrar->messenger(), "dev.thermion.flutter/event", &flutter::StandardMethodCodec::GetInstance()); auto plugin = std::make_unique( diff --git a/thermion_flutter/thermion_flutter_ffi/CHANGELOG.md b/thermion_flutter/thermion_flutter_ffi/CHANGELOG.md index feb7a4bf..95d3d6e1 100644 --- a/thermion_flutter/thermion_flutter_ffi/CHANGELOG.md +++ b/thermion_flutter/thermion_flutter_ffi/CHANGELOG.md @@ -1,2 +1,22 @@ +## 0.1.0+5 + + - Update a dependency to the latest release. + +## 0.1.0+4 + + - Update a dependency to the latest release. + +## 0.1.0+3 + + - Update a dependency to the latest release. + +## 0.1.0+2 + + - **REFACTOR**: rearrange some stubs/imports for easier web WASM deployment. + +## 0.1.0+1 + + - Update a dependency to the latest release. + ## 0.0.1 * First release of Dart-only package diff --git a/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_ffi.dart b/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_ffi.dart index 415925a3..c34e3fea 100644 --- a/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_ffi.dart +++ b/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_ffi.dart @@ -2,25 +2,29 @@ import 'dart:async'; import 'package:flutter/services.dart'; import 'dart:ffi'; import 'package:thermion_dart/thermion_dart.dart'; +import 'package:thermion_dart/thermion_dart/thermion_viewer_ffi.dart'; import 'package:thermion_flutter_platform_interface/thermion_flutter_platform_interface.dart'; import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart'; /// -/// A subclass of [ThermionViewerFFI] that uses Flutter platform channels -/// to create rendering contexts, callbacks and surfaces (either backing texture(s). +/// An implementation of [ThermionFlutterPlatform] that uses a Flutter platform +/// channel to create a rendering context, resource loaders, and +/// render target(s). /// class ThermionFlutterFFI extends ThermionFlutterPlatform { - final _channel = const MethodChannel("app.polyvox.filament/event"); + final _channel = const MethodChannel("dev.thermion.flutter/event"); - late final ThermionViewerFFI viewer; + ThermionViewerFFI? _viewer; + + ThermionFlutterFFI._() {} static void registerWith() { - ThermionFlutterPlatform.instance = ThermionFlutterFFI(); + ThermionFlutterPlatform.instance = ThermionFlutterFFI._(); } final _textures = {}; - Future initialize({String? uberArchivePath}) async { + Future createViewer({String? uberArchivePath}) async { var resourceLoader = Pointer.fromAddress( await _channel.invokeMethod("getResourceLoaderWrapper")); @@ -46,22 +50,24 @@ class ThermionFlutterFFI extends ThermionFlutterPlatform { ? nullptr : Pointer.fromAddress(sharedContext); - viewer = ThermionViewerFFI( + _viewer = ThermionViewerFFI( resourceLoader: resourceLoader, renderCallback: renderCallback, renderCallbackOwner: renderCallbackOwner, driver: driverPtr, sharedContext: sharedContextPtr, uberArchivePath: uberArchivePath); - await viewer.initialized; + await _viewer!.initialized; + return _viewer!; } bool _creatingTexture = false; + bool _destroyingTexture = false; Future _waitForTextureCreationToComplete() async { var iter = 0; - while (_creatingTexture) { + while (_creatingTexture || _destroyingTexture) { await Future.delayed(Duration(milliseconds: 50)); iter++; if (iter > 10) { @@ -104,10 +110,15 @@ class ThermionFlutterFFI extends ThermionFlutterPlatform { if (_textures.length > 1) { throw Exception("Multiple textures not yet supported"); - } else if (_textures.length == 1 && - _textures.first.height == height && - _textures.first.width == width) { - return _textures.first; + } else if (_textures.length == 1) { + if (_textures.first.height == height && _textures.first.width == width) { + return _textures.first; + } else { + await _viewer!.setRendering(false); + await _viewer!.destroySwapChain(); + await destroyTexture(_textures.first); + _textures.clear(); + } } _creatingTexture = true; @@ -125,39 +136,44 @@ class ThermionFlutterFFI extends ThermionFlutterPlatform { print( "Created texture with flutter texture id ${flutterTextureId}, hardwareTextureId $hardwareTextureId and surfaceAddress $surfaceAddress"); - viewer.viewportDimensions = (width.toDouble(), height.toDouble()); + _viewer?.viewportDimensions = (width.toDouble(), height.toDouble()); final texture = ThermionFlutterTexture( - flutterTextureId, hardwareTextureId, width, height, surfaceAddress); + flutterTextureId, hardwareTextureId, width, height, surfaceAddress); - await viewer.createSwapChain(width.toDouble(), height.toDouble(), - surface: texture.surfaceAddress == null - ? nullptr - : Pointer.fromAddress(texture.surfaceAddress!)); + await _viewer?.createSwapChain(width.toDouble(), height.toDouble(), + surface: texture.surfaceAddress == null + ? nullptr + : Pointer.fromAddress(texture.surfaceAddress!)); if (texture.hardwareTextureId != null) { - print("Creating render target"); // ignore: unused_local_variable - var renderTarget = await viewer.createRenderTarget( - width.toDouble(), height.toDouble(), texture.hardwareTextureId!); + var renderTarget = await _viewer?.createRenderTarget( + width.toDouble(), height.toDouble(), texture.hardwareTextureId!); } - - await viewer.updateViewportAndCameraProjection( + + await _viewer?.updateViewportAndCameraProjection( width.toDouble(), height.toDouble()); - viewer.render(); + _viewer?.render(); _creatingTexture = false; - + _textures.add(texture); - + return texture; } /// - /// Called by [ThermionWidget] to destroy a texture. Don't call this yourself. + /// Destroy a texture and clean up the texture cache (if applicable). /// Future destroyTexture(ThermionFlutterTexture texture) async { - await _channel.invokeMethod("destroyTexture", texture.flutterTextureId); + if (_creatingTexture || _destroyingTexture) { + throw Exception( + "Cannot destroy texture while concurrent call to createTexture/destroyTexture has not completed"); + } + _destroyingTexture = true; _textures.remove(texture); + await _channel.invokeMethod("destroyTexture", texture.flutterTextureId); + _destroyingTexture = false; } bool _resizing = false; @@ -172,14 +188,14 @@ class ThermionFlutterFFI extends ThermionFlutterPlatform { throw Exception("Resize underway"); } - if ((width - viewer.viewportDimensions.$1).abs() < 0.001 || - (height - viewer.viewportDimensions.$2).abs() < 0.001) { + if ((width - _viewer!.viewportDimensions.$1).abs() < 0.001 || + (height - _viewer!.viewportDimensions.$2).abs() < 0.001) { return texture; } _resizing = true; - bool wasRendering = viewer.rendering; - await viewer.setRendering(false); - await viewer.destroySwapChain(); + bool wasRendering = _viewer!.rendering; + await _viewer!.setRendering(false); + await _viewer!.destroySwapChain(); await destroyTexture(texture); var result = await _channel @@ -188,34 +204,29 @@ class ThermionFlutterFFI extends ThermionFlutterPlatform { if (result == null || result[0] == -1) { throw Exception("Failed to create texture"); } - viewer.viewportDimensions = (width.toDouble(), height.toDouble()); + _viewer!.viewportDimensions = (width.toDouble(), height.toDouble()); var newTexture = ThermionFlutterTexture(result[0], result[1], width, height, result[2]); - await viewer.createSwapChain(width.toDouble(), height.toDouble(), + await _viewer!.createSwapChain(width.toDouble(), height.toDouble(), surface: newTexture.surfaceAddress == null ? nullptr : Pointer.fromAddress(newTexture.surfaceAddress!)); if (newTexture.hardwareTextureId != null) { // ignore: unused_local_variable - var renderTarget = await viewer.createRenderTarget( + var renderTarget = await _viewer!.createRenderTarget( width.toDouble(), height.toDouble(), newTexture.hardwareTextureId!); } - await viewer.updateViewportAndCameraProjection( - width.toDouble(), height.toDouble()); + await _viewer! + .updateViewportAndCameraProjection(width.toDouble(), height.toDouble()); - viewer.viewportDimensions = (width.toDouble(), height.toDouble()); + _viewer!.viewportDimensions = (width.toDouble(), height.toDouble()); if (wasRendering) { - await viewer.setRendering(true); + await _viewer!.setRendering(true); } _textures.add(newTexture); _resizing = false; return newTexture; } - - @override - void dispose() { - // TODO: implement dispose - } } diff --git a/thermion_flutter/thermion_flutter_ffi/pubspec.yaml b/thermion_flutter/thermion_flutter_ffi/pubspec.yaml index f68e2118..01b42d78 100644 --- a/thermion_flutter/thermion_flutter_ffi/pubspec.yaml +++ b/thermion_flutter/thermion_flutter_ffi/pubspec.yaml @@ -1,7 +1,7 @@ name: thermion_flutter_ffi description: An FFI interface for the thermion_flutter plugin (all platforms except web). repository: https://github.com/nmfisher/thermion_flutter/thermion_flutter -version: 0.0.1 +version: 0.1.0+5 environment: sdk: ">=3.3.0 <4.0.0" @@ -22,8 +22,8 @@ dependencies: flutter: sdk: flutter plugin_platform_interface: ^2.1.0 - thermion_flutter_platform_interface: ^0.0.1-pre - thermion_dart: ^0.0.1-pre2 + thermion_flutter_platform_interface: ^0.1.0+5 + thermion_dart: ^0.1.1+1 dev_dependencies: flutter_test: diff --git a/thermion_flutter/thermion_flutter_platform_interface/CHANGELOG.md b/thermion_flutter/thermion_flutter_platform_interface/CHANGELOG.md index 24e9bcce..554835f0 100644 --- a/thermion_flutter/thermion_flutter_platform_interface/CHANGELOG.md +++ b/thermion_flutter/thermion_flutter_platform_interface/CHANGELOG.md @@ -1,2 +1,22 @@ +## 0.1.0+5 + + - Update a dependency to the latest release. + +## 0.1.0+4 + + - Update a dependency to the latest release. + +## 0.1.0+3 + + - Update a dependency to the latest release. + +## 0.1.0+2 + + - Update a dependency to the latest release. + +## 0.1.0+1 + + - Update a dependency to the latest release. + ## 0.0.1 * First release diff --git a/thermion_flutter/thermion_flutter_platform_interface/lib/thermion_flutter_platform_interface.dart b/thermion_flutter/thermion_flutter_platform_interface/lib/thermion_flutter_platform_interface.dart index b7425e64..1ba13a90 100644 --- a/thermion_flutter/thermion_flutter_platform_interface/lib/thermion_flutter_platform_interface.dart +++ b/thermion_flutter/thermion_flutter_platform_interface/lib/thermion_flutter_platform_interface.dart @@ -9,8 +9,7 @@ abstract class ThermionFlutterPlatform extends PlatformInterface { static final Object _token = Object(); - static late ThermionFlutterPlatform _instance; - + static late final ThermionFlutterPlatform _instance; static ThermionFlutterPlatform get instance => _instance; static set instance(ThermionFlutterPlatform instance) { @@ -18,9 +17,7 @@ abstract class ThermionFlutterPlatform extends PlatformInterface { _instance = instance; } - ThermionViewer get viewer; - - Future initialize({String? uberArchivePath}); + Future createViewer({String? uberArchivePath}); Future createTexture( int width, int height, int offsetLeft, int offsetRight); @@ -30,5 +27,4 @@ abstract class ThermionFlutterPlatform extends PlatformInterface { Future resizeTexture(ThermionFlutterTexture texture, int width, int height, int offsetLeft, int offsetRight); - void dispose(); } diff --git a/thermion_flutter/thermion_flutter_platform_interface/pubspec.yaml b/thermion_flutter/thermion_flutter_platform_interface/pubspec.yaml index d2ef55ab..c9f48b4b 100644 --- a/thermion_flutter/thermion_flutter_platform_interface/pubspec.yaml +++ b/thermion_flutter/thermion_flutter_platform_interface/pubspec.yaml @@ -1,7 +1,7 @@ name: thermion_flutter_platform_interface description: A common platform interface for the thermion_flutter plugin. repository: https://github.com/nmfisher/thermion_flutter/thermion_flutter -version: 0.0.1 +version: 0.1.0+5 environment: sdk: ">=3.3.0 <4.0.0" @@ -11,7 +11,7 @@ dependencies: flutter: sdk: flutter plugin_platform_interface: ^2.1.0 - thermion_dart: ^0.0.1-pre2 + thermion_dart: ^0.1.1+1 dev_dependencies: flutter_test: diff --git a/thermion_flutter/thermion_flutter_web/CHANGELOG.md b/thermion_flutter/thermion_flutter_web/CHANGELOG.md index feb7a4bf..6d161ca4 100644 --- a/thermion_flutter/thermion_flutter_web/CHANGELOG.md +++ b/thermion_flutter/thermion_flutter_web/CHANGELOG.md @@ -1,2 +1,22 @@ +## 0.0.1+5 + + - Update a dependency to the latest release. + +## 0.0.1+4 + + - Update a dependency to the latest release. + +## 0.0.1+3 + + - Update a dependency to the latest release. + +## 0.0.1+2 + + - Update a dependency to the latest release. + +## 0.0.1+1 + + - **REFACTOR**: export ThermionViewerWasm for web and hide FFI/WASM version. + ## 0.0.1 * First release of Dart-only package diff --git a/thermion_flutter/thermion_flutter_web/lib/thermion_flutter_web.dart b/thermion_flutter/thermion_flutter_web/lib/thermion_flutter_web.dart index c5a20055..5cad8d52 100644 --- a/thermion_flutter/thermion_flutter_web/lib/thermion_flutter_web.dart +++ b/thermion_flutter/thermion_flutter_web/lib/thermion_flutter_web.dart @@ -1,9 +1,9 @@ +import 'package:thermion_dart/thermion_dart/compatibility/web/interop/thermion_viewer_wasm.dart'; import 'package:thermion_dart/thermion_dart/thermion_viewer.dart'; import 'package:thermion_flutter_platform_interface/thermion_flutter_platform_interface.dart'; import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; -import 'package:thermion_dart/thermion_dart/compatibility/web/interop/thermion_dart_js_extension_type.dart'; -import 'package:thermion_dart/thermion_dart/compatibility/web/interop/js_interop_filament_viewer.dart'; +import 'package:web/web.dart'; class ThermionFlutterWebPlugin extends ThermionFlutterPlatform { static void registerWith(Registrar registrar) { @@ -12,30 +12,31 @@ class ThermionFlutterWebPlugin extends ThermionFlutterPlatform { @override Future createTexture( - int width, int height, int offsetLeft, int offsetRight) async {} - - @override - Future destroyTexture(ThermionFlutterTexture texture) async {} - - @override - void dispose() { - // TODO: implement dispose + int width, int height, int offsetLeft, int offsetRight) async { + return ThermionFlutterTexture(null, null, 0, 0, null); } @override - Future initialize({String? uberArchivePath}) async { - print("Creating viewer in web plugin"); - viewer = JsInteropThermionViewerFFI("filamentViewer"); - print("Waiting for initialized"); - await viewer.initialized; - print("int complete"); + Future destroyTexture(ThermionFlutterTexture texture) async { + // noop } @override Future resizeTexture(ThermionFlutterTexture texture, - int width, int height, int offsetLeft, int offsetRight) async {} + int width, int height, int offsetLeft, int offsetRight) async { + return ThermionFlutterTexture(null, null, 0, 0, null); + } - @override - // TODO: implement viewer - late final ThermionViewer viewer; + Future createViewer({String? uberArchivePath}) async { + final canvas = document.getElementById("canvas") as HTMLCanvasElement; + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + + var width = window.innerWidth; + var height = window.innerHeight; + + var viewer = ThermionViewerWasm(); + await viewer.initialize(width, height, uberArchivePath: uberArchivePath); + return viewer; + } } diff --git a/thermion_flutter/thermion_flutter_web/pubspec.yaml b/thermion_flutter/thermion_flutter_web/pubspec.yaml index f96ff058..4e0ea4f1 100644 --- a/thermion_flutter/thermion_flutter_web/pubspec.yaml +++ b/thermion_flutter/thermion_flutter_web/pubspec.yaml @@ -1,7 +1,7 @@ name: thermion_flutter_web description: A web platform interface for the thermion_flutter plugin. repository: https://github.com/nmfisher/thermion_flutter/thermion_flutter -version: 0.0.1 +version: 0.0.1+5 environment: sdk: ">=3.3.0 <4.0.0" @@ -20,8 +20,8 @@ dependencies: sdk: flutter plugin_platform_interface: ^2.1.0 web: ^0.5.1 - thermion_dart: ^0.0.1-pre - thermion_flutter_platform_interface: ^0.0.1-pre + thermion_dart: ^0.1.1+1 + thermion_flutter_platform_interface: ^0.1.0+5 flutter_web_plugins: sdk: flutter