o3d 3.1.3 o3d: ^3.1.3 copied to clipboard
The Flutter 3D objects easy controller ( glb format ) on mobile and web platforms
import 'dart:developer';
import 'package:example/logger.dart';
import 'package:flutter/material.dart';
import 'package:o3d/o3d.dart';
import 'package:url_launcher/url_launcher.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'O3D Viewer',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const MyHomePage(title: 'O3D Viewer'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// to control the animation
final O3DController controller = O3DController(),
controller2 = O3DController(),
controller3 = O3DController(),
controller4 = O3DController();
List<String> logs = [];
bool cameraControls = false;
List<String>? availableVariants;
List<String>? availableAnimations;
@override
void initState() {
super.initState();
controller.logger = (data) {
logs.add(data.toString());
};
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
centerTitle: true,
leading: IconButton.filledTonal(
onPressed: () {
launchUrl(Uri.parse('https://pub.dev/packages/o3d'));
},
icon: const Icon(Icons.source)),
actions: [
IconButton(
onPressed: () => {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LoggerPage(logs),
))
},
icon: const Icon(Icons.login_rounded)),
],
),
body: SingleChildScrollView(
child: Column(
children: [
/// model 1
ModelDetail(
actions: [
FilledButton(
onPressed: () => controller.variantName = null,
child: const Text('Default'),
),
FilledButton(
onPressed: () => controller.variantName = 'beach',
child: const Text('beach'),
),
FilledButton(
onPressed: () => controller.variantName = 'street',
child: const Text('street'),
),
FilledButton(
onPressed: () {
controller.availableVariants().then((value) {
setState(() {
availableVariants = value;
});
});
},
child: const Text('available variants'),
),
if (availableVariants != null)
ListView.builder(
shrinkWrap: true,
itemCount: availableVariants!.length,
itemBuilder: (context, index) {
String variant = availableVariants![index];
return Text('variant $index is $variant');
},
)
],
o3d: O3D(
controller: controller,
progressBarColor: Colors.red,
src: 'assets/glb/materials_variants_shoe.glb',
// variantName: 'street',
),
),
const Divider(),
/// model 2
ModelDetail(
actions: [
FilledButton(
onPressed: () => controller2.animationName = 'Survey',
child: const Text('Survey'),
),
FilledButton(
onPressed: () => controller2.animationName = 'Walk',
child: const Text('Walk'),
),
FilledButton(
onPressed: () => controller2.animationName = 'Run',
child: const Text('Run'),
),
FilledButton(
onPressed: () => controller2.play(repetitions: 1),
child: const Text('Play Animation'),
),
FilledButton(
onPressed: () async {
controller2.availableAnimations().then((value) {
setState(() {
availableAnimations = value;
log("Available animations: $value");
});
});
},
child: const Text('available animations'),
),
if (availableAnimations != null)
ListView.builder(
shrinkWrap: true,
itemCount: availableAnimations!.length,
itemBuilder: (context, index) {
String animation = availableAnimations![index];
return Text('animation $index is $animation');
},
)
],
o3d: O3D(
controller: controller2,
src: 'assets/glb/fox.glb',
),
),
const Divider(),
// model 3
ModelDetail(
actions: [
FilledButton(
onPressed: () => controller3.animationName = 'Running',
child: const Text('Running'),
),
FilledButton(
onPressed: () => controller3.animationName = 'Dance',
child: const Text('Dance'),
),
FilledButton(
onPressed: () => controller3.animationName = 'Wave',
child: const Text('Wave'),
),
FilledButton(
onPressed: () => controller3.animationName = 'Idle',
child: const Text('Idle'),
),
FilledButton(
onPressed: () {
controller2
.availableAnimations()
.then((value) => log("Available animations: $value"));
},
child: const Text('available animations'),
),
],
o3d: O3D(
controller: controller3,
src:
'https://modelviewer.dev/shared-assets/models/RobotExpressive.glb',
autoPlay: true,
),
),
const Divider(),
// model 4
ModelDetail(
actions: [
const Text("animations:"),
FilledButton.icon(
onPressed: () => controller4.animationName = 'FallingBack',
icon: const Icon(Icons.add),
label: const Text("shoot him")),
FilledButton.icon(
onPressed: () =>
controller4.animationName = 'FallingForward',
icon: const Icon(Icons.add),
label: const Text("shoot leg")),
FilledButton(
onPressed: () => controller4.animationName = 'Run',
child: const Text('Run')),
FilledButton(
onPressed: () => controller4.animationName = 'Idle',
child: const Text('Idle')),
FilledButton(
onPressed: () => controller4.cameraTarget(0, 1, 2.5),
child: const Text('ZoomOut')),
FilledButton(
onPressed: () => controller4.cameraTarget(0, 1, 0),
child: const Text('ZoomIn'))
],
o3d: O3D(
controller: controller4,
src: 'assets/glb/zombie.glb',
autoPlay: true,
cameraControls: false,
cameraTarget: CameraTarget(0, 1, 0)),
),
],
),
),
);
}
}
class ModelDetail extends StatelessWidget {
final List<Widget> actions;
final Widget o3d;
const ModelDetail({
super.key,
required this.actions,
required this.o3d,
});
@override
Widget build(BuildContext context) {
return Card(
color: Colors.blue.shade100.withOpacity(.3),
elevation: 0,
margin: const EdgeInsets.all(16),
clipBehavior: Clip.antiAliasWithSaveLayer,
child: Container(
padding: const EdgeInsets.all(4),
width: double.infinity,
height: 400,
child: Column(
children: [
Wrap(
children: actions
.map((child) => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 2),
child: child,
))
.toList(),
),
Expanded(
child: Card(
color: Colors.blue.shade100.withOpacity(.3),
elevation: 0,
child: AspectRatio(aspectRatio: 1, child: o3d),
))
],
),
),
);
}
}