update ViewerWidget

This commit is contained in:
Nick Fisher
2025-03-28 11:47:46 +08:00
parent 9978d9d75c
commit 973804ed14

View File

@@ -1,77 +1,97 @@
import 'package:flutter/material.dart';
import 'package:thermion_flutter/thermion_flutter.dart' hide Texture;
enum ViewerManipulatorType { ORBIT, FIRST_PERSON }
enum ManipulatorType { NONE, ORBIT, FREE_FLIGHT }
class ViewerOptions {
///
class ViewerWidget extends StatefulWidget {
///
/// The widget to display before the viewport has loaded.
///
final Widget initial;
///
/// When true, an FPS counter will be displayed at the top right of the widget
/// The initial position for the camera (looking towards (0,0,0)).
///
late final Vector3 initialCameraPosition;
///
/// When true, an FPS counter will be overlaid above the viewer widget.
///
final bool showFpsCounter;
///
///
/// The path to the (glTF) asset to be loaded into the scene.
///
final String? assetPath;
///
///
/// The path to the (KTX) skybox to be loaded into the scene.
///
final String? skyboxPath;
///
///
/// The path to the (KTX) image-based light to be loaded into the scene.
///
final String? iblPath;
///
///
/// A direct light to add to the scene.
///
final LightType? directLightType;
///
///
/// If true, the glTF asset will be rescaled so its bounding box fits within a 1x1x1 cube. Defaults to true.
///
final bool transformToUnitCube;
///
///
/// If true, enables postprocessing (ACES tone mapping and basic anti-aliaising). Defaults to true.
///
final bool postProcessing;
///
///
/// The fill color to use for the background. If a skybox is provided, the fill color won't be visible.
///
final Color? background;
///
/// Disposing this widget will unload all scene resources (i.e. the asset, skybox, etc). but will leave the underlying engine intact.
/// If [destroyEngineOnUnload] is true, disposing the widget will also destroy the engine and rendering thread.
/// Defaults to false.
///
///
final bool destroyAppOnUnload;
final bool destroyEngineOnUnload;
const ViewerOptions(
{this.initial =
///
/// The type of camera manipulator to use to respond to viewport gestures. Defaults to ORBIT (pinch to zoom in/out, swipe to rotate around the asset at a fixed distance).
///
final ManipulatorType manipulatorType;
///
/// A callback that can be used to access the viewer.
///
final Future Function(ThermionViewer)? onViewerAvailable;
///
///
///
ViewerWidget(
{super.key,
this.initial =
const DecoratedBox(decoration: BoxDecoration(color: Colors.red)),
Vector3? initialCameraPosition,
this.showFpsCounter = false,
this.transformToUnitCube = true,
this.postProcessing = true,
this.destroyAppOnUnload = false,
this.destroyEngineOnUnload = false,
this.assetPath,
this.skyboxPath,
this.iblPath,
this.directLightType,
this.background});
}
class ViewerWidget extends StatefulWidget {
final ViewerOptions options;
const ViewerWidget({super.key, this.options = const ViewerOptions()});
this.background,
this.onViewerAvailable,
this.manipulatorType = ManipulatorType.ORBIT}) {
this.initialCameraPosition = initialCameraPosition ?? Vector3(0, 0, 5);
}
@override
State<StatefulWidget> createState() {
@@ -86,14 +106,98 @@ class _ViewerWidgetState extends State<ViewerWidget> {
void initState() {
super.initState();
ThermionFlutterPlugin.createViewer().then((viewer) async {
viewer = viewer;
this.viewer = viewer;
await _configure();
setState(() {});
});
}
void didUpdateWidget(ViewerWidget oldWidget) {
if (oldWidget.manipulatorType != widget.manipulatorType) {
_setViewportWidget();
setState(() {});
}
if (oldWidget.initialCameraPosition != widget.initialCameraPosition ||
oldWidget.showFpsCounter != widget.showFpsCounter ||
oldWidget.assetPath != widget.assetPath ||
oldWidget.skyboxPath != widget.skyboxPath ||
oldWidget.iblPath != widget.iblPath ||
oldWidget.directLightType != widget.directLightType ||
oldWidget.transformToUnitCube != widget.transformToUnitCube ||
oldWidget.postProcessing != widget.postProcessing ||
oldWidget.background != widget.background ||
oldWidget.destroyEngineOnUnload != widget.destroyEngineOnUnload) {
throw UnsupportedError(
"Only manipulatorType can be changed at runtime. To change any other properties, create a new widget.");
}
}
void _setViewportWidget() {
switch (widget.manipulatorType) {
case ManipulatorType.NONE:
viewport = thermionWidget;
case ManipulatorType.ORBIT:
viewport = ThermionListenerWidget(
key: ObjectKey(ManipulatorType.ORBIT),
inputHandler: DelegateInputHandler.fixedOrbit(viewer!,
minimumDistance: widget.initialCameraPosition.length),
child: thermionWidget);
case ManipulatorType.FREE_FLIGHT:
viewport = ThermionListenerWidget(
key: ObjectKey(ManipulatorType.FREE_FLIGHT),
inputHandler: DelegateInputHandler.flight(viewer!),
child: thermionWidget);
}
}
ThermionAsset? asset;
late final ThermionWidget? thermionWidget;
Widget? viewport;
Future _configure() async {
if (widget.assetPath != null) {
asset = await viewer!.loadGltf(widget.assetPath!);
}
if (widget.skyboxPath != null) {
await viewer!.loadSkybox(widget.skyboxPath!);
}
if (widget.iblPath != null) {
await viewer!.loadIbl(widget.iblPath!);
}
if (widget.postProcessing) {
await viewer!.setPostProcessing(true);
await viewer!.setAntiAliasing(false, true, false);
}
final camera = await viewer!.getActiveCamera();
await camera.lookAt(widget.initialCameraPosition);
if (widget.background != null) {
await viewer!.setBackgroundColor(widget.background!.r,
widget.background!.g, widget.background!.b, widget.background!.a);
}
await viewer!.setRendering(true);
thermionWidget = ThermionWidget(
key: ObjectKey(DateTime.now()),
viewer: viewer!,
showFpsCounter: widget.showFpsCounter,
);
_setViewportWidget();
widget.onViewerAvailable?.call(viewer!);
}
@override
void dispose() {
super.dispose();
if (viewer != null) {
_tearDown();
}
@@ -101,16 +205,13 @@ class _ViewerWidgetState extends State<ViewerWidget> {
Future _tearDown() async {
await viewer!.dispose();
if (widget.options.destroyAppOnUnload) {
if (widget.destroyEngineOnUnload) {
await FilamentApp.instance!.destroy();
}
}
@override
Widget build(BuildContext context) {
if (viewer == null) {
return widget.options.initial!;
}
return ThermionWidget(viewer: viewer!);
return viewport != null ? SizedBox.expand(child: viewport) : widget.initial;
}
}