diff --git a/example/integration_test/plugin_integration_test.dart b/example/integration_test/plugin_integration_test.dart index 82904acc..ea543418 100644 --- a/example/integration_test/plugin_integration_test.dart +++ b/example/integration_test/plugin_integration_test.dart @@ -49,14 +49,15 @@ void main() { } Future tap(WidgetTester tester, String label, [int seconds = 0]) async { - var target = find.text(label).first; - await tester.dragUntilVisible( - target, - find.byType(SingleChildScrollView), - // widget you want to scroll - const Offset(0, 500), // delta to move - duration: Duration(milliseconds: 10)); - await tester.tap(target); + var target = find.text(label, skipOffstage: false); + + if (!target.hasFound) { + print("Couldn't find target, waiting 100ms"); + await tester.pump(const Duration(milliseconds: 100)); + target = find.text(label); + } + + await tester.tap(target.first); await _snapshot(tester, label.replaceAll(RegExp("[ -:]"), ""), seconds); } @@ -87,14 +88,30 @@ void main() { await _snapshot(tester, "fresh"); - await tap(tester, "create FilamentController (default ubershader)", 1); - await tap(tester, "create FilamentViewer", 4); + await tap(tester, "Controller / Viewer"); + await tap(tester, "Create FilamentController (default ubershader)"); + await tap(tester, "Controller / Viewer"); + await tap(tester, "Create FilamentViewer", + 4); // on older devices this may take a while, so let's insert a length delay - await tap(tester, "Rendering: false", 2); + await tap(tester, "Scene"); + await tap(tester, "Rendering"); + await tap(tester, "Set continuous rendering to true"); - await tap(tester, "load skybox", 2); - await tap(tester, "load IBL", 2); - await tap(tester, "load shapes GLB", 2); + await tap(tester, "Scene"); + await tap(tester, "Assets"); + await tap(tester, "Shapes"); + await tap(tester, "Load GLB"); + + await tester.pump(); + + await tap(tester, "Scene"); + await tap(tester, "Assets"); + await tap(tester, "Load skybox", 1); + + await tap(tester, "Scene"); + await tap(tester, "Assets"); + await tap(tester, "Load IBL", 1); final Offset pointerLocation = tester.getCenter(find.byType(FilamentWidget)); @@ -141,17 +158,33 @@ void main() { await _snapshot(tester, "pan"); - await tap(tester, "transform to unit cube"); - await tap(tester, "set shapes position to 1, 1, -1"); - await tap(tester, "Disable frustum culling"); - await tap(tester, "Set tone mapping to linear"); - await tap(tester, "Move camera to asset"); - await tap(tester, "move camera to 1, 1, -1"); - await tap(tester, 'set camera to first camera in shapes GLB'); + await tap(tester, "Scene"); + await tap(tester, "Assets"); + await tap(tester, "Shapes"); + await tap(tester, "Transform to unit cube"); - await tap(tester, 'resize', 1); - await tap(tester, 'resize', 1); - await tap(tester, 'resize', 1); - await tap(tester, 'resize', 1); + await tap(tester, "Scene"); + await tap(tester, "Assets"); + await tap(tester, "Shapes"); + await tap(tester, "Set position to 1, 1, -1"); + + await tap(tester, "Scene"); + await tap(tester, "Camera"); + await tap(tester, "Disable frustum culling"); + + await tap(tester, "Scene"); + await tap(tester, "Rendering"); + await tap(tester, "Set tone mapping to linear"); + + await tap(tester, "Scene"); + await tap(tester, "Camera"); + await tap(tester, 'Set to first camera in last added asset'); + + await tap(tester, "Move to last added asset"); + await tap(tester, "Move to 1, 1, -1"); + + await tap(tester, 'Toggle viewport size', 1); + await tap(tester, 'Toggle viewport size', 1); + await tap(tester, 'Toggle viewport size', 1); }); } diff --git a/example/lib/main.dart b/example/lib/main.dart index 25c12d39..ab30eea3 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,10 +1,12 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_filament/filament_controller_ffi.dart'; import 'package:flutter_filament_example/camera_matrix_overlay.dart'; -import 'package:flutter_filament_example/controller_menu.dart'; +import 'package:flutter_filament_example/menus/controller_menu.dart'; import 'package:flutter_filament_example/example_viewport.dart'; import 'package:flutter_filament_example/picker_result_widget.dart'; -import 'package:flutter_filament_example/scene_menu.dart'; +import 'package:flutter_filament_example/menus/scene_menu.dart'; import 'package:flutter_filament/filament_controller.dart'; @@ -28,7 +30,7 @@ class _MyAppState extends State with SingleTickerProviderStateMixin { return MaterialApp( theme: ThemeData(useMaterial3: true), // showPerformanceOverlay: true, - home: Scaffold(body: ExampleWidget())); + home: const Scaffold(body: ExampleWidget())); } } @@ -37,35 +39,45 @@ class ExampleWidget extends StatefulWidget { @override State createState() { - return _ExampleWidgetState(); + return ExampleWidgetState(); } } enum MenuType { controller, assets, camera, misc } -class _ExampleWidgetState extends State { +class ExampleWidgetState extends State { FilamentController? _filamentController; - FilamentEntity? _flightHelmet; - FilamentEntity? _buster; - FilamentEntity? _light; - - final weights = List.filled(255, 0.0); - EdgeInsets _viewportMargin = EdgeInsets.zero; - Widget _item(void Function() onTap, String text) { - return GestureDetector( - onTap: () { - setState(() { - onTap(); - }); - }, - child: Container( - color: Colors.transparent, - padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10), - child: Text(text))); - } + // these are all the options that can be set via the menu + // we store them here + static bool rendering = false; + static int framerate = 60; + static bool postProcessing = true; + static bool frustumCulling = true; + static ManipulatorMode cameraManipulatorMode = ManipulatorMode.ORBIT; + + static double zoomSpeed = 0.01; + static double orbitSpeedX = 0.01; + static double orbitSpeedY = 0.01; + + static FilamentEntity? last; + + static bool hasSkybox = false; + static bool coneHidden = false; + + static FilamentEntity? shapes; + static FilamentEntity? flightHelmet; + static FilamentEntity? buster; + + static List? animations; + + static FilamentEntity? directionalLight; + + static bool loop = false; + + late StreamSubscription _listener; @override void initState() { @@ -87,6 +99,12 @@ class _ExampleWidgetState extends State { } } + @override + void dispose() { + super.dispose(); + _listener.cancel(); + } + @override Widget build(BuildContext context) { return Stack(children: [ @@ -102,18 +120,38 @@ class _ExampleWidgetState extends State { padding: const EdgeInsets.only(bottom: 30), height: 100, color: Colors.white, - child: - Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ + child: Row(crossAxisAlignment: CrossAxisAlignment.end, children: [ ControllerMenu( controller: _filamentController, onControllerDestroyed: () {}, onControllerCreated: (controller) { setState(() { _filamentController = controller; + _listener = _filamentController!.onLoad + .listen((FilamentEntity entity) { + print("Set last to $entity"); + last = entity; + if (mounted) { + setState(() {}); + } + print(_filamentController!.getNameForEntity(entity) ?? + "NAME NOT FOUND"); + }); }); }), SceneMenu( controller: _filamentController, + ), + Expanded(child: Container()), + TextButton( + child: const Text("Toggle viewport size"), + onPressed: () { + setState(() { + _viewportMargin = (_viewportMargin == EdgeInsets.zero) + ? const EdgeInsets.all(30) + : EdgeInsets.zero; + }); + }, ) ]))), _filamentController == null @@ -129,373 +167,5 @@ class _ExampleWidgetState extends State { child: PickerResultWidget(controller: _filamentController!), ) ]); - -// _item(() { - -// _item(() async { -// _animations = await _filamentController!.setCamera(_shapes!, null); -// setState(() {}); -// }, 'set camera to first camera in shapes GLB'), -// _item(() async { -// if (_coneHidden) { -// _filamentController!.reveal(_shapes!, "Cone"); -// } else { -// _filamentController!.hide(_shapes!, "Cone"); -// } -// setState(() { -// _coneHidden = !_coneHidden; -// }); -// }, _coneHidden ? 'show cone' : 'hide cone'), -// _item(() async { -// if (_shapes != null) { -// _filamentController!.removeAsset(_shapes!); -// } -// _shapes = await _filamentController! -// .loadGltf('assets/shapes/shapes.gltf', 'assets/shapes'); -// }, 'load shapes GLTF'), -// _item(() async { -// _filamentController!.transformToUnitCube(_shapes!); -// }, 'transform to unit cube'), -// _item(() async { -// _filamentController!.setPosition(_shapes!, 1.0, 1.0, -1.0); -// }, 'set shapes position to 1, 1, -1'), -// _item(() async { -// _filamentController!.setCameraPosition(1.0, 1.0, -1.0); -// }, 'move camera to 1, 1, -1'), -// _item(() async { -// var frameData = Float32List.fromList( -// List.generate(120, (i) => i / 120).expand((x) { -// var vals = List.filled(7, x); -// vals[3] = 1.0; -// // vals[4] = 0; -// vals[5] = 0; -// vals[6] = 0; -// return vals; -// }).toList()); - -// _filamentController!.setBoneAnimation( -// _shapes!, -// BoneAnimationData( -// "Bone.001", ["Cube.001"], frameData, 1000.0 / 60.0)); -// // , -// // "Bone.001", -// // "Cube.001", -// // BoneTransform([Vec3(x: 0, y: 0.0, z: 0.0)], -// // [Quaternion(x: 1, y: 1, z: 1, w: 1)])); -// }, 'construct bone animation'), -// _item(() async { -// _filamentController!.removeAsset(_shapes!); -// _shapes = null; -// }, 'remove shapes'), -// _item(() async { -// _filamentController!.clearAssets(); -// _shapes = null; -// }, 'clear all assets'), -// _item(() async { -// var names = await _filamentController! -// .getMorphTargetNames(_shapes!, "Cylinder"); -// await showDialog( -// context: context, -// builder: (ctx) { -// return Container( -// height: 100, -// width: 100, -// color: Colors.white, -// child: Text(names.join(","))); -// }); -// }, "show morph target names for Cylinder"), -// _item(() { -// _filamentController!.setMorphTargetWeights( -// _shapes!, "Cylinder", List.filled(4, 1.0)); -// }, "set Cylinder morph weights to 1"), -// _item(() { -// _filamentController!.setMorphTargetWeights( -// _shapes!, "Cylinder", List.filled(4, 0.0)); -// }, "set Cylinder morph weights to 0.0"), -// _item(() async { -// var morphs = await _filamentController! -// .getMorphTargetNames(_shapes!, "Cylinder"); -// final animation = AnimationBuilder( -// availableMorphs: morphs, -// framerate: 30, -// meshName: "Cylinder") -// .setDuration(4) -// .setMorphTargets(["Key 1", "Key 2"]) -// .interpolateMorphWeights(0, 4, 0, 1) -// .build(); -// _filamentController!.setMorphAnimationData(_shapes!, animation); -// }, "animate cylinder morph weights #1 and #2"), -// _item(() async { -// var morphs = await _filamentController! -// .getMorphTargetNames(_shapes!, "Cylinder"); -// final animation = AnimationBuilder( -// availableMorphs: morphs, -// framerate: 30, -// meshName: "Cylinder") -// .setDuration(4) -// .setMorphTargets(["Key 3", "Key 4"]) -// .interpolateMorphWeights(0, 4, 0, 1) -// .build(); -// _filamentController!.setMorphAnimationData(_shapes!, animation); -// }, "animate cylinder morph weights #3 and #4"), -// _item(() async { -// var morphs = await _filamentController! -// .getMorphTargetNames(_shapes!, "Cube"); -// final animation = AnimationBuilder( -// availableMorphs: morphs, framerate: 30, meshName: "Cube") -// .setDuration(4) -// .setMorphTargets(["Key 1", "Key 2"]) -// .interpolateMorphWeights(0, 4, 0, 1) -// .build(); -// _filamentController!.setMorphAnimationData(_shapes!, animation); -// }, "animate shapes morph weights #1 and #2"), -// _item(() { -// _filamentController! -// .setMaterialColor(_shapes!, "Cone", 0, Colors.purple); -// }, "set cone material color to purple"), -// _item(() { -// _loop = !_loop; -// setState(() {}); -// }, "toggle animation looping ${_loop ? "OFF" : "ON"}"), -// _item(() { -// setState(() { -// _viewportMargin = _viewportMargin == EdgeInsets.zero -// ? EdgeInsets.all(50) -// : EdgeInsets.zero; -// }); -// }, "resize"), -// _item(() async { -// await Permission.microphone.request(); -// }, "request permissions (tests inactive->resume)") -// ]); -// if (_animations != null) { -// children.addAll(_animations!.map((a) => _item(() { -// _filamentController!.playAnimation( -// _shapes!, _animations!.indexOf(a), -// replaceActive: true, crossfade: 0.5, loop: _loop); -// }, "play animation ${_animations!.indexOf(a)} (replace/fade)"))); -// children.addAll(_animations!.map((a) => _item(() { -// _filamentController!.playAnimation( -// _shapes!, _animations!.indexOf(a), -// replaceActive: false, loop: _loop); -// }, "play animation ${_animations!.indexOf(a)} (noreplace)"))); -// } - -// children.addAll([ -// _item(() async { -// await Permission.microphone.request(); -// }, "request permissions (tests inactive->resume)"), -// _item(() async { -// if (_buster != null) { -// await _filamentController!.removeAsset(_buster!); -// } -// _buster = await (_filamentController as FilamentControllerFFI) -// .loadGltf("assets/BusterDrone/scene.gltf", "assets/BusterDrone", -// force: true); -// await _filamentController!.playAnimation(_buster!, 0, loop: true); -// }, "load buster") -// ]); -// } - -// if (_animations != null) { -// children.addAll(_animations!.map((a) => _item(() { -// _filamentController!.playAnimation( -// _shapes!, _animations!.indexOf(a), -// replaceActive: true, crossfade: 0.5, loop: _loop); -// }, "play animation ${_animations!.indexOf(a)} (replace/fade)"))); -// children.addAll(_animations!.map((a) => _item(() { -// _filamentController!.playAnimation( -// _shapes!, _animations!.indexOf(a), -// replaceActive: false, loop: _loop); -// }, "play animation ${_animations!.indexOf(a)} (noreplace)"))); -// } -// } - // return Stack(children: [ - // Viewport(_filamentController, _viewportPadding), - // Positioned( - // right: 50, - // top: 50, - // child: PickerResultWidget(controller: _filamentController!)), - // _cameraTimer == null - // ? Container() - // : Positioned( - // top: 10, - // left: 10, - // child: , - // Align( - // alignment: Alignment.bottomCenter, - // child: OrientationBuilder(builder: (ctx, orientation) { - // return Container( - // alignment: Alignment.bottomCenter, - // height: orientation == Orientation.landscape ? 100 : 200, - // color: Colors.white.withOpacity(0.75), - // child: SingleChildScrollView(child: Wrap(children: children))); - // })) - // ]); } } - -// _item(24 () async { 'rotate by pi around Y axis'), -// _item(5 () async { 'load flight helmet'), - -// _item(7 () async { 'set all weights to 1'), -// _item(8 () async { 'set all weights to 0'), -// _item(9 () async { 'play all animations'), -// _item(34 () async { 'play animation 0'), -// _item(34 () async { 'play animation 0 (noreplace)'), -// _item(35 () async { 'play animation 1'), -// _item(34 () async { 'play animation 0 (noreplace)'), -// _item(36 () async { 'play animation 2'), -// _item(34 () async { 'play animation 0 (noreplace)'), -// _item(36 () async { 'play animation 3'), -// _item(34 () async { 'play animation 3 (noreplace)'), -// _item(37 () async { 'stop animation 0'), - -// _item(14 () async { 'set camera'), -// _item(15 () async { 'animate weights'), -// _item(16 () async { 'get target names'), -// _item(17 () async { 'get animation names'), -// _item(18 () async { 'pan left'), -// _item(19 () async { 'pan right'), -// _item(25 () async { -// Text(_vertical ? 'set horizontal' : 'set vertical')), -// _item(26 () async { 'set camera pos to 0,0,3'), -// _item(27 () async { 'toggle framerate'), -// _item(28 () async { 'set bg image pos'), -// _item(29 () async { 'add light'), -// _item(30 () async { 'remove light'), -// _item(31 () async { 'clear all lights'), -// _item(32 () async { 'set camera model matrix'), - -// case -1: - -// break; -// case -2: -// _filamentController!.render(); -// break; -// case -4: -// setState(() { -// _rendering = !_rendering; -// _filamentController!.setRendering(_rendering); -// }); -// break; -// case -5: -// setState(() { -// _framerate = _framerate == 60 ? 30 : 60; -// _filamentController!.setFrameRate(_framerate); -// }); -// break; -// case -6: -// _filamentController!.setBackgroundColor(Color(0xFF73C9FA)); -// break; - -// case 5: -// _flightHelmet ??= await _filamentController!.loadGltf( -// 'assets/FlightHelmet/FlightHelmet.gltf', 'assets/FlightHelmet'); -// break; - -// case 11: -// setState(() { -// _loop = !_loop; -// }); -// break; -// case 14: -// _filamentController!.setCamera(_shapes!, "Camera_Orientation"); -// break; -// case 15: - -// break; -// case 17: -// var animationNames = -// await _filamentController!.getAnimationNames(_shapes!); - -// await showDialog( -// context: context, -// builder: (ctx) { -// return Container( -// height: 100, -// width: 100, -// color: Colors.white, -// child: Text(animationNames.join(","))); -// }); - -// break; -// case 18: -// _filamentController!.panStart(1, 1); -// _filamentController!.panUpdate(1, 2); -// _filamentController!.panEnd(); -// break; -// case 19: -// _filamentController!.panStart(1, 1); -// _filamentController!.panUpdate(0, 0); -// _filamentController!.panEnd(); -// break; -// case 20: -// _filamentController!.clearAssets(); -// break; -// case 21: -// break; -// case 22: -// break; -// case 23: -// break; -// case 24: -// _filamentController!.setRotation(_shapes!, pi / 2, 0.0, 1.0, 0.0); -// break; -// case 26: -// _filamentController!.setCameraPosition(0, 0, 3); -// _filamentController!.setCameraRotation(0, 0, 1, 0); -// break; -// case 27: -// _framerate = _framerate == 60 ? 30 : 60; -// _filamentController!.setFrameRate(_framerate); -// break; -// case 28: -// _filamentController!.setBackgroundImagePosition(25, 25); -// break; - -// case 30: -// if (_light != null) { -// _filamentController!.removeLight(_light!); -// _light = null; -// } -// break; -// case 31: - -// break; -// case 32: - -// // break; -// break; -// case 33: - -// break; -// case 34: -// var duration = -// await _filamentController!.getAnimationDuration(_shapes!, 0); -// _filamentController!.playAnimation(_shapes!, 0, -// loop: false, crossfade: 0.5); -// await Future.delayed( -// Duration(milliseconds: (duration * 1000.0).toInt())); -// print("animation complete"); -// // showDialog( -// // context: context, -// // builder: (context) { -// // return Container( -// // width: 100, -// // height: 100, -// // color: Colors.white, -// // child: "animation complete!"); -// // }); -// break; -// case 35: -// _filamentController!.playAnimation(_shapes!, 1, -// loop: false, crossfade: 0.5); -// break; -// case 36: -// _filamentController!.playAnimation(_shapes!, 2, -// loop: false, crossfade: 0.5); -// break; -// case 37: -// _filamentController!.stopAnimation(_shapes!, 0); -// break; diff --git a/example/lib/menus/asset_submenu.dart b/example/lib/menus/asset_submenu.dart new file mode 100644 index 00000000..6443bc15 --- /dev/null +++ b/example/lib/menus/asset_submenu.dart @@ -0,0 +1,309 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_filament/filament_controller.dart'; +import 'package:flutter_filament_example/main.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class AssetSubmenu extends StatefulWidget { + final FilamentController controller; + const AssetSubmenu({super.key, required this.controller}); + + @override + State createState() => _AssetSubmenuState(); +} + +class _AssetSubmenuState extends State { + @override + void initState() { + super.initState(); + } + + Widget _shapesSubmenu() { + var children = [ + MenuItemButton( + onPressed: () async { + if (ExampleWidgetState.shapes == null) { + ExampleWidgetState.shapes = + await widget.controller.loadGlb('assets/shapes/shapes.glb'); + ExampleWidgetState.animations = await widget.controller + .getAnimationNames(ExampleWidgetState.shapes!); + } else { + await widget.controller.removeAsset(ExampleWidgetState.shapes!); + ExampleWidgetState.shapes = null; + ExampleWidgetState.animations = null; + } + }, + child: const Text('Load GLB')), + MenuItemButton( + onPressed: ExampleWidgetState.shapes != null + ? null + : () async { + if (ExampleWidgetState.shapes != null) { + widget.controller.removeAsset(ExampleWidgetState.shapes!); + } + ExampleWidgetState.shapes = await widget.controller + .loadGltf('assets/shapes/shapes.gltf', 'assets/shapes'); + }, + child: const Text('Load GLTF')), + MenuItemButton( + onPressed: ExampleWidgetState.shapes == null + ? null + : () async { + await widget.controller + .transformToUnitCube(ExampleWidgetState.shapes!); + }, + child: const Text('Transform to unit cube')), + MenuItemButton( + onPressed: () async { + var names = await widget.controller + .getMorphTargetNames(ExampleWidgetState.shapes!, "Cylinder"); + await showDialog( + context: context, + builder: (ctx) { + return Container( + height: 100, + width: 100, + color: Colors.white, + child: Text(names.join(","))); + }); + }, + child: const Text("Show morph target names for Cylinder")), + MenuItemButton( + onPressed: () async { + widget.controller.setMorphTargetWeights( + ExampleWidgetState.shapes!, "Cylinder", List.filled(4, 1.0)); + }, + child: const Text("set Cylinder morph weights to 1")), + MenuItemButton( + onPressed: () async { + widget.controller.setMorphTargetWeights( + ExampleWidgetState.shapes!, "Cylinder", List.filled(4, 0.0)); + }, + child: const Text("Set Cylinder morph weights to 0")), + MenuItemButton( + onPressed: () async { + widget.controller + .setPosition(ExampleWidgetState.shapes!, 1.0, 1.0, -1.0); + }, + child: const Text('Set position to 1, 1, -1'), + ), + MenuItemButton( + onPressed: () async { + if (ExampleWidgetState.coneHidden) { + widget.controller.reveal(ExampleWidgetState.shapes!, "Cone"); + } else { + widget.controller.hide(ExampleWidgetState.shapes!, "Cone"); + } + + ExampleWidgetState.coneHidden = !ExampleWidgetState.coneHidden; + }, + child: + Text(ExampleWidgetState.coneHidden ? 'show cone' : 'hide cone')), + MenuItemButton( + onPressed: ExampleWidgetState.shapes == null + ? null + : () async { + widget.controller.setMaterialColor( + ExampleWidgetState.shapes!, "Cone", 0, Colors.purple); + }, + child: const Text("Set cone material color to purple")), + MenuItemButton( + onPressed: () async { + ExampleWidgetState.loop = !ExampleWidgetState.loop; + }, + child: Text( + "Toggle animation looping ${ExampleWidgetState.loop ? "OFF" : "ON"}")) + ]; + if (ExampleWidgetState.animations != null) { + children.addAll(ExampleWidgetState.animations!.map((a) => MenuItemButton( + onPressed: () { + widget.controller.playAnimation(ExampleWidgetState.shapes!, + ExampleWidgetState.animations!.indexOf(a), + replaceActive: true, + crossfade: 0.5, + loop: ExampleWidgetState.loop); + }, + child: Text( + "play animation ${ExampleWidgetState.animations!.indexOf(a)} (replace/fade)")))); + children.addAll(ExampleWidgetState.animations!.map((a) => MenuItemButton( + onPressed: () { + widget.controller.playAnimation(ExampleWidgetState.shapes!, + ExampleWidgetState.animations!.indexOf(a), + replaceActive: false, loop: ExampleWidgetState.loop); + }, + child: Text( + "Play animation ${ExampleWidgetState.animations!.indexOf(a)} (noreplace)")))); + } + + return SubmenuButton(menuChildren: children, child: const Text("Shapes")); + } + + @override + Widget build(BuildContext context) { + return SubmenuButton( + menuChildren: [ + _shapesSubmenu(), + MenuItemButton( + onPressed: () async { + ExampleWidgetState.directionalLight = await widget.controller + .addLight(1, 6500, 150000, 0, 1, 0, 0, -1, 0, true); + }, + child: const Text("Add directional light"), + ), + MenuItemButton( + onPressed: () async { + await widget.controller.clearLights(); + }, + child: const Text("Clear lights"), + ), + MenuItemButton( + onPressed: () async { + if (ExampleWidgetState.buster == null) { + ExampleWidgetState.buster = await widget.controller.loadGltf( + "assets/BusterDrone/scene.gltf", "assets/BusterDrone", + force: true); + await widget.controller + .playAnimation(ExampleWidgetState.buster!, 0, loop: true); + ExampleWidgetState.animations = await widget.controller + .getAnimationNames(ExampleWidgetState.shapes!); + } else { + await widget.controller.removeAsset(ExampleWidgetState.buster!); + ExampleWidgetState.buster = null; + } + }, + child: Text(ExampleWidgetState.shapes == null + ? 'Load buster' + : 'Remove buster')), + MenuItemButton( + onPressed: () async { + if (ExampleWidgetState.flightHelmet == null) { + ExampleWidgetState.flightHelmet ??= await widget.controller + .loadGltf('assets/FlightHelmet/FlightHelmet.gltf', + 'assets/FlightHelmet', + force: true); + } else { + await widget.controller + .removeAsset(ExampleWidgetState.flightHelmet!); + ExampleWidgetState.flightHelmet = null; + } + }, + child: Text(ExampleWidgetState.shapes == null + ? 'Load flight helmet' + : 'Remove flight helmet')), + MenuItemButton( + onPressed: () { + widget.controller.setBackgroundColor(const Color(0xFF73C9FA)); + }, + child: const Text("Set background color")), + MenuItemButton( + onPressed: () { + widget.controller.setBackgroundImage('assets/background.ktx'); + }, + child: const Text("Load background image")), + MenuItemButton( + onPressed: () { + widget.controller.setBackgroundImage('assets/background.ktx', + fillHeight: true); + }, + child: const Text("Load background image (fill height)")), + MenuItemButton( + onPressed: () { + if (ExampleWidgetState.hasSkybox) { + widget.controller.removeSkybox(); + } else { + widget.controller + .loadSkybox('assets/default_env/default_env_skybox.ktx'); + } + ExampleWidgetState.hasSkybox = !ExampleWidgetState.hasSkybox; + }, + child: Text(ExampleWidgetState.hasSkybox + ? 'Remove skybox' + : 'Load skybox')), + MenuItemButton( + onPressed: () { + widget.controller + .loadIbl('assets/default_env/default_env_ibl.ktx'); + }, + child: const Text('Load IBL')), + MenuItemButton( + onPressed: () async { + await Permission.microphone.request(); + }, + child: const Text("Request permissions (tests inactive->resume)")), + MenuItemButton( + onPressed: () async { + await widget.controller.clearAssets(); + ExampleWidgetState.flightHelmet = null; + ExampleWidgetState.buster = null; + ExampleWidgetState.shapes = null; + }, + child: const Text('Clear assets')), + ], + child: const Text("Assets"), + ); + } +} + + +// _item(() async { +// var frameData = Float32List.fromList( +// List.generate(120, (i) => i / 120).expand((x) { +// var vals = List.filled(7, x); +// vals[3] = 1.0; +// // vals[4] = 0; +// vals[5] = 0; +// vals[6] = 0; +// return vals; +// }).toList()); + +// widget.controller!.setBoneAnimation( +// _shapes!, +// BoneAnimationData( +// "Bone.001", ["Cube.001"], frameData, 1000.0 / 60.0)); +// // , +// // "Bone.001", +// // "Cube.001", +// // BoneTransform([Vec3(x: 0, y: 0.0, z: 0.0)], +// // [Quaternion(x: 1, y: 1, z: 1, w: 1)])); +// }, 'construct bone animation'), + +// _item(() async { +// var morphs = await widget.controller! +// .getMorphTargetNames(_shapes!, "Cylinder"); +// final animation = AnimationBuilder( +// availableMorphs: morphs, +// framerate: 30, +// meshName: "Cylinder") +// .setDuration(4) +// .setMorphTargets(["Key 1", "Key 2"]) +// .interpolateMorphWeights(0, 4, 0, 1) +// .build(); +// widget.controller!.setMorphAnimationData(_shapes!, animation); +// }, "animate cylinder morph weights #1 and #2"), +// _item(() async { +// var morphs = await widget.controller! +// .getMorphTargetNames(_shapes!, "Cylinder"); +// final animation = AnimationBuilder( +// availableMorphs: morphs, +// framerate: 30, +// meshName: "Cylinder") +// .setDuration(4) +// .setMorphTargets(["Key 3", "Key 4"]) +// .interpolateMorphWeights(0, 4, 0, 1) +// .build(); +// widget.controller!.setMorphAnimationData(_shapes!, animation); +// }, "animate cylinder morph weights #3 and #4"), +// _item(() async { +// var morphs = await widget.controller! +// .getMorphTargetNames(_shapes!, "Cube"); +// final animation = AnimationBuilder( +// availableMorphs: morphs, framerate: 30, meshName: "Cube") +// .setDuration(4) +// .setMorphTargets(["Key 1", "Key 2"]) +// .interpolateMorphWeights(0, 4, 0, 1) +// .build(); +// widget.controller!.setMorphAnimationData(_shapes!, animation); +// }, "animate shapes morph weights #1 and #2"), + diff --git a/example/lib/menus/camera_submenu.dart b/example/lib/menus/camera_submenu.dart new file mode 100644 index 00000000..3628adf1 --- /dev/null +++ b/example/lib/menus/camera_submenu.dart @@ -0,0 +1,136 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import 'package:flutter_filament/filament_controller.dart'; +import 'package:flutter_filament_example/main.dart'; + +class CameraSubmenu extends StatefulWidget { + final FilamentController controller; + const CameraSubmenu({super.key, required this.controller}); + + @override + State createState() => _CameraSubmenuState(); +} + +class _CameraSubmenuState extends State { + List _cameraMenu() { + return [ + MenuItemButton( + onPressed: () async { + widget.controller.setCameraPosition(1.0, 1.0, -1.0); + }, + child: const Text('Move to 1, 1, -1'), + ), + MenuItemButton( + onPressed: ExampleWidgetState.last == null + ? null + : () async { + await widget.controller + .setCamera(ExampleWidgetState.last!, null); + }, + child: const Text('Set to first camera in last added asset'), + ), + MenuItemButton( + onPressed: ExampleWidgetState.last == null + ? null + : () async { + await widget.controller + .moveCameraToAsset(ExampleWidgetState.last!); + }, + child: const Text("Move to last added asset"), + ), + MenuItemButton( + onPressed: () { + widget.controller.setCameraRotation(pi / 4, 0.0, 1.0, 0.0); + }, + child: const Text("Rotate camera 45 degrees around y axis"), + ), + MenuItemButton( + onPressed: () { + ExampleWidgetState.frustumCulling = + !ExampleWidgetState.frustumCulling; + widget.controller + .setViewFrustumCulling(ExampleWidgetState.frustumCulling); + }, + child: Text( + "${ExampleWidgetState.frustumCulling ? "Disable" : "Enable"} frustum culling"), + ), + SubmenuButton( + menuChildren: ManipulatorMode.values.map((mm) { + return MenuItemButton( + onPressed: () { + ExampleWidgetState.cameraManipulatorMode = mm; + widget.controller.setCameraManipulatorOptions( + mode: ExampleWidgetState.cameraManipulatorMode, + orbitSpeedX: ExampleWidgetState.orbitSpeedX, + orbitSpeedY: ExampleWidgetState.orbitSpeedY, + zoomSpeed: ExampleWidgetState.zoomSpeed); + }, + child: Text( + mm.name, + style: TextStyle( + fontWeight: ExampleWidgetState.cameraManipulatorMode == mm + ? FontWeight.bold + : FontWeight.normal), + ), + ); + }).toList(), + child: const Text("Manipulator mode")), + SubmenuButton( + menuChildren: [0.01, 0.1, 1.0, 10.0, 100.0].map((speed) { + return MenuItemButton( + onPressed: () { + ExampleWidgetState.zoomSpeed = speed; + widget.controller.setCameraManipulatorOptions( + mode: ExampleWidgetState.cameraManipulatorMode, + orbitSpeedX: ExampleWidgetState.orbitSpeedX, + orbitSpeedY: ExampleWidgetState.orbitSpeedY, + zoomSpeed: ExampleWidgetState.zoomSpeed); + }, + child: Text( + speed.toString(), + style: TextStyle( + fontWeight: + (speed - ExampleWidgetState.zoomSpeed).abs() < 0.0001 + ? FontWeight.bold + : FontWeight.normal), + ), + ); + }).toList(), + child: const Text("Zoom speed")), + SubmenuButton( + menuChildren: [0.001, 0.01, 0.1, 1.0].map((speed) { + return MenuItemButton( + onPressed: () { + ExampleWidgetState.orbitSpeedX = speed; + ExampleWidgetState.orbitSpeedY = speed; + widget.controller.setCameraManipulatorOptions( + mode: ExampleWidgetState.cameraManipulatorMode, + orbitSpeedX: ExampleWidgetState.orbitSpeedX, + orbitSpeedY: ExampleWidgetState.orbitSpeedY, + zoomSpeed: ExampleWidgetState.zoomSpeed); + }, + child: Text( + speed.toString(), + style: TextStyle( + fontWeight: + (speed - ExampleWidgetState.orbitSpeedX).abs() < 0.0001 + ? FontWeight.bold + : FontWeight.normal), + ), + ); + }).toList(), + child: const Text("Orbit speed (X & Y)")) + ]; + } + + @override + Widget build(BuildContext context) { + return SubmenuButton( + menuChildren: _cameraMenu(), + child: const Text("Camera"), + ); + } +} diff --git a/example/lib/controller_menu.dart b/example/lib/menus/controller_menu.dart similarity index 93% rename from example/lib/controller_menu.dart rename to example/lib/menus/controller_menu.dart index eec8177b..b18c3461 100644 --- a/example/lib/controller_menu.dart +++ b/example/lib/menus/controller_menu.dart @@ -54,7 +54,7 @@ class _ControllerMenuState extends State { if (_filamentController?.hasViewer.value != true) { items.addAll([ MenuItemButton( - child: const Text("create viewer"), + child: const Text("Create FilamentViewer"), onPressed: _filamentController == null ? null : () { @@ -62,14 +62,14 @@ class _ControllerMenuState extends State { }, ), MenuItemButton( - child: const Text("create FilamentController (default ubershader)"), + child: const Text("Create FilamentController (default ubershader)"), onPressed: () { _createController(); }, ), MenuItemButton( child: const Text( - "create FilamentController (custom ubershader - lit opaque only)"), + "Create FilamentController (custom ubershader - lit opaque only)"), onPressed: () { _createController( uberArchivePath: Platform.isWindows @@ -85,7 +85,7 @@ class _ControllerMenuState extends State { } else { items.addAll([ MenuItemButton( - child: const Text("destroy viewer"), + child: const Text("Destroy viewer"), onPressed: () { _filamentController!.destroy(); _filamentController = null; diff --git a/example/lib/menus/rendering_submenu.dart b/example/lib/menus/rendering_submenu.dart new file mode 100644 index 00000000..6f65d4be --- /dev/null +++ b/example/lib/menus/rendering_submenu.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_filament/filament_controller.dart'; +import 'package:flutter_filament_example/main.dart'; + +class RenderingSubmenu extends StatefulWidget { + final FilamentController controller; + + const RenderingSubmenu({super.key, required this.controller}); + + @override + State createState() => _RenderingSubmenuState(); +} + +class _RenderingSubmenuState extends State { + @override + Widget build(BuildContext context) { + return SubmenuButton( + menuChildren: [ + MenuItemButton( + onPressed: () { + widget.controller.render(); + }, + child: const Text("Render single frame"), + ), + MenuItemButton( + onPressed: () { + ExampleWidgetState.rendering = !ExampleWidgetState.rendering; + widget.controller.setRendering(ExampleWidgetState.rendering); + }, + child: Text( + "Set continuous rendering to ${!ExampleWidgetState.rendering}"), + ), + MenuItemButton( + onPressed: () { + ExampleWidgetState.framerate = + ExampleWidgetState.framerate == 60 ? 30 : 60; + widget.controller.setFrameRate(ExampleWidgetState.framerate); + }, + child: Text( + "Toggle framerate (currently $ExampleWidgetState.framerate) "), + ), + MenuItemButton( + onPressed: () { + widget.controller.setToneMapping(ToneMapper.LINEAR); + }, + child: const Text("Set tone mapping to linear"), + ), + MenuItemButton( + onPressed: () { + ExampleWidgetState.postProcessing = + !ExampleWidgetState.postProcessing; + widget.controller + .setPostProcessing(ExampleWidgetState.postProcessing); + }, + child: Text( + "${ExampleWidgetState.postProcessing ? "Disable" : "Enable"} postprocessing"), + ), + ], + child: const Text("Rendering"), + ); + } +} diff --git a/example/lib/menus/scene_menu.dart b/example/lib/menus/scene_menu.dart new file mode 100644 index 00000000..56367c14 --- /dev/null +++ b/example/lib/menus/scene_menu.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_filament/filament_controller.dart'; +import 'package:flutter_filament_example/menus/asset_submenu.dart'; +import 'package:flutter_filament_example/menus/camera_submenu.dart'; +import 'package:flutter_filament_example/menus/rendering_submenu.dart'; + +class SceneMenu extends StatefulWidget { + final FilamentController? controller; + + const SceneMenu({super.key, required this.controller}); + + @override + State createState() { + return _SceneMenuState(); + } +} + +class _SceneMenuState extends State { + @override + void didUpdateWidget(SceneMenu oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.controller != null && + widget.controller != oldWidget.controller || + widget.controller!.hasViewer != oldWidget.controller!.hasViewer) { + setState(() {}); + } + } + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: + widget.controller?.hasViewer ?? ValueNotifier(false), + builder: (BuildContext ctx, bool hasViewer, Widget? child) { + return MenuAnchor( + menuChildren: widget.controller == null + ? [] + : [ + RenderingSubmenu( + controller: widget.controller!, + ), + AssetSubmenu(controller: widget.controller!), + CameraSubmenu( + controller: widget.controller!, + ), + ], + builder: (BuildContext context, MenuController controller, + Widget? child) { + return Align( + alignment: Alignment.bottomLeft, + child: TextButton( + onPressed: !hasViewer + ? null + : () { + if (controller.isOpen) { + controller.close(); + } else { + controller.open(); + } + }, + child: const Text("Scene"), + )); + }, + ); + }); + } +} diff --git a/example/lib/scene_menu.dart b/example/lib/scene_menu.dart deleted file mode 100644 index a36c68b2..00000000 --- a/example/lib/scene_menu.dart +++ /dev/null @@ -1,295 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/material.dart'; - -import 'package:flutter_filament/filament_controller.dart'; - -class SceneMenu extends StatefulWidget { - final FilamentController? controller; - - const SceneMenu({super.key, required this.controller}); - - @override - State createState() { - return _SceneMenuState(); - } -} - -class _SceneMenuState extends State { - bool _postProcessing = true; - - final FocusNode _buttonFocusNode = FocusNode(debugLabel: 'Camera Menu'); - - @override - void didUpdateWidget(SceneMenu oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.controller != null && - widget.controller != oldWidget.controller || - widget.controller!.hasViewer != oldWidget.controller!.hasViewer) { - setState(() {}); - } - } - - bool _coneHidden = false; - FilamentEntity? _shapes; - FilamentEntity? _directionalLight; - List? _animations; - bool _loop = false; - - bool _hasSkybox = false; - - List _assetMenu() { - return [ - MenuItemButton( - onPressed: () async { - if (_shapes == null) { - _shapes = - await widget.controller!.loadGlb('assets/shapes/shapes.glb'); - _animations = - await widget.controller!.getAnimationNames(_shapes!); - } else { - await widget.controller!.removeAsset(_shapes!); - _shapes = null; - _animations = null; - } - setState(() {}); - }, - child: - Text(_shapes == null ? 'load shapes GLB' : 'remove shapes GLB')), - MenuItemButton( - onPressed: () { - widget.controller!.setBackgroundColor(const Color(0xFF73C9FA)); - }, - child: const Text("set background color")), - MenuItemButton( - onPressed: () { - widget.controller!.setBackgroundImage('assets/background.ktx'); - }, - child: const Text("load background image")), - MenuItemButton( - onPressed: () { - widget.controller! - .setBackgroundImage('assets/background.ktx', fillHeight: true); - }, - child: const Text("load background image (fill height)")), - MenuItemButton( - onPressed: () { - if (_hasSkybox) { - widget.controller!.removeSkybox(); - } else { - widget.controller! - .loadSkybox('assets/default_env/default_env_skybox.ktx'); - } - _hasSkybox = !_hasSkybox; - setState(() {}); - }, - child: Text(_hasSkybox ? 'remove skybox' : 'load skybox')), - MenuItemButton( - onPressed: () { - widget.controller! - .loadIbl('assets/default_env/default_env_ibl.ktx'); - }, - child: const Text('load IBL')) - ]; - } - - bool _frustumCulling = true; - ManipulatorMode _cameraManipulatorMode = ManipulatorMode.ORBIT; - - double _zoomSpeed = 0.01; - double _orbitSpeedX = 0.01; - double _orbitSpeedY = 0.01; - - List _cameraMenu() { - return [ - MenuItemButton( - onPressed: () { - widget.controller!.moveCameraToAsset(_shapes!); - }, - child: const Text("Move camera to shapes asset"), - ), - MenuItemButton( - onPressed: () { - widget.controller!.setCameraRotation(pi / 4, 0.0, 1.0, 0.0); - }, - child: const Text("Rotate camera 45 degrees around y axis"), - ), - MenuItemButton( - onPressed: () { - setState(() { - _frustumCulling = !_frustumCulling; - }); - widget.controller!.setViewFrustumCulling(_frustumCulling); - }, - child: - Text("${_frustumCulling ? "Disable" : "Enable"} frustum culling"), - ), - SubmenuButton( - menuChildren: ManipulatorMode.values.map((mm) { - return MenuItemButton( - onPressed: () { - _cameraManipulatorMode = mm; - widget.controller!.setCameraManipulatorOptions( - mode: _cameraManipulatorMode, - orbitSpeedX: _orbitSpeedX, - orbitSpeedY: _orbitSpeedY, - zoomSpeed: _zoomSpeed); - setState(() {}); - }, - child: Text( - mm.name, - style: TextStyle( - fontWeight: _cameraManipulatorMode == mm - ? FontWeight.bold - : FontWeight.normal), - ), - ); - }).toList(), - child: Text("Manipulator mode")), - SubmenuButton( - menuChildren: [0.01, 0.1, 1.0, 10.0, 100.0].map((speed) { - return MenuItemButton( - onPressed: () { - _zoomSpeed = speed; - widget.controller!.setCameraManipulatorOptions( - mode: _cameraManipulatorMode, - orbitSpeedX: _orbitSpeedX, - orbitSpeedY: _orbitSpeedY, - zoomSpeed: _zoomSpeed); - setState(() {}); - }, - child: Text( - speed.toString(), - style: TextStyle( - fontWeight: (speed - _zoomSpeed).abs() < 0.0001 - ? FontWeight.bold - : FontWeight.normal), - ), - ); - }).toList(), - child: const Text("Zoom speed")), - SubmenuButton( - menuChildren: [0.001, 0.01, 0.1, 1.0].map((speed) { - return MenuItemButton( - onPressed: () { - _orbitSpeedX = speed; - _orbitSpeedY = speed; - widget.controller!.setCameraManipulatorOptions( - mode: _cameraManipulatorMode, - orbitSpeedX: _orbitSpeedX, - orbitSpeedY: _orbitSpeedY, - zoomSpeed: _zoomSpeed); - setState(() {}); - }, - child: Text( - speed.toString(), - style: TextStyle( - fontWeight: (speed - _orbitSpeedX).abs() < 0.0001 - ? FontWeight.bold - : FontWeight.normal), - ), - ); - }).toList(), - child: const Text("Orbit speed (X & Y)")) - ]; - } - - bool _rendering = false; - int _framerate = 60; - - List _renderingMenu() { - return [ - MenuItemButton( - onPressed: () { - widget.controller!.render(); - }, - child: const Text("Render single frame"), - ), - MenuItemButton( - onPressed: () { - _rendering = !_rendering; - widget.controller!.setRendering(_rendering); - }, - child: Text("Set continuous rendering to ${!_rendering}"), - ), - MenuItemButton( - onPressed: () { - _framerate = _framerate == 60 ? 30 : 60; - widget.controller!.setFrameRate(_framerate); - }, - child: Text("Toggle framerate (currently $_framerate) "), - ), - MenuItemButton( - onPressed: () { - widget.controller!.setToneMapping(ToneMapper.LINEAR); - }, - child: const Text("Set tone mapping to linear"), - ), - MenuItemButton( - onPressed: () { - setState(() { - _postProcessing = !_postProcessing; - }); - widget.controller!.setPostProcessing(_postProcessing); - }, - child: Text("${_postProcessing ? "Disable" : "Enable"} postprocessing"), - ), - MenuItemButton( - onPressed: () async { - _directionalLight = await widget.controller! - .addLight(1, 6500, 150000, 0, 1, 0, 0, -1, 0, true); - }, - child: const Text("add directional light"), - ), - MenuItemButton( - onPressed: () async { - await widget.controller!.clearLights(); - }, - child: const Text("clear all lights"), - ), - ]; - } - - @override - Widget build(BuildContext context) { - return ValueListenableBuilder( - valueListenable: - widget.controller?.hasViewer ?? ValueNotifier(false), - builder: (BuildContext ctx, bool hasViewer, Widget? child) { - return MenuAnchor( - childFocusNode: _buttonFocusNode, - menuChildren: [ - SubmenuButton( - menuChildren: _renderingMenu(), - child: const Text("Rendering"), - ), - SubmenuButton( - menuChildren: _assetMenu(), - child: const Text("Assets"), - ), - SubmenuButton( - menuChildren: _cameraMenu(), - child: const Text("Camera"), - ), - ], - builder: (BuildContext context, MenuController controller, - Widget? child) { - return Align( - alignment: Alignment.bottomLeft, - child: TextButton( - onPressed: !hasViewer - ? null - : () { - if (controller.isOpen) { - controller.close(); - } else { - controller.open(); - } - }, - child: const Text("Scene"), - )); - }, - ); - }); - } -}