particles.py (10898B)
1 ### EXESOFT PYIGNITION ### 2 # Copyright David Barker 2010 3 # 4 # Particle and ParticleSource objects 5 6 7 import keyframes, interpolate, random, math, pygame 8 from constants import * 9 10 11 class Particle: 12 def __init__(self, parent, initpos, velocity, life, drawtype = DRAWTYPE_POINT, colour = (0, 0, 0), radius = 0.0, length = 0.0, image = None, keyframes = []): 13 self.parent = parent 14 self.pos = initpos 15 self.velocity = velocity 16 self.life = life 17 self.drawtype = drawtype 18 self.colour = colour 19 self.radius = radius 20 self.length = length 21 self.image = image 22 23 self.keyframes = [] 24 self.keyframes.extend(keyframes[:]) 25 self.curframe = 0 26 27 self.alive = True 28 29 def Update(self): 30 self.pos = [self.pos[0] + self.velocity[0], self.pos[1] + self.velocity[1]] 31 32 if self.life != -1 and self.curframe > self.life: 33 self.alive = False 34 else: 35 if self.life == -1 and self.curframe >= len(self.parent.particlecache): # If the particle has infinite life and has passed the last cached frame 36 self.colour = (self.parent.particlecache[len(self.parent.particlecache) - 1]['colour_r'], self.parent.particlecache[len(self.parent.particlecache) - 1]['colour_g'], self.parent.particlecache[len(self.parent.particlecache) - 1]['colour_b']) 37 self.radius = self.parent.particlecache[len(self.parent.particlecache) - 1]['radius'] 38 self.length = self.parent.particlecache[len(self.parent.particlecache) - 1]['length'] 39 else: # Otherwise, get the values for the current frame 40 self.colour = (self.parent.particlecache[self.curframe]['colour_r'], self.parent.particlecache[self.curframe]['colour_g'], self.parent.particlecache[self.curframe]['colour_b']) 41 self.radius = self.parent.particlecache[self.curframe]['radius'] 42 self.length = self.parent.particlecache[self.curframe]['length'] 43 44 self.curframe = self.curframe + 1 45 46 def Draw(self, display): 47 offset = self.parent.parenteffect.pos 48 49 if (self.pos[0] > 10000) or (self.pos[1] > 10000) or (self.pos[0] < -10000) or (self.pos[1] < -10000): 50 return 51 52 if self.drawtype == DRAWTYPE_POINT: # Point 53 pygame.draw.circle(display, self.colour, (offset[0] + int(self.pos[0]), offset[1] + int(self.pos[1])), 0) 54 55 elif self.drawtype == DRAWTYPE_CIRCLE: # Circle 56 pygame.draw.circle(display, self.colour, (offset[0] + int(self.pos[0]), offset[1] + int(self.pos[1])), int(self.radius)) 57 58 elif self.drawtype == DRAWTYPE_LINE: 59 if self.length == 0.0: 60 pygame.draw.circle(display, self.colour, (offset[0] + int(self.pos[0]), offset[1] + int(self.pos[1])), 0) 61 62 else: 63 # Vector = (velocity / mag(velocity)) * length (a line of magnitude 'length' in 64 # direction of velocity); this is calculated as velocity / (mag(velocity) / length) 65 # so that parts consistent for both components in the final calculation are only calculated once 66 velocitymagoverlength = math.sqrt(self.velocity[0]**2 + self.velocity[1]**2) / self.length 67 68 if velocitymagoverlength > 0.0: # Avoid division-by-zero errors by handling lines with zero velocity separately 69 linevec = [(self.velocity[0] / velocitymagoverlength), (self.velocity[1] / velocitymagoverlength)] 70 else: 71 linevec = [self.length, 0.0] # Draw a line pointing to the right 72 73 endpoint = [offset[0] + int(self.pos[0] + linevec[0]), offset[1] + int(self.pos[1] + linevec[1])] 74 pygame.draw.aaline(display, self.colour, (offset[0] + int(self.pos[0]), offset[1] + int(self.pos[1])), endpoint) 75 76 elif self.drawtype == DRAWTYPE_SCALELINE: # Scaling line (scales with velocity) 77 endpoint = [offset[0] + int(self.pos[0] + self.velocity[0]), offset[1] + int(self.pos[1] + self.velocity[1])] 78 pygame.draw.aaline(display, self.colour, (offset[0] + int(self.pos[0]), offset[1] + int(self.pos[1])), endpoint) 79 80 elif self.drawtype == DRAWTYPE_BUBBLE: # Bubble 81 if self.radius >= 1.0: 82 pygame.draw.circle(display, self.colour, (offset[0] + int(self.pos[0]), offset[1] + int(self.pos[1])), int(self.radius), 1) 83 else: # Pygame won't draw circles with thickness < radius, so if radius is smaller than one don't bother trying to set thickness 84 pygame.draw.circle(display, self.colour, (offset[0] + int(self.pos[0]), offset[1] + int(self.pos[1])), int(self.radius)) 85 86 elif self.drawtype == DRAWTYPE_IMAGE: # Image 87 if self.image != None: 88 size = self.image.get_size() 89 display.blit(self.image, (offset[0] + int(self.pos[0] - (size[1]* 0.5)), offset[1] + int(self.pos[1] - (size[1] * 0.5)))) 90 91 def CreateKeyframe(self, frame, colour = (None, None, None), radius = None, length = None): 92 keyframes.CreateKeyframe(self.keyframes, frame, {'colour_r':colour[0], 'colour_g':colour[1], 'colour_b':colour[2], 'radius':radius, 'length':length}) 93 94 95 class ParticleSource: 96 def __init__(self, parenteffect, pos, initspeed, initdirection, initspeedrandrange, initdirectionrandrange, particlesperframe, particlelife, genspacing, drawtype = 0, colour = (0, 0, 0), radius = 0.0, length = 0.0, imagepath = None): 97 self.selected = False 98 self.parenteffect = parenteffect 99 self.pos = pos 100 self.initspeed = initspeed 101 self.initdirection = initdirection 102 self.initspeedrandrange = initspeedrandrange 103 self.initdirectionrandrange = initdirectionrandrange 104 self.particlesperframe = particlesperframe 105 self.particlelife = particlelife 106 self.genspacing = genspacing 107 self.colour = colour 108 self.drawtype = drawtype 109 self.radius = radius 110 self.length = length 111 self.imagepath = imagepath 112 if self.imagepath == None: 113 self.image = None 114 else: 115 self.image = pygame.image.load(self.imagepath).convert_alpha() 116 self.drawtype = drawtype 117 118 self.keyframes = [] 119 self.CreateKeyframe(0, self.pos, self.initspeed, self.initdirection, self.initspeedrandrange, self.initdirectionrandrange, self.particlesperframe, self.genspacing) 120 self.particlekeyframes = [] 121 self.particlecache = [] 122 self.CreateParticleKeyframe(0, colour = self.colour, radius = self.radius, length = self.length) 123 self.curframe = 0 124 125 def Update(self): 126 newvars = interpolate.InterpolateKeyframes(self.curframe, {'pos_x':self.pos[0], 'pos_y':self.pos[1], 'initspeed':self.initspeed, 'initdirection':self.initdirection, 'initspeedrandrange':self.initspeedrandrange, 'initdirectionrandrange':self.initdirectionrandrange, 'particlesperframe':self.particlesperframe, 'genspacing':self.genspacing}, self.keyframes) 127 self.pos = (newvars['pos_x'], newvars['pos_y']) 128 self.initspeed = newvars['initspeed'] 129 self.initdirection = newvars['initdirection'] 130 self.initspeedrandrange = newvars['initspeedrandrange'] 131 self.initdirectionrandrange = newvars['initdirectionrandrange'] 132 self.particlesperframe = newvars['particlesperframe'] 133 self.genspacing = newvars['genspacing'] 134 135 particlesperframe = self.particlesperframe 136 137 if (self.genspacing == 0) or ((self.curframe % self.genspacing) == 0): 138 for i in range(0, int(particlesperframe)): 139 self.CreateParticle() 140 141 self.curframe = self.curframe + 1 142 143 def CreateParticle(self): 144 if self.initspeedrandrange != 0.0: 145 speed = self.initspeed + (float(random.randrange(int(-self.initspeedrandrange * 100.0), int(self.initspeedrandrange * 100.0))) / 100.0) 146 else: 147 speed = self.initspeed 148 if self.initdirectionrandrange != 0.0: 149 direction = self.initdirection + (float(random.randrange(int(-self.initdirectionrandrange * 100.0), int(self.initdirectionrandrange * 100.0))) / 100.0) 150 else: 151 direction = self.initdirection 152 velocity = [speed * math.sin(direction), -speed * math.cos(direction)] 153 newparticle = Particle(self, initpos = self.pos, velocity = velocity, life = self.particlelife, drawtype = self.drawtype, colour = self.colour, radius = self.radius, length = self.length, image = self.image, keyframes = self.particlekeyframes) 154 self.parenteffect.AddParticle(newparticle) 155 156 def CreateKeyframe(self, frame, pos = (None, None), initspeed = None, initdirection = None, initspeedrandrange = None, initdirectionrandrange = None, particlesperframe = None, genspacing = None, interpolationtype = INTERPOLATIONTYPE_LINEAR): 157 return keyframes.CreateKeyframe(self.keyframes, frame, {'pos_x':pos[0], 'pos_y':pos[1], 'initspeed':initspeed, 'initdirection':initdirection, 'initspeedrandrange':initspeedrandrange, 'initdirectionrandrange':initdirectionrandrange, 'particlesperframe':particlesperframe, 'genspacing':genspacing, 'interpolationtype':interpolationtype}) 158 159 def CreateParticleKeyframe(self, frame, colour = (None, None, None), radius = None, length = None, interpolationtype = INTERPOLATIONTYPE_LINEAR): 160 newframe = keyframes.CreateKeyframe(self.particlekeyframes, frame, {'colour_r':colour[0], 'colour_g':colour[1], 'colour_b':colour[2], 'radius':radius, 'length':length, 'interpolationtype':interpolationtype}) 161 self.PreCalculateParticles() 162 return newframe 163 164 def GetKeyframeValue(self, keyframe): 165 return keyframe.frame 166 167 def PreCalculateParticles(self): 168 self.particlecache = [] # Clear the cache 169 170 # If the particle has infinite life, interpolate for each frame up until its last keyframe 171 if self.particlelife == -1: 172 particlelife = max(self.particlekeyframes, key = self.GetKeyframeValue).frame 173 else: # Otherwise, interpolate the particle variables for each frame of its life 174 particlelife = self.particlelife 175 176 for i in range(0, particlelife + 1): 177 vars = interpolate.InterpolateKeyframes(i, {'colour_r':0, 'colour_g':0, 'colour_b':0, 'radius':0, 'length':0}, self.particlekeyframes) 178 self.particlecache.append(vars) 179 180 def ConsolidateKeyframes(self): 181 keyframes.ConsolidateKeyframes(self.keyframes, self.curframe, {'pos_x':self.pos[0], 'pos_y':self.pos[1], 'initspeed':self.initspeed, 'initdirection':self.initdirection, 'initspeedrandrange':self.initspeedrandrange, 'initdirectionrandrange':self.initdirectionrandrange, 'particlesperframe':self.particlesperframe, 'genspacing':self.genspacing}) 182 183 def SetPos(self, newpos): 184 self.CreateKeyframe(self.curframe, pos = newpos) 185 186 def SetInitSpeed(self, newinitspeed): 187 self.CreateKeyframe(self.curframe, initspeed = newinitspeed) 188 189 def SetInitDirection(self, newinitdirection): 190 self.CreateKeyframe(self.curframe, initdirection = newinitdirection) 191 192 def SetInitSpeedRandRange(self, newinitspeedrandrange): 193 self.CreateKeyframe(self.curframe, initspeedrandrange = newinitspeedrandrange) 194 195 def SetInitDirectionRandRange(self, newinitdirectionrandrange): 196 self.CreateKeyframe(self.curframe, initdirectionrandrange = newinitdirectionrandrange) 197 198 def SetParticlesPerFrame(self, newparticlesperframe): 199 self.CreateKeyframe(self.curframe, particlesperframe = newparticlesperframe) 200 201 def SetGenSpacing(self, newgenspacing): 202 self.CreateKeyframe(self.curframe, genspacing = newgenspacing)