flutter3d
Showcase Changelog 38 packages API reference

Collision shapes: the source

since 0.5.1 Physics and particles

Regions the guide quotes: shapes (line 100), check (line 202), probe (line 204).

1/// The five collision shapes, standing in a row, each with the flutter3d
2/// mesh that draws roughly the same volume beside it.
3///
4/// Quoted by `collision_shapes.md` and shown whole in the Source tab.
5library;
6
7import 'dart:typed_data';
8
9import 'package:flutter3d/flutter3d.dart';
10import 'package:flutter3d_physics/flutter3d_physics.dart';
11import 'package:flutter3d_showcase/src/demo/demo.dart';
12import 'package:vector_math/vector_math.dart';
14final class CollisionShapesDemo extends ShowcaseDemo {
15 late final CollisionWorld _world;
16 late final Collider _sphereCollider;
18 final List<Collider> _hits = <Collider>[];
20 @override
21 void configureView(DemoContext context) {
22 context.orbit
23 ..distance = 10.0
24 ..pitch = 0.2
25 ..yaw = 0.5;
26 }
28 /// A box with one edge cut away, matching `CollisionWedge(halfExtents,
29 /// uphill: WedgeUphill.positiveX)`: full height at `+x`, tapering to an
30 /// edge at `-x`.
31 MeshData _wedgeMesh(Vector3 half) {
32 final MeshBuilder builder = MeshBuilder(VertexLayout.standard);
33 Vector3 v(double x, double y, double z) => Vector3(x, y, z);
34 final int a = builder.addVertex(position: v(-half.x, -half.y, -half.z));
35 final int b = builder.addVertex(position: v(half.x, -half.y, -half.z));
36 final int c = builder.addVertex(position: v(half.x, half.y, -half.z));
37 final int d = builder.addVertex(position: v(-half.x, -half.y, half.z));
38 final int e = builder.addVertex(position: v(half.x, -half.y, half.z));
39 final int g = builder.addVertex(position: v(half.x, half.y, half.z));
40 builder
41 ..addTriangle(a, c, b) // back
42 ..addTriangle(d, e, g) // front
43 ..addQuad(a, b, e, d) // floor
44 ..addQuad(b, c, g, e) // top wall
45 ..addQuad(a, d, g, c); // slope
46 return builder.build();
47 }
49 /// A small bumpy grid, matching the heights fed to a `CollisionHeightfield`
50 /// of the same `columns`, `rows` and `cellSize`.
51 MeshData _terrainMesh(
52 int columns,
53 int rows,
54 double cellSize,
55 Float32List heights,
56 ) {
57 final MeshBuilder builder = MeshBuilder(VertexLayout.standard);
58 final double width = (columns - 1) * cellSize;
59 final double depth = (rows - 1) * cellSize;
60 final List<int> indices = <int>[];
61 for (var row = 0; row < rows; row++) {
62 for (var column = 0; column < columns; column++) {
63 indices.add(
64 builder.addVertex(
65 position: Vector3(
66 column * cellSize - width * 0.5,
67 heights[row * columns + column],
68 row * cellSize - depth * 0.5,
69 ),
70 normal: Vector3(0.0, 1.0, 0.0),
71 ),
72 );
73 }
74 }
75 for (var row = 0; row + 1 < rows; row++) {
76 for (var column = 0; column + 1 < columns; column++) {
77 final int tl = indices[row * columns + column];
78 final int tr = indices[row * columns + column + 1];
79 final int bl = indices[(row + 1) * columns + column];
80 final int br = indices[(row + 1) * columns + column + 1];
81 builder.addQuad(tl, bl, br, tr);
82 }
83 }
84 return builder.build();
85 }
87 MeshNode _node(
88 DemoContext context,
89 MeshData mesh,
90 Material material,
91 Vector3 position,
92 ) => MeshNode(
93 DeviceMesh.upload(context.device, mesh),
94 material,
95 name: material.name,
96 )..setPositionFrom(position);
98 @override
99 Scene build(DemoContext context) {
100 _world = CollisionWorld();
101 final Collider box = _world.add(
102 Collider(
103 shape: CollisionBox(Vector3(0.5, 0.5, 0.5)),
104 position: Vector3(-6, 0, 0),
105 ),
106 );
107 _sphereCollider = _world.add(
108 Collider(shape: CollisionSphere(0.5), position: Vector3(-3, 0, 0)),
109 );
110 final Collider capsule = _world.add(
111 Collider(
112 shape: CollisionCapsule(radius: 0.35, halfHeight: 0.4),
113 position: Vector3(0, 0, 0),
114 ),
115 );
116 final Collider wedge = _world.add(
117 Collider(
118 shape: CollisionWedge(Vector3(0.6, 0.5, 0.5)),
119 position: Vector3(3, 0, 0),
120 ),
121 );
122 final Float32List heights = Float32List.fromList(<double>[
123 0.0,
124 0.1,
125 0.0,
126 -0.1,
127 0.1,
128 0.3,
129 0.2,
130 0.0,
131 0.0,
132 0.2,
133 0.4,
134 0.1,
135 -0.1,
136 0.0,
137 0.1,
138 0.0,
139 ]);
140 final Collider field = _world.add(
141 Collider(
142 shape: CollisionHeightfield(
143 columns: 4,
144 rows: 4,
145 cellSize: 0.5,
146 heights: heights,
147 ),
148 position: Vector3(6, 0, 0),
149 ),
150 );
152 final Scene scene = Scene()
153 ..add(
154 _node(
155 context,
156 CuboidShape(size: Vector3(1, 1, 1)).build(),
157 Material(name: 'box', baseColor: Vector4(0.8, 0.4, 0.3, 1.0)),
158 box.position,
159 ),
160 )
161 ..add(
162 _node(
163 context,
164 SphereShape(segments: 24, rings: 12).build(),
165 Material(name: 'sphere', baseColor: Vector4(0.3, 0.6, 0.8, 1.0)),
166 _sphereCollider.position,
167 ),
168 )
169 ..add(
170 _node(
171 context,
172 const CapsuleShape(radius: 0.35, height: 0.8).build(),
173 Material(name: 'capsule', baseColor: Vector4(0.5, 0.8, 0.4, 1.0)),
174 capsule.position,
175 ),
176 )
177 ..add(
178 _node(
179 context,
180 _wedgeMesh(Vector3(0.6, 0.5, 0.5)),
181 Material(name: 'wedge', baseColor: Vector4(0.8, 0.7, 0.3, 1.0)),
182 wedge.position,
183 ),
184 )
185 ..add(
186 _node(
187 context,
188 _terrainMesh(4, 4, 0.5, heights),
189 Material(name: 'field', baseColor: Vector4(0.6, 0.55, 0.4, 1.0)),
190 field.position,
191 ),
192 )
193 ..add(
194 LightNode(name: 'sun', intensity: 2.5)
195 ..setLocalForward(Vector3(-0.3, -0.6, -0.4)),
196 );
197 return scene;
198 }
200 @override
201 void verify(Scene scene, FrameResult frame) {
202 // A probe placed exactly at the sphere's own centre should find the
203 // sphere, and nothing else: the shapes are spaced apart on purpose.
204 _world.overlap(
205 CollisionBox(Vector3(0.1, 0.1, 0.1)),
206 _sphereCollider.position,
207 _hits,
208 );
209 if (_hits.length != 1 || !identical(_hits.first, _sphereCollider)) {
210 throw StateError('the probe should have found only the sphere');
211 }
212 if (frame.drawCalls < 5) {
213 throw StateError('all five shapes should have drawn');
214 }
215 }