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

1"""Defines physics engine for 2d games.""" 

2import os 

3import numpy as np 

4 

5os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "hide" 

6import pygame # noqa 

7 

8 

9class Physics2dParams: 

10 """Parameters for the 2d physics system to be parsed by the config parser. 

11 

12 The types defined in this class determine what type the parser uses. 

13 

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 """ 

31 

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 

39 

40 @property 

41 def update_dt(self): 

42 """Timestep for physics updates.""" 

43 return self.dt / self.step_factor 

44 

45 

46class Collision2dParams: 

47 """Parameters of an axis aligned bounding box for 2d objects. 

48 

49 The types defined in this class determine what type the parser uses. 

50 

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 """ 

58 

59 def __init__(self): 

60 super().__init__() 

61 self.width = -1 

62 self.height = -1 

63 

64 

65def check_collision2d(bb1, bb2): 

66 """Check for a collision between 2 bounding boxes. 

67 

68 Parameters 

69 ---------- 

70 bb1 : pygame rect 

71 First axis aligned bounding box. 

72 bb2 : pygame rect 

73 Second axis aligned bounding box. 

74 

75 Returns 

76 ------- 

77 bool 

78 Flag indicating if there was a collision. 

79 """ 

80 return pygame.Rect.colliderect(bb1, bb2) 

81 

82 

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 

89 

90 

91def resolve_collision2d(bb1, bb2, trans1, trans2): 

92 """Resolve a collision between 2 bounding boxes by moving one. 

93 

94 This assumes that the two bounding boxes are colliding. This should first be 

95 checked. 

96 

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 ) 

114 

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 

127 

128 trans1.pos[0] = bb1.centerx 

129 trans1.pos[1] = bb1.centery 

130 

131 

132def clamp_window_bounds2d(bb, trans, width, height): 

133 """Checks for the bounding box leaving the window and halts it. 

134 

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. 

145 

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 

162 

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 

172 

173 trans.pos[0] = bb.centerx 

174 trans.pos[1] = bb.centery 

175 

176 return out_side, out_top 

177 

178 

179def pixels_to_dist(pt, dist_per_pix, min_pos=None): 

180 """Convert pixel units to real units. 

181 

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). 

191 

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 

207 

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 

215 

216 

217def dist_to_pixels(pt, dist_per_pix, min_pos=None): 

218 """Convert real units to pixel units. 

219 

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). 

229 

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 

245 

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)