update integration test

This commit is contained in:
Nick Fisher
2023-11-08 17:46:23 +08:00
parent e15722b15b
commit 7b0dcf2c1c
8 changed files with 701 additions and 717 deletions

View File

@@ -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);
});
}

View File

@@ -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<MyApp> 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<StatefulWidget> createState() {
return _ExampleWidgetState();
return ExampleWidgetState();
}
}
enum MenuType { controller, assets, camera, misc }
class _ExampleWidgetState extends State<ExampleWidget> {
class ExampleWidgetState extends State<ExampleWidget> {
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<String>? animations;
static FilamentEntity? directionalLight;
static bool loop = false;
late StreamSubscription _listener;
@override
void initState() {
@@ -87,6 +99,12 @@ class _ExampleWidgetState extends State<ExampleWidget> {
}
}
@override
void dispose() {
super.dispose();
_listener.cancel();
}
@override
Widget build(BuildContext context) {
return Stack(children: [
@@ -102,18 +120,38 @@ class _ExampleWidgetState extends State<ExampleWidget> {
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<ExampleWidget> {
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<double>.generate(120, (i) => i / 120).expand((x) {
// var vals = List<double>.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;

View File

@@ -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<StatefulWidget> createState() => _AssetSubmenuState();
}
class _AssetSubmenuState extends State<AssetSubmenu> {
@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<double>.generate(120, (i) => i / 120).expand((x) {
// var vals = List<double>.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"),

View File

@@ -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<StatefulWidget> createState() => _CameraSubmenuState();
}
class _CameraSubmenuState extends State<CameraSubmenu> {
List<Widget> _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"),
);
}
}

View File

@@ -54,7 +54,7 @@ class _ControllerMenuState extends State<ControllerMenu> {
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<ControllerMenu> {
},
),
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<ControllerMenu> {
} else {
items.addAll([
MenuItemButton(
child: const Text("destroy viewer"),
child: const Text("Destroy viewer"),
onPressed: () {
_filamentController!.destroy();
_filamentController = null;

View File

@@ -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<StatefulWidget> createState() => _RenderingSubmenuState();
}
class _RenderingSubmenuState extends State<RenderingSubmenu> {
@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"),
);
}
}

View File

@@ -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<StatefulWidget> createState() {
return _SceneMenuState();
}
}
class _SceneMenuState extends State<SceneMenu> {
@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<bool>(false),
builder: (BuildContext ctx, bool hasViewer, Widget? child) {
return MenuAnchor(
menuChildren: widget.controller == null
? []
: <Widget>[
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"),
));
},
);
});
}
}

View File

@@ -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<StatefulWidget> createState() {
return _SceneMenuState();
}
}
class _SceneMenuState extends State<SceneMenu> {
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<String>? _animations;
bool _loop = false;
bool _hasSkybox = false;
List<MenuItemButton> _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<Widget> _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<MenuItemButton> _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<bool>(false),
builder: (BuildContext ctx, bool hasViewer, Widget? child) {
return MenuAnchor(
childFocusNode: _buttonFocusNode,
menuChildren: <Widget>[
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"),
));
},
);
});
}
}