A chain of any length: the source
Open the live demo · Read the guide · View on GitHub
Regions the guide quotes: chain (line 35), solve (line 101).
1/// A four-joint chain reaching for a point: `FabrikIk` solves a chain of any
2/// length, the same way `TwoBoneIk` solves an arm, on a `Pose` with no scene
3/// behind it.
4///
5/// Quoted by `fabrik_ik.md` and shown whole in the Source tab.
6library;
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 FabrikIkDemo extends ShowcaseDemo {
15 double reach = 0.6;
17 late final Pose _pose;
18 late final List<SceneNode> _joints;
19 late final MeshNode _targetMarker;
20 double _lastError = 0.0;
22 static const int _jointCount = 4;
23 static const double _boneLength = 0.7;
26 void configureView(DemoContext context) {
27 context.orbit
28 ..distance = 5.5
29 ..pitch = 0.2
30 ..yaw = 0.3;
31 }
34 Scene build(DemoContext context) {
35 final List<int> parents = <int>[
36 -1,
37 for (var i = 1; i < _jointCount; i++) i - 1,
38 ];
39 final Float32List translations = Float32List(_jointCount * 3);
40 final Float32List rotations = Float32List(_jointCount * 4);
41 final Float32List scales = Float32List(_jointCount * 3);
42 for (var i = 0; i < _jointCount; i++) {
43 translations[i * 3 + 1] = i == 0 ? 0.0 : -_boneLength;
44 rotations[i * 4 + 3] = 1.0;
45 scales[i * 3] = 1.0;
46 scales[i * 3 + 1] = 1.0;
47 scales[i * 3 + 2] = 1.0;
48 }
49 _pose = Pose(
50 parents: parents,
51 restTranslations: translations,
52 restRotations: rotations,
53 restScales: scales,
54 );
56 final DeviceMesh bead = DeviceMesh.upload(
57 context.device,
58 SphereShape(radius: 0.1, segments: 14, rings: 7).build(),
59 );
60 _joints = <SceneNode>[];
61 SceneNode? previous;
62 for (var i = 0; i < _jointCount; i++) {
63 final MeshNode node = MeshNode(
64 bead,
65 Material(
66 name: 'bead $i',
67 baseColor: i == _jointCount - 1
68 ? Vector4(0.9, 0.5, 0.2, 1.0)
69 : Vector4(0.5, 0.65, 0.9, 1.0),
70 ),
71 name: 'bead $i',
72 );
73 if (previous == null) {
74 _joints.add(node);
75 } else {
76 previous.add(node);
77 _joints.add(node);
78 }
79 previous = node;
80 }
81 _targetMarker = MeshNode(
82 DeviceMesh.upload(
83 context.device,
84 SphereShape(radius: 0.08, segments: 12, rings: 6).build(),
85 ),
86 Material(name: 'target', baseColor: Vector4(0.9, 0.2, 0.2, 1.0)),
87 name: 'target',
88 );
90 return Scene()
91 ..add(_joints.first)
92 ..add(_targetMarker)
93 ..add(
94 LightNode(name: 'sun', intensity: 3.0)
95 ..setLocalForward(Vector3(-0.4, -0.7, -0.5)),
96 );
97 }
100 void update(DemoContext context, double dt) {
101 final double maxReach = _boneLength * (_jointCount - 1);
102 final Vector3 direction = Vector3(0.5, -0.8, 0.4)..normalize();
103 final Vector3 target = direction * (reach * maxReach * 0.85);
105 _pose.resetToRest();
106 FabrikIk.solve(
107 _pose,
108 joints: <int>[for (var i = 0; i < _jointCount; i++) i],
109 target: target,
110 );
111 _pose.writeTo(_joints);
112 _targetMarker.setPositionFrom(target);
114 final Vector3 tip = _pose.worldMatrices()[_jointCount - 1].getTranslation();
115 _lastError = (tip - target).length;
116 }
119 List<DemoControl> controls(DemoContext context) => <DemoControl>[
120 SliderControl(
121 'Reach',
122 min: 0,
123 max: 1,
124 value: () => reach,
125 onChanged: (double v) => reach = v,
126 ),
127 ];
130 void verify(Scene scene, FrameResult frame) {
131 if (_lastError > 1e-2) {
132 throw StateError(
133 'the last bead did not reach the target within tolerance',
134 );
135 }
136 }
137}