mima-engine 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mima/__init__.py +4 -0
- mima/backend/__init__.py +1 -0
- mima/backend/pygame_assets.py +401 -0
- mima/backend/pygame_audio.py +78 -0
- mima/backend/pygame_backend.py +603 -0
- mima/backend/pygame_camera.py +63 -0
- mima/backend/pygame_events.py +695 -0
- mima/backend/touch_control_scheme_a.py +126 -0
- mima/backend/touch_control_scheme_b.py +132 -0
- mima/core/__init__.py +0 -0
- mima/core/collision.py +325 -0
- mima/core/database.py +58 -0
- mima/core/engine.py +367 -0
- mima/core/mode_engine.py +81 -0
- mima/core/scene_engine.py +81 -0
- mima/integrated/__init__.py +0 -0
- mima/integrated/entity.py +183 -0
- mima/integrated/layered_map.py +351 -0
- mima/integrated/sprite.py +156 -0
- mima/layered/__init__.py +0 -0
- mima/layered/assets.py +56 -0
- mima/layered/scene.py +415 -0
- mima/layered/shape.py +99 -0
- mima/layered/shaped_sprite.py +78 -0
- mima/layered/virtual_input.py +302 -0
- mima/maps/__init__.py +0 -0
- mima/maps/template.py +71 -0
- mima/maps/tile.py +20 -0
- mima/maps/tile_animation.py +7 -0
- mima/maps/tile_info.py +10 -0
- mima/maps/tile_layer.py +52 -0
- mima/maps/tiled/__init__.py +0 -0
- mima/maps/tiled/tiled_layer.py +48 -0
- mima/maps/tiled/tiled_map.py +95 -0
- mima/maps/tiled/tiled_object.py +79 -0
- mima/maps/tiled/tiled_objectgroup.py +25 -0
- mima/maps/tiled/tiled_template.py +49 -0
- mima/maps/tiled/tiled_tile.py +90 -0
- mima/maps/tiled/tiled_tileset.py +51 -0
- mima/maps/tilemap.py +216 -0
- mima/maps/tileset.py +39 -0
- mima/maps/tileset_info.py +9 -0
- mima/maps/transition_map.py +146 -0
- mima/objects/__init__.py +0 -0
- mima/objects/animated_sprite.py +217 -0
- mima/objects/attribute_effect.py +26 -0
- mima/objects/attributes.py +126 -0
- mima/objects/creature.py +384 -0
- mima/objects/dynamic.py +206 -0
- mima/objects/effects/__init__.py +0 -0
- mima/objects/effects/colorize_screen.py +60 -0
- mima/objects/effects/debug_box.py +133 -0
- mima/objects/effects/light.py +103 -0
- mima/objects/effects/show_sprite.py +50 -0
- mima/objects/effects/walking_on_grass.py +70 -0
- mima/objects/effects/walking_on_water.py +57 -0
- mima/objects/loader.py +111 -0
- mima/objects/projectile.py +111 -0
- mima/objects/sprite.py +116 -0
- mima/objects/world/__init__.py +0 -0
- mima/objects/world/color_gate.py +67 -0
- mima/objects/world/color_switch.py +101 -0
- mima/objects/world/container.py +175 -0
- mima/objects/world/floor_switch.py +109 -0
- mima/objects/world/gate.py +178 -0
- mima/objects/world/light_source.py +121 -0
- mima/objects/world/logic_gate.py +157 -0
- mima/objects/world/movable.py +399 -0
- mima/objects/world/oneway.py +195 -0
- mima/objects/world/pickup.py +157 -0
- mima/objects/world/switch.py +179 -0
- mima/objects/world/teleport.py +308 -0
- mima/py.typed +0 -0
- mima/scripts/__init__.py +2 -0
- mima/scripts/command.py +38 -0
- mima/scripts/commands/__init__.py +0 -0
- mima/scripts/commands/add_quest.py +19 -0
- mima/scripts/commands/change_map.py +34 -0
- mima/scripts/commands/close_dialog.py +9 -0
- mima/scripts/commands/equip_weapon.py +23 -0
- mima/scripts/commands/give_item.py +26 -0
- mima/scripts/commands/give_resource.py +51 -0
- mima/scripts/commands/move_map.py +152 -0
- mima/scripts/commands/move_to.py +49 -0
- mima/scripts/commands/oneway_move.py +58 -0
- mima/scripts/commands/parallel.py +66 -0
- mima/scripts/commands/play_sound.py +13 -0
- mima/scripts/commands/present_item.py +53 -0
- mima/scripts/commands/progress_quest.py +12 -0
- mima/scripts/commands/quit_game.py +8 -0
- mima/scripts/commands/save_game.py +14 -0
- mima/scripts/commands/screen_fade.py +83 -0
- mima/scripts/commands/serial.py +69 -0
- mima/scripts/commands/set_facing_direction.py +21 -0
- mima/scripts/commands/set_spawn_map.py +17 -0
- mima/scripts/commands/show_choices.py +52 -0
- mima/scripts/commands/show_dialog.py +118 -0
- mima/scripts/commands/take_coins.py +23 -0
- mima/scripts/script_processor.py +61 -0
- mima/standalone/__init__.py +0 -0
- mima/standalone/camera.py +153 -0
- mima/standalone/geometry.py +1318 -0
- mima/standalone/multicolumn_list.py +54 -0
- mima/standalone/pixel_font.py +84 -0
- mima/standalone/scripting.py +145 -0
- mima/standalone/spatial.py +186 -0
- mima/standalone/sprite.py +158 -0
- mima/standalone/tiled_map.py +1247 -0
- mima/standalone/transformed_view.py +433 -0
- mima/standalone/user_input.py +563 -0
- mima/states/__init__.py +0 -0
- mima/states/game_state.py +189 -0
- mima/states/memory.py +28 -0
- mima/states/quest.py +71 -0
- mima/types/__init__.py +0 -0
- mima/types/alignment.py +7 -0
- mima/types/blend.py +8 -0
- mima/types/damage.py +42 -0
- mima/types/direction.py +44 -0
- mima/types/gate_color.py +7 -0
- mima/types/graphic_state.py +23 -0
- mima/types/keys.py +64 -0
- mima/types/mode.py +9 -0
- mima/types/nature.py +12 -0
- mima/types/object.py +22 -0
- mima/types/player.py +9 -0
- mima/types/position.py +13 -0
- mima/types/start.py +7 -0
- mima/types/terrain.py +9 -0
- mima/types/tile_collision.py +11 -0
- mima/types/weapon_slot.py +6 -0
- mima/types/window.py +44 -0
- mima/usables/__init__.py +0 -0
- mima/usables/item.py +51 -0
- mima/usables/weapon.py +68 -0
- mima/util/__init__.py +1 -0
- mima/util/colors.py +50 -0
- mima/util/constants.py +55 -0
- mima/util/functions.py +38 -0
- mima/util/input_defaults.py +170 -0
- mima/util/logging.py +51 -0
- mima/util/property.py +8 -0
- mima/util/runtime_config.py +327 -0
- mima/util/trading_item.py +23 -0
- mima/view/__init__.py +0 -0
- mima/view/camera.py +192 -0
- mima/view/mima_mode.py +618 -0
- mima/view/mima_scene.py +231 -0
- mima/view/mima_view.py +12 -0
- mima/view/mima_window.py +244 -0
- mima_engine-0.4.0.dist-info/METADATA +47 -0
- mima_engine-0.4.0.dist-info/RECORD +153 -0
- mima_engine-0.4.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,1318 @@
|
|
|
1
|
+
# noqa: E741
|
|
2
|
+
import math
|
|
3
|
+
|
|
4
|
+
from pygame.math import Vector2
|
|
5
|
+
from typing_extensions import Any, TypeAlias, overload
|
|
6
|
+
|
|
7
|
+
EPSILON = 1e-3
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Line:
|
|
11
|
+
def __init__(
|
|
12
|
+
self, start: Vector2 | None = None, end: Vector2 | None = None
|
|
13
|
+
) -> None:
|
|
14
|
+
self.start: Vector2 = start if start is not None else Vector2()
|
|
15
|
+
self.end: Vector2 = end if end is not None else Vector2()
|
|
16
|
+
|
|
17
|
+
def vector(self) -> Vector2:
|
|
18
|
+
"""Get vector pointing from start to end"""
|
|
19
|
+
return self.end - self.start
|
|
20
|
+
|
|
21
|
+
def length(self) -> float:
|
|
22
|
+
return self.vector().magnitude()
|
|
23
|
+
|
|
24
|
+
def length2(self) -> float:
|
|
25
|
+
return self.vector().magnitude_squared()
|
|
26
|
+
|
|
27
|
+
def rpoint(self, distance: float) -> Vector2:
|
|
28
|
+
"""Get point along the line for a real distance."""
|
|
29
|
+
return self.start + self.vector().normalize() * distance
|
|
30
|
+
|
|
31
|
+
def upoint(self, distance: float) -> Vector2:
|
|
32
|
+
"""Get point along the line for a unit distance."""
|
|
33
|
+
return self.start + self.vector() * distance
|
|
34
|
+
|
|
35
|
+
def side(self, point: Vector2) -> int:
|
|
36
|
+
"""Return which side of the line does a point lie"""
|
|
37
|
+
d = self.vector().cross(point - self.start)
|
|
38
|
+
return -1 if d < 0 else 1 if d > 0 else 0
|
|
39
|
+
|
|
40
|
+
def coefficients(self) -> Vector2:
|
|
41
|
+
"""Returns line equation coefficients.
|
|
42
|
+
|
|
43
|
+
Calculates line equation "mx + a" coefficients where:
|
|
44
|
+
x: m
|
|
45
|
+
y: a
|
|
46
|
+
|
|
47
|
+
Returns (inf, inf) when abs(end.x - start.x) < EPSILON
|
|
48
|
+
"""
|
|
49
|
+
# Check if line is vertical or close to vertical
|
|
50
|
+
if abs(self.end.x - self.start.y) < EPSILON:
|
|
51
|
+
return Vector2(math.inf, math.inf)
|
|
52
|
+
|
|
53
|
+
m = (self.end.y - self.start.y) / (self.end.x - self.start.x)
|
|
54
|
+
return Vector2(m, -m * self.start.x + self.start.y)
|
|
55
|
+
|
|
56
|
+
def __len__(self) -> float:
|
|
57
|
+
return self.length()
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def pos(self) -> Vector2:
|
|
61
|
+
return self.start
|
|
62
|
+
|
|
63
|
+
@pos.setter
|
|
64
|
+
def pos(self, pos: Vector2) -> None:
|
|
65
|
+
self.start = pos
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class Ray:
|
|
69
|
+
def __init__(
|
|
70
|
+
self, origin: Vector2 | None = None, direction: Vector2 | None = None
|
|
71
|
+
) -> None:
|
|
72
|
+
self.origin: Vector2 = origin if origin is not None else Vector2()
|
|
73
|
+
self.direction: Vector2 = direction if direction is not None else Vector2()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class Rect:
|
|
77
|
+
def __init__(self, pos: Vector2 | None = None, size: Vector2 | None = None) -> None:
|
|
78
|
+
self.pos: Vector2 = pos if pos is not None else Vector2()
|
|
79
|
+
self.size: Vector2 = size if size is not None else Vector2()
|
|
80
|
+
|
|
81
|
+
def middle(self) -> Vector2:
|
|
82
|
+
"""Get point in the center of the rectangle."""
|
|
83
|
+
return self.pos + (self.size * 0.5)
|
|
84
|
+
|
|
85
|
+
def top(self) -> Line:
|
|
86
|
+
"""Get line segment from top side of the rectangle."""
|
|
87
|
+
return Line(self.pos, Vector2(self.pos.x + self.size.x, self.pos.y))
|
|
88
|
+
|
|
89
|
+
def bottom(self) -> Line:
|
|
90
|
+
"""Get line segment from bottom side of the rectangle."""
|
|
91
|
+
return Line(Vector2(self.pos.x, self.pos.y + self.size.y), self.pos + self.size)
|
|
92
|
+
|
|
93
|
+
def left(self) -> Line:
|
|
94
|
+
"""Get line segment from left side of the rectangle."""
|
|
95
|
+
return Line(self.pos, Vector2(self.pos.x, self.pos.y + self.size.y))
|
|
96
|
+
|
|
97
|
+
def right(self) -> Line:
|
|
98
|
+
"""Get line segment from right side of the rectangle."""
|
|
99
|
+
return Line(Vector2(self.pos.x + self.size.x, self.pos.y), self.pos + self.size)
|
|
100
|
+
|
|
101
|
+
def side(self, index: int) -> Line:
|
|
102
|
+
"""Get a line from an indexed side.
|
|
103
|
+
|
|
104
|
+
Starting top, going clockwise:
|
|
105
|
+
0 = top
|
|
106
|
+
1 = right
|
|
107
|
+
2 = bottom
|
|
108
|
+
3 = left
|
|
109
|
+
|
|
110
|
+
"""
|
|
111
|
+
if index % 4 == 0:
|
|
112
|
+
return self.top()
|
|
113
|
+
elif index % 4 == 1:
|
|
114
|
+
return self.right()
|
|
115
|
+
elif index % 4 == 2:
|
|
116
|
+
return self.bottom()
|
|
117
|
+
else:
|
|
118
|
+
return self.left()
|
|
119
|
+
|
|
120
|
+
def area(self) -> float:
|
|
121
|
+
"""Get area of the rectangle."""
|
|
122
|
+
return self.size.x * self.size.y
|
|
123
|
+
|
|
124
|
+
def perimeter(self) -> float:
|
|
125
|
+
"""Get perimeter of the rectangle."""
|
|
126
|
+
return 2.0 * (self.size.x + self.size.y)
|
|
127
|
+
|
|
128
|
+
def side_count(self) -> int:
|
|
129
|
+
return 4
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class Circle:
|
|
133
|
+
def __init__(self, pos: Vector2 | None = None, radius: float = 0.0) -> None:
|
|
134
|
+
self.pos = pos if pos is not None else Vector2()
|
|
135
|
+
self.radius = radius
|
|
136
|
+
|
|
137
|
+
def area(self) -> float:
|
|
138
|
+
return math.pi * self.radius**2
|
|
139
|
+
|
|
140
|
+
def perimeter(self) -> float:
|
|
141
|
+
return math.pi * self.radius * 2.0
|
|
142
|
+
|
|
143
|
+
def circumference(self) -> float:
|
|
144
|
+
return self.perimeter()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def clamp(val: float, low: float, high: float) -> float:
|
|
148
|
+
return max(low, min(high, val))
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def filter_duplicates(points: list[Vector2]) -> list[Vector2]:
|
|
152
|
+
filtered = []
|
|
153
|
+
for p in points:
|
|
154
|
+
is_duplicate = False
|
|
155
|
+
for fp in filtered:
|
|
156
|
+
if abs(p.x - fp.x) < EPSILON and abs(p.y - fp.y) < EPSILON:
|
|
157
|
+
is_duplicate = True
|
|
158
|
+
break
|
|
159
|
+
|
|
160
|
+
if not is_duplicate:
|
|
161
|
+
filtered.append(p)
|
|
162
|
+
|
|
163
|
+
return filtered
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
Shape: TypeAlias = Line | Rect | Circle
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def shape_from_str(
|
|
170
|
+
shape: str, pos: Vector2, pos2_or_size: Vector2, radius: float
|
|
171
|
+
) -> Shape:
|
|
172
|
+
if shape == "circle":
|
|
173
|
+
return Circle(pos, radius)
|
|
174
|
+
elif shape == "rect":
|
|
175
|
+
return Rect(pos, pos2_or_size)
|
|
176
|
+
elif shape == "line":
|
|
177
|
+
return Line(pos, pos2_or_size)
|
|
178
|
+
|
|
179
|
+
msg = f"Unsupported shape {shape}. Use circle, line, or rect."
|
|
180
|
+
raise ValueError(msg)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def shape_from_dict(shape_dict: dict[str, Any]) -> Shape:
|
|
184
|
+
return shape_from_str(
|
|
185
|
+
shape_dict["shape"], shape_dict["pos"], shape_dict["size"], shape_dict["radius"]
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@overload
|
|
190
|
+
def copy_shape(shape: Rect) -> Rect: ...
|
|
191
|
+
@overload
|
|
192
|
+
def copy_shape(shape: Circle) -> Circle: ...
|
|
193
|
+
@overload
|
|
194
|
+
def copy_shape(shape: Line) -> Line: ...
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def copy_shape(shape: Shape) -> Shape:
|
|
198
|
+
if isinstance(shape, Rect):
|
|
199
|
+
return Rect(Vector2(shape.pos), Vector2(shape.size))
|
|
200
|
+
elif isinstance(shape, Circle):
|
|
201
|
+
return Circle(Vector2(shape.pos), shape.radius)
|
|
202
|
+
elif isinstance(shape, Line):
|
|
203
|
+
return Line(Vector2(shape.start), Vector2(shape.end))
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@overload
|
|
207
|
+
def contains(g1: Vector2, g2: Vector2) -> bool: ...
|
|
208
|
+
@overload
|
|
209
|
+
def contains(g1: Vector2, g2: Line) -> bool: ...
|
|
210
|
+
@overload
|
|
211
|
+
def contains(g1: Vector2, g2: Rect) -> bool: ...
|
|
212
|
+
@overload
|
|
213
|
+
def contains(g1: Vector2, g2: Circle) -> bool: ...
|
|
214
|
+
@overload
|
|
215
|
+
def contains(g1: Line, g2: Vector2) -> bool: ...
|
|
216
|
+
@overload
|
|
217
|
+
def contains(g1: Line, g2: Line) -> bool: ...
|
|
218
|
+
@overload
|
|
219
|
+
def contains(g1: Line, g2: Rect) -> bool: ...
|
|
220
|
+
@overload
|
|
221
|
+
def contains(g1: Line, g2: Circle) -> bool: ...
|
|
222
|
+
@overload
|
|
223
|
+
def contains(g1: Rect, g2: Vector2) -> bool: ...
|
|
224
|
+
@overload
|
|
225
|
+
def contains(g1: Rect, g2: Line) -> bool: ...
|
|
226
|
+
@overload
|
|
227
|
+
def contains(g1: Rect, g2: Rect) -> bool: ...
|
|
228
|
+
@overload
|
|
229
|
+
def contains(g1: Rect, g2: Circle) -> bool: ...
|
|
230
|
+
@overload
|
|
231
|
+
def contains(g1: Circle, g2: Vector2) -> bool: ...
|
|
232
|
+
@overload
|
|
233
|
+
def contains(g1: Circle, g2: Line) -> bool: ...
|
|
234
|
+
@overload
|
|
235
|
+
def contains(g1: Circle, g2: Rect) -> bool: ...
|
|
236
|
+
@overload
|
|
237
|
+
def contains(g1: Circle, g2: Circle) -> bool: ...
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def contains(g1: Vector2 | Shape, g2: Vector2 | Shape) -> bool:
|
|
241
|
+
if isinstance(g1, Vector2):
|
|
242
|
+
if isinstance(g2, Vector2):
|
|
243
|
+
return _point_contains_point(g1, g2)
|
|
244
|
+
elif isinstance(g2, Line):
|
|
245
|
+
return False # It can't!
|
|
246
|
+
if isinstance(g2, Rect):
|
|
247
|
+
return False # It can't!
|
|
248
|
+
elif isinstance(g2, Circle):
|
|
249
|
+
return False # It can't!
|
|
250
|
+
elif isinstance(g1, Line):
|
|
251
|
+
if isinstance(g2, Vector2):
|
|
252
|
+
return _line_contains_point(g1, g2)
|
|
253
|
+
elif isinstance(g2, Line):
|
|
254
|
+
return _line_contains_line(g1, g2)
|
|
255
|
+
elif isinstance(g2, Rect):
|
|
256
|
+
return False # It can't!
|
|
257
|
+
elif isinstance(g2, Circle):
|
|
258
|
+
return False # It can't!
|
|
259
|
+
elif isinstance(g1, Rect):
|
|
260
|
+
if isinstance(g2, Vector2):
|
|
261
|
+
return _rect_contains_point(g1, g2)
|
|
262
|
+
elif isinstance(g2, Line):
|
|
263
|
+
return _rect_contains_line(g1, g2)
|
|
264
|
+
elif isinstance(g2, Rect):
|
|
265
|
+
return _rect_contains_rect(g1, g2)
|
|
266
|
+
elif isinstance(g2, Circle):
|
|
267
|
+
return _rect_contains_circle(g1, g2)
|
|
268
|
+
elif isinstance(g1, Circle):
|
|
269
|
+
if isinstance(g2, Vector2):
|
|
270
|
+
return _circle_contains_point(g1, g2)
|
|
271
|
+
elif isinstance(g2, Line):
|
|
272
|
+
return _circle_contains_line(g1, g2)
|
|
273
|
+
elif isinstance(g2, Rect):
|
|
274
|
+
return _circle_contains_rect(g1, g2)
|
|
275
|
+
elif isinstance(g2, Circle):
|
|
276
|
+
return _circle_contains_circle(g1, g2)
|
|
277
|
+
|
|
278
|
+
msg = f"Unsupported types {type(g1)} and {type(g2)}"
|
|
279
|
+
raise ValueError(msg)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
# Point contains ...
|
|
283
|
+
def _point_contains_point(p1: Vector2, p2: Vector2) -> bool:
|
|
284
|
+
return (p1 - p2).magnitude_squared() < EPSILON
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
# Line contains ...
|
|
288
|
+
def _line_contains_point(l: Line, p: Vector2) -> bool: # noqa: E741
|
|
289
|
+
d = (p.x - l.start.x) * (l.end.y - l.start.y) - (p.y - l.start.y) * (
|
|
290
|
+
l.end.x - l.start.x
|
|
291
|
+
)
|
|
292
|
+
if abs(d) < EPSILON:
|
|
293
|
+
# point is on line
|
|
294
|
+
u = l.vector().dot(p - l.start) / l.vector().magnitude_squared()
|
|
295
|
+
return 0.0 <= u <= 1.0
|
|
296
|
+
return False
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _line_contains_line(l1: Line, l2: Line) -> bool:
|
|
300
|
+
return overlaps(l1, l2.start) and overlaps(l1, l2.end)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
# Rect contains ...
|
|
304
|
+
def _rect_contains_point(r1: Rect, p2: Vector2) -> bool:
|
|
305
|
+
return not (
|
|
306
|
+
p2.x < r1.pos.x
|
|
307
|
+
or p2.y < r1.pos.y
|
|
308
|
+
or p2.x > (r1.pos.x + r1.size.x)
|
|
309
|
+
or p2.y > (r1.pos.y + r1.size.y)
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _rect_contains_line(r: Rect, l: Line) -> bool: # noqa: E741
|
|
314
|
+
return contains(r, l.start) and contains(r, l.end)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _rect_contains_rect(r1: Rect, r2: Rect) -> bool:
|
|
318
|
+
return (
|
|
319
|
+
(r2.pos.x >= r1.pos.x)
|
|
320
|
+
and (r2.pos.x + r2.size.x <= r1.pos.x + r1.size.x)
|
|
321
|
+
and (r2.pos.y >= r1.pos.y)
|
|
322
|
+
and (r2.pos.y + r2.size.y <= r1.pos.y + r1.size.y)
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _rect_contains_circle(r: Rect, c: Circle) -> bool:
|
|
327
|
+
return (
|
|
328
|
+
r.pos.x + c.radius <= c.pos.x
|
|
329
|
+
and c.pos.x <= r.pos.x + r.size.x - c.radius
|
|
330
|
+
and r.pos.y + c.radius <= c.pos.y
|
|
331
|
+
and c.pos.y <= r.pos.y + r.size.y - c.radius
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
# Circle contains ...
|
|
336
|
+
def _circle_contains_point(c1: Circle, p2: Vector2) -> bool:
|
|
337
|
+
return (c1.pos - p2).magnitude_squared() <= c1.radius**2
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _circle_contains_line(c: Circle, l: Line) -> bool: # noqa: E741
|
|
341
|
+
return contains(c, l.start) and contains(c, l.end)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _circle_contains_rect(c: Circle, r: Rect) -> bool:
|
|
345
|
+
return (
|
|
346
|
+
contains(c, r.pos)
|
|
347
|
+
and contains(c, Vector2(r.pos.x + r.size.x, r.pos.y))
|
|
348
|
+
and contains(c, Vector2(r.pos.x, r.pos.y + r.size.y))
|
|
349
|
+
and contains(c, r.pos + r.size)
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _circle_contains_circle(c1: Circle, c2: Circle) -> bool:
|
|
354
|
+
return (
|
|
355
|
+
math.sqrt((c2.pos.x - c1.pos.x) ** 2 + (c2.pos.y - c1.pos.y) ** 2) + c2.radius
|
|
356
|
+
) <= c1.radius
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
@overload
|
|
360
|
+
def closest(g1: Vector2, g2: Vector2) -> Vector2: ...
|
|
361
|
+
@overload
|
|
362
|
+
def closest(g1: Vector2, g2: Line) -> Vector2: ...
|
|
363
|
+
@overload
|
|
364
|
+
def closest(g1: Vector2, g2: Rect) -> Vector2: ...
|
|
365
|
+
@overload
|
|
366
|
+
def closest(g1: Vector2, g2: Circle) -> Vector2: ...
|
|
367
|
+
@overload
|
|
368
|
+
def closest(g1: Line, g2: Vector2) -> Vector2: ...
|
|
369
|
+
@overload
|
|
370
|
+
def closest(g1: Line, g2: Line) -> Vector2: ...
|
|
371
|
+
@overload
|
|
372
|
+
def closest(g1: Line, g2: Rect) -> Vector2: ...
|
|
373
|
+
@overload
|
|
374
|
+
def closest(g1: Line, g2: Circle) -> Vector2: ...
|
|
375
|
+
@overload
|
|
376
|
+
def closest(g1: Rect, g2: Vector2) -> Vector2: ...
|
|
377
|
+
@overload
|
|
378
|
+
def closest(g1: Rect, g2: Line) -> Vector2: ...
|
|
379
|
+
@overload
|
|
380
|
+
def closest(g1: Rect, g2: Rect) -> Vector2: ...
|
|
381
|
+
@overload
|
|
382
|
+
def closest(g1: Rect, g2: Circle) -> Vector2: ...
|
|
383
|
+
@overload
|
|
384
|
+
def closest(g1: Circle, g2: Vector2) -> Vector2: ...
|
|
385
|
+
@overload
|
|
386
|
+
def closest(g1: Circle, g2: Line) -> Vector2: ...
|
|
387
|
+
@overload
|
|
388
|
+
def closest(g1: Circle, g2: Rect) -> Vector2: ...
|
|
389
|
+
@overload
|
|
390
|
+
def closest(g1: Circle, g2: Circle) -> Vector2: ...
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def closest(
|
|
394
|
+
g1: Vector2 | Line | Rect | Circle, g2: Vector2 | Line | Rect | Circle
|
|
395
|
+
) -> Vector2:
|
|
396
|
+
if isinstance(g1, Vector2):
|
|
397
|
+
return g1
|
|
398
|
+
elif isinstance(g1, Line):
|
|
399
|
+
if isinstance(g2, Vector2):
|
|
400
|
+
return _closest_point_on_line_to_point(g1, g2)
|
|
401
|
+
elif isinstance(g2, Line):
|
|
402
|
+
return _closest_point_on_line_to_line(g1, g2)
|
|
403
|
+
elif isinstance(g2, Rect):
|
|
404
|
+
return _closest_point_on_line_to_rect(g1, g2)
|
|
405
|
+
elif isinstance(g2, Circle):
|
|
406
|
+
return _closest_point_on_line_to_circle(g1, g2)
|
|
407
|
+
elif isinstance(g1, Rect):
|
|
408
|
+
if isinstance(g2, Vector2):
|
|
409
|
+
return _closest_point_on_rect_to_point(g1, g2)
|
|
410
|
+
elif isinstance(g2, Line):
|
|
411
|
+
return _closest_point_on_rect_to_line(g1, g2)
|
|
412
|
+
elif isinstance(g2, Rect):
|
|
413
|
+
return _closest_point_on_rect_to_rect(g1, g2)
|
|
414
|
+
elif isinstance(g2, Circle):
|
|
415
|
+
return _closest_point_on_rect_to_circle(g1, g2)
|
|
416
|
+
elif isinstance(g1, Circle):
|
|
417
|
+
if isinstance(g2, Vector2):
|
|
418
|
+
return _closest_point_on_circle_to_point(g1, g2)
|
|
419
|
+
elif isinstance(g2, Line):
|
|
420
|
+
return _closest_point_on_circle_to_line(g1, g2)
|
|
421
|
+
elif isinstance(g2, Rect):
|
|
422
|
+
return _closest_point_on_circle_to_rect(g1, g2)
|
|
423
|
+
elif isinstance(g2, Circle):
|
|
424
|
+
return _closest_point_on_circle_to_circle(g1, g2)
|
|
425
|
+
|
|
426
|
+
msg = f"Unsupported types {type(g1)} and {type(g2)}"
|
|
427
|
+
raise ValueError(msg)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
# Closest point on Line to ...
|
|
431
|
+
def _closest_point_on_line_to_point(
|
|
432
|
+
l: Line, # noqa: E741
|
|
433
|
+
p: Vector2,
|
|
434
|
+
) -> Vector2:
|
|
435
|
+
d = l.vector()
|
|
436
|
+
u = clamp(d.dot(p - l.start) / d.magnitude_squared(), 0.0, 1.0)
|
|
437
|
+
return l.start + u * d
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def _closest_point_on_line_to_line(l1: Line, l2: Line) -> Vector2:
|
|
441
|
+
raise NotImplementedError
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def _closest_point_on_line_to_rect(l: Line, r: Rect) -> Vector2: # noqa: E741
|
|
445
|
+
raise NotImplementedError
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def _closest_point_on_line_to_circle(
|
|
449
|
+
l: Line, # noqa: E741
|
|
450
|
+
c: Circle, # noqa: E741
|
|
451
|
+
) -> Vector2:
|
|
452
|
+
p = closest(c, l)
|
|
453
|
+
return closest(l, p)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
# Closest point on rectangle to ...
|
|
457
|
+
def _closest_point_on_rect_to_point(r: Rect, p: Vector2) -> Vector2:
|
|
458
|
+
edges = [r.top(), r.bottom(), r.left(), r.right()]
|
|
459
|
+
|
|
460
|
+
closest_to_edges = [closest(e, p) for e in edges]
|
|
461
|
+
distance_to_edges = [(c - p).magnitude_squared() for c in closest_to_edges]
|
|
462
|
+
|
|
463
|
+
dmin = distance_to_edges[0]
|
|
464
|
+
cmin = closest_to_edges[0]
|
|
465
|
+
for i in range(1, len(distance_to_edges)):
|
|
466
|
+
if distance_to_edges[i] < dmin:
|
|
467
|
+
dmin = distance_to_edges[i]
|
|
468
|
+
cmin = closest_to_edges[i]
|
|
469
|
+
return cmin
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _closest_point_on_rect_to_line(r: Rect, l: Line) -> Vector2: # noqa: E741
|
|
473
|
+
raise NotImplementedError
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def _closest_point_on_rect_to_rect(r1: Rect, r2: Rect) -> Vector2:
|
|
477
|
+
raise NotImplementedError
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def _closest_point_on_rect_to_circle(r: Rect, c: Circle) -> Vector2:
|
|
481
|
+
return Vector2(
|
|
482
|
+
max(r.pos.x, min(c.pos.x, r.pos.x + r.size.x)),
|
|
483
|
+
max(r.pos.y, min(c.pos.y, r.pos.y + r.size.y)),
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
# Closest point on circle to ...
|
|
488
|
+
def _closest_point_on_circle_to_point(c: Circle, p: Vector2) -> Vector2:
|
|
489
|
+
return c.pos + (p - c.pos).normalize() * c.radius
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def _closest_point_on_circle_to_line(
|
|
493
|
+
c: Circle,
|
|
494
|
+
l: Line, # noqa: E741
|
|
495
|
+
) -> Vector2:
|
|
496
|
+
p = closest(l, c.pos)
|
|
497
|
+
return c.pos + (p - c.pos).normalize() * c.radius
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def _closest_point_on_circle_to_rect(c: Circle, r: Rect) -> Vector2:
|
|
501
|
+
# Find the closest point on the rectangle to the center of the circle
|
|
502
|
+
closest_point = Vector2(
|
|
503
|
+
max(r.pos.x, min(c.pos.x, r.pos.x + r.size.x)),
|
|
504
|
+
max(r.pos.y, min(c.pos.y, r.pos.y + r.size.y)),
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
# Compute the direction vector from the circle's center to the closest
|
|
508
|
+
# rectangle point
|
|
509
|
+
direction = closest_point - c.pos
|
|
510
|
+
|
|
511
|
+
# Normalize the direction vector
|
|
512
|
+
if direction.length() > 0:
|
|
513
|
+
direction = direction.normalize()
|
|
514
|
+
else:
|
|
515
|
+
direction = Vector2(0, 0)
|
|
516
|
+
|
|
517
|
+
# Scale the direction vector by the circle's radius and add the circle's
|
|
518
|
+
# position
|
|
519
|
+
return c.pos + direction * c.radius
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def _closest_point_on_circle_to_circle(c1: Circle, c2: Circle) -> Vector2:
|
|
523
|
+
return closest(c1, c2.pos)
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
@overload
|
|
527
|
+
def overlaps(g1: Vector2, g2: Vector2) -> bool: ...
|
|
528
|
+
@overload
|
|
529
|
+
def overlaps(g1: Vector2, g2: Line) -> bool: ...
|
|
530
|
+
@overload
|
|
531
|
+
def overlaps(g1: Vector2, g2: Rect) -> bool: ...
|
|
532
|
+
@overload
|
|
533
|
+
def overlaps(g1: Vector2, g2: Circle) -> bool: ...
|
|
534
|
+
@overload
|
|
535
|
+
def overlaps(g1: Line, g2: Vector2) -> bool: ...
|
|
536
|
+
@overload
|
|
537
|
+
def overlaps(g1: Line, g2: Line) -> bool: ...
|
|
538
|
+
@overload
|
|
539
|
+
def overlaps(g1: Line, g2: Rect) -> bool: ...
|
|
540
|
+
@overload
|
|
541
|
+
def overlaps(g1: Line, g2: Circle) -> bool: ...
|
|
542
|
+
@overload
|
|
543
|
+
def overlaps(g1: Rect, g2: Vector2) -> bool: ...
|
|
544
|
+
@overload
|
|
545
|
+
def overlaps(g1: Rect, g2: Line) -> bool: ...
|
|
546
|
+
@overload
|
|
547
|
+
def overlaps(g1: Rect, g2: Rect) -> bool: ...
|
|
548
|
+
@overload
|
|
549
|
+
def overlaps(g1: Rect, g2: Circle) -> bool: ...
|
|
550
|
+
@overload
|
|
551
|
+
def overlaps(g1: Circle, g2: Vector2) -> bool: ...
|
|
552
|
+
@overload
|
|
553
|
+
def overlaps(g1: Circle, g2: Line) -> bool: ...
|
|
554
|
+
@overload
|
|
555
|
+
def overlaps(g1: Circle, g2: Rect) -> bool: ...
|
|
556
|
+
@overload
|
|
557
|
+
def overlaps(g1: Circle, g2: Circle) -> bool: ...
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def overlaps(
|
|
561
|
+
g1: Vector2 | Line | Rect | Circle, g2: Vector2 | Line | Rect | Circle
|
|
562
|
+
) -> bool:
|
|
563
|
+
if isinstance(g1, Vector2):
|
|
564
|
+
if isinstance(g2, Vector2):
|
|
565
|
+
return _point_overlaps_point(g1, g2)
|
|
566
|
+
elif isinstance(g2, Line):
|
|
567
|
+
return _point_overlaps_line(g1, g2)
|
|
568
|
+
if isinstance(g2, Rect):
|
|
569
|
+
return _point_overlaps_rect(g1, g2)
|
|
570
|
+
elif isinstance(g2, Circle):
|
|
571
|
+
return _point_overlaps_circle(g1, g2)
|
|
572
|
+
elif isinstance(g1, Line):
|
|
573
|
+
if isinstance(g2, Vector2):
|
|
574
|
+
return _line_overlaps_point(g1, g2)
|
|
575
|
+
elif isinstance(g2, Line):
|
|
576
|
+
return _line_overlaps_line(g1, g2)
|
|
577
|
+
elif isinstance(g2, Rect):
|
|
578
|
+
return _line_overlaps_rect(g1, g2)
|
|
579
|
+
elif isinstance(g2, Circle):
|
|
580
|
+
return _line_overlaps_circle(g1, g2)
|
|
581
|
+
elif isinstance(g1, Rect):
|
|
582
|
+
if isinstance(g2, Vector2):
|
|
583
|
+
return _rect_overlaps_point(g1, g2)
|
|
584
|
+
elif isinstance(g2, Line):
|
|
585
|
+
return _rect_overlaps_line(g1, g2)
|
|
586
|
+
elif isinstance(g2, Rect):
|
|
587
|
+
return _rect_overlaps_rect(g1, g2)
|
|
588
|
+
elif isinstance(g2, Circle):
|
|
589
|
+
return _rect_overlaps_circle(g1, g2)
|
|
590
|
+
elif isinstance(g1, Circle):
|
|
591
|
+
if isinstance(g2, Vector2):
|
|
592
|
+
return _circle_overlaps_point(g1, g2)
|
|
593
|
+
elif isinstance(g2, Line):
|
|
594
|
+
return _circle_overlaps_line(g1, g2)
|
|
595
|
+
elif isinstance(g2, Rect):
|
|
596
|
+
return _circle_overlaps_rect(g1, g2)
|
|
597
|
+
elif isinstance(g2, Circle):
|
|
598
|
+
return _circle_overlaps_circle(g1, g2)
|
|
599
|
+
|
|
600
|
+
msg = f"Unsupported types {type(g1)} and {type(g2)}"
|
|
601
|
+
raise ValueError(msg)
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
# Point overlaps ...
|
|
605
|
+
def _point_overlaps_point(p1: Vector2, p2: Vector2) -> bool:
|
|
606
|
+
return contains(p1, p2)
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def _point_overlaps_line(p: Vector2, l: Line) -> bool: # noqa: E741
|
|
610
|
+
return contains(l, p)
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
def _point_overlaps_rect(p: Vector2, r: Rect) -> bool:
|
|
614
|
+
return overlaps(r, p)
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def _point_overlaps_circle(p: Vector2, c: Circle) -> bool:
|
|
618
|
+
return overlaps(c, p)
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
# Line overlaps ...
|
|
622
|
+
def _line_overlaps_point(l: Line, p: Vector2) -> bool: # noqa: E741
|
|
623
|
+
return contains(l, p)
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def _line_overlaps_line(l1: Line, l2: Line) -> bool:
|
|
627
|
+
d = (l2.end.y - l2.start.y) * (l1.end.x - l1.start.x) - (l2.end.x - l2.start.x) * (
|
|
628
|
+
l1.end.y - l1.start.y
|
|
629
|
+
)
|
|
630
|
+
ua = (
|
|
631
|
+
(l2.end.x - l2.start.x) * (l1.start.y - l2.start.y)
|
|
632
|
+
- (l2.end.y - l2.start.y) * (l1.start.x - l2.start.x)
|
|
633
|
+
) / d
|
|
634
|
+
ub = (
|
|
635
|
+
(l1.end.x - l1.start.x) * (l1.start.y - l2.start.y)
|
|
636
|
+
- (l1.end.y - l1.start.y) * (l1.start.x - l2.start.x)
|
|
637
|
+
) / d
|
|
638
|
+
return 0 <= ua <= 1 and 0 <= ub <= 1
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def _line_overlaps_rect(l: Line, r: Rect) -> bool: # noqa: E741
|
|
642
|
+
return overlaps(r, l)
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
def _line_overlaps_circle(l: Line, c: Circle) -> bool: # noqa: E741
|
|
646
|
+
return overlaps(c, l)
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
# Rect overlaps ...
|
|
650
|
+
def _rect_overlaps_point(r: Rect, p: Vector2) -> bool:
|
|
651
|
+
return contains(r, p)
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def _rect_overlaps_line(r: Rect, l: Line) -> bool: # noqa: E741
|
|
655
|
+
return (
|
|
656
|
+
contains(r, l.start)
|
|
657
|
+
or overlaps(r.top(), l)
|
|
658
|
+
or overlaps(r.bottom(), l)
|
|
659
|
+
or overlaps(r.right(), l)
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
def _rect_overlaps_rect(r1: Rect, r2: Rect) -> bool:
|
|
664
|
+
return (
|
|
665
|
+
r1.pos.x <= r2.pos.x + r2.size.x
|
|
666
|
+
and r1.pos.x + r1.size.x >= r2.pos.x
|
|
667
|
+
and r1.pos.y <= r2.pos.y + r2.size.y
|
|
668
|
+
and r1.pos.y + r1.size.y >= r2.pos.y
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
def _rect_overlaps_circle(r: Rect, c: Circle) -> bool:
|
|
673
|
+
return overlaps(c, r)
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
# Circle overlaps ...
|
|
677
|
+
def _circle_overlaps_point(c: Circle, p: Vector2) -> bool:
|
|
678
|
+
return contains(c, p)
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
def _circle_overlaps_line(c: Circle, l: Line) -> bool: # noqa: E741
|
|
682
|
+
closest_p = closest(l, c.pos)
|
|
683
|
+
return (c.pos - closest_p).magnitude_squared() <= (c.radius**2)
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def _circle_overlaps_rect(c: Circle, r: Rect) -> bool:
|
|
687
|
+
overlap = (
|
|
688
|
+
Vector2(
|
|
689
|
+
clamp(c.pos.x, r.pos.x, r.pos.x + r.size.x),
|
|
690
|
+
clamp(c.pos.y, r.pos.y, r.pos.y + r.size.y),
|
|
691
|
+
)
|
|
692
|
+
- c.pos
|
|
693
|
+
).magnitude_squared()
|
|
694
|
+
# TODO: if (std::isnan(overlap)) overlap = 0
|
|
695
|
+
return (overlap - (c.radius**2)) < 0
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
def _circle_overlaps_circle(c1: Circle, c2: Circle) -> bool:
|
|
699
|
+
return (c1.pos - c2.pos).magnitude_squared() <= (c1.radius + c2.radius) * (
|
|
700
|
+
c1.radius + c2.radius
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
@overload
|
|
705
|
+
def intersects(g1: Vector2, g2: Vector2) -> list[Vector2]: ...
|
|
706
|
+
@overload
|
|
707
|
+
def intersects(g1: Vector2, g2: Line) -> list[Vector2]: ...
|
|
708
|
+
@overload
|
|
709
|
+
def intersects(g1: Vector2, g2: Rect) -> list[Vector2]: ...
|
|
710
|
+
@overload
|
|
711
|
+
def intersects(g1: Vector2, g2: Circle) -> list[Vector2]: ...
|
|
712
|
+
@overload
|
|
713
|
+
def intersects(g1: Line, g2: Vector2) -> list[Vector2]: ...
|
|
714
|
+
@overload
|
|
715
|
+
def intersects(g1: Line, g2: Line) -> list[Vector2]: ...
|
|
716
|
+
@overload
|
|
717
|
+
def intersects(g1: Line, g2: Rect) -> list[Vector2]: ...
|
|
718
|
+
@overload
|
|
719
|
+
def intersects(g1: Line, g2: Circle) -> list[Vector2]: ...
|
|
720
|
+
@overload
|
|
721
|
+
def intersects(g1: Rect, g2: Vector2) -> list[Vector2]: ...
|
|
722
|
+
@overload
|
|
723
|
+
def intersects(g1: Rect, g2: Line) -> list[Vector2]: ...
|
|
724
|
+
@overload
|
|
725
|
+
def intersects(g1: Rect, g2: Rect) -> list[Vector2]: ...
|
|
726
|
+
@overload
|
|
727
|
+
def intersects(g1: Rect, g2: Circle) -> list[Vector2]: ...
|
|
728
|
+
@overload
|
|
729
|
+
def intersects(g1: Circle, g2: Vector2) -> list[Vector2]: ...
|
|
730
|
+
@overload
|
|
731
|
+
def intersects(g1: Circle, g2: Line) -> list[Vector2]: ...
|
|
732
|
+
@overload
|
|
733
|
+
def intersects(g1: Circle, g2: Rect) -> list[Vector2]: ...
|
|
734
|
+
@overload
|
|
735
|
+
def intersects(g1: Circle, g2: Circle) -> list[Vector2]: ...
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
def intersects(
|
|
739
|
+
g1: Vector2 | Line | Rect | Circle, g2: Vector2 | Line | Rect | Circle
|
|
740
|
+
) -> list[Vector2]:
|
|
741
|
+
if isinstance(g1, Vector2):
|
|
742
|
+
if isinstance(g2, Vector2):
|
|
743
|
+
return _point_intersects_point(g1, g2)
|
|
744
|
+
elif isinstance(g2, Line):
|
|
745
|
+
return _point_intersects_line(g1, g2)
|
|
746
|
+
if isinstance(g2, Rect):
|
|
747
|
+
return _point_intersects_rect(g1, g2)
|
|
748
|
+
elif isinstance(g2, Circle):
|
|
749
|
+
return _point_intersects_circle(g1, g2)
|
|
750
|
+
elif isinstance(g1, Line):
|
|
751
|
+
if isinstance(g2, Vector2):
|
|
752
|
+
return _line_intersects_point(g1, g2)
|
|
753
|
+
elif isinstance(g2, Line):
|
|
754
|
+
return _line_intersects_line(g1, g2)
|
|
755
|
+
elif isinstance(g2, Rect):
|
|
756
|
+
return _line_intersects_rect(g1, g2)
|
|
757
|
+
elif isinstance(g2, Circle):
|
|
758
|
+
return _line_intersects_circle(g1, g2)
|
|
759
|
+
elif isinstance(g1, Rect):
|
|
760
|
+
if isinstance(g2, Vector2):
|
|
761
|
+
return _rect_intersects_point(g1, g2)
|
|
762
|
+
elif isinstance(g2, Line):
|
|
763
|
+
return _rect_intersects_line(g1, g2)
|
|
764
|
+
elif isinstance(g2, Rect):
|
|
765
|
+
return _rect_intersects_rect(g1, g2)
|
|
766
|
+
elif isinstance(g2, Circle):
|
|
767
|
+
return _rect_intersects_circle(g1, g2)
|
|
768
|
+
elif isinstance(g1, Circle):
|
|
769
|
+
if isinstance(g2, Vector2):
|
|
770
|
+
return _circle_intersects_point(g1, g2)
|
|
771
|
+
elif isinstance(g2, Line):
|
|
772
|
+
return _circle_intersects_line(g1, g2)
|
|
773
|
+
elif isinstance(g2, Rect):
|
|
774
|
+
return _circle_intersects_rect(g1, g2)
|
|
775
|
+
elif isinstance(g2, Circle):
|
|
776
|
+
return _circle_intersects_circle(g1, g2)
|
|
777
|
+
|
|
778
|
+
msg = f"Unsupported types {type(g1)} and {type(g2)}"
|
|
779
|
+
raise ValueError(msg)
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
# Point intersects ...
|
|
783
|
+
def _point_intersects_point(p1: Vector2, p2: Vector2) -> list[Vector2]:
|
|
784
|
+
return [p1] if contains(p1, p2) else []
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
def _point_intersects_line(p: Vector2, l: Line) -> list[Vector2]: # noqa: E741
|
|
788
|
+
return intersects(l, p)
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
def _point_intersects_rect(p: Vector2, r: Rect) -> list[Vector2]:
|
|
792
|
+
return intersects(r, p)
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
def _point_intersects_circle(p: Vector2, c: Circle) -> list[Vector2]:
|
|
796
|
+
return intersects(c, p)
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
# Line intersects ...
|
|
800
|
+
def _line_intersects_point(l: Line, p: Vector2) -> list[Vector2]: # noqa: E741
|
|
801
|
+
return [p] if contains(l, p) else []
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
def _line_intersects_line(l1: Line, l2: Line, infinite: bool = False) -> list[Vector2]:
|
|
805
|
+
rd = l1.vector().cross(l2.vector())
|
|
806
|
+
if rd == 0:
|
|
807
|
+
# Parallel or Colinear TODO: return two points
|
|
808
|
+
return []
|
|
809
|
+
|
|
810
|
+
rd = 1.0 / rd
|
|
811
|
+
rn = (
|
|
812
|
+
(l2.end.x - l2.start.x) * (l1.start.y - l2.start.y)
|
|
813
|
+
- (l2.end.y - l2.start.y) * (l1.start.x - l2.start.x)
|
|
814
|
+
) * rd
|
|
815
|
+
sn = (
|
|
816
|
+
(l1.end.x - l1.start.x) * (l1.start.y - l2.start.y)
|
|
817
|
+
- (l1.end.y - l1.start.y) * (l1.start.x - l2.start.x)
|
|
818
|
+
) * rd
|
|
819
|
+
|
|
820
|
+
if not infinite and (rn < 0.0 or rn > 1.0 or sn < 0.0 or sn > 1.0):
|
|
821
|
+
return [] # Intersection not within line segment
|
|
822
|
+
return [l1.start + rn * l1.vector()]
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
def _line_intersects_rect(l: Line, r: Rect) -> list[Vector2]: # noqa: E741
|
|
826
|
+
return intersects(r, l)
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
def _line_intersects_circle(l: Line, c: Circle) -> list[Vector2]: # noqa: E741
|
|
830
|
+
return intersects(c, l)
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
# Rect intersects ...
|
|
834
|
+
def _rect_intersects_point(r: Rect, p: Vector2) -> list[Vector2]:
|
|
835
|
+
for i in range(r.side_count()):
|
|
836
|
+
if contains(r.side(i), p):
|
|
837
|
+
return [p]
|
|
838
|
+
return []
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
def _rect_intersects_line(r: Rect, l: Line) -> list[Vector2]: # noqa: E741
|
|
842
|
+
intersections = []
|
|
843
|
+
for i in range(r.side_count()):
|
|
844
|
+
intersections.extend(intersects(r.side(i), l))
|
|
845
|
+
|
|
846
|
+
return filter_duplicates(intersections)
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
def _rect_intersects_rect(r1: Rect, r2: Rect) -> list[Vector2]:
|
|
850
|
+
intersections = []
|
|
851
|
+
for i in range(r2.side_count()):
|
|
852
|
+
intersections.extend(intersects(r1, r2.side(i)))
|
|
853
|
+
|
|
854
|
+
return filter_duplicates(intersections)
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
def _rect_intersects_circle(r: Rect, c: Circle) -> list[Vector2]:
|
|
858
|
+
return intersects(c, r)
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
# Circle intersects ...
|
|
862
|
+
def _circle_intersects_point(c: Circle, p: Vector2) -> list[Vector2]:
|
|
863
|
+
if abs((p - c.pos).magnitude_squared() - (c.radius**2)) <= EPSILON:
|
|
864
|
+
return [p]
|
|
865
|
+
return []
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
def _circle_intersects_line(c: Circle, l: Line) -> list[Vector2]: # noqa: E741
|
|
869
|
+
closest_point_to_segment = closest(l, c.pos)
|
|
870
|
+
|
|
871
|
+
if overlaps(c, closest_point_to_segment):
|
|
872
|
+
# Circle is too far away
|
|
873
|
+
return []
|
|
874
|
+
|
|
875
|
+
# Compute closest to the circle on the line
|
|
876
|
+
d = l.vector()
|
|
877
|
+
u_line = d.dot(c.pos - l.start) / d.magnitude_squared()
|
|
878
|
+
closest_point_to_line = l.start + u_line * d
|
|
879
|
+
dist_to_line = (c.pos - closest_point_to_line).magnitude_squared()
|
|
880
|
+
|
|
881
|
+
if abs(dist_to_line - c.radius**2) < EPSILON:
|
|
882
|
+
# Circle "kisses" the line
|
|
883
|
+
return [closest_point_to_line]
|
|
884
|
+
|
|
885
|
+
# Circle intersects the line
|
|
886
|
+
length = math.sqrt(c.radius**2 - dist_to_line)
|
|
887
|
+
p1 = closest_point_to_line + l.vector().normalize() * length
|
|
888
|
+
p2 = closest_point_to_line - l.vector().normalize() * length
|
|
889
|
+
|
|
890
|
+
intersections = []
|
|
891
|
+
if (p1 - closest(l, p1)).magnitude_squared() < EPSILON**2:
|
|
892
|
+
intersections.append(p1)
|
|
893
|
+
if (p2 - closest(l, p2)).magnitude_squared() < EPSILON**2:
|
|
894
|
+
intersections.append(p2)
|
|
895
|
+
|
|
896
|
+
return filter_duplicates(intersections)
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
def _circle_intersects_rect(c: Circle, r: Rect) -> list[Vector2]:
|
|
900
|
+
intersections = []
|
|
901
|
+
for i in range(r.side_count()):
|
|
902
|
+
intersections.extend(intersects(c, r.side(i)))
|
|
903
|
+
|
|
904
|
+
return filter_duplicates(intersections)
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
def _circle_intersects_circle(c1: Circle, c2: Circle) -> list[Vector2]:
|
|
908
|
+
if c1.pos == c2.pos:
|
|
909
|
+
# Circles are either within one another so cannot intersect or
|
|
910
|
+
# are identical so share all point but this cannot be
|
|
911
|
+
# represented in a good way in the return value
|
|
912
|
+
return []
|
|
913
|
+
|
|
914
|
+
between = c2.pos - c1.pos
|
|
915
|
+
dist2 = between.magnitude_squared()
|
|
916
|
+
radius_sum = c1.radius + c2.radius
|
|
917
|
+
if dist2 > radius_sum**2:
|
|
918
|
+
# Circles are too far apart to be touching
|
|
919
|
+
return []
|
|
920
|
+
if contains(c1, c2) or contains(c2, c1):
|
|
921
|
+
# One circle is inside of the other, they can't be intersecting
|
|
922
|
+
return []
|
|
923
|
+
if dist2 == radius_sum:
|
|
924
|
+
# Circles are touching at exactly 1 point
|
|
925
|
+
return [Vector2(c1.pos + between.normalize() * c1.radius)]
|
|
926
|
+
|
|
927
|
+
# Otherwise, they're touching at 2 points
|
|
928
|
+
dist = math.sqrt(dist2)
|
|
929
|
+
cc_dist = (dist2 + c1.radius**2 - c2.radius**2) / (2 * dist)
|
|
930
|
+
chord_center = c1.pos + between.normalize() * cc_dist
|
|
931
|
+
half_chord = between.normalize().rotate(-90) * math.sqrt(
|
|
932
|
+
c1.radius**2 - cc_dist**2
|
|
933
|
+
) # FIXME: 90 or -90
|
|
934
|
+
return [chord_center + half_chord, chord_center - half_chord]
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
@overload
|
|
938
|
+
def resolve_collision(
|
|
939
|
+
g1: Vector2, g2: Vector2, move_both: bool = False
|
|
940
|
+
) -> tuple[Vector2, Vector2]: ...
|
|
941
|
+
@overload
|
|
942
|
+
def resolve_collision(
|
|
943
|
+
g1: Vector2, g2: Line, move_both: bool = False
|
|
944
|
+
) -> tuple[Vector2, Vector2]: ...
|
|
945
|
+
@overload
|
|
946
|
+
def resolve_collision(
|
|
947
|
+
g1: Vector2, g2: Rect, move_both: bool = False
|
|
948
|
+
) -> tuple[Vector2, Vector2]: ...
|
|
949
|
+
@overload
|
|
950
|
+
def resolve_collision(
|
|
951
|
+
g1: Vector2, g2: Circle, move_both: bool = False
|
|
952
|
+
) -> tuple[Vector2, Vector2]: ...
|
|
953
|
+
@overload
|
|
954
|
+
def resolve_collision(
|
|
955
|
+
g1: Line, g2: Vector2, move_both: bool = False
|
|
956
|
+
) -> tuple[Vector2, Vector2]: ...
|
|
957
|
+
@overload
|
|
958
|
+
def resolve_collision(
|
|
959
|
+
g1: Line, g2: Line, move_both: bool = False
|
|
960
|
+
) -> tuple[Vector2, Vector2]: ...
|
|
961
|
+
@overload
|
|
962
|
+
def resolve_collision(
|
|
963
|
+
g1: Line, g2: Rect, move_both: bool = False
|
|
964
|
+
) -> tuple[Vector2, Vector2]: ...
|
|
965
|
+
@overload
|
|
966
|
+
def resolve_collision(
|
|
967
|
+
g1: Line, g2: Circle, move_both: bool = False
|
|
968
|
+
) -> tuple[Vector2, Vector2]: ...
|
|
969
|
+
@overload
|
|
970
|
+
def resolve_collision(
|
|
971
|
+
g1: Rect, g2: Vector2, move_both: bool = False
|
|
972
|
+
) -> tuple[Vector2, Vector2]: ...
|
|
973
|
+
@overload
|
|
974
|
+
def resolve_collision(
|
|
975
|
+
g1: Rect, g2: Line, move_both: bool = False
|
|
976
|
+
) -> tuple[Vector2, Vector2]: ...
|
|
977
|
+
@overload
|
|
978
|
+
def resolve_collision(
|
|
979
|
+
g1: Rect, g2: Rect, move_both: bool = False
|
|
980
|
+
) -> tuple[Vector2, Vector2]: ...
|
|
981
|
+
@overload
|
|
982
|
+
def resolve_collision(
|
|
983
|
+
g1: Rect, g2: Circle, move_both: bool = False
|
|
984
|
+
) -> tuple[Vector2, Vector2]: ...
|
|
985
|
+
@overload
|
|
986
|
+
def resolve_collision(
|
|
987
|
+
g1: Circle, g2: Vector2, move_both: bool = False
|
|
988
|
+
) -> tuple[Vector2, Vector2]: ...
|
|
989
|
+
@overload
|
|
990
|
+
def resolve_collision(
|
|
991
|
+
g1: Circle, g2: Line, move_both: bool = False
|
|
992
|
+
) -> tuple[Vector2, Vector2]: ...
|
|
993
|
+
@overload
|
|
994
|
+
def resolve_collision(
|
|
995
|
+
g1: Circle, g2: Rect, move_both: bool = False
|
|
996
|
+
) -> tuple[Vector2, Vector2]: ...
|
|
997
|
+
@overload
|
|
998
|
+
def resolve_collision(
|
|
999
|
+
g1: Circle, g2: Circle, move_both: bool = False
|
|
1000
|
+
) -> tuple[Vector2, Vector2]: ...
|
|
1001
|
+
|
|
1002
|
+
|
|
1003
|
+
def resolve_collision(
|
|
1004
|
+
g1: Vector2 | Line | Rect | Circle,
|
|
1005
|
+
g2: Vector2 | Line | Rect | Circle,
|
|
1006
|
+
move_both: bool = False,
|
|
1007
|
+
) -> tuple[Vector2, Vector2]:
|
|
1008
|
+
if isinstance(g1, Vector2):
|
|
1009
|
+
if isinstance(g2, Vector2):
|
|
1010
|
+
return _point_collides_with_point(g1, g2, move_both)
|
|
1011
|
+
elif isinstance(g2, Line):
|
|
1012
|
+
return _point_collides_with_line(g1, g2, move_both)
|
|
1013
|
+
if isinstance(g2, Rect):
|
|
1014
|
+
return _point_collides_with_rect(g1, g2, move_both)
|
|
1015
|
+
elif isinstance(g2, Circle):
|
|
1016
|
+
return _point_collides_with_circle(g1, g2, move_both)
|
|
1017
|
+
elif isinstance(g1, Line):
|
|
1018
|
+
if isinstance(g2, Vector2):
|
|
1019
|
+
return _line_collides_with_point(g1, g2, move_both)
|
|
1020
|
+
elif isinstance(g2, Line):
|
|
1021
|
+
return _line_collides_with_line(g1, g2, move_both)
|
|
1022
|
+
elif isinstance(g2, Rect):
|
|
1023
|
+
return _line_collides_with_rect(g1, g2, move_both)
|
|
1024
|
+
elif isinstance(g2, Circle):
|
|
1025
|
+
return _line_collides_with_circle(g1, g2, move_both)
|
|
1026
|
+
elif isinstance(g1, Rect):
|
|
1027
|
+
if isinstance(g2, Vector2):
|
|
1028
|
+
return _rect_collides_with_point(g1, g2, move_both)
|
|
1029
|
+
elif isinstance(g2, Line):
|
|
1030
|
+
return _rect_collides_with_line(g1, g2, move_both)
|
|
1031
|
+
elif isinstance(g2, Rect):
|
|
1032
|
+
return _rect_collides_with_rect(g1, g2, move_both)
|
|
1033
|
+
elif isinstance(g2, Circle):
|
|
1034
|
+
return _rect_collides_with_circle(g1, g2, move_both)
|
|
1035
|
+
elif isinstance(g1, Circle):
|
|
1036
|
+
if isinstance(g2, Vector2):
|
|
1037
|
+
return _circle_collides_with_point(g1, g2, move_both)
|
|
1038
|
+
elif isinstance(g2, Line):
|
|
1039
|
+
return _circle_collides_with_line(g1, g2, move_both)
|
|
1040
|
+
elif isinstance(g2, Rect):
|
|
1041
|
+
return _circle_collides_with_rect(g1, g2, move_both)
|
|
1042
|
+
elif isinstance(g2, Circle):
|
|
1043
|
+
return _circle_collides_with_circle(g1, g2, move_both)
|
|
1044
|
+
|
|
1045
|
+
msg = f"Unsupported types {type(g1)} and {type(g2)}"
|
|
1046
|
+
raise ValueError(msg)
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
# Point collides_with ...
|
|
1050
|
+
def _point_collides_with_point(
|
|
1051
|
+
p1: Vector2, p2: Vector2, move_both: bool = False
|
|
1052
|
+
) -> tuple[Vector2, Vector2]:
|
|
1053
|
+
raise NotImplementedError
|
|
1054
|
+
|
|
1055
|
+
|
|
1056
|
+
def _point_collides_with_line(
|
|
1057
|
+
p1: Vector2, l2: Line, move_both: bool = False
|
|
1058
|
+
) -> tuple[Vector2, Vector2]:
|
|
1059
|
+
raise NotImplementedError
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
def _point_collides_with_rect(
|
|
1063
|
+
p1: Vector2, r2: Rect, move_both: bool = False
|
|
1064
|
+
) -> tuple[Vector2, Vector2]:
|
|
1065
|
+
raise NotImplementedError
|
|
1066
|
+
|
|
1067
|
+
|
|
1068
|
+
def _point_collides_with_circle(
|
|
1069
|
+
p1: Vector2, c2: Circle, move_both: bool = False
|
|
1070
|
+
) -> tuple[Vector2, Vector2]:
|
|
1071
|
+
raise NotImplementedError
|
|
1072
|
+
|
|
1073
|
+
|
|
1074
|
+
# Line collides_with ...
|
|
1075
|
+
def _line_collides_with_point(
|
|
1076
|
+
l1: Line, p2: Vector2, move_both: bool = False
|
|
1077
|
+
) -> tuple[Vector2, Vector2]:
|
|
1078
|
+
raise NotImplementedError
|
|
1079
|
+
|
|
1080
|
+
|
|
1081
|
+
def _line_collides_with_line(
|
|
1082
|
+
l1: Line, l2: Line, move_both: bool = False
|
|
1083
|
+
) -> tuple[Vector2, Vector2]:
|
|
1084
|
+
raise NotImplementedError
|
|
1085
|
+
|
|
1086
|
+
|
|
1087
|
+
def _line_collides_with_rect(
|
|
1088
|
+
l1: Line, r2: Rect, move_both: bool = False
|
|
1089
|
+
) -> tuple[Vector2, Vector2]:
|
|
1090
|
+
raise NotImplementedError
|
|
1091
|
+
|
|
1092
|
+
|
|
1093
|
+
def _line_collides_with_circle(
|
|
1094
|
+
l1: Line, c2: Circle, move_both: bool = False
|
|
1095
|
+
) -> tuple[Vector2, Vector2]:
|
|
1096
|
+
raise NotImplementedError
|
|
1097
|
+
|
|
1098
|
+
|
|
1099
|
+
# Rect collides_with ...
|
|
1100
|
+
def _rect_collides_with_point(
|
|
1101
|
+
r1: Rect, p2: Vector2, move_both: bool = False
|
|
1102
|
+
) -> tuple[Vector2, Vector2]:
|
|
1103
|
+
raise NotImplementedError
|
|
1104
|
+
|
|
1105
|
+
|
|
1106
|
+
def _rect_collides_with_line(
|
|
1107
|
+
r1: Rect, l2: Line, move_both: bool = False
|
|
1108
|
+
) -> tuple[Vector2, Vector2]:
|
|
1109
|
+
raise NotImplementedError
|
|
1110
|
+
|
|
1111
|
+
|
|
1112
|
+
def _rect_collides_with_rect(
|
|
1113
|
+
r1: Rect, r2: Rect, move_both: bool = False
|
|
1114
|
+
) -> tuple[Vector2, Vector2]:
|
|
1115
|
+
"""Calculate the collision between two rectangles.
|
|
1116
|
+
|
|
1117
|
+
This function calculates the overlap of two rectangles r1 and r2
|
|
1118
|
+
and returns the new positions of both rectangles as tuple. By
|
|
1119
|
+
default, only the first rectangle will be moved and the second will
|
|
1120
|
+
be considered 'solid'. This can be changed with the move_both flag.
|
|
1121
|
+
In that case, both rectangles will be pushed away from each other
|
|
1122
|
+
by the same amount.
|
|
1123
|
+
|
|
1124
|
+
Args:
|
|
1125
|
+
r1: Rectangle with `pos` and `size`
|
|
1126
|
+
r2: Second rectangle, solid by default
|
|
1127
|
+
move_both: If True, both rectangles will be moved.
|
|
1128
|
+
|
|
1129
|
+
Returns:
|
|
1130
|
+
Tuple with the new positions of the rectangles.
|
|
1131
|
+
"""
|
|
1132
|
+
|
|
1133
|
+
# Calculate the horizontal (X) and vertical (Y) overlap
|
|
1134
|
+
br1 = r1.pos + r1.size
|
|
1135
|
+
br2 = r2.pos + r2.size
|
|
1136
|
+
overlap = vmin(br1, br2) - vmax(r1.pos, r2.pos)
|
|
1137
|
+
|
|
1138
|
+
if overlap.x <= 0 or overlap.y <= 0:
|
|
1139
|
+
return r1.pos, r2.pos
|
|
1140
|
+
|
|
1141
|
+
# Check if both rectangles have the same center point
|
|
1142
|
+
r1_center = r1.pos + r1.size / 2.0
|
|
1143
|
+
r2_center = r2.pos + r2.size / 2.0
|
|
1144
|
+
if r1_center == r2_center:
|
|
1145
|
+
# Special case: Both rectangles have identical center points
|
|
1146
|
+
# In this case, one (or both) of the rectangles will get a
|
|
1147
|
+
# Small adjustment to break perfect overlap
|
|
1148
|
+
push = Vector2(0.1, 0.1)
|
|
1149
|
+
if move_both:
|
|
1150
|
+
new_pos1 = r1.pos - push / 2.0
|
|
1151
|
+
new_pos2 = r2.pos + push / 2.0
|
|
1152
|
+
else:
|
|
1153
|
+
new_pos1 = r1.pos - push
|
|
1154
|
+
new_pos2 = r2.pos
|
|
1155
|
+
return new_pos1, new_pos2
|
|
1156
|
+
|
|
1157
|
+
push = Vector2()
|
|
1158
|
+
# Resolve horizontal overlap
|
|
1159
|
+
if overlap.x > 0:
|
|
1160
|
+
roverlap = br2.x - r1.pos.x
|
|
1161
|
+
loverlap = br1.x - r2.pos.x
|
|
1162
|
+
if roverlap < loverlap:
|
|
1163
|
+
push.x = roverlap
|
|
1164
|
+
else:
|
|
1165
|
+
push.x = -loverlap
|
|
1166
|
+
|
|
1167
|
+
# Resolve vertical overlap
|
|
1168
|
+
if overlap.y > 0:
|
|
1169
|
+
boverlap = br2.y - r1.pos.y
|
|
1170
|
+
toverlap = br1.y - r2.pos.y
|
|
1171
|
+
if boverlap < toverlap:
|
|
1172
|
+
push.y = boverlap
|
|
1173
|
+
else:
|
|
1174
|
+
push.y = -toverlap
|
|
1175
|
+
|
|
1176
|
+
# Calculate new positions based on the move_both flag
|
|
1177
|
+
if move_both:
|
|
1178
|
+
new_pos1 = r1.pos + push / 2
|
|
1179
|
+
new_pos2 = r2.pos - push / 2
|
|
1180
|
+
else:
|
|
1181
|
+
new_pos1 = r1.pos + push
|
|
1182
|
+
new_pos2 = r2.pos
|
|
1183
|
+
return new_pos1, new_pos2
|
|
1184
|
+
|
|
1185
|
+
|
|
1186
|
+
def _rect_collides_with_circle(
|
|
1187
|
+
r: Rect, c: Circle, move_both: bool = False
|
|
1188
|
+
) -> tuple[Vector2, Vector2]:
|
|
1189
|
+
closest_point = closest(c, r)
|
|
1190
|
+
ray = closest_point - c.pos
|
|
1191
|
+
overlap = c.radius - ray.magnitude()
|
|
1192
|
+
# TODO: std::isnan(overlap): overlap=0
|
|
1193
|
+
new_pos1 = Vector2(r.pos.x, r.pos.y)
|
|
1194
|
+
new_pos2 = Vector2(c.pos.x, c.pos.y)
|
|
1195
|
+
try:
|
|
1196
|
+
ray.normalize()
|
|
1197
|
+
except ValueError:
|
|
1198
|
+
# breakpoint()
|
|
1199
|
+
overlap = 0
|
|
1200
|
+
|
|
1201
|
+
if overlap > 0:
|
|
1202
|
+
if move_both:
|
|
1203
|
+
new_pos1 -= ray.normalize() * overlap * 0.5
|
|
1204
|
+
new_pos2 += ray.normalize() * overlap * 0.5
|
|
1205
|
+
else:
|
|
1206
|
+
new_pos1 -= ray.normalize() * overlap
|
|
1207
|
+
|
|
1208
|
+
return new_pos1, new_pos2
|
|
1209
|
+
|
|
1210
|
+
|
|
1211
|
+
# Circle collides_with ...
|
|
1212
|
+
def _circle_collides_with_point(
|
|
1213
|
+
c1: Circle, p2: Vector2, move_both: bool = False
|
|
1214
|
+
) -> tuple[Vector2, Vector2]:
|
|
1215
|
+
raise NotImplementedError
|
|
1216
|
+
|
|
1217
|
+
|
|
1218
|
+
def _circle_collides_with_line(
|
|
1219
|
+
c1: Circle, l2: Line, move_both: bool = False
|
|
1220
|
+
) -> tuple[Vector2, Vector2]:
|
|
1221
|
+
raise NotImplementedError
|
|
1222
|
+
|
|
1223
|
+
|
|
1224
|
+
def _circle_collides_with_rect(
|
|
1225
|
+
c: Circle, r: Rect, move_both: bool = False
|
|
1226
|
+
) -> tuple[Vector2, Vector2]:
|
|
1227
|
+
new_pos1 = Vector2(c.pos.x, c.pos.y)
|
|
1228
|
+
new_pos2 = Vector2(r.pos.x, r.pos.y)
|
|
1229
|
+
|
|
1230
|
+
closest_point = closest(r, c)
|
|
1231
|
+
# ray = closest_point - c.pos
|
|
1232
|
+
# overlap = c.radius - ray.magnitude()
|
|
1233
|
+
# TODO: std::isnan(overlap): overlap=0
|
|
1234
|
+
# try:
|
|
1235
|
+
# ray.normalize()
|
|
1236
|
+
# except ValueError:
|
|
1237
|
+
# # breakpoint()
|
|
1238
|
+
# overlap = 0
|
|
1239
|
+
|
|
1240
|
+
# if overlap > 0:
|
|
1241
|
+
# if move_both:
|
|
1242
|
+
# new_pos1 -= ray.normalize() * overlap * 0.5
|
|
1243
|
+
# new_pos2 += ray.normalize() * overlap * 0.5
|
|
1244
|
+
# else:
|
|
1245
|
+
# new_pos1 -= ray.normalize() * overlap
|
|
1246
|
+
|
|
1247
|
+
delta = c.pos - closest_point
|
|
1248
|
+
dist_sq = delta.length_squared()
|
|
1249
|
+
if dist_sq == 0:
|
|
1250
|
+
dx = min(abs(c.pos.x - r.left().pos.x), abs(c.pos.x - r.right().pos.x))
|
|
1251
|
+
dy = min(abs(c.pos.y - r.top().pos.y), abs(c.pos.y - r.bottom().pos.y))
|
|
1252
|
+
if dx < dy:
|
|
1253
|
+
normal = Vector2(1, 0) if c.pos.x > r.middle().x else Vector2(-1, 0)
|
|
1254
|
+
else:
|
|
1255
|
+
normal = Vector2(0, 1) if c.pos.y > r.middle().y else Vector2(0, -1)
|
|
1256
|
+
distance = 0
|
|
1257
|
+
else:
|
|
1258
|
+
distance = math.sqrt(dist_sq)
|
|
1259
|
+
normal = delta / distance
|
|
1260
|
+
|
|
1261
|
+
penetration = c.radius - distance
|
|
1262
|
+
if move_both:
|
|
1263
|
+
new_pos1 += normal * penetration * 0.5
|
|
1264
|
+
new_pos2 -= normal * penetration * 0.5
|
|
1265
|
+
else:
|
|
1266
|
+
new_pos1 += normal * penetration
|
|
1267
|
+
return new_pos1, new_pos2
|
|
1268
|
+
|
|
1269
|
+
|
|
1270
|
+
def _circle_collides_with_circle(
|
|
1271
|
+
c1: Circle, c2: Circle, move_both: bool = False
|
|
1272
|
+
) -> tuple[Vector2, Vector2]:
|
|
1273
|
+
new_pos1 = Vector2(c1.pos.x, c1.pos.y)
|
|
1274
|
+
new_pos2 = Vector2(c2.pos.x, c2.pos.y)
|
|
1275
|
+
|
|
1276
|
+
# distance = (c1.pos - c2.pos).magnitude()
|
|
1277
|
+
# overlap = distance - c1.radius - c2.radius
|
|
1278
|
+
|
|
1279
|
+
# offset = c1.pos - c2.pos
|
|
1280
|
+
|
|
1281
|
+
# if distance == 0:
|
|
1282
|
+
# offset = Vector2(overlap, overlap)
|
|
1283
|
+
# else:
|
|
1284
|
+
# offset *= overlap / distance
|
|
1285
|
+
|
|
1286
|
+
# if move_both:
|
|
1287
|
+
# new_pos1 -= offset * 0.5
|
|
1288
|
+
# new_pos2 += offset * 0.5
|
|
1289
|
+
# else:
|
|
1290
|
+
# new_pos1 -= offset
|
|
1291
|
+
delta = c1.pos - c2.pos
|
|
1292
|
+
dist_sq = delta.length_squared()
|
|
1293
|
+
|
|
1294
|
+
if dist_sq == 0:
|
|
1295
|
+
# Deterministic fallback
|
|
1296
|
+
normal = Vector2(1, 0) if id(c1) < id(c2) else Vector2(-1, 0)
|
|
1297
|
+
distance = 0
|
|
1298
|
+
else:
|
|
1299
|
+
distance = math.sqrt(dist_sq)
|
|
1300
|
+
normal = delta / distance
|
|
1301
|
+
|
|
1302
|
+
penetration = c1.radius + c2.radius - distance
|
|
1303
|
+
if penetration <= 0:
|
|
1304
|
+
return new_pos1, new_pos2
|
|
1305
|
+
|
|
1306
|
+
if move_both:
|
|
1307
|
+
correction = normal * (penetration * 0.5)
|
|
1308
|
+
return new_pos1 + correction, new_pos2 - correction
|
|
1309
|
+
|
|
1310
|
+
return new_pos1 + normal * penetration, new_pos2
|
|
1311
|
+
|
|
1312
|
+
|
|
1313
|
+
def vmax(val: Vector2, other: Vector2) -> Vector2:
|
|
1314
|
+
return Vector2(max(val.x, other.x), max(val.y, other.y))
|
|
1315
|
+
|
|
1316
|
+
|
|
1317
|
+
def vmin(val: Vector2, other: Vector2) -> Vector2:
|
|
1318
|
+
return Vector2(min(val.x, other.x), min(val.y, other.y))
|