Files
cup_edit/examples/flutter/animations/lib/main.dart
2025-05-17 22:19:47 +08:00

167 lines
5.1 KiB
Dart

import 'dart:async';
import 'package:logging/logging.dart';
import 'package:flutter/material.dart' hide View;
import 'package:thermion_flutter/thermion_flutter.dart';
void main() {
runApp(const MyApp());
Logger.root.onRecord.listen((record) {
print(record);
});
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Thermion Animation Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Thermion Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late DelegateInputHandler _inputHandler;
ThermionViewer? _thermionViewer;
ThermionAsset? _asset;
final _droneUri = "assets/BusterDrone/scene.gltf";
final _cubeUri = "assets/cube_with_morph_targets.glb";
String? _loaded;
final gltfAnimations = <String>[];
int selectedGltfAnimation = -1;
Future _load(String? uri) async {
if (_asset != null) {
await _thermionViewer!.destroyAsset(_asset!);
_asset = null;
}
if (uri == null) {
return;
}
_asset = await _thermionViewer!.loadGltf(uri);
await _asset!.transformToUnitCube();
final animations = await _asset!.getGltfAnimationNames();
final durations = await Future.wait(List.generate(
animations.length, (i) => _asset!.getGltfAnimationDuration(i)));
final labels = animations
.asMap()
.map((index, animation) =>
MapEntry(index, "$animation (${durations[index]}s"))
.values;
gltfAnimations.clear();
gltfAnimations.addAll(labels);
selectedGltfAnimation = 0;
_loaded = uri;
setState(() {});
}
Future _playGltfAnimation() async {
if (selectedGltfAnimation == -1) {
throw Exception();
}
await _asset!.playGltfAnimation(selectedGltfAnimation);
}
Future _stopGltfAnimation() async {
if (selectedGltfAnimation == -1) {
throw Exception();
}
await _asset!.stopGltfAnimation(selectedGltfAnimation);
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
_thermionViewer = await ThermionFlutterPlugin.createViewer();
final camera = await _thermionViewer!.getActiveCamera();
await camera.lookAt(Vector3(0, 0, 10));
await _thermionViewer!.loadSkybox("assets/default_env_skybox.ktx");
await _thermionViewer!.loadIbl("assets/default_env_ibl.ktx");
await _thermionViewer!.setPostProcessing(true);
await _thermionViewer!.setRendering(true);
_inputHandler = DelegateInputHandler.fixedOrbit(_thermionViewer!);
await _load(_droneUri);
setState(() {});
});
}
@override
Widget build(BuildContext context) {
if (_thermionViewer == null) {
return Container();
}
return Stack(children: [
Positioned.fill(
child: ThermionListenerWidget(
inputHandler: _inputHandler,
child: ThermionWidget(
viewer: _thermionViewer!,
))),
Card(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
child: Row(children: [
Text("Asset: "),
DropdownButton<String>(
value: _loaded,
items: [_droneUri, _cubeUri]
.map((uri) => DropdownMenuItem<String>(
value: uri,
child: Text(
uri,
style: TextStyle(fontSize: 12),
)))
.toList(),
onChanged: _load),
Text("Animation: "),
DropdownButton<String>(
value: selectedGltfAnimation == -1
? null
: gltfAnimations[selectedGltfAnimation],
items: gltfAnimations
.map((animation) => DropdownMenuItem<String>(
value: animation,
child: Text(
animation,
style: TextStyle(fontSize: 12),
)))
.toList(),
onChanged: (value) {
if (value == null) {
selectedGltfAnimation = -1;
} else {
selectedGltfAnimation = gltfAnimations.indexOf(value);
}
}),
IconButton(
onPressed: _playGltfAnimation,
icon: const Icon(Icons.play_arrow)),
IconButton(
onPressed: _stopGltfAnimation, icon: const Icon(Icons.stop))
])))
]);
}
}