diff --git a/packages/flame/lib/src/collisions/hitboxes/circle_hitbox.dart b/packages/flame/lib/src/collisions/hitboxes/circle_hitbox.dart index 062d11dc9cf..3ac345a3186 100644 --- a/packages/flame/lib/src/collisions/hitboxes/circle_hitbox.dart +++ b/packages/flame/lib/src/collisions/hitboxes/circle_hitbox.dart @@ -67,34 +67,53 @@ class CircleHitbox extends CircleComponent with ShapeHitbox { return null; } else { final result = out ?? RaycastResult(); - final intersectionPoint = intersections.first; - _temporaryNormal - ..setFrom(intersectionPoint) - ..sub(_temporaryAbsoluteCenter) - ..normalize(); - if (isInsideHitbox) { - _temporaryNormal.invert(); - } - final reflectionDirection = - (out?.reflectionRay?.direction ?? Vector2.zero()) - ..setFrom(ray.direction) - ..reflect(_temporaryNormal); - - final reflectionRay = (out?.reflectionRay - ?..setWith( - origin: intersectionPoint, - direction: reflectionDirection, - )) ?? - Ray2(intersectionPoint, reflectionDirection); + result.hitboxCenter = (result.hitboxCenter + ?..setFrom(_temporaryAbsoluteCenter)) ?? + _temporaryAbsoluteCenter.clone(); result.setWith( hitbox: this, - reflectionRay: reflectionRay, - normal: _temporaryNormal, - distance: ray.origin.distanceTo(intersectionPoint), isInsideHitbox: isInsideHitbox, + intersectionPointFunction: () => intersections.first, + ); + result.setWith( + distanceFunction: () => + result.originatingRay?.origin.distanceTo(result.intersectionPoint!), + normalFunction: () => + _rayNormal(result, isInsideHitbox: isInsideHitbox), + ); + result.setWith( + reflectionRayFunction: () => _rayReflection(result), ); return result; } } + + Vector2 _rayNormal(RaycastResult result, {required bool isInsideHitbox}) { + _temporaryNormal + ..setFrom(result.intersectionPoint!) + ..sub(result.hitboxCenter!) + ..normalize(); + if (isInsideHitbox) { + _temporaryNormal.invert(); + } + return _temporaryNormal; + } + + Ray2 _rayReflection(RaycastResult result) { + final intersectionPoint = result.intersectionPoint!; + + final reflectionDirection = + (result.rawReflectionRay?.direction ?? Vector2.zero()) + ..setFrom(result.originatingRay!.direction) + ..reflect(result.normal!); + + final reflectionRay = (result.rawReflectionRay + ?..setWith( + origin: intersectionPoint, + direction: reflectionDirection, + )) ?? + Ray2(intersectionPoint, reflectionDirection); + return reflectionRay; + } } diff --git a/packages/flame/lib/src/collisions/raycast_result.dart b/packages/flame/lib/src/collisions/raycast_result.dart index 72c0e5d166e..3dc757a0590 100644 --- a/packages/flame/lib/src/collisions/raycast_result.dart +++ b/packages/flame/lib/src/collisions/raycast_result.dart @@ -1,6 +1,7 @@ import 'package:flame/collisions.dart'; import 'package:flame/extensions.dart'; import 'package:flame/src/geometry/ray2.dart'; +import 'package:meta/meta.dart'; /// The result of a raycasting operation. /// @@ -10,15 +11,22 @@ import 'package:flame/src/geometry/ray2.dart'; class RaycastResult> { RaycastResult({ T? hitbox, - Ray2? reflectionRay, - Vector2? normal, - double? distance, bool isInsideHitbox = false, - }) : _isInsideHitbox = isInsideHitbox, - _hitbox = hitbox, - _reflectionRay = reflectionRay ?? Ray2.zero(), - _normal = normal ?? Vector2.zero(), - _distance = distance ?? double.maxFinite; + Ray2? originatingRay, + double? Function()? distanceFunction, + Vector2? Function()? intersectionPointFunction, + Vector2? Function()? normalFunction, + Ray2? Function()? reflectionRayFunction, + }) : _hitbox = hitbox, + _isInsideHitbox = isInsideHitbox, + rawOriginatingRay = originatingRay, + _distanceFunction = distanceFunction, + _intersectionPointFunction = intersectionPointFunction, + _normalFunction = normalFunction, + _reflectionRayFunction = reflectionRayFunction; + + T? get hitbox => isActive ? _hitbox : null; + T? _hitbox; /// Whether this result has active results in it. /// @@ -30,59 +38,162 @@ class RaycastResult> { bool get isInsideHitbox => _isInsideHitbox; bool _isInsideHitbox; - T? _hitbox; - T? get hitbox => isActive ? _hitbox : null; + /// The casted ray that the result came from. + Ray2? get originatingRay => isActive ? rawOriginatingRay : null; + @internal + Ray2? rawOriginatingRay; + + double? Function()? _distanceFunction; + Vector2? Function()? _intersectionPointFunction; + Vector2? Function()? _normalFunction; + Ray2? Function()? _reflectionRayFunction; - final Ray2 _reflectionRay; - Ray2? get reflectionRay => isActive ? _reflectionRay : null; + double? _rawDistance; + bool _validDistance = false; + double? get distance { + if (!_validDistance) { + _rawDistance = _distanceFunction?.call(); + _validDistance = true; + } + return _rawDistance; + } - Vector2? get intersectionPoint => reflectionRay?.origin; + @internal + Vector2? rawIntersectionPoint; + bool _validIntersectionPoint = false; + Vector2? get intersectionPoint { + if (!isActive) { + return null; + } + if (!_validIntersectionPoint) { + rawIntersectionPoint = _intersectionPointFunction?.call(); + _validIntersectionPoint = true; + } + return rawIntersectionPoint; + } + + @internal + Vector2? rawNormal; + bool _validNormal = false; + Vector2? get normal { + if (!isActive) { + return null; + } + if (!_validNormal) { + rawNormal = _normalFunction?.call(); + _validNormal = true; + } + return rawNormal; + } - double _distance; - double? get distance => isActive ? _distance : null; + @internal + Ray2? rawReflectionRay; + bool _validReflectionRay = false; + Ray2? get reflectionRay { + if (!isActive) { + return null; + } + if (!_validReflectionRay) { + rawReflectionRay = _reflectionRayFunction?.call(); + _validReflectionRay = true; + } + return rawReflectionRay; + } - final Vector2 _normal; - Vector2? get normal => isActive ? _normal : null; + /// Used for storing the center of the [CircleHitbox] to be able to lazily + /// compute other values. + @internal + Vector2? hitboxCenter; - void reset() => _hitbox = null; + void reset() { + _hitbox = null; + _validDistance = false; + _validIntersectionPoint = false; + _validNormal = false; + _validReflectionRay = false; + } /// Sets this [RaycastResult]'s objects to the values stored in [other]. void setFrom(RaycastResult other) { setWith( hitbox: other.hitbox, - reflectionRay: other.reflectionRay, - normal: other.normal, - distance: other.distance, isInsideHitbox: other.isInsideHitbox, + originatingRay: other.rawOriginatingRay, + distanceFunction: other._distanceFunction, + intersectionPointFunction: other._intersectionPointFunction, + normalFunction: other._normalFunction, + reflectionRayFunction: other._reflectionRayFunction, ); + _validDistance = other._validDistance; + _rawDistance = other._rawDistance; + + _validIntersectionPoint = other._validIntersectionPoint; + if (_validIntersectionPoint) { + rawIntersectionPoint = (rawIntersectionPoint + ?..setFrom(other.rawIntersectionPoint!)) ?? + other.rawIntersectionPoint!.clone(); + } + + _validNormal = other._validNormal; + if (_validNormal) { + rawNormal = + (rawNormal?..setFrom(other.rawNormal!)) ?? other.rawNormal!.clone(); + } + + _validReflectionRay = other._validReflectionRay; + if (_validReflectionRay) { + rawReflectionRay = (rawReflectionRay + ?..setFrom(other.rawReflectionRay!)) ?? + other.rawReflectionRay!.clone(); + } } /// Sets the values of the result from the specified arguments. + /// + /// Values that are not specified are kept as their previous values. void setWith({ T? hitbox, - Ray2? reflectionRay, - Vector2? normal, - double? distance, - bool isInsideHitbox = false, + bool? isInsideHitbox, + Ray2? originatingRay, + double? Function()? distanceFunction, + Vector2? Function()? intersectionPointFunction, + Vector2? Function()? normalFunction, + Ray2? Function()? reflectionRayFunction, }) { - _hitbox = hitbox; - if (reflectionRay != null) { - _reflectionRay.setFrom(reflectionRay); + _hitbox = hitbox ?? _hitbox; + _isInsideHitbox = isInsideHitbox ?? _isInsideHitbox; + if (distanceFunction != null) { + _distanceFunction = distanceFunction; + _validDistance = false; + } + if (intersectionPointFunction != null) { + _intersectionPointFunction = intersectionPointFunction; + _validIntersectionPoint = false; + } + if (normalFunction != null) { + _normalFunction = normalFunction; + _validNormal = false; + } + if (reflectionRayFunction != null) { + _reflectionRayFunction = reflectionRayFunction; + _validReflectionRay = false; } - if (normal != null) { - _normal.setFrom(normal); + if (rawOriginatingRay != null && originatingRay != null) { + rawOriginatingRay?.setFrom(originatingRay); + } else { + rawOriginatingRay = originatingRay?.clone() ?? rawOriginatingRay; } - _distance = distance ?? double.maxFinite; - _isInsideHitbox = isInsideHitbox; } RaycastResult clone() { return RaycastResult( hitbox: hitbox, - reflectionRay: _reflectionRay.clone(), - normal: _normal.clone(), - distance: distance, isInsideHitbox: isInsideHitbox, + originatingRay: originatingRay?.clone(), + distanceFunction: _distanceFunction, + intersectionPointFunction: _intersectionPointFunction, + normalFunction: _normalFunction, + reflectionRayFunction: _reflectionRayFunction, ); } } diff --git a/packages/flame/lib/src/geometry/polygon_ray_intersection.dart b/packages/flame/lib/src/geometry/polygon_ray_intersection.dart index e1ef1d2bbcd..4c66c82fc7b 100644 --- a/packages/flame/lib/src/geometry/polygon_ray_intersection.dart +++ b/packages/flame/lib/src/geometry/polygon_ray_intersection.dart @@ -5,8 +5,6 @@ import 'package:flame/geometry.dart'; /// Used to add the [rayIntersection] method to [RectangleHitbox] and /// [PolygonHitbox], used by the raytracing and raycasting methods. mixin PolygonRayIntersection on PolygonComponent { - late final _temporaryNormal = Vector2.zero(); - /// Returns whether the [RaycastResult] if the [ray] intersects the polygon. /// /// If [out] is defined that is used to populate with the result and then @@ -36,43 +34,64 @@ mixin PolygonRayIntersection on PolygonComponent { } } } + if (crossings > 0) { - final intersectionPoint = - ray.point(closestDistance, out: out?.intersectionPoint); - // This is "from" to "to" since it is defined ccw in the canvas - // coordinate system - _temporaryNormal - ..setFrom(closestSegment!.from) - ..sub(closestSegment.to); - _temporaryNormal - ..setValues(_temporaryNormal.y, -_temporaryNormal.x) - ..normalize(); - var isInsideHitbox = false; - if (crossings == 1 || isOverlappingPoint) { - _temporaryNormal.invert(); - isInsideHitbox = true; - } - final reflectionDirection = - (out?.reflectionRay?.direction ?? Vector2.zero()) - ..setFrom(ray.direction) - ..reflect(_temporaryNormal); + Vector2 intersectionPointFunction() => + ray.point(closestDistance, out: out?.rawIntersectionPoint); - final reflectionRay = (out?.reflectionRay - ?..setWith( - origin: intersectionPoint, - direction: reflectionDirection, - )) ?? - Ray2(intersectionPoint, reflectionDirection); - return (out ?? RaycastResult()) + final result = (out ?? RaycastResult()) ..setWith( hitbox: this as T, - reflectionRay: reflectionRay, - normal: _temporaryNormal, - distance: closestDistance, - isInsideHitbox: isInsideHitbox, + isInsideHitbox: crossings == 1 || isOverlappingPoint, + originatingRay: ray, + distanceFunction: () => closestDistance, + intersectionPointFunction: intersectionPointFunction, ); + result.setWith( + normalFunction: () => _rayNormal( + closestSegment: closestSegment!, + result: result, + ), + ); + result.setWith( + reflectionRayFunction: () => _rayReflection(result: result), + ); + return result; } out?.reset(); return null; } + + /// This method is used to pass to the [RaycastResult] to lazily compute the + /// normal. + Vector2 _rayNormal({ + required LineSegment closestSegment, + required RaycastResult result, + }) { + final normal = result.rawNormal ?? Vector2.zero(); + // This is "from" to "to" since it is defined ccw in the canvas + // coordinate system + normal + ..setFrom(closestSegment.from) + ..sub(closestSegment.to); + normal + ..setValues(normal.y, -normal.x) + ..normalize(); + return result.isInsideHitbox ? (normal..invert()) : normal; + } + + /// This method is used to pass to the [RaycastResult] to lazily compute the + /// reflection. + Ray2 _rayReflection({required RaycastResult result}) { + final reflection = result.rawReflectionRay ?? Ray2.zero(); + final reflectionDirection = reflection.direction + ..setFrom(result.originatingRay!.direction) + ..reflect(result.normal!); + + return reflection + ..setWith( + origin: result.intersectionPoint!, + direction: reflectionDirection, + ); + } }