Coverage for src/gncpy/game_engine/physics2d.py: 0%
90 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-13 06:15 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-13 06:15 +0000
1"""Defines physics engine for 2d games."""
2import os
3import numpy as np
5os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "hide"
6import pygame # noqa
9class Physics2dParams:
10 """Parameters for the 2d physics system to be parsed by the config parser.
12 The types defined in this class determine what type the parser uses.
14 Attributes
15 ----------
16 dt : float
17 Main dt of the game, this is the rendering dt and may be the same as
18 the update dt.
19 step_factor : int
20 Factor to divide the base dt by to get the update dt. Must be positive.
21 Allows multiple incremental physics steps inbetween the main rendering
22 calls. Helps ensure objects don't pass through each other from moving too
23 far in a given frame.
24 min_pos : numpy array
25 Minimium position in real units in the order x, y.
26 dist_width : float
27 Distance in real units for the width of the game world.
28 dist_height : float
29 Distance in real units for the height of the game world.
30 """
32 def __init__(self):
33 super().__init__()
34 self.dt = 0
35 self.step_factor = 1
36 self.min_pos = np.array([])
37 self.dist_width = 0.0
38 self.dist_height = 0.0
40 @property
41 def update_dt(self):
42 """Timestep for physics updates."""
43 return self.dt / self.step_factor
46class Collision2dParams:
47 """Parameters of an axis aligned bounding box for 2d objects.
49 The types defined in this class determine what type the parser uses.
51 Attributes
52 ----------
53 width : int
54 Width in pixels of the bounding box.
55 height : int
56 Height in pixels of the bounding box.
57 """
59 def __init__(self):
60 super().__init__()
61 self.width = -1
62 self.height = -1
65def check_collision2d(bb1, bb2):
66 """Check for a collision between 2 bounding boxes.
68 Parameters
69 ----------
70 bb1 : pygame rect
71 First axis aligned bounding box.
72 bb2 : pygame rect
73 Second axis aligned bounding box.
75 Returns
76 -------
77 bool
78 Flag indicating if there was a collision.
79 """
80 return pygame.Rect.colliderect(bb1, bb2)
83def _get_overlap2d(pt1, pt2, bb1, bb2):
84 """Find the overlap between 2 bounding boxes with some previous position."""
85 delta = (abs(pt1[0] - pt2[0]), abs(pt1[1] - pt2[1]))
86 ox = bb1.width / 2 + bb2.width / 2 - delta[0]
87 oy = bb1.height / 2 + bb2.height / 2 - delta[1]
88 return ox, oy
91def resolve_collision2d(bb1, bb2, trans1, trans2):
92 """Resolve a collision between 2 bounding boxes by moving one.
94 This assumes that the two bounding boxes are colliding. This should first be
95 checked.
97 Parameters
98 ----------
99 bb1 : pygame rect
100 Bounding box to move to resolve collision.
101 bb2 : pygame rect
102 Colliding bounding box.
103 trans1 : :class:`.components.CTransform`
104 Transform component associated with the first bounding box.
105 trans2 : :class:`.components.CTransform`
106 Transform component associated with the second bounding box.
107 """
108 ox, oy = _get_overlap2d(
109 (bb1.centerx, bb1.centery), (bb2.centerx, bb2.centery), bb1, bb2
110 )
111 opx, opy = _get_overlap2d(
112 trans1.last_pos.ravel(), trans2.last_pos.ravel(), bb1, bb2
113 )
115 if opy > 0:
116 trans1.vel[0] = 0
117 if trans1.last_pos[0] < trans1.pos[0]:
118 bb1.centerx -= ox
119 else:
120 bb1.centerx += ox
121 elif opx > 0:
122 trans1.vel[1] = 0
123 if trans1.last_pos[1] < trans1.pos[1]:
124 bb1.centery -= oy
125 else:
126 bb1.centery += oy
128 trans1.pos[0] = bb1.centerx
129 trans1.pos[1] = bb1.centery
132def clamp_window_bounds2d(bb, trans, width, height):
133 """Checks for the bounding box leaving the window and halts it.
135 Parameters
136 ----------
137 bb : pygame rect
138 Axis aligned bounding box.
139 trans : :class:`.components.CTransform`
140 Transform component associated with the bounding box.
141 width : int
142 Width of the window in pixels.
143 height : int
144 Height of the window in pixels.
146 Returns
147 -------
148 out_side : bool
149 Flag for if there was a collision with the side.
150 out_top : bool
151 Flag for if there was a collision with the top or bottom.
152 """
153 out_side = False
154 if bb.left < 0:
155 bb.left = 0
156 trans.vel[0] = 0
157 out_side = True
158 elif bb.right > width:
159 bb.right = width
160 trans.vel[0] = 0
161 out_side = True
163 out_top = False
164 if bb.top < 0:
165 bb.top = 0
166 trans.vel[1] = 0
167 out_top = True
168 elif bb.bottom > height:
169 bb.bottom = height
170 trans.vel[1] = 0
171 out_top = True
173 trans.pos[0] = bb.centerx
174 trans.pos[1] = bb.centery
176 return out_side, out_top
179def pixels_to_dist(pt, dist_per_pix, min_pos=None):
180 """Convert pixel units to real units.
182 Parameters
183 ----------
184 pt : numpy array, float
185 Point to convert to real distance.
186 dist_per_pix : numpy array, float
187 real distance per pixel.
188 min_pos : numpy array or float, optional
189 Minimum position to use for translation. The default is None meaning no
190 translation is applied (i.e. velocity transform).
192 Returns
193 -------
194 numpy array, float
195 converted point
196 """
197 if min_pos is not None:
198 if isinstance(pt, np.ndarray):
199 res = pt.ravel() * dist_per_pix.ravel() + min_pos.ravel()
200 else:
201 res = pt * dist_per_pix + min_pos
202 else:
203 if isinstance(pt, np.ndarray):
204 res = pt.ravel() * dist_per_pix.ravel()
205 else:
206 res = pt * dist_per_pix
208 if isinstance(res, np.ndarray):
209 if res.size > 1:
210 return res.reshape(pt.shape)
211 else:
212 return res.item()
213 else:
214 return res
217def dist_to_pixels(pt, dist_per_pix, min_pos=None):
218 """Convert real units to pixel units.
220 Parameters
221 ----------
222 pt : numpy array, float
223 Point to convert to pixels.
224 dist_per_pix : numpy array, float
225 real distance per pixel.
226 min_pos : numpy array or float, optional
227 Minimum position to use for translation (real units). The default is
228 None meaning no translation is applied (i.e. velocity transform).
230 Returns
231 -------
232 numpy array, float
233 converted point
234 """
235 if min_pos is not None:
236 if isinstance(pt, np.ndarray):
237 res = (pt.ravel() - min_pos.ravel()) / dist_per_pix.ravel()
238 else:
239 res = (pt - min_pos) / dist_per_pix
240 else:
241 if isinstance(pt, np.ndarray):
242 res = pt.ravel() / dist_per_pix.ravel()
243 else:
244 res = pt / dist_per_pix
246 if isinstance(res, np.ndarray):
247 if res.size > 1:
248 return res.reshape(pt.shape).astype(int)
249 else:
250 return res.astype(int).item()
251 else:
252 return int(res)