flutter3d
Showcase Changelog 38 packages API reference

Raycast, sweep and overlap: the source

since 0.5.0 Physics and particles

Regions the guide quotes: world (line 32), raycast (line 51), sweep (line 53), overlap (line 60), check (line 106).

1/// A raycast, a sweep and an overlap against the same small level, reported
2/// as text: three questions a `CollisionWorld` answers exactly rather than
3/// a picture asking you to take its word for it.
4///
5/// Quoted by `collision_queries.md` and shown whole in the Source tab.
6library;
7
8import 'package:flutter/widgets.dart';
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 CollisionQueriesDemo extends ShowcaseDemo {
15 late final CollisionWorld _world;
16 late final Collider _pickup;
18 final RayHit _ray = RayHit();
19 final SweepHit _sweep = SweepHit();
20 final List<Collider> _overlapping = <Collider>[];
22 @override
23 void configureView(DemoContext context) {
24 context.orbit
25 ..distance = 8.0
26 ..pitch = 0.25
27 ..yaw = 0.6;
28 }
30 @override
31 Scene build(DemoContext context) {
32 _world = CollisionWorld();
33 final Collider wall = _world.add(
34 Collider(
35 shape: CollisionBox(Vector3(2.0, 1.0, 0.2)),
36 position: Vector3(0.0, 1.0, 0.0),
37 ),
38 );
39 _pickup = _world.add(
40 Collider(
41 shape: CollisionSphere(0.4),
42 position: Vector3(2.0, 0.5, 2.0),
43 kind: ColliderKind.trigger,
44 ),
45 );
46 // A trigger is not level geometry: `add` files anything that is not
47 // `ColliderKind.static` as a mover, and a mover's grid cell is only
48 // filled in when the world is asked to index it.
49 _world.reindex();
51 _world.raycast(Vector3(0.0, 1.0, 5.0), Vector3(0.0, 0.0, -1.0), 10.0, _ray);
53 _world.sweep(
54 CollisionBox(Vector3(0.3, 0.5, 0.3)),
55 Vector3(0.0, 0.5, 3.0),
56 Vector3(0.0, 0.0, -4.0),
57 _sweep,
58 );
60 _world.overlap(
61 CollisionBox(Vector3(0.1, 0.1, 0.1)),
62 _pickup.position,
63 _overlapping,
64 );
66 return Scene()
67 ..add(
68 MeshNode(
69 DeviceMesh.upload(
70 context.device,
71 CuboidShape(size: Vector3(4.0, 2.0, 0.4)).build(),
72 ),
73 Material(name: 'wall', baseColor: Vector4(0.5, 0.5, 0.6, 1.0)),
74 name: 'wall',
75 )..setPositionFrom(wall.position),
76 )
77 ..add(
78 LightNode(name: 'sun', intensity: 2.5)
79 ..setLocalForward(Vector3(-0.3, -0.6, -0.4)),
80 );
81 }
83 @override
84 Widget? customBody(BuildContext buildContext, DemoContext context) {
85 String vec3(Vector3 v) =>
86 '(${v.x.toStringAsFixed(2)}, ${v.y.toStringAsFixed(2)}, '
87 '${v.z.toStringAsFixed(2)})';
88 return Container(
89 color: const Color(0xFF14161A),
90 padding: const EdgeInsets.all(24),
91 alignment: Alignment.topLeft,
92 child: DefaultTextStyle(
93 style: const TextStyle(color: Color(0xFFE8E8EC), fontSize: 15),
94 child: Text(
95 'raycast: ${_ray.hit ? 'hit at ${vec3(_ray.point)}' : 'miss'}\n'
96 'sweep: ${_sweep.hit ? 'stopped at fraction ${_sweep.fraction.toStringAsFixed(3)}, '
97 'normal ${vec3(_sweep.normal)}' : 'clear'}\n'
98 'overlap: ${_overlapping.length} collider(s) at the pickup',
99 ),
100 ),
101 );
102 }
104 @override
105 void verify(Scene scene, FrameResult frame) {
106 if (!_ray.hit || (_ray.distance - 4.8).abs() > 0.01) {
107 throw StateError('the ray should meet the wall 4.8m out');
108 }
109 if (!_sweep.hit || _sweep.normal.z < 0.9) {
110 throw StateError('the sweep should stop on the wall\'s near face');
111 }
112 if (_overlapping.length != 1 || !identical(_overlapping.first, _pickup)) {
113 throw StateError('the overlap should find only the pickup');
114 }
115 if (frame.drawCalls < 1) {
116 throw StateError('the wall did not reach the frame');
117 }
118 }