Morph targets: the source
Open the live demo · Read the guide · View on GitHub
Regions the guide quotes: target (line 28), texture (line 45), morph (line 64), live (line 89).
1/// A grid that grows a bump: the deltas live in a `MorphTexture`, and
2/// `MorphState` says how much of it to add right now.
3///
4/// Quoted by `morph_targets.md` and shown whole in the Source tab.
5library;
7import 'dart:math' as math;
8import 'dart:typed_data';
10import 'package:flutter3d/flutter3d.dart';
11import 'package:flutter3d_showcase/src/demo/demo.dart';
12import 'package:vector_math/vector_math.dart';
14final class MorphTargetsDemo extends ShowcaseDemo {
15 double weight = 0.7;
17 MeshNode? _sheet;
20 void configureView(DemoContext context) {
21 context.orbit
22 ..distance = 4.0
23 ..pitch = 0.5;
24 }
27 Scene build(DemoContext context) {
28 const int segments = 16;
29 final MeshData flat = const PlaneShape(
30 width: 2.0,
31 depth: 2.0,
32 widthSegments: segments,
33 depthSegments: segments,
34 ).build();
35 final Float32List bump = Float32List(flat.vertexCount * 3);
36 for (var v = 0; v < flat.vertexCount; v++) {
37 final Vector3 p = flat.positionAt(v);
38 final double falloff = _gaussian(p.x, p.z);
39 bump[v * 3 + 1] = falloff * 0.9;
40 }
41 final MeshData withBump = flat.withMorphTargets(<MorphTarget>[
42 MorphTarget(vertexCount: flat.vertexCount, positions: bump, name: 'bump'),
43 ]);
45 final MorphTexture packed = MorphTexture.pack(withBump)!;
46 final TextureHandle? deltaTexture = context.device.createTextureFromPixels(
47 width: packed.width,
48 height: packed.height,
49 format: TextureFormat.r32g32b32a32Float,
50 pixels: packed.bytes,
51 );
53 final Material sheetMaterial = Material(
54 name: 'sheet',
55 baseColor: Vector4(0.3, 0.6, 0.85, 1.0),
56 doubleSided: true,
57 );
58 final MeshNode sheet = MeshNode(
59 DeviceMesh.upload(context.device, withBump),
60 sheetMaterial,
61 name: 'sheet',
62 );
63 if (deltaTexture != null) {
64 sheet.morph = MorphState(
65 texture: deltaTexture,
66 targetCount: packed.targetCount,
67 reaches: packed.reaches,
68 )..setWeights(<double>[weight]);
69 }
70 _sheet = sheet;
72 return Scene()
73 ..add(sheet)
74 ..add(
75 LightNode(name: 'sun', intensity: 3.0)
76 ..setLocalForward(Vector3(-0.4, -0.8, -0.3)),
77 );
78 }
80 static double _gaussian(double x, double z) {
81 final double r2 = x * x + z * z;
82 return _exp(-r2 / 0.35);
83 }
85 static double _exp(double x) => x < -30 ? 0.0 : math.exp(x);
88 void update(DemoContext context, double dt) {
89 _sheet?.morph?.setWeights(<double>[weight]);
90 }
93 List<DemoControl> controls(DemoContext context) => <DemoControl>[
94 SliderControl(
95 'Weight',
96 min: 0,
97 max: 1,
98 value: () => weight,
99 onChanged: (double v) => weight = v,
100 ),
101 ];
104 void verify(Scene scene, FrameResult frame) {
105 final MorphState? morph = _sheet?.morph;
106 if (morph == null) {
107 throw StateError('the device would not take the morph delta texture');
108 }
109 if (morph.growth.max.y <= 0.0) {
110 throw StateError('the weighted target did not grow the mesh upward');
111 }
112 }
113}