Line data Source code
1 : import 'dart:ui'; 2 : 3 : import '../../components.dart'; 4 : import '../../game.dart'; 5 : import '../../palette.dart'; 6 : import '../components/cache/value_cache.dart'; 7 : import '../extensions/vector2.dart'; 8 : import 'shape_intersections.dart' as intersection_system; 9 : 10 : /// A shape can represent any geometrical shape with optionally a size, position 11 : /// and angle. It can also have an anchor if it shouldn't be rotated around its 12 : /// center. 13 : /// A point can be determined to be within of outside of a shape. 14 : abstract class Shape { 15 : final ValueCache<Vector2> _halfSizeCache = ValueCache(); 16 : final ValueCache<Vector2> _localCenterCache = ValueCache(); 17 : final ValueCache<Vector2> _absoluteCenterCache = ValueCache(); 18 : 19 : /// Should be the center of that [offsetPosition] and [relativeOffset] 20 : /// should be calculated from, if they are not set this is the center of the 21 : /// shape 22 : Vector2 position = Vector2.zero(); 23 : 24 : /// The size is the bounding box of the [Shape] 25 : Vector2 size; 26 : 27 7 : Vector2 get halfSize { 28 28 : if (!_halfSizeCache.isCacheValid([size])) { 29 49 : _halfSizeCache.updateCache(size / 2, [size.clone()]); 30 : } 31 14 : return _halfSizeCache.value!; 32 : } 33 : 34 : /// The angle of the shape from its initial definition 35 : double angle; 36 : 37 : /// The local position of your shape, so the diff from the [position] of the 38 : /// shape 39 : Vector2 offsetPosition = Vector2.zero(); 40 : 41 : /// The position of your shape in relation to its size from (-1,-1) to (1,1) 42 : Vector2 relativeOffset = Vector2.zero(); 43 : 44 : /// The [relativeOffset] converted to a length vector 45 0 : Vector2 get relativePosition => (size / 2)..multiply(relativeOffset); 46 : 47 : /// The angle of the parent that has to be taken into consideration for some 48 : /// applications of [Shape], for example [HitboxShape] 49 : double parentAngle; 50 : 51 : /// Whether the context that the shape is in has already prepared (rotated 52 : /// and translated) the canvas before coming to the shape's render method. 53 : bool isCanvasPrepared = false; 54 : 55 : /// The center position of the shape within itself, without rotation 56 0 : Vector2 get localCenter { 57 0 : final stateValues = [ 58 0 : size, 59 0 : relativeOffset, 60 0 : offsetPosition, 61 : ]; 62 0 : if (!_localCenterCache.isCacheValid(stateValues)) { 63 0 : final center = (size / 2)..add(relativePosition)..add(offsetPosition); 64 0 : _localCenterCache.updateCache( 65 : center, 66 0 : stateValues.map((e) => e.clone()).toList(growable: false), 67 : ); 68 : } 69 0 : return _localCenterCache.value!; 70 : } 71 : 72 : /// The shape's absolute center with rotation taken into account 73 7 : Vector2 get absoluteCenter { 74 7 : final stateValues = [ 75 7 : position, 76 7 : offsetPosition, 77 7 : relativeOffset, 78 7 : angle, 79 7 : parentAngle, 80 : ]; 81 14 : if (!_absoluteCenterCache.isCacheValid(stateValues)) { 82 : /// The center of the shape, before any rotation 83 21 : final center = position + offsetPosition; 84 14 : if (!relativeOffset.isZero()) { 85 0 : center.add(relativePosition); 86 : } 87 28 : if (angle != 0 || parentAngle != 0) { 88 5 : center.rotate(parentAngle + angle, center: position); 89 : } 90 21 : _absoluteCenterCache.updateCache(center, [ 91 14 : position.clone(), 92 14 : offsetPosition.clone(), 93 14 : relativeOffset.clone(), 94 7 : angle, 95 7 : parentAngle, 96 : ]); 97 : } 98 14 : return _absoluteCenterCache.value!; 99 : } 100 : 101 9 : Shape({ 102 : Vector2? position, 103 : Vector2? size, 104 : this.angle = 0, 105 : this.parentAngle = 0, 106 4 : }) : position = position ?? Vector2.zero(), 107 3 : size = size ?? Vector2.zero(); 108 : 109 : /// Whether the point [p] is within the shapes boundaries or not 110 : bool containsPoint(Vector2 p); 111 : 112 : void render(Canvas canvas, Paint paint); 113 : 114 : /// Where this [Shape] has intersection points with another shape 115 2 : Set<Vector2> intersections(Shape other) { 116 2 : return intersection_system.intersections(this, other); 117 : } 118 : 119 : /// Turns a [Shape] into a [ShapeComponent] 120 : /// 121 : /// Do note that while a [Shape] is defined from the center, a 122 : /// [ShapeComponent] like all other components default to an [Anchor] in the 123 : /// top left corner. 124 1 : ShapeComponent toComponent({Paint? paint, Anchor anchor = Anchor.topLeft}) { 125 1 : return ShapeComponent( 126 : this, 127 1 : paint ?? BasicPalette.white.paint(), 128 : anchor: anchor, 129 : ); 130 : } 131 : } 132 : 133 : mixin HitboxShape on Shape { 134 : late PositionComponent component; 135 : 136 4 : @override 137 8 : Vector2 get size => component.scaledSize; 138 : 139 4 : @override 140 8 : double get parentAngle => component.angle; 141 : 142 4 : @override 143 8 : Vector2 get position => component.absoluteCenter; 144 : 145 : /// Assign your own [CollisionCallback] if you want a callback when this 146 : /// shape collides with another [HitboxShape] 147 : CollisionCallback onCollision = emptyCollisionCallback; 148 : 149 : /// Assign your own [CollisionEndCallback] if you want a callback when this 150 : /// shape stops colliding with another [HitboxShape] 151 : CollisionEndCallback onCollisionEnd = emptyCollisionEndCallback; 152 : } 153 : 154 : typedef CollisionCallback = void Function( 155 : Set<Vector2> intersectionPoints, 156 : HitboxShape other, 157 : ); 158 : 159 : typedef CollisionEndCallback = void Function(HitboxShape other); 160 : 161 1 : void emptyCollisionCallback(Set<Vector2> _, HitboxShape __) {} 162 0 : void emptyCollisionEndCallback(HitboxShape _) {}