flutter3d
Showcase Changelog 38 packages API reference

A face per copy: the source

since 0.5.2 Animation

Regions the guide quotes: batch (line 54), live (line 88).

1/// One mesh, one draw call, six different faces: `InstancedMeshNode
2/// .setMorphWeights` gives each copy its own weights, read from a texture by
3/// instance id instead of from a uniform every copy would share.
4///
5/// Quoted by `instanced_morphs.md` and shown whole in the Source tab.
6library;
7
8import 'dart:typed_data';
9
10import 'package:flutter3d/flutter3d.dart';
11import 'package:flutter3d_showcase/src/demo/demo.dart';
12import 'package:vector_math/vector_math.dart';
14final class InstancedMorphsDemo extends ShowcaseDemo {
15 double spread = 1.0;
17 static const int _count = 6;
19 InstancedMeshNode? _faces;
21 @override
22 void configureView(DemoContext context) {
23 context.orbit
24 ..distance = 7.0
25 ..pitch = 0.35;
26 }
28 @override
29 Scene build(DemoContext context) {
30 const int segments = 10;
31 final MeshData flat = const PlaneShape(
32 width: 0.9,
33 depth: 0.9,
34 widthSegments: segments,
35 depthSegments: segments,
36 ).build();
37 final Float32List bump = Float32List(flat.vertexCount * 3);
38 for (var v = 0; v < flat.vertexCount; v++) {
39 final Vector3 p = flat.positionAt(v);
40 final double r2 = p.x * p.x + p.y * p.y;
41 bump[v * 3 + 2] = r2 < 0.16 ? (0.16 - r2) * 2.0 : 0.0;
42 }
43 final MeshData withBump = flat.withMorphTargets(<MorphTarget>[
44 MorphTarget(vertexCount: flat.vertexCount, positions: bump, name: 'bump'),
45 ]);
46 final MorphTexture packed = MorphTexture.pack(withBump)!;
47 final TextureHandle? deltaTexture = context.device.createTextureFromPixels(
48 width: packed.width,
49 height: packed.height,
50 format: TextureFormat.r32g32b32a32Float,
51 pixels: packed.bytes,
52 );
54 final InstancedMeshNode faces = InstancedMeshNode(
55 DeviceMesh.upload(context.device, withBump),
56 Material(
57 name: 'faces',
58 baseColor: Vector4(0.8, 0.8, 0.85, 1.0),
59 doubleSided: true,
60 ),
61 capacity: _count,
62 name: 'faces',
63 );
64 for (var i = 0; i < _count; i++) {
65 faces.addInstance(
66 Matrix4.translation(Vector3((i - (_count - 1) / 2) * 1.1, 0.0, 0.0)),
67 );
68 }
69 if (deltaTexture != null) {
70 faces.morph = MorphState(
71 texture: deltaTexture,
72 targetCount: packed.targetCount,
73 reaches: packed.reaches,
74 );
75 }
76 _faces = faces;
78 return Scene()
79 ..add(faces)
80 ..add(
81 LightNode(name: 'sun', intensity: 3.0)
82 ..setLocalForward(Vector3(-0.3, -0.6, -0.7)),
83 );
84 }
86 @override
87 void update(DemoContext context, double dt) {
88 final InstancedMeshNode? faces = _faces;
89 if (faces == null) return;
90 for (var i = 0; i < _count; i++) {
91 final double weight = (i / (_count - 1)) * spread;
92 faces.setMorphWeights(i, <double>[weight]);
93 }
94 }
96 @override
97 List<DemoControl> controls(DemoContext context) => <DemoControl>[
98 SliderControl(
99 'Spread',
100 min: 0,
101 max: 1,
102 value: () => spread,
103 onChanged: (double v) => spread = v,
104 ),
105 ];
107 @override
108 void verify(Scene scene, FrameResult frame) {
109 final InstancedMeshNode? faces = _faces;
110 if (faces == null || !faces.hasInstanceMorphWeights) {
111 throw StateError('no instance carries its own morph weights');
112 }
113 if (frame.instances < _count) {
114 throw StateError('the batch did not draw all $_count instances');
115 }
116 final double first = faces.morphWeightsOf(0)[0];
117 final double last = faces.morphWeightsOf(_count - 1)[0];
118 if ((last - first).abs() < 1e-3) {
119 throw StateError('every instance ended up with the same weight');
120 }
121 }