PyIgnition

https://github.com/animatinator/PyIgnition update for Python 3
Clone: git clone https://git.frombelow.net/PyIgnition.git
Log | Files | Refs | README

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)