obstacles.py (17202B)
1 ### EXESOFT PYIGNITION ### 2 # Copyright David Barker 2010 3 # 4 # Obstacle objects 5 6 import pygame 7 from math import sqrt, pow 8 from gravity import UNIVERSAL_CONSTANT_OF_MAKE_GRAVITY_LESS_STUPIDLY_SMALL 9 import keyframes, interpolate 10 from constants import * 11 12 13 MAXDIST = 20.0 14 15 16 def dotproduct2d(v1, v2): 17 return ((v1[0] * v2[0]) + (v1[1] * v2[1])) 18 19 def magnitude(vec): 20 try: 21 return sqrt(vec[0] ** 2 + vec[1] ** 2) 22 except: 23 return 1.0 24 25 def magnitudesquared(vec): 26 try: 27 return (vec[0] ** 2 + vec[1] ** 2) 28 except: 29 return 1.0 30 31 def normalise(vec): 32 mag = magnitude(vec) 33 return [vec[0] / mag, vec[1] / mag] 34 35 36 class Obstacle: 37 def __init__(self, parenteffect, pos, colour, bounce): 38 self.selected = False 39 self.parenteffect = parenteffect 40 self.pos = pos 41 self.colour = colour 42 self.bounce = bounce 43 self.maxdist = MAXDIST # The maximum (square-based, not circle-based) distance away for which forces will still be calculated 44 self.curframe = 0 45 self.keyframes = [] 46 47 def Draw(self, display): 48 pass 49 50 def Update(self): 51 self.curframe = self.curframe + 1 52 53 def OutOfRange(self, pos): 54 return (abs(pos[0] - self.pos[0]) > self.maxdist) or (abs(pos[1] - self.pos[1]) > self.maxdist) 55 56 def InsideObject(self, pos, pradius): 57 pass 58 59 def GetResolved(self, pos, pradius): # Get a resolved position for a particle located inside the object 60 pass 61 62 def GetDist(self, pos): 63 return magnitude([pos[0] - self.pos[0], pos[1] - self.pos[1]]) 64 65 def GetNormal(self, pos): # Gets the normal relevant to a particle at the supplied potision (for example, that of the appropriate side of a squre) 66 pass 67 68 def GetForceFactor(self, pos, pradius): # Gets the force as a factor of maximum available force (0.0 - 1.0), determined by an inverse cube distance law 69 pass 70 71 def GetForce(self, pos, velocity, pradius = 0.0): # Gets the final (vector) force 72 if self.OutOfRange(pos) or self.bounce == 0.0: 73 return [0.0, 0.0] 74 75 if (pos[0] == self.pos[0]) and (pos[1] == self.pos[1]): 76 return [0.0, 0.0] 77 78 normal = self.GetNormal(pos) 79 scalingfactor = -dotproduct2d(normal, velocity) # An integer between 0.0 and 1.0 used to ensure force is maximised for head-on collisions and minimised for scrapes 80 81 if scalingfactor <= 0.0: # The force should never be attractive, so take any negative value of the scaling factor to be zero 82 return [0.0, 0.0] # A scaling factor of zero always results in zero force 83 84 forcefactor = (self.GetForceFactor(pos, pradius)) 85 velmag = magnitude(velocity) # Magnitude of the velocity - multiplied in the force equation 86 87 # Force = bounce factor * velocity * distance force factor (0.0 - 1.0) * angle force factor (0.0 - 1.0), along the direction of the normal pointing away from the obstacle 88 return [normal[0] * forcefactor * velmag * scalingfactor * self.bounce, normal[1] * forcefactor * velmag * scalingfactor * self.bounce] 89 90 def CreateKeyframe(self): 91 pass 92 93 def SetPos(self, newpos): 94 self.CreateKeyframe(self.curframe, pos = newpos) 95 96 def SetColour(self, newcolour): 97 self.CreateKeyframe(self.curframe, colour = newcolour) 98 99 def SetBounce(self, newbounce): 100 self.CreateKeyframe(self.curframe, bounce = newbounce) 101 102 103 class Circle(Obstacle): 104 def __init__(self, parenteffect, pos, colour, bounce, radius): 105 Obstacle.__init__(self, parenteffect, pos, colour, bounce) 106 self.type = "circle" 107 self.radius = radius 108 self.radiussquared = self.radius ** 2 109 self.maxdist = MAXDIST + self.radius 110 self.CreateKeyframe(0, self.pos, self.colour, self.bounce, self.radius) 111 112 def Draw(self, display): 113 offset = self.parenteffect.pos 114 pygame.draw.circle(display, self.colour, (offset[0] + int(self.pos[0]), offset[1] + int(self.pos[1])), int(self.radius)) 115 116 def Update(self): 117 newvars = interpolate.InterpolateKeyframes(self.curframe, {'pos_x':self.pos[0], 'pos_y':self.pos[1], 'colour_r':self.colour[0], 'colour_g':self.colour[1], 'colour_b':self.colour[2], 'bounce':self.bounce, 'radius':self.radius}, self.keyframes) 118 self.pos = (newvars['pos_x'], newvars['pos_y']) 119 self.colour = (newvars['colour_r'], newvars['colour_g'], newvars['colour_b']) 120 self.bounce = newvars['bounce'] 121 self.radius = newvars['radius'] 122 123 Obstacle.Update(self) 124 125 def InsideObject(self, pos, pradius = 0.0): 126 mag = magnitude([pos[0] - self.pos[0], pos[1] - self.pos[1]]) 127 return (((mag - pradius) ** 2) < self.radiussquared) 128 129 def GetResolved(self, pos, pradius = 0.0): 130 if pos == self.pos: # If the position is at this obstacle's origin, shift it up a pixel to avoid divide-by-zero errors 131 return self.GetResolved([pos[0], pos[1] - 1]) 132 133 vec = [pos[0] - self.pos[0], pos[1] - self.pos[1]] 134 mag = magnitude(vec) 135 nor = [vec[0] / mag, vec[1] / mag] 136 137 # Split the pradius into appropriate components by multiplying the normal vector by pradius 138 pradiusvec = [(nor[0] * pradius), (nor[1] * pradius)] 139 140 correctedvec = [nor[0] * (self.radius), nor[1] * (self.radius)] 141 142 return [self.pos[0] + correctedvec[0] + pradiusvec[0], self.pos[1] + correctedvec[1] + pradiusvec[1]] 143 144 def GetNormal(self, pos): 145 vec = [pos[0] - self.pos[0], pos[1] - self.pos[1]] 146 mag = magnitude(vec) 147 148 return [vec[0] / mag, vec[1] / mag] 149 150 def GetForceFactor(self, pos, pradius = 0.0): 151 nvec = self.GetNormal(pos) 152 tempradius = self.radius 153 vec = [tempradius * nvec[0], tempradius * nvec[1]] 154 newpos = [pos[0] - vec[0], pos[1] - vec[1]] 155 156 distcubed = (abs(pow(float(newpos[0] - self.pos[0]), 3.0)) + abs(pow(float(newpos[1] - self.pos[1]), 3.0))) - (pradius * pradius * pradius) 157 if distcubed <= 1.0: 158 return 1.0 159 160 force = (1.0 / distcubed) 161 162 return force 163 164 def CreateKeyframe(self, frame, pos = (None, None), colour = (None, None, None), bounce = None, radius = None, interpolationtype = INTERPOLATIONTYPE_LINEAR): 165 return keyframes.CreateKeyframe(self.keyframes, frame, {'pos_x':pos[0], 'pos_y':pos[1], 'colour_r':colour[0], 'colour_g':colour[1], 'colour_b':colour[2], 'bounce':bounce, 'radius':radius, 'interpolationtype':interpolationtype}) 166 167 def ConsolidateKeyframes(self): 168 keyframes.ConsolidateKeyframes(self.keyframes, self.curframe, {'pos_x':self.pos[0], 'pos_y':self.pos[1], 'colour_r':self.colour[0], 'colour_g':self.colour[1], 'colour_b':self.colour[2], 'bounce':self.bounce, 'radius':self.radius}) 169 170 def SetRadius(self, newradius): 171 self.CreateKeyframe(self.curframe, radius = newradius) 172 173 174 class Rectangle(Obstacle): 175 def __init__(self, parenteffect, pos, colour, bounce, width, height): 176 Obstacle.__init__(self, parenteffect, pos, colour, bounce) 177 self.type = "rectangle" 178 self.width = width 179 self.halfwidth = self.width / 2.0 180 self.height = height 181 self.halfheight = height / 2.0 182 self.maxdist = max(self.halfwidth, self.halfheight) + MAXDIST 183 self.CreateKeyframe(0, self.pos, self.colour, self.bounce, self.width, self.height) 184 185 def Draw(self, display): 186 offset = self.parenteffect.pos 187 pygame.draw.rect(display, self.colour, pygame.Rect(offset[0] + (self.pos[0] - self.halfwidth), offset[1] + (self.pos[1] - self.halfheight), self.width, self.height)) 188 189 def Update(self): 190 newvars = interpolate.InterpolateKeyframes(self.curframe, {'pos_x':self.pos[0], 'pos_y':self.pos[1], 'colour_r':self.colour[0], 'colour_g':self.colour[1], 'colour_b':self.colour[2], 'bounce':self.bounce, 'width':self.width, 'height':self.height}, self.keyframes) 191 self.pos = (newvars['pos_x'], newvars['pos_y']) 192 self.colour = (newvars['colour_r'], newvars['colour_g'], newvars['colour_b']) 193 self.bounce = newvars['bounce'] 194 self.width = newvars['width'] 195 self.halfwidth = self.width / 2.0 196 self.height = newvars['height'] 197 self.halfheight = self.height / 2.0 198 self.maxdist = max(self.halfwidth, self.halfheight) + MAXDIST 199 200 Obstacle.Update(self) 201 202 def InsideObject(self, pos, pradius = 0.0): 203 return (((pos[0] + pradius) > (self.pos[0] - self.halfwidth)) and ((pos[0] - pradius) < (self.pos[0] + self.halfwidth)) and ((pos[1] + pradius) > (self.pos[1] - self.halfheight)) and ((pos[1] - pradius) < self.pos[1] + self.halfheight)) 204 205 def GetResolved(self, pos, pradius = 0.0): 206 if pos == self.pos: # If the position is at this obstacle's origin, shift it up a pixel to avoid divide-by-zero errors 207 return self.GetResolved([pos[0], pos[1] - 1]) 208 209 # Where 'triangles' within the rectangle are referred to, imagine a rectangle with diagonals drawn between its vertices. The four triangles formed by this process are the ones referred to 210 if pos[0] == self.pos[0]: # If it's directly above the centre of the rectangle 211 if pos[1] > self.pos[1]: 212 return [pos[0], self.pos[1] + self.halfheight + pradius] 213 else: 214 return [pos[0], self.pos[1] - (self.halfheight + pradius)] 215 elif pos[1] == self.pos[1]: # If it's directly to one side of the centre of the rectangle 216 if pos[0] > self.pos[0]: 217 return [self.pos[0] + self.halfwidth + pradius, pos[1]] 218 else: 219 return [self.pos[0] - (self.halfwidth + pradius), pos[1]] 220 elif abs(float(pos[1] - self.pos[1]) / float(pos[0] - self.pos[0])) > (float(self.height) / float(self.width)): # If it's in the upper or lower triangle of the rectangle 221 return [pos[0], self.pos[1] + ((self.halfheight + pradius) * ((pos[1] - self.pos[1]) / abs(pos[1] - self.pos[1])))] # Halfheight is multiplied by a normalised version of (pos[1] - self.pos[1]) - thus if (pos[1] - self.pos[1]) is negative, it should be subtracted as the point is in the upper triangle 222 else: # If it's in the left or right triangle of the rectangle 223 return [self.pos[0] + ((self.halfwidth + pradius) * ((pos[0] - self.pos[0]) / abs(pos[0] - self.pos[0]))), pos[1]] 224 225 def GetNormal(self, pos): 226 if pos[1] < (self.pos[1] - self.halfheight): 227 return [0, -1] 228 elif pos[1] > (self.pos[1] + self.halfheight): 229 return [0, 1] 230 elif pos[0] < (self.pos[0] - self.halfwidth): 231 return [-1, 0] 232 elif pos[0] > (self.pos[0] + self.halfwidth): 233 return [1, 0] 234 else: 235 vect = [pos[0] - self.pos[0], pos[1] - self.pos[1]] 236 mag = magnitude(vect) 237 return [vect[0] / mag, vect[1] / mag] 238 239 def GetForceFactor(self, pos, pradius = 0.0): 240 nor = self.GetNormal(pos) 241 242 if nor[0] == 0: 243 if (pos[0] > (self.pos[0] - self.halfwidth)) and (pos[0] < (self.pos[0] + self.halfwidth)): 244 r = (abs(pos[1] - self.pos[1]) - self.halfheight) - pradius 245 else: 246 return 0.0 247 elif nor[1] == 0: 248 if (pos[1] > (self.pos[1] - self.halfheight)) and (pos[1] < (self.pos[1] + self.halfheight)): 249 r = (abs(pos[0] - self.pos[0]) - self.halfwidth) - pradius 250 else: 251 return 0.0 252 else: 253 return 1.0 254 255 if r <= 1.0: 256 return 1.0 257 258 return (1.0 / pow(float(r), 3.0)) 259 260 def CreateKeyframe(self, frame, pos = (None, None), colour = (None, None, None), bounce = None, width = None, height = None, interpolationtype = INTERPOLATIONTYPE_LINEAR): 261 return keyframes.CreateKeyframe(self.keyframes, frame, {'pos_x':pos[0], 'pos_y':pos[1], 'colour_r':colour[0], 'colour_g':colour[1], 'colour_b':colour[2], 'bounce':bounce, 'width':width, 'height':height, 'interpolationtype':interpolationtype}) 262 263 def ConsolidateKeyframes(self): 264 keyframes.ConsolidateKeyframes(self.keyframes, self.curframe, {'pos_x':self.pos[0], 'pos_y':self.pos[1], 'colour_r':self.colour[0], 'colour_g':self.colour[1], 'colour_b':self.colour[2], 'bounce':self.bounce, 'width':self.width, 'height':self.height}) 265 266 def SetWidth(self, newwidth): 267 self.CreateKeyframe(self.curframe, width = newwidth) 268 269 def SetHeight(self, newheight): 270 self.CreateKeyframe(self.curframe, height = newheight) 271 272 273 class BoundaryLine(Obstacle): 274 def __init__(self, parenteffect, pos, colour, bounce, normal): 275 Obstacle.__init__(self, parenteffect, pos, colour, bounce) 276 self.type = "boundaryline" 277 self.normal = normalise(normal) 278 self.edgecontacts = [] 279 self.hascontacts = False 280 self.storedw, self.storedh = None, None 281 self.curframe = 0 282 self.CreateKeyframe(0, self.pos, self.colour, self.bounce, self.normal) 283 284 def Draw(self, display): 285 offset = self.parenteffect.pos 286 287 W = display.get_width() 288 H = display.get_height() 289 290 if (W != self.storedw) or (H != self.storedh): 291 self.hascontacts = False 292 293 if not self.hascontacts: 294 self.storedw, self.storedh = W, H 295 296 edgecontacts = [] # Where the line touches the screen edges 297 298 if self.normal[0] == 0.0: 299 edgecontacts = [[0, self.pos[1]], [W, self.pos[1]]] 300 301 elif self.normal[1] == 0.0: 302 edgecontacts = [[self.pos[0], 0], [self.pos[0], H]] 303 304 else: 305 pdotn = (self.pos[0] * self.normal[0]) + (self.pos[1] * self.normal[1]) 306 reciprocaln0 = (1.0 / self.normal[0]) 307 reciprocaln1 = (1.0 / self.normal[1]) 308 309 # Left-hand side of the screen 310 pointl = [0, 0] 311 pointl[1] = pdotn * reciprocaln1 312 if (pointl[1] >= 0) and (pointl[1] <= H): 313 edgecontacts.append(pointl) 314 315 # Top of the screen 316 pointt = [0, 0] 317 pointt[0] = pdotn * reciprocaln0 318 if (pointt[0] >= 0) and (pointt[0] <= W): 319 edgecontacts.append(pointt) 320 321 # Right-hand side of the screen 322 pointr = [W, 0] 323 pointr[1] = (pdotn - (W * self.normal[0])) * reciprocaln1 324 if (pointr[1] >= 0) and (pointr[1] <= H): 325 edgecontacts.append(pointr) 326 327 # Bottom of the screen 328 pointb = [0, H] 329 pointb[0] = (pdotn - (H * self.normal[1])) * reciprocaln0 330 if (pointb[0] >= 0) and (pointb[0] <= W): 331 edgecontacts.append(pointb) 332 333 self.edgecontacts = edgecontacts 334 self.hascontacts = True 335 336 tempedgecontacts = [] 337 338 for contact in self.edgecontacts: 339 tempedgecontacts.append([offset[0] + contact[0], offset[1] + contact[1]]) 340 341 if len(tempedgecontacts) >= 2: 342 pygame.draw.aalines(display, self.colour, True, tempedgecontacts) 343 else: 344 pass # The line must be completely outwith the boundaries of the display 345 346 def Update(self): 347 newvars = interpolate.InterpolateKeyframes(self.curframe, {'pos_x':self.pos[0], 'pos_y':self.pos[1], 'colour_r':self.colour[0], 'colour_g':self.colour[1], 'colour_b':self.colour[2], 'bounce':self.bounce, 'normal_x':self.normal[0], 'normal_y':self.normal[1]}, self.keyframes) 348 self.pos = (newvars['pos_x'], newvars['pos_y']) 349 self.colour = (newvars['colour_r'], newvars['colour_g'], newvars['colour_b']) 350 self.bounce = newvars['bounce'] 351 oldnormal = self.normal[:] 352 self.normal = [newvars['normal_x'], newvars['normal_y']] 353 if self.normal != oldnormal: 354 self.hascontacts = False 355 356 Obstacle.Update(self) 357 358 def OutOfRange(self, pos): 359 return (self.GetDist(pos) > MAXDIST) 360 361 def InsideObject(self, pos, pradius = 0.0): 362 if pradius == 0.0: # If the particle has no radius, just test whether its centre position has crossed the line 363 return (((float(pos[0] - self.pos[0]) * self.normal[0]) + (float(pos[1] - self.pos[1]) * self.normal[1])) <= 0.0) 364 365 radialnormal = [self.normal[0] * pradius, self.normal[1] * pradius] 366 leftside = [pos[0] + radialnormal[0], pos[1] + radialnormal[1]] 367 rightside = [pos[0] - radialnormal[0], pos[1] - radialnormal[1]] 368 369 return ((((float(leftside[0] - self.pos[0]) * self.normal[0]) + (float(leftside[1] - self.pos[1]) * self.normal[1])) <= 0.0) 370 or (((float(rightside[0] - self.pos[0]) * self.normal[0]) + (float(rightside[1] - self.pos[1]) * self.normal[1])) <= 0.0)) 371 372 def GetResolved(self, pos, pradius = 0.0): 373 if pos == self.pos: # If the position is at this obstacle's origin, shift it up a pixel to avoid divide-by-zero errors 374 return self.GetResolved([pos[0], pos[1] - 1]) 375 376 dist = abs(self.GetDist(pos, pradius)) 377 vec = [dist * self.normal[0], dist * self.normal[1]] 378 379 return [pos[0] + vec[0], pos[1] + vec[1]] 380 381 def GetNormal(self, pos): 382 return self.normal 383 384 def GetDist(self, pos, pradius = 0.0): 385 v = [float(pos[0] - self.pos[0]), float(pos[1] - self.pos[1])] 386 return (v[0] * self.normal[0]) + (v[1] * self.normal[1]) - pradius 387 388 def GetForceFactor(self, pos, pradius = 0.0): 389 r = self.GetDist(pos) - pradius 390 391 if r <= 1.0: 392 return 1.0 393 394 return (1.0 / pow(r, 3.0)) 395 396 def CreateKeyframe(self, frame, pos = (None, None), colour = (None, None, None), bounce = None, normal = [None, None], interpolationtype = INTERPOLATIONTYPE_LINEAR): 397 if (normal != [None, None]) and (abs(magnitudesquared(normal) - 1.0) >= 0.3): 398 normal = normalise(normal) 399 return keyframes.CreateKeyframe(self.keyframes, frame, {'pos_x':pos[0], 'pos_y':pos[1], 'colour_r':colour[0], 'colour_g':colour[1], 'colour_b':colour[2], 'bounce':bounce, 'normal_x':normal[0], 'normal_y':normal[1], 'interpolationtype':interpolationtype}) 400 401 def ConsolidateKeyframes(self): 402 keyframes.ConsolidateKeyframes(self.keyframes, self.curframe, {'pos_x':self.pos[0], 'pos_y':self.pos[1], 'colour_r':self.colour[0], 'colour_g':self.colour[1], 'colour_b':self.colour[2], 'bounce':self.bounce, 'normal_x':self.normal[0], 'normal_y':self.normal[1]}) 403 404 def SetNormal(self, newnormal): 405 self.CreateKeyframe(self.curframe, normal = newnormal)