Files
flame/examples/lib/stories/collision_detection/raytrace_example.dart
Lukas Klingsbo ed452dd172 feat!: Raycasting and raytracing (#1785)
This PR implements raytracing and raycasting for the built-in hitboxes.

If you pass in your own collision detection system to the HasCollisionDetection mixin you have to change the signature of that to: CollisionDetection<ShapeHitbox>instead of CollisionDetection<Hitbox>.
2022-08-19 22:44:18 +02:00

192 lines
5.0 KiB
Dart

import 'dart:math';
import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:flame/game.dart';
import 'package:flame/geometry.dart';
import 'package:flame/palette.dart';
import 'package:flutter/material.dart';
class RaytraceExample extends FlameGame
with
HasCollisionDetection,
TapDetector,
MouseMovementDetector,
TapDetector {
static const description = '''
In this example the raytrace functionality is showcased.
Click to start sending out a ray which will bounce around to visualize how it
works. If you move the mouse around the canvas, rays and their reflections will
be moved rendered and if you click again some more objects that the rays can
bounce on will appear.
''';
final _colorTween = ColorTween(
begin: Colors.amber.withOpacity(1.0),
end: Colors.lightBlueAccent.withOpacity(1.0),
);
final random = Random();
Ray2? ray;
Ray2? reflection;
Vector2? origin;
bool isOriginCasted = false;
Paint rayPaint = Paint();
final boxPaint = BasicPalette.gray.paint()
..style = PaintingStyle.stroke
..strokeWidth = 2.0;
final List<Ray2> rays = [];
final List<RaycastResult<ShapeHitbox>> results = [];
late Path path;
@override
Future<void> onLoad() async {
addAll([
ScreenHitbox(),
CircleComponent(
radius: min(camera.canvasSize.x, camera.canvasSize.y) / 2,
paint: boxPaint,
children: [CircleHitbox()],
),
]);
}
bool isClicked = false;
final extraChildren = <Component>[];
@override
void onTap() {
if (!isClicked) {
isClicked = true;
return;
}
_timePassed = 0;
if (extraChildren.isEmpty) {
addAll(
extraChildren
..addAll(
[
CircleComponent(
position: Vector2(100, 100),
radius: 50,
paint: boxPaint,
children: [CircleHitbox()],
),
CircleComponent(
position: Vector2(150, 500),
radius: 50,
paint: boxPaint,
anchor: Anchor.center,
children: [CircleHitbox()],
),
CircleComponent(
position: Vector2(150, 500),
radius: 150,
paint: boxPaint,
anchor: Anchor.center,
children: [CircleHitbox()],
),
RectangleComponent(
position: Vector2.all(300),
size: Vector2.all(100),
paint: boxPaint,
children: [RectangleHitbox()],
),
RectangleComponent(
position: Vector2.all(500),
size: Vector2(100, 200),
paint: boxPaint,
children: [RectangleHitbox()],
),
CircleComponent(
position: Vector2(650, 275),
radius: 50,
paint: boxPaint,
anchor: Anchor.center,
children: [CircleHitbox()],
),
RectangleComponent(
position: Vector2(550, 200),
size: Vector2(200, 150),
paint: boxPaint,
children: [RectangleHitbox()],
),
RectangleComponent(
position: Vector2(350, 30),
size: Vector2(200, 150),
paint: boxPaint,
angle: tau / 10,
children: [RectangleHitbox()],
),
],
),
);
} else {
removeAll(extraChildren);
extraChildren.clear();
}
}
@override
void onMouseMove(PointerHoverInfo info) {
final origin = info.eventPosition.game;
isOriginCasted = origin == this.origin;
this.origin = origin;
}
final Ray2 _ray = Ray2.zero();
var _timePassed = 0.0;
@override
void update(double dt) {
super.update(dt);
if (isClicked) {
_timePassed += dt;
}
rayPaint.color = _colorTween.transform(0.5 + (sin(_timePassed) / 2))!;
if (origin != null) {
_ray.origin.setFrom(origin!);
_ray.direction
..setValues(1, 1)
..normalize();
collisionDetection
.raytrace(
_ray,
maxDepth: min((_timePassed * 8).ceil(), 1000),
out: results,
)
.toList();
isOriginCasted = true;
}
}
@override
void render(Canvas canvas) {
super.render(canvas);
if (origin != null) {
renderResult(canvas, origin!, results, rayPaint);
}
}
void renderResult(
Canvas canvas,
Vector2 origin,
List<RaycastResult<ShapeHitbox>> results,
Paint paint,
) {
var originOffset = origin.toOffset();
for (final result in results) {
if (!result.isActive) {
continue;
}
final intersectionPoint = result.intersectionPoint!.toOffset();
canvas.drawLine(
originOffset,
intersectionPoint,
paint,
);
originOffset = intersectionPoint;
}
}
}