commit a8b2ba411221193108d12dd40908322cfedc1879
Author: Gerd Beuster <gerd@frombelow.net>
Date: Sun, 16 Feb 2020 10:29:15 +0100
Animations, some based on PyIgnition
Diffstat:
11 files changed, 2272 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,2 @@
+pyignition/__pycache__/
+**/*.pyc
diff --git a/notes.txt b/notes.txt
@@ -0,0 +1,13 @@
+sudo sh -c "echo blacklist snd_bcm2835 > /etc/modprobe.d/snd-blacklist.conf"
+# In /boot/config.txt auskommentieren:
+# dtparam=audio=on
+# /boot/cmdline.txt hinzufügen:
+spidev.bufsiz=32768
+sudo apt-get install gcc make build-essential python-dev git scons swig
+git clone https://github.com/jgarff/rpi_ws281x
+cd rpi_ws281x/
+sudo scons
+cd python
+sudo python setup.py build
+sudo python setup.py install
+
diff --git a/patterns.py b/patterns.py
@@ -0,0 +1,421 @@
+#!/usr/bin/env python3
+# Based on NeoPixel library strandtest example
+# Author: Tony DiCola (tony@tonydicola.com)
+#
+# Source: https://github.com/rpi-ws281x/rpi-ws281x-python
+
+import time
+from rpi_ws281x import PixelStrip, Color
+import argparse
+import random
+import sys
+sys.path.append('pyignition')
+import PyIgnition, pygame, sys, os
+import subprocess
+import math
+import pdb
+import multiprocessing
+
+# LED strip/matrix configuration
+
+# Matrix
+#LED_MATRIX_WIDTH = 16
+#LED_MATRIX_HEIGHT = 16
+# Is row layout of LEDs is left-right, right-left, left-right, ...
+# or left-right, left-right, left-right, ...?
+#LED_STRIP_SNAKE = True
+
+# Strip
+#LED_MATRIX_WIDTH = 299
+#LED_MATRIX_HEIGHT = 1
+#LED_STRIP_SNAKE = False
+
+# Pipe with 5 m LED strip, density 60 LEDs / m
+#LED_MATRIX_WIDTH = 14.5
+#LED_MATRIX_HEIGHT = 21
+#LED_STRIP_SNAKE = False
+
+# Pipe with 5 m LED strip, density 144 LEDs / m
+LED_MATRIX_WIDTH = 34.7
+LED_MATRIX_HEIGHT = 21
+LED_STRIP_SNAKE = False
+
+# LED_PIN = 18 # GPIO pin connected to the pixels (18 uses PWM!) - requires root!
+LED_PIN = 10 # GPIO pin connected to the pixels (10 uses SPI /dev/spidev0.0) - user must be in group gpio!
+LED_FREQ_HZ = 800000 # LED signal frequency in hertz (usually 800khz)
+LED_DMA = 10 # DMA channel to use for generating signal (try 10)
+# Set to 0 for darkest and 255 for brightest
+# LED_BRIGHTNESS = 255
+LED_BRIGHTNESS = 16
+# LED_BRIGHTNESS = 8
+# LED_BRIGHTNESS = 4
+# LED_BRIGHTNESS = 1
+LED_INVERT = False # True to invert the signal (when using NPN transistor level shift)
+LED_CHANNEL = 0 # set to '1' for GPIOs 13, 19, 41, 45 or 53
+
+
+class Pattern():
+ def __init__(self, strip):
+ self.strip = strip
+ self.clear_strip()
+
+ def step(self):
+ pass
+
+ def stop(self):
+ self.clear_strip()
+
+ def clear_strip(self):
+ for i in range(math.ceil(LED_MATRIX_WIDTH) * math.ceil(LED_MATRIX_HEIGHT)):
+ self.strip.setPixelColor(i, Color(0x00, 0x00, 0x00))
+ self.strip.show()
+
+ def xy2i(self, x, y):
+ i = y * LED_MATRIX_WIDTH
+ if LED_STRIP_SNAKE and (y % 2):
+ i += LED_MATRIX_WIDTH - x -1
+ else:
+ i += x
+ return math.floor(i)
+
+
+class TestPattern(Pattern):
+
+ CELL_ON = Color(0x00, 0xFF, 0x00)
+ CELL_OFF = Color(0x00, 0x00, 0x00)
+
+ def __init__(self, strip):
+ Pattern.__init__(self, strip=strip)
+ # Initialize dummy display
+ os.environ["SDL_VIDEODRIVER"] = "dummy"
+ subprocess.Popen(["Xvfb", ":1", "-screen", "0", "1024x768x24"],
+ stderr=subprocess.DEVNULL)
+ time.sleep(1)
+ os.environ["DISPLAY"] = ":1"
+ pygame.display.init()
+
+ self.x = 0
+ self.y = 0
+
+ def step(self):
+ print("({x}/{y})".format(x=self.x, y=self.y))
+ for i in range(math.ceil(LED_MATRIX_WIDTH * LED_MATRIX_HEIGHT)):
+ self.strip.setPixelColor(i, self.CELL_OFF)
+ for col in range(math.ceil(LED_MATRIX_WIDTH)):
+ self.strip.setPixelColor(self.xy2i(col, self.y), self.CELL_ON)
+ for row in range(math.ceil(LED_MATRIX_HEIGHT)):
+ self.strip.setPixelColor(self.xy2i(self.x, row), self.CELL_ON)
+ self.strip.show()
+ time.sleep(1)
+ self.x = (self.x + 1) % LED_MATRIX_WIDTH
+ self.y = (self.y + 1) % LED_MATRIX_HEIGHT
+
+
+class AllOn(Pattern):
+ def __init__(self, strip):
+ Pattern.__init__(self, strip=strip)
+ for i in range(math.ceil(LED_MATRIX_WIDTH*LED_MATRIX_HEIGHT)):
+ strip.setPixelColor(i, Color(LED_BRIGHTNESS, LED_BRIGHTNESS, LED_BRIGHTNESS))
+ strip.show()
+
+
+class Worm(Pattern):
+ def __init__(self, strip):
+ Pattern.__init__(self, strip=strip)
+ # LED_BRIGHTNESS should be at least 15.
+ self.worm_pattern = list(map(lambda x: Color(x[0], x[1], x[2]),
+ [(0x00, 0x05, 0x00),
+ (0x00, 0x20, 0x00),
+ (0x20, 0x20, 0x00),
+ (0xFF, 0x00, 0x00),
+ (0xFF, 0x00, 0x00),
+ (0xFF, 0x00, 0x00),
+ (0xFF, 0x00, 0x00),
+ (0xFF, 0x00, 0x00),
+ (0x20, 0x20, 0x00),
+ (0x00, 0x20, 0x00),
+ (0x00, 0x05, 0x00),
+ ]))
+ # Set background color
+ for i in range(math.ceil(LED_MATRIX_WIDTH*LED_MATRIX_HEIGHT)):
+ self.strip.setPixelColor(i, Color(0x00, 0x05, 0x00))
+ self.strip.show()
+ self.direction = 1
+ self.pos = LED_MATRIX_WIDTH // 2
+
+ def step(self):
+ # Larson worm
+ for i in range(len(self.worm_pattern)):
+ self.strip.setPixelColor(int(self.pos+i), self.worm_pattern[i])
+ if random.random() < .02:
+ self.direction *= -1
+ if self.pos == 0:
+ self.direction = 1
+ if (self.pos + len(self.worm_pattern)) == LED_MATRIX_WIDTH*LED_MATRIX_HEIGHT:
+ self.direction = -1
+ self.pos += self.direction
+ # Random set blue pixel
+ if (self.pos % 8) == 0:
+ self.strip.setPixelColor(random.randint(0, math.ceil(LED_MATRIX_WIDTH*LED_MATRIX_HEIGHT-1)), Color(0x00, 0x00, 0x10))
+ # Update Strip
+ self.strip.show()
+ time.sleep(.04)
+
+
+class CellularAutomaton(Pattern):
+
+ CELL_ON_COLOR = (0x00, 0xFF, 0x00)
+ CELL_OFF_COLOR = (0x00, 0x00, 0x00)
+
+ def cell_is_on(self, buf, x, y):
+ x = (x + LED_MATRIX_WIDTH) % LED_MATRIX_WIDTH
+ y = (y + LED_MATRIX_HEIGHT) % LED_MATRIX_HEIGHT
+ return (buf[self.xy2i(x, y)] == self.CELL_ON_COLOR)
+
+
+ def show_buf(self, buf):
+ for i in range(len(buf)):
+ if(buf[i]):
+ self.strip.setPixelColor(i, Color(buf[i][0], buf[i][1], buf[i][2]))
+ else:
+ self.strip.setPixelColor(i, Color(0x00, 0x00, 0x00))
+ self.strip.show()
+
+
+ def __init__(self, strip):
+ Pattern.__init__(self, strip=strip)
+ self.matrix_buf = [[None] * (math.ceil(LED_MATRIX_WIDTH) * math.ceil(LED_MATRIX_HEIGHT)),
+ [None] * (math.ceil(LED_MATRIX_WIDTH) * math.ceil(LED_MATRIX_HEIGHT))]
+ self.number_of_updates = 0
+ self.stable_pattern = True
+ self.current_buf = self.matrix_buf[0]
+ for index in range(math.ceil(LED_MATRIX_WIDTH) * math.ceil(LED_MATRIX_HEIGHT)):
+ self.current_buf[index] = self.CELL_OFF_COLOR
+
+
+ def step(self):
+ self.current_buf = self.matrix_buf[self.number_of_updates % 2]
+ self.next_buf = self.matrix_buf[(self.number_of_updates + 1) % 2]
+ # Since we get a stable pattern after sometime, we restart with a
+ # new random cellular automaton after a fixed number of steps or
+ # when we detect a stable situation.
+ if self.stable_pattern or (self.number_of_updates == 100):
+ if True:
+ # Initialize with random values
+ for index in range(math.ceil(LED_MATRIX_WIDTH) * math.ceil(LED_MATRIX_HEIGHT)):
+ if (random.random() < .5):
+ self.current_buf[index] = self.CELL_ON_COLOR
+ else:
+ self.current_buf[index] = self.CELL_OFF_COLOR
+ else:
+ # Start with a glider
+ self.current_buf[self.xy2i(3,6)] = self.CELL_ON_COLOR
+ self.current_buf[self.xy2i(4,6)] = self.CELL_ON_COLOR
+ self.current_buf[self.xy2i(5,6)] = self.CELL_ON_COLOR
+ self.current_buf[self.xy2i(5,7)] = self.CELL_ON_COLOR
+ self.current_buf[self.xy2i(4,8)] = self.CELL_ON_COLOR
+ self.number_of_updates = 0
+ self.stable_pattern = False
+
+ self.number_of_updates += 1
+ # Cycle over all cells
+ for y in range(math.ceil(LED_MATRIX_HEIGHT)):
+ for x in range(math.ceil(LED_MATRIX_WIDTH)):
+ # Count neighbors
+ neighbors = 0
+ for y_neighbor in range(3):
+ for x_neighbor in range(3):
+ if (((x_neighbor != 1) or (y_neighbor != 1)) and
+ self.cell_is_on(self.current_buf,
+ (x-x_neighbor+1) % LED_MATRIX_WIDTH,
+ (y-y_neighbor+1) % LED_MATRIX_HEIGHT)):
+ neighbors += 1
+ # Update cell
+ if (neighbors < 2) or (neighbors > 3):
+ self.next_buf[self.xy2i(x, y)] = self.CELL_OFF_COLOR
+ elif neighbors == 3:
+ self.next_buf[self.xy2i(x, y)] = self.CELL_ON_COLOR
+ else:
+ self.next_buf[self.xy2i(x, y)] = self.current_buf[self.xy2i(x, y)]
+ # Check if pattern has changed since last update.
+ # (We will exit the loop if the pattern becomes stable)
+ self.stable_pattern = True
+ for i in range(math.ceil(LED_MATRIX_HEIGHT) * math.ceil(LED_MATRIX_WIDTH)):
+ if self.next_buf[i] != self.current_buf[i]:
+ self.stable_pattern = False
+ break
+ self.show_buf(self.current_buf)
+ time.sleep(.1)
+
+
+class BlueGreenFlames(Pattern):
+ def __init__(self, strip):
+ Pattern.__init__(self, strip=strip)
+ # This mode works great with LED_BRIGHTNESS set to 16.
+ self.SCALE=40 # Scaling factor for screen
+
+ # Also show in window?
+ # self.HEADLESS=False
+ self.HEADLESS=True
+
+ if self.HEADLESS:
+ # On Raspbian, setting a dummy videodriver
+ # does not prevent pygame from trying to
+ # open a X display. Therefore we use a
+ # virtual framebuffer.
+ os.environ["SDL_VIDEODRIVER"] = "dummy"
+ subprocess.Popen(["Xvfb", ":1", "-screen", "0", "1024x768x24"],
+ stderr=subprocess.DEVNULL)
+ time.sleep(1)
+ os.environ["DISPLAY"] = ":1"
+
+ pygame.display.init()
+ self.screen = pygame.Surface((math.ceil(LED_MATRIX_WIDTH),
+ math.ceil(LED_MATRIX_HEIGHT)))
+ self.screen_scaled = pygame.display.set_mode((math.ceil(LED_MATRIX_WIDTH*self.SCALE),
+ math.ceil(LED_MATRIX_HEIGHT*self.SCALE)))
+ pygame.display.set_caption("Blue Green Fire")
+ self.clock = pygame.time.Clock()
+
+ self.fire = PyIgnition.ParticleEffect(self.screen, (0, 0),
+ (math.ceil(LED_MATRIX_WIDTH),
+ math.ceil(LED_MATRIX_HEIGHT)))
+ gravity = self.fire.CreateDirectedGravity(strength=0.1, direction=[0, -1])
+
+ # Behavior of blue particles
+ self.blue_flames = []
+ blue_flames_scale_factor = 4
+ for x_pos in range(0, math.ceil(LED_MATRIX_WIDTH),
+ math.ceil(LED_MATRIX_WIDTH//5)):
+ new_flame = self.fire.CreateSource(pos=(x_pos, LED_MATRIX_HEIGHT*1.1),
+ initspeed = .01, initdirection = 0.0,
+ initspeedrandrange = .1,
+ initdirectionrandrange = 0.5,
+ particlesperframe = 1,
+ particlelife = 100,
+ drawtype = PyIgnition.DRAWTYPE_CIRCLE,
+ colour = (50, 20, 200),
+ radius = LED_MATRIX_WIDTH/32.0*blue_flames_scale_factor)
+ new_flame.CreateParticleKeyframe(1, colour = (50, 20, 200),
+ radius = LED_MATRIX_WIDTH/32.0*blue_flames_scale_factor)
+ new_flame.CreateParticleKeyframe(3, colour = (0, 0, 150),
+ radius = LED_MATRIX_WIDTH/32.0*blue_flames_scale_factor)
+ new_flame.CreateParticleKeyframe(6, colour = (20, 20, 50),
+ radius = LED_MATRIX_WIDTH/32.0*blue_flames_scale_factor)
+ new_flame.CreateParticleKeyframe(9, colour = (0, 0, 0),
+ radius = LED_MATRIX_WIDTH/32.0*blue_flames_scale_factor)
+ self.blue_flames += [new_flame]
+
+
+ # Behavior of green particle
+ self.green_flame_x = LED_MATRIX_WIDTH / 2
+ self.green_flame_y = LED_MATRIX_HEIGHT + 1
+ self.green_flame = self.fire.CreateSource(pos=(self.green_flame_x, self.green_flame_y),
+ initspeed = .3, initdirection = 0.0,
+ initspeedrandrange = .3,
+ initdirectionrandrange = 0.5,
+ particlesperframe = 1, particlelife = 100,
+ drawtype = PyIgnition.DRAWTYPE_CIRCLE,
+ colour = (255, 200, 100),
+ radius = LED_MATRIX_WIDTH/36.0)
+ self.green_flame.CreateParticleKeyframe(5, colour = (50, 200, 20),
+ radius = LED_MATRIX_WIDTH/36.0)
+ self.green_flame.CreateParticleKeyframe(10, colour = (0, 150, 0),
+ radius = LED_MATRIX_WIDTH/36.0)
+ self.green_flame.CreateParticleKeyframe(15, colour = (20, 50, 20),
+ radius = LED_MATRIX_WIDTH/36.0)
+ self.green_flame.CreateParticleKeyframe(20, colour = (0, 0, 0),
+ radius = LED_MATRIX_WIDTH/36.0)
+
+
+
+
+ def step(self):
+ for event in pygame.event.get():
+ if event.type == pygame.QUIT:
+ sys.exit()
+ # Draw on screen
+ self.screen.fill((0, 0, 0))
+ self.green_flame_x = random.randint(0, math.ceil(LED_MATRIX_WIDTH)-1)
+ self.green_flame.pos = (self.green_flame_x, self.green_flame_y)
+ if self.green_flame.curframe % 10 == 0:
+ self.green_flame.ConsolidateKeyframes()
+ for b in self.blue_flames:
+ if b.curframe % 30 == 0:
+ b.ConsolidateKeyframes()
+ self.fire.Update()
+ self.fire.Redraw()
+ pygame.transform.scale(self.screen, (math.ceil(LED_MATRIX_WIDTH*self.SCALE),
+ math.ceil(LED_MATRIX_HEIGHT*self.SCALE)),
+ self.screen_scaled)
+ pygame.display.update()
+ # Draw on LED matrix
+ fb = pygame.PixelArray(self.screen)
+ for y in range(math.ceil(LED_MATRIX_HEIGHT)):
+ for x in range(math.ceil(LED_MATRIX_WIDTH)):
+ r = fb[x][y] >> 16 & 0xFF
+ g = fb[x][y] >> 8 & 0xFF
+ b = fb[x][y] & 0xFF
+ self.strip.setPixelColor(self.xy2i(x,
+ math.ceil(LED_MATRIX_HEIGHT)-1-y),
+ Color(r, g, b))
+ self.strip.show()
+ self.clock.tick(5)
+
+MODE_ALL_ON = 0
+MODE_WORM = 1
+MODE_CELLULAR_AUTOMATON = 2
+MODE_BLUE_GREEN_FLAMES = 3
+MODE_TEST_PATTERN = 4
+
+mode = MODE_BLUE_GREEN_FLAMES
+# mode = MODE_WORM
+# mode = MODE_CELLULAR_AUTOMATON
+
+def run(cmd_queue):
+ global mode
+ # Create NeoPixel object with appropriate configuration.
+ strip = PixelStrip(math.ceil(LED_MATRIX_WIDTH * LED_MATRIX_HEIGHT), LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS, LED_CHANNEL)
+ # Intialize the library (must be called once before other functions).
+ strip.begin()
+ # This will be immediately replaced by the actual initial mode.
+ p = Pattern(strip)
+ current_mode = -1
+ try:
+ while True:
+ if not cmd_queue.empty():
+ mode = cmd_queue.get_nowait()
+ print("Got {}".format(mode))
+ if current_mode != mode:
+ # External mode switch
+ p.stop()
+ current_mode = mode
+ p = {
+ MODE_ALL_ON : AllOn,
+ MODE_WORM : Worm,
+ MODE_CELLULAR_AUTOMATON : CellularAutomaton,
+ MODE_BLUE_GREEN_FLAMES : BlueGreenFlames,
+ MODE_TEST_PATTERN : TestPattern
+ }[current_mode](strip)
+ p.step()
+ except KeyboardInterrupt:
+ p.stop()
+
+def cmd_demo(cmd_queue):
+ mode = 0
+ while True:
+ mode = (mode + 1) % 5
+ print("Putting {}".format(mode))
+ cmd_queue.put(mode)
+ time.sleep(10)
+
+
+# Main program logic follows:
+if __name__ == '__main__':
+ cmd_queue = multiprocessing.Queue()
+ run = multiprocessing.Process(target=run, args=(cmd_queue,))
+ run.start()
+ if False:
+ cmd_demo = multiprocessing.Process(target=cmd_demo, args=(cmd_queue,))
+ cmd_demo.start()
+ run.join()
diff --git a/pyignition/PyIgnition.py b/pyignition/PyIgnition.py
@@ -0,0 +1,731 @@
+### EXESOFT PYIGNITION ###
+# Copyright David Barker 2010
+#
+# Particle effect manager
+
+
+import particles, gravity, obstacles, constants, sys, pygame, xml
+from constants import *
+
+
+class ParticleEffect:
+ def __init__(self, display, pos = (0, 0), size = (0, 0)):
+ self.display = display
+ self.pos = pos
+ self.size = size
+
+ self.left = pos[0]
+ self.top = pos[1]
+ self.right = pos[0] + size[0]
+ self.bottom = pos[1] + size[1]
+
+ self.particles = []
+ self.sources = []
+ self.gravities = []
+ self.obstacles = []
+
+ def Update(self):
+ for source in self.sources:
+ source.Update()
+
+ for gravity in self.gravities:
+ gravity.Update()
+
+ for obstacle in self.obstacles:
+ obstacle.Update()
+
+ for particle in self.particles:
+ radius = 0.0 # Used for making sure radii don't overlap objects...
+
+ if particle.drawtype == DRAWTYPE_CIRCLE or particle.drawtype == DRAWTYPE_BUBBLE:
+ radius = particle.radius * (1.0 - RADIUS_PERMITTIVITY) # ...But only set if the particle is actually circular
+
+ # First calculate the forces acting on the particle
+ totalforce = [0.0, 0.0]
+
+ for gravity in self.gravities:
+ force = gravity.GetForceOnParticle(particle)
+ totalforce[0] += force[0]
+ totalforce[1] += force[1]
+
+ for obstacle in self.obstacles:
+ force = obstacle.GetForce(particle.pos, particle.velocity, radius)
+ totalforce[0] += force[0]
+ totalforce[1] += force[1]
+
+ # Apply the forces to the velocity and update the particle
+ particle.velocity = [particle.velocity[0] + totalforce[0], particle.velocity[1] + totalforce[1]]
+
+ particle.Update()
+
+ # Resolve collisions
+ for obstacle in self.obstacles:
+ if (not obstacle.OutOfRange(particle.pos)) and (obstacle.InsideObject(particle.pos, radius)):
+ particle.pos = obstacle.GetResolved(particle.pos, radius)
+
+ # Delete dead particles
+ for particle in self.particles:
+ if not particle.alive:
+ self.particles.remove(particle)
+
+ def Redraw(self):
+ for particle in self.particles:
+ particle.Draw(self.display)
+
+ for obstacle in self.obstacles:
+ obstacle.Draw(self.display)
+
+ def CreateSource(self, pos = (0, 0), initspeed = 0.0, initdirection = 0.0, initspeedrandrange = 0.0, initdirectionrandrange = 0.0, particlesperframe = 0, particlelife = -1, genspacing = 0, drawtype = 0, colour = (0, 0, 0), radius = 0.0, length = 0.0, imagepath = None):
+ newsource = particles.ParticleSource(self, pos, initspeed, initdirection, initspeedrandrange, initdirectionrandrange, particlesperframe, particlelife, genspacing, drawtype, colour, radius, length, imagepath)
+ self.sources.append(newsource)
+ return newsource # Effectively a reference
+
+ def CreatePointGravity(self, strength = 0.0, strengthrandrange = 0.0, pos = (0, 0)):
+ newgrav = gravity.PointGravity(strength, strengthrandrange, pos)
+ self.gravities.append(newgrav)
+ return newgrav
+
+ def CreateDirectedGravity(self, strength = 0.0, strengthrandrange = 0.0, direction = [0, 1]):
+ newgrav = gravity.DirectedGravity(strength, strengthrandrange, direction)
+ self.gravities.append(newgrav)
+ return newgrav
+
+ def CreateVortexGravity(self, strength = 0.0, strengthrandrange = 0.0, pos = (0, 0)):
+ newgrav = gravity.VortexGravity(strength, strengthrandrange, pos)
+ self.gravities.append(newgrav)
+ return newgrav
+
+ def CreateCircle(self, pos = (0, 0), colour = (0, 0, 0), bounce = 1.0, radius = 0.0):
+ newcircle = obstacles.Circle(self, pos, colour, bounce, radius)
+ self.obstacles.append(newcircle)
+ return newcircle
+
+ def CreateRectangle(self, pos = (0, 0), colour = (0, 0, 0), bounce = 1.0, width = 0.0, height = 0.0):
+ newrect = obstacles.Rectangle(self, pos, colour, bounce, width, height)
+ self.obstacles.append(newrect)
+ return newrect
+
+ def CreateBoundaryLine(self, pos = (0, 0), colour = (0, 0, 0), bounce = 1.0, normal = [0, 1]):
+ newline = obstacles.BoundaryLine(self, pos, colour, bounce, normal)
+ self.obstacles.append(newline)
+ return newline
+
+ def AddParticle(self, particle):
+ self.particles.append(particle)
+
+ def GetDrawtypeAsString(self, drawtype):
+ if drawtype == DRAWTYPE_POINT:
+ return "point"
+ elif drawtype == DRAWTYPE_CIRCLE:
+ return "circle"
+ elif drawtype == DRAWTYPE_LINE:
+ return "line"
+ elif drawtype == DRAWTYPE_SCALELINE:
+ return "scaleline"
+ elif drawtype == DRAWTYPE_BUBBLE:
+ return "bubble"
+ elif drawtype == DRAWTYPE_IMAGE:
+ return "image"
+ else:
+ return "ERROR: Invalid drawtype"
+
+ def GetStringAsDrawtype(self, string):
+ if string == "point":
+ return DRAWTYPE_POINT
+ elif string == "circle":
+ return DRAWTYPE_CIRCLE
+ elif string == "line":
+ return DRAWTYPE_LINE
+ elif string == "scaleline":
+ return DRAWTYPE_SCALELINE
+ elif string == "bubble":
+ return DRAWTYPE_BUBBLE
+ elif string == "image":
+ return DRAWTYPE_IMAGE
+ else:
+ return DRAWTYPE_POINT
+
+ def GetInterpolationtypeAsString(self, interpolationtype):
+ if interpolationtype == INTERPOLATIONTYPE_LINEAR:
+ return "linear"
+ elif interpolationtype == INTERPOLATIONTYPE_COSINE:
+ return "cosine"
+
+ def GetStringAsInterpolationtype(self, string):
+ if string == "linear":
+ return INTERPOLATIONTYPE_LINEAR
+ elif string == "cosine":
+ return INTERPOLATIONTYPE_COSINE
+ else:
+ return INTERPOLATIONTYPE_LINEAR
+
+ def TranslatePos(self, pos):
+ return (pos[0] - self.pos[0], pos[1] - self.pos[1])
+
+ def ConvertXMLTuple(self, string):
+ # 'string' must be of the form "(value, value, value, [...])"
+ bracketless = string.replace("(", "").replace(")", "")
+ strings = bracketless.split(", ")
+ finaltuple = []
+ for string in strings:
+ temp = string.split(".")
+ if len(temp) > 1:
+ finaltuple.append(float(string))
+ else:
+ finaltuple.append(int(string))
+
+ return tuple(finaltuple)
+
+ def SaveToFile(self, outfilename):
+ outfile = open(outfilename, 'w')
+
+ outfile.write("<?xml version = \"1.0\"?>\n<?pyignition version = \"%f\"?>\n\n" % PYIGNITION_VERSION)
+ outfile.write("<effect>\n")
+
+ # Write out sources
+ for source in self.sources:
+ outfile.write("\t<source>\n")
+
+ # Write out source variables
+ outfile.write("\t\t<pos>(%i, %i)</pos>\n" % source.pos)
+ outfile.write("\t\t<initspeed>%f</initspeed>\n" % source.initspeed)
+ outfile.write("\t\t<initdirection>%f</initdirection>\n" % source.initdirection)
+ outfile.write("\t\t<initspeedrandrange>%f</initspeedrandrange>\n" % source.initspeedrandrange)
+ outfile.write("\t\t<initdirectionrandrange>%f</initdirectionrandrange>\n" % source.initdirectionrandrange)
+ outfile.write("\t\t<particlesperframe>%i</particlesperframe>\n" % source.particlesperframe)
+ outfile.write("\t\t<particlelife>%i</particlelife>\n" % source.particlelife)
+ outfile.write("\t\t<genspacing>%i</genspacing>\n" % source.genspacing)
+ outfile.write("\t\t<drawtype>%s</drawtype>\n" % self.GetDrawtypeAsString(source.drawtype))
+ outfile.write("\t\t<colour>(%i, %i, %i)</colour>\n" % source.colour)
+ outfile.write("\t\t<radius>%f</radius>\n" % source.radius)
+ outfile.write("\t\t<length>%f</length>\n" % source.length)
+ outfile.write("\t\t<imagepath>%s</imagepath>\n" % source.imagepath)
+
+ # Write out source keyframes
+ outfile.write("\t\t<keyframes>\n")
+
+ for keyframe in source.keyframes:
+ if keyframe.frame == 0: # Don't bother writing out the first keyframe
+ continue
+
+ outfile.write("\t\t\t<keyframe frame = \"%i\">\n" % keyframe.frame)
+
+ # Write out keyframed variables
+ for variable in keyframe.variables.keys():
+ if variable == "interpolationtype":
+ outfile.write("\t\t\t\t<%s>%s</%s>\n" % (variable, self.GetInterpolationtypeAsString(keyframe.variables[variable]), variable))
+ else:
+ outfile.write("\t\t\t\t<%s>%s</%s>\n" % (variable, str(keyframe.variables[variable]), variable))
+
+ outfile.write("\t\t\t</keyframe>\n")
+
+ outfile.write("\t\t</keyframes>\n")
+
+ # Write out source particle keyframes
+ outfile.write("\t\t<particlekeyframes>\n")
+
+ for keyframe in source.particlekeyframes:
+ if keyframe.frame == 0: # Don't bother writing out the first keyframe
+ continue
+
+ outfile.write("\t\t\t<keyframe frame = \"%i\">\n" % keyframe.frame)
+
+ # Write out keyframed variables
+ for variable in keyframe.variables.keys():
+ if variable == "interpolationtype":
+ outfile.write("\t\t\t\t<%s>%s</%s>\n" % (variable, self.GetInterpolationtypeAsString(keyframe.variables[variable]), variable))
+ else:
+ outfile.write("\t\t\t\t<%s>%s</%s>\n" % (variable, str(keyframe.variables[variable]), variable))
+
+ outfile.write("\t\t\t</keyframe>\n")
+
+ outfile.write("\t\t</particlekeyframes>\n")
+
+ outfile.write("\t</source>\n\n")
+
+ # Write out gravities
+ for gravity in self.gravities:
+ # Identify type
+ gtype = gravity.type
+
+ outfile.write("\t<%sgravity>\n" % gtype)
+
+ # Write out gravity variables
+ outfile.write("\t\t<strength>%f</strength>\n" % gravity.initstrength)
+ outfile.write("\t\t<strengthrandrange>%f</strengthrandrange>\n" % gravity.strengthrandrange)
+ if gtype == "directed":
+ outfile.write("\t\t<direction>(%f, %f)</direction>\n" % tuple(gravity.direction))
+ elif gtype == "point" or gtype == "vortex":
+ outfile.write("\t\t<pos>(%i, %i)</pos>\n" % gravity.pos)
+
+ # Write out gravity keyframes
+ outfile.write("\t\t<keyframes>\n")
+
+ for keyframe in gravity.keyframes:
+ if keyframe.frame == 0: # Don't bother writing out the first keyframe
+ continue
+
+ outfile.write("\t\t\t<keyframe frame = \"%i\">\n" % keyframe.frame)
+
+ # Write out keyframed variables
+ for variable in keyframe.variables.keys():
+ if variable == "interpolationtype":
+ outfile.write("\t\t\t\t<%s>%s</%s>\n" % (variable, self.GetInterpolationtypeAsString(keyframe.variables[variable]), variable))
+ else:
+ outfile.write("\t\t\t\t<%s>%s</%s>\n" % (variable, str(keyframe.variables[variable]), variable))
+
+ outfile.write("\t\t\t</keyframe>\n")
+
+ outfile.write("\t\t</keyframes>\n")
+
+ outfile.write("\t</%sgravity>\n\n" % gtype)
+
+ # Write out obstacles
+ for obstacle in self.obstacles:
+ # Identify type
+ otype = obstacle.type
+
+ outfile.write("\t<%s>\n" % otype)
+
+ # Write out obstacle variables
+ outfile.write("\t\t<pos>(%i, %i)</pos>\n" % obstacle.pos)
+ outfile.write("\t\t<colour>(%i, %i, %i)</colour>\n" % obstacle.colour)
+ outfile.write("\t\t<bounce>%f</bounce>\n" % obstacle.bounce)
+ if otype == "circle":
+ outfile.write("\t\t<radius>%f</radius>\n" % obstacle.radius)
+ elif otype == "rectangle":
+ outfile.write("\t\t<width>%i</width>\n" % obstacle.width)
+ outfile.write("\t\t<height>%i</height>\n" % obstacle.height)
+ elif otype == "boundaryline":
+ outfile.write("\t\t<normal>(%f, %f)</normal>\n" % tuple(obstacle.normal))
+
+ # Write out obstacle keyframes
+ outfile.write("\t\t<keyframes>\n")
+
+ for keyframe in obstacle.keyframes:
+ if keyframe.frame == 0: # Don't bother writing out the first keyframe
+ continue
+
+ outfile.write("\t\t\t<keyframe frame = \"%i\">\n" % keyframe.frame)
+
+ # Write out keyframed variables
+ for variable in keyframe.variables.keys():
+ if variable == "interpolationtype":
+ outfile.write("\t\t\t\t<%s>%s</%s>\n" % (variable, self.GetInterpolationtypeAsString(keyframe.variables[variable]), variable))
+ else:
+ outfile.write("\t\t\t\t<%s>%s</%s>\n" % (variable, str(keyframe.variables[variable]), variable))
+
+ outfile.write("\t\t\t</keyframe>\n")
+
+ outfile.write("\t\t</keyframes>\n")
+
+ outfile.write("\t</%s>\n\n" % otype)
+
+ outfile.write("</effect>")
+ outfile.close()
+
+ def LoadFromFile(self, infilename):
+ infile = open(infilename, "r")
+
+ data = xml.XMLParser(infile.read()).Parse()
+ infile.close()
+
+ for child in data.children:
+ if child.tag == "source": # Source object
+ pos = (0, 0)
+ initspeed = 0.0
+ initdirection = 0.0
+ initspeedrandrange = 0.0
+ initdirectionrandrange = 0.0
+ particlesperframe = 0
+ particlelife = 0
+ genspacing = 0
+ drawtype = DRAWTYPE_POINT
+ colour = (0, 0, 0)
+ radius = 0.0
+ length = 0.0
+ imagepath = None
+
+ keyframes = None
+ particlekeyframes = None
+
+ for parameter in child.children:
+ if parameter.tag == "pos":
+ pos = self.ConvertXMLTuple(parameter.inside)
+ elif parameter.tag == "initspeed":
+ initspeed = float(parameter.inside)
+ elif parameter.tag == "initdirection":
+ initdirection = float(parameter.inside)
+ elif parameter.tag == "initspeedrandrange":
+ initspeedrandrange = float(parameter.inside)
+ elif parameter.tag == "initdirectionrandrange":
+ initdirectionrandrange = float(parameter.inside)
+ elif parameter.tag == "particlesperframe":
+ particlesperframe = int(parameter.inside)
+ elif parameter.tag == "particlelife":
+ particlelife = int(parameter.inside)
+ elif parameter.tag == "genspacing":
+ genspacing = int(parameter.inside)
+ elif parameter.tag == "drawtype":
+ drawtype = self.GetStringAsDrawtype(parameter.inside)
+ elif parameter.tag == "colour":
+ colour = self.ConvertXMLTuple(parameter.inside)
+ elif parameter.tag == "radius":
+ radius = float(parameter.inside)
+ elif parameter.tag == "length":
+ length = float(parameter.inside)
+ elif parameter.tag == "image":
+ imagepath = float(parameter.inside)
+ elif parameter.tag == "keyframes":
+ keyframes = parameter.children
+ elif parameter.tag == "particlekeyframes":
+ particlekeyframes = parameter.children
+
+ newsource = self.CreateSource(pos, initspeed, initdirection, initspeedrandrange, initdirectionrandrange, particlesperframe, particlelife, genspacing, drawtype, colour, radius, length, imagepath)
+
+ for keyframe in keyframes:
+ frame = int(keyframe.meta['frame'])
+ variables = {}
+
+ for variable in keyframe.children:
+ if variable.tag == "pos_x" and variable.inside != "None":
+ variables['pos_x'] = int(variable.inside)
+ elif variable.tag == "pos_y" and variable.inside != "None":
+ variables['pos_y'] = int(variable.inside)
+ elif variable.tag == "initspeed" and variable.inside != "None":
+ variables['initspeed'] = float(variable.inside)
+ elif variable.tag == "initdirection" and variable.inside != "None":
+ variables['initdirection'] = float(variable.inside)
+ elif variable.tag == "initspeedrandrange" and variable.inside != "None":
+ variables['initspeedrandrange'] = float(variable.inside)
+ elif variable.tag == "initdirectionrandrange" and variable.inside != "None":
+ variables['initdirectionrandrange'] = float(variable.inside)
+ elif variable.tag == "particlesperframe" and variable.inside != "None":
+ variables['particlesperframe'] = int(variable.inside)
+ elif variable.tag == "genspacing" and variable.inside != "None":
+ variables['genspacing'] = int(variable.inside)
+ elif variable.tag == "interpolationtype" and variable.inside != "None":
+ variables['interpolationtype'] = self.GetStringAsInterpolationtype(variable.inside)
+
+ newframe = newsource.CreateKeyframe(frame = frame)
+ newframe.variables = variables
+
+ for keyframe in particlekeyframes:
+ frame = int(keyframe.meta['frame'])
+ variables = {}
+
+ for variable in keyframe.children:
+ if variable.tag == "colour_r" and variable.inside != "None":
+ variables['colour_r'] = int(variable.inside)
+ elif variable.tag == "colour_g" and variable.inside != "None":
+ variables['colour_g'] = int(variable.inside)
+ elif variable.tag == "colour_b" and variable.inside != "None":
+ variables['colour_b'] = int(variable.inside)
+ elif variable.tag == "radius" and variable.inside != "None":
+ variables['radius'] = float(variable.inside)
+ elif variable.tag == "length" and variable.inside != "None":
+ variables['length'] = float(variable.inside)
+ elif variable.tag == "interpolationtype" and variable.inside != "None":
+ variables['interpolationtype'] = self.GetStringAsInterpolationtype(variable.inside)
+
+ newframe = newsource.CreateParticleKeyframe(frame = frame)
+ newframe.variables = variables
+ newsource.PreCalculateParticles()
+
+ elif child.tag == "directedgravity":
+ strength = 0.0
+ strengthrandrange = 0.0
+ direction = [0, 0]
+
+ keyframes = None
+
+ for parameter in child.children:
+ if parameter.tag == "strength":
+ strength = float(parameter.inside)
+ elif parameter.tag == "strengthrandrange":
+ strengthrandrange = float(parameter.inside)
+ elif parameter.tag == "direction":
+ direction = self.ConvertXMLTuple(parameter.inside)
+ elif parameter.tag == "keyframes":
+ keyframes = parameter.children
+
+ newgrav = self.CreateDirectedGravity(strength, strengthrandrange, direction)
+
+ for keyframe in keyframes:
+ frame = int(keyframe.meta['frame'])
+ variables = {}
+
+ for variable in keyframe.children:
+ if variable.tag == "strength" and variable.inside != "None":
+ variables['strength'] = float(variable.inside)
+ elif variable.tag == "strengthrandrange" and variable.inside != "None":
+ variables['strengthrandrange'] = float(variable.inside)
+ elif variable.tag == "direction_x" and variable.inside != "None":
+ variables['direction_x'] = float(variable.inside)
+ elif variable.tag == "direction_y" and variable.inside != "None":
+ variables['direction_y'] = float(variable.inside)
+ elif variable.tag == "interpolationtype" and variable.inside != "None":
+ variables['interpolationtype'] = self.GetStringAsInterpolationtype(variable.inside)
+
+ newframe = newgrav.CreateKeyframe(frame = frame)
+ newframe.variables = variables
+
+ elif child.tag == "pointgravity":
+ strength = 0.0
+ strengthrandrange = 0.0
+ pos = (0, 0)
+
+ keyframes = None
+
+ for parameter in child.children:
+ if parameter.tag == "strength":
+ strength = float(parameter.inside)
+ elif parameter.tag == "strengthrandrange":
+ strengthrandrange = float(parameter.inside)
+ elif parameter.tag == "pos":
+ pos = self.ConvertXMLTuple(parameter.inside)
+ elif parameter.tag == "keyframes":
+ keyframes = parameter.children
+
+ newgrav = self.CreatePointGravity(strength, strengthrandrange, pos)
+
+ for keyframe in keyframes:
+ frame = int(keyframe.meta['frame'])
+ variables = {}
+
+ for variable in keyframe.children:
+ if variable.tag == "strength" and variable.inside != "None":
+ variables['strength'] = float(variable.inside)
+ elif variable.tag == "strengthrandrange" and variable.inside != "None":
+ variables['strengthrandrange'] = float(variable.inside)
+ elif variable.tag == "pos_x" and variable.inside != "None":
+ variables['pos_x'] = int(variable.inside)
+ elif variable.tag == "pos_y" and variable.inside != "None":
+ variables['pos_y'] = int(variable.inside)
+ elif variable.tag == "interpolationtype" and variable.inside != "None":
+ variables['interpolationtype'] = self.GetStringAsInterpolationtype(variable.inside)
+
+ newframe = newgrav.CreateKeyframe(frame = frame)
+ newframe.variables = variables
+
+ elif child.tag == "vortexgravity":
+ strength = 0.0
+ strengthrandrange = 0.0
+ pos = (0, 0)
+
+ keyframes = None
+
+ for parameter in child.children:
+ if parameter.tag == "strength":
+ strength = float(parameter.inside)
+ elif parameter.tag == "strengthrandrange":
+ strengthrandrange = float(parameter.inside)
+ elif parameter.tag == "pos":
+ direction = self.ConvertXMLTuple(parameter.inside)
+ elif parameter.tag == "keyframes":
+ keyframes = parameter.children
+
+ newgrav = self.CreateVortexGravity(strength, strengthrandrange, pos)
+
+ for keyframe in keyframes:
+ frame = int(keyframe.meta['frame'])
+ variables = {}
+
+ for variable in keyframe.children:
+ if variable.tag == "strength" and variable.inside != "None":
+ variables['strength'] = float(variable.inside)
+ elif variable.tag == "strengthrandrange" and variable.inside != "None":
+ variables['strengthrandrange'] = float(variable.inside)
+ elif variable.tag == "pos_x" and variable.inside != "None":
+ variables['pos_x'] = int(variable.inside)
+ elif variable.tag == "pos_y" and variable.inside != "None":
+ variables['pos_y'] = int(variable.inside)
+ elif variable.tag == "interpolationtype" and variable.inside != "None":
+ variables['interpolationtype'] = self.GetStringAsInterpolationtype(variable.inside)
+
+ newframe = newgrav.CreateKeyframe(frame = frame)
+ newframe.variables = variables
+
+ elif child.tag == "circle":
+ pos = (0, 0)
+ colour = (0, 0, 0)
+ bounce = 0.0
+ radius = 0.0
+
+ keyframes = None
+
+ for parameter in child.children:
+ if parameter.tag == "pos":
+ pos = self.ConvertXMLTuple(parameter.inside)
+ elif parameter.tag == "colour":
+ colour = self.ConvertXMLTuple(parameter.inside)
+ elif parameter.tag == "bounce":
+ bounce = float(parameter.inside)
+ elif parameter.tag == "radius":
+ radius = float(parameter.inside)
+ elif parameter.tag == "keyframes":
+ keyframes = parameter.children
+
+ newobstacle = self.CreateCircle(pos, colour, bounce, radius)
+
+ for keyframe in keyframes:
+ frame = int(keyframe.meta['frame'])
+ variables = {}
+
+ for variable in keyframe.children:
+ if variable.tag == "pos_x" and variable.inside != "None":
+ variables['pos_x'] = int(variable.inside)
+ elif variable.tag == "pos_y" and variable.inside != "None":
+ variables['pos_y'] = int(variable.inside)
+ elif variable.tag == "colour_r" and variable.inside != "None":
+ variables['colour_r'] = int(variable.inside)
+ elif variable.tag == "colour_g" and variable.inside != "None":
+ variables['colour_g'] = int(variable.inside)
+ elif variable.tag == "colour_b" and variable.inside != "None":
+ variables['colour_b'] = int(variable.inside)
+ elif variable.tag == "bounce" and variable.inside != "None":
+ variables['bounce'] = float(variable.inside)
+ elif variable.tag == "radius" and variable.inside != "None":
+ variables['radius'] = float(variable.inside)
+ elif variable.tag == "interpolationtype" and variable.inside != "None":
+ variables['interpolationtype'] = self.GetStringAsInterpolationtype(variable.inside)
+
+ newframe = newobstacle.CreateKeyframe(frame = frame)
+ newframe.variables = variables
+
+ elif child.tag == "rectangle":
+ pos = (0, 0)
+ colour = (0, 0, 0)
+ bounce = 0.0
+ width = 0.0
+ height = 0.0
+
+ keyframes = None
+
+ for parameter in child.children:
+ if parameter.tag == "pos":
+ pos = self.ConvertXMLTuple(parameter.inside)
+ elif parameter.tag == "colour":
+ colour = self.ConvertXMLTuple(parameter.inside)
+ elif parameter.tag == "bounce":
+ bounce = float(parameter.inside)
+ elif parameter.tag == "width":
+ width = float(parameter.inside)
+ elif parameter.tag == "height":
+ height = float(parameter.inside)
+ elif parameter.tag == "keyframes":
+ keyframes = parameter.children
+
+ newobstacle = self.CreateRectangle(pos, colour, bounce, width, height)
+
+ for keyframe in keyframes:
+ frame = int(keyframe.meta['frame'])
+ variables = {}
+
+ for variable in keyframe.children:
+ if variable.tag == "pos_x" and variable.inside != "None":
+ variables['pos_x'] = int(variable.inside)
+ elif variable.tag == "pos_y" and variable.inside != "None":
+ variables['pos_y'] = int(variable.inside)
+ elif variable.tag == "colour_r" and variable.inside != "None":
+ variables['colour_r'] = int(variable.inside)
+ elif variable.tag == "colour_g" and variable.inside != "None":
+ variables['colour_g'] = int(variable.inside)
+ elif variable.tag == "colour_b" and variable.inside != "None":
+ variables['colour_b'] = int(variable.inside)
+ elif variable.tag == "bounce" and variable.inside != "None":
+ variables['bounce'] = float(variable.inside)
+ elif variable.tag == "width" and variable.inside != "None":
+ variables['width'] = float(variable.inside)
+ elif variable.tag == "height" and variable.inside != "None":
+ variables['height'] = float(variable.inside)
+ elif variable.tag == "interpolationtype" and variable.inside != "None":
+ variables['interpolationtype'] = self.GetStringAsInterpolationtype(variable.inside)
+
+ newframe = newobstacle.CreateKeyframe(frame = frame)
+ newframe.variables = variables
+
+ elif child.tag == "boundaryline":
+ pos = (0, 0)
+ colour = (0, 0, 0)
+ bounce = 0.0
+ direction = [0.0, 0.0]
+
+ keyframes = None
+
+ for parameter in child.children:
+ if parameter.tag == "pos":
+ pos = self.ConvertXMLTuple(parameter.inside)
+ elif parameter.tag == "colour":
+ colour = self.ConvertXMLTuple(parameter.inside)
+ elif parameter.tag == "bounce":
+ bounce = float(parameter.inside)
+ elif parameter.tag == "normal":
+ normal = self.ConvertXMLTuple(parameter.inside)
+ elif parameter.tag == "keyframes":
+ keyframes = parameter.children
+
+ newobstacle = self.CreateBoundaryLine(pos, colour, bounce, normal)
+
+ for keyframe in keyframes:
+ frame = int(keyframe.meta['frame'])
+ variables = {}
+
+ for variable in keyframe.children:
+ if variable.tag == "pos_x" and variable.inside != "None":
+ variables['pos_x'] = int(variable.inside)
+ elif variable.tag == "pos_y" and variable.inside != "None":
+ variables['pos_y'] = int(variable.inside)
+ elif variable.tag == "colour_r" and variable.inside != "None":
+ variables['colour_r'] = int(variable.inside)
+ elif variable.tag == "colour_g" and variable.inside != "None":
+ variables['colour_g'] = int(variable.inside)
+ elif variable.tag == "colour_b" and variable.inside != "None":
+ variables['colour_b'] = int(variable.inside)
+ elif variable.tag == "bounce" and variable.inside != "None":
+ variables['bounce'] = float(variable.inside)
+ elif variable.tag == "normal_x" and variable.inside != "None":
+ variables['normal_x'] = float(variable.inside)
+ elif variable.tag == "normal_y" and variable.inside != "None":
+ variables['normal_y'] = float(variable.inside)
+ elif variable.tag == "interpolationtype" and variable.inside != "None":
+ variables['interpolationtype'] = self.GetStringAsInterpolationtype(variable.inside)
+
+ newframe = newobstacle.CreateKeyframe(frame = frame)
+ newframe.variables = variables
+
+
+## Begin testing code
+if __name__ == '__main__':
+ screen = pygame.display.set_mode((800, 600))
+ pygame.display.set_caption("PyIgnition demo")
+ clock = pygame.time.Clock()
+ test = ParticleEffect(screen, (0, 0), (800, 600))
+ testgrav = test.CreatePointGravity(strength = 1.0, pos = (500, 380))
+ testgrav.CreateKeyframe(300, strength = 10.0, pos = (0, 0))
+ testgrav.CreateKeyframe(450, strength = 10.0, pos = (40, 40))
+ testgrav.CreateKeyframe(550, strength = -2.0, pos = (600, 480))
+ testgrav.CreateKeyframe(600, strength = -20.0, pos = (600, 0))
+ testgrav.CreateKeyframe(650, strength = 1.0, pos = (500, 380))
+ anothertestgrav = test.CreateDirectedGravity(strength = 0.04, direction = [1, 0])
+ anothertestgrav.CreateKeyframe(300, strength = 1.0, direction = [-0.5, 1])
+ anothertestgrav.CreateKeyframe(600, strength = 1.0, direction = [1.0, -0.1])
+ anothertestgrav.CreateKeyframe(650, strength = 0.04, direction = [1, 0])
+ testsource = test.CreateSource((10, 10), initspeed = 5.0, initdirection = 2.35619449, initspeedrandrange = 2.0, initdirectionrandrange = 1.0, particlesperframe = 5, particlelife = 125, drawtype = DRAWTYPE_SCALELINE, colour = (255, 255, 255), length = 10.0)
+ testsource.CreateParticleKeyframe(50, colour = (0, 255, 0), length = 10.0)
+ testsource.CreateParticleKeyframe(75, colour = (255, 255, 0), length = 10.0)
+ testsource.CreateParticleKeyframe(100, colour = (0, 255, 255), length = 10.0)
+ testsource.CreateParticleKeyframe(125, colour = (0, 0, 0), length = 10.0)
+
+ test.SaveToFile("PyIgnition test.ppe")
+
+ while True:
+ for event in pygame.event.get():
+ if event.type == pygame.QUIT:
+ sys.exit()
+
+ screen.fill((0, 0, 0))
+ test.Update()
+ test.Redraw()
+ pygame.display.update()
+ clock.tick(20)
diff --git a/pyignition/constants.py b/pyignition/constants.py
@@ -0,0 +1,28 @@
+### EXESOFT PYIGNITION ###
+# Copyright David Barker 2010
+#
+# Global constants module
+
+
+# Which version is this?
+PYIGNITION_VERSION = 1.0
+
+# Drawtype constants
+DRAWTYPE_POINT = 100
+DRAWTYPE_CIRCLE = 101
+DRAWTYPE_LINE = 102
+DRAWTYPE_SCALELINE = 103
+DRAWTYPE_BUBBLE = 104
+DRAWTYPE_IMAGE = 105
+
+# Interpolation type constants
+INTERPOLATIONTYPE_LINEAR = 200
+INTERPOLATIONTYPE_COSINE = 201
+
+# Gravity constants
+UNIVERSAL_CONSTANT_OF_MAKE_GRAVITY_LESS_STUPIDLY_SMALL = 1000.0 # Well, Newton got one to make it less stupidly large.
+VORTEX_ACCELERATION = 0.01 # A tiny value added to the centripetal force exerted by vortex gravities to draw in particles
+VORTEX_SWALLOWDIST = 20.0 # Particles closer than this will be swallowed up and regurgitated in the bit bucket
+
+# Fraction of radius which can go inside an object
+RADIUS_PERMITTIVITY = 0.3+
\ No newline at end of file
diff --git a/pyignition/gravity.py b/pyignition/gravity.py
@@ -0,0 +1,197 @@
+### EXESOFT PYIGNITION ###
+# Copyright David Barker 2010
+#
+# Gravity objects
+
+
+from math import sqrt
+import keyframes, interpolate, random
+from constants import *
+
+
+def RandomiseStrength(base, range):
+ return base + (float(random.randrange(int(-range * 100), int(range * 100))) / 100.0)
+
+
+class DirectedGravity:
+ def __init__(self, strength = 0.0, strengthrandrange = 0.0, direction = [0, 1]):
+ self.type = "directed"
+ self.initstrength = strength
+ self.strength = strength
+ self.strengthrandrange = strengthrandrange
+ directionmag = sqrt(direction[0]**2 + direction[1]**2)
+ self.direction = [direction[0] / directionmag, direction[1] / directionmag]
+
+ self.keyframes = []
+ self.CreateKeyframe(0, self.strength, self.strengthrandrange, self.direction)
+ self.curframe = 0
+
+ def Update(self):
+ newvars = interpolate.InterpolateKeyframes(self.curframe, {'strength':self.initstrength, 'strengthrandrange':self.strengthrandrange, 'direction_x':self.direction[0], 'direction_y':self.direction[1]}, self.keyframes)
+ self.initstrength = newvars['strength']
+ self.strengthrandrange = newvars['strengthrandrange']
+ self.direction = [newvars['direction_x'], newvars['direction_y']]
+
+ if self.strengthrandrange != 0.0:
+ self.strength = RandomiseStrength(self.initstrength, self.strengthrandrange)
+
+ self.curframe = self.curframe + 1
+
+ def GetForce(self, pos):
+ force = [self.strength * self.direction[0], self.strength * self.direction[1]]
+
+ return force
+
+ def GetForceOnParticle(self, particle):
+ return self.GetForce(particle.pos)
+
+ def CreateKeyframe(self, frame, strength = None, strengthrandrange = None, direction = [None, None], interpolationtype = INTERPOLATIONTYPE_LINEAR):
+ return keyframes.CreateKeyframe(self.keyframes, frame, {'strength':strength, 'strengthrandrange':strengthrandrange, 'direction_x':direction[0], 'direction_y':direction[1], 'interpolationtype':interpolationtype})
+
+ def ConsolidateKeyframes(self):
+ keyframes.ConsolidateKeyframes(self.keyframes, self.curframe, {'strength':self.initstrength, 'strengthrandrange':self.strengthrandrange, 'direction_x':self.direction[0], 'direction_y':self.direction[1]})
+
+ def SetStrength(self, newstrength):
+ self.CreateKeyframe(self.curframe, strength = newstrength)
+
+ def SetStrengthRandRange(self, newstrengthrandrange):
+ self.CreateKeyframe(self.curframe, strengthrandrange = newstrengthrandrange)
+
+ def SetDirection(self, newdirection):
+ self.CreateKeyframe(self.curframe, direction = newdirection)
+
+
+class PointGravity:
+ def __init__(self, strength = 0.0, strengthrandrange = 0.0, pos = (0, 0)):
+ self.type = "point"
+ self.initstrength = strength
+ self.strength = strength
+ self.strengthrandrange = strengthrandrange
+ self.pos = pos
+
+ self.keyframes = []
+ self.CreateKeyframe(0, self.strength, self.strengthrandrange, self.pos)
+ self.curframe = 0
+
+ def Update(self):
+ newvars = interpolate.InterpolateKeyframes(self.curframe, {'strength':self.initstrength, 'strengthrandrange':self.strengthrandrange, 'pos_x':self.pos[0], 'pos_y':self.pos[1]}, self.keyframes)
+ self.initstrength = newvars['strength']
+ self.strengthrandrange = newvars['strengthrandrange']
+ self.pos = (newvars['pos_x'], newvars['pos_y'])
+
+ if self.strengthrandrange != 0.0:
+ self.strength = RandomiseStrength(self.initstrength, self.strengthrandrange)
+ else:
+ self.strength = self.initstrength
+
+ self.curframe = self.curframe + 1
+
+ def GetForce(self, pos):
+ distsquared = (pow(float(pos[0] - self.pos[0]), 2.0) + pow(float(pos[1] - self.pos[1]), 2.0))
+ if distsquared == 0.0:
+ return [0.0, 0.0]
+
+ forcemag = (self.strength * UNIVERSAL_CONSTANT_OF_MAKE_GRAVITY_LESS_STUPIDLY_SMALL) / (distsquared)
+
+ # Calculate normal vector from pos to the gravity point and multiply by force magnitude to find force vector
+ dist = sqrt(distsquared)
+ dx = float(self.pos[0] - pos[0]) / dist
+ dy = float(self.pos[1] - pos[1]) / dist
+
+ force = [forcemag * dx, forcemag * dy]
+
+ return force
+
+ def GetForceOnParticle(self, particle):
+ return self.GetForce(particle.pos)
+
+ def GetMaxForce(self):
+ return self.strength * UNIVERSAL_CONSTANT_OF_MAKE_GRAVITY_LESS_STUPIDLY_SMALL
+
+ def CreateKeyframe(self, frame, strength = None, strengthrandrange = None, pos = (None, None), interpolationtype = INTERPOLATIONTYPE_LINEAR):
+ return keyframes.CreateKeyframe(self.keyframes, frame, {'strength':strength, 'strengthrandrange':strengthrandrange, 'pos_x':pos[0], 'pos_y':pos[1], 'interpolationtype':interpolationtype})
+
+ def ConsolidateKeyframes(self):
+ keyframes.ConsolidateKeyframes(self.keyframes, self.curframe, {'strength':self.initstrength, 'strengthrandrange':self.strengthrandrange, 'pos_x':self.pos[0], 'pos_y':self.pos[1]})
+
+ def SetStrength(self, newstrength):
+ self.CreateKeyframe(self.curframe, strength = newstrength)
+
+ def SetStrengthRandRange(self, newstrengthrandrange):
+ self.CreateKeyframe(self.curframe, strengthrandrange = newstrengthrandrange)
+
+ def SetPos(self, newpos):
+ self.CreateKeyframe(self.curframe, pos = newpos)
+
+
+class VortexGravity(PointGravity):
+ def __init__(self, strength = 0.0, strengthrandrange = 0.0, pos = (0, 0)):
+ PointGravity.__init__(self, strength, strengthrandrange, pos)
+ self.type = "vortex"
+
+ self.CreateKeyframe(0, self.strength, self.strengthrandrange, self.pos)
+
+ def Update(self):
+ newvars = interpolate.InterpolateKeyframes(self.curframe, {'strength':self.initstrength, 'strengthrandrange':self.strengthrandrange, 'pos_x':self.pos[0], 'pos_y':self.pos[1]}, self.keyframes)
+ self.initstrength = newvars['strength']
+ self.strengthrandrange = newvars['strengthrandrange']
+ self.pos = (newvars['pos_x'], newvars['pos_y'])
+
+ if self.strengthrandrange != 0.0:
+ self.strength = RandomiseStrength(self.initstrength, self.strengthrandrange)
+ else:
+ self.strength = self.initstrength
+
+ self.curframe = self.curframe + 1
+
+ def GetForce(self, pos):
+ try:
+ self.alreadyshownerror
+ except:
+ print("WARNING: VortexGravity relies upon particle velocities as well as positions, and so its \
+force can only be obtained using GetForceOnParticle([PyIgnition particle object]).")
+ self.alreadyshownerror = True
+
+ return [0.0, 0.0]
+
+ def GetForceOnParticle(self, particle):
+ # This uses F = m(v^2 / r) (the formula for centripetal force on an object moving in a circle)
+ # to determine what force should be applied to keep an object circling the gravity. A small extra
+ # force (self.strength * VORTEX_ACCELERATION) is added in order to accelerate objects inward as
+ # well, thus creating a spiralling effect. Note that unit mass is assumed throughout.
+
+ distvector = [self.pos[0] - particle.pos[0], self.pos[1] - particle.pos[1]] # Vector from the particle to this gravity
+ try:
+ distmag = sqrt(float(distvector[0] ** 2) + float(distvector[1] ** 2)) # Distance from the particle to this gravity
+ except:
+ return [0.0, 0.0] # This prevents OverflowErrors
+
+ if distmag == 0.0:
+ return [0.0, 0.0]
+
+ if distmag <= VORTEX_SWALLOWDIST:
+ particle.alive = False
+
+ normal = [float(distvector[0]) / distmag, float(distvector[1]) / distmag] # Normal from particle to this gravity
+
+ velocitymagsquared = (particle.velocity[0] ** 2) + (particle.velocity[1] ** 2) # Velocity magnitude squared
+ forcemag = (velocitymagsquared / distmag) + (self.strength * VORTEX_ACCELERATION) # Force magnitude = (v^2 / r) + radial acceleration
+
+ #velparmag = (particle.velocity[0] * normal[0]) + (particle.velocity[1] * normal[1]) # Magnitude of velocity parallel to normal
+ #velpar = [normal[0] * velparmag, normal[1] * velparmag] # Vector of velocity parallel to normal
+ #velperp = [particle.velocity[0] - velpar[0], particle.velocity[1] - velpar[1]] # Vector of velocity perpendicular to normal
+ #
+ #fnpar = [-velperp[1], velperp[0]] # Force normal parallel to normal
+ #fnperp = [velpar[1], -velpar[0]] # Force normal perpendicular to normal
+ #
+ #force = [(fnpar[0] + fnperp[0]) * forcemag, (fnpar[1] + fnperp[1]) * forcemag]
+
+ force = [normal[0] * forcemag, normal[1] * forcemag] # Works, but sometimes goes straight to the gravity w/ little spiraling
+
+ return force
+
+ def CreateKeyframe(self, frame, strength = None, strengthrandrange = None, pos = (None, None), interpolationtype = INTERPOLATIONTYPE_LINEAR):
+ return keyframes.CreateKeyframe(self.keyframes, frame, {'strength':strength, 'strengthrandrange':strengthrandrange, 'pos_x':pos[0], 'pos_y':pos[1], 'interpolationtype':interpolationtype})
+
+ def ConsolidateKeyframes(self):
+ keyframes.ConsolidateKeyframes(self.keyframes, self.curframe, {'strength':self.initstrength, 'strengthrandrange':self.strengthrandrange, 'pos_x':self.pos[0], 'pos_y':self.pos[1]})
diff --git a/pyignition/interpolate.py b/pyignition/interpolate.py
@@ -0,0 +1,77 @@
+### EXESOFT PYIGNITION ###
+# Copyright David Barker 2010
+#
+# Utility module for interpolating between keyframed values
+
+
+import math
+from constants import *
+
+
+def LinearInterpolate(val1, val2, t):
+ diff = val2 - val1
+ dist = float(diff) * t
+
+ return val1 + dist
+
+def CosineInterpolate(val1, val2, t):
+ amplitude = float(val2 - val1) / 2.0
+ midpoint = float(val1 + val2) / 2.0
+
+ return (amplitude * math.cos(math.pi * (1.0 - t))) + midpoint
+
+
+def LinearInterpolateKeyframes(curframe, key1, key2, val1, val2):
+ if key1 == key2:
+ return val2
+
+ factor = float(curframe - key1) / float(key2 - key1)
+
+ return LinearInterpolate(val1, val2, factor)
+
+def CosineInterpolateKeyframes(curframe, key1, key2, val1, val2):
+ if key1 == key2:
+ return val2
+
+ factor = float(curframe - key1) / float(key2 - key1)
+
+ return CosineInterpolate(val1, val2, factor)
+
+
+def InterpolateKeyframes(curframe, variables, keyframes):
+ if len(keyframes) == 1:
+ return keyframes[0].variables
+
+ finalvariables = {}
+
+ if not ('interpolationtype' in variables):
+ variables['interpolationtype'] = INTERPOLATIONTYPE_LINEAR
+
+ keys = list(variables.keys())
+
+ for i in range(len(keys)): # Determine current keyframe and next one for this variable
+ key = keys[i]
+ curkeyframe = None
+ nextkeyframe = None
+
+ for i in range(len(keyframes)):
+ try:
+ frame = keyframes[i]
+ if (frame.variables[key] != None): # If the current keyframe has a keyed value for the current variable
+ if frame.frame <= curframe: # If its frame is below or equal to the current, it is the current keyframe
+ curkeyframe = i
+ if (nextkeyframe == None) and (frame.frame > curframe): # If this is the first keyframe with a frame higher than the current, it is the next keyframe
+ nextkeyframe = i
+ except KeyError:
+ pass
+
+ if nextkeyframe == None or key == "interpolationtype": # If there is no next key frame, maintain the value specified by the current one
+ finalvariables[key] = keyframes[curkeyframe].variables[key] # (Also do this if it is an interpolation type variable; they should only change once their next keyframe has been reached
+
+ else: # Interpolate between the current and next keyframes
+ if keyframes[nextkeyframe].variables['interpolationtype'] == INTERPOLATIONTYPE_LINEAR:
+ finalvariables[key] = LinearInterpolateKeyframes(curframe, keyframes[curkeyframe].frame, keyframes[nextkeyframe].frame, keyframes[curkeyframe].variables[key], keyframes[nextkeyframe].variables[key])
+ elif keyframes[nextkeyframe].variables['interpolationtype'] == INTERPOLATIONTYPE_COSINE:
+ finalvariables[key] = CosineInterpolateKeyframes(curframe, keyframes[curkeyframe].frame, keyframes[nextkeyframe].frame, keyframes[curkeyframe].variables[key], keyframes[nextkeyframe].variables[key])
+
+ return finalvariables
diff --git a/pyignition/keyframes.py b/pyignition/keyframes.py
@@ -0,0 +1,61 @@
+### EXESOFT PYIGNITION ###
+# Copyright David Barker 2010
+#
+# Keyframe object and generic keyframe creation function
+
+
+from constants import *
+import sys
+
+def CreateKeyframe(parentframes, frame, variables):
+ newframe = Keyframe(frame, variables)
+
+ # Look for duplicate keyframes and copy other defined variables
+ try:
+ if sys.version_info.major == 2:
+ oldkey = (keyframe for keyframe in parentframes if keyframe.frame == frame).next()
+ else:
+ oldkey = (keyframe for keyframe in parentframes if keyframe.frame == frame).__next__()
+ except StopIteration:
+ oldkey = None
+
+ if oldkey != None:
+ for var in oldkey.variables.keys(): # For every variable defined by the old keyframe
+ if (var not in newframe.variables or newframe.variables[var] == None) and (oldkey.variables[var] != None): # If a variable is undefined, copy its value to the new keyframe
+ newframe.variables[var] = oldkey.variables[var]
+
+ # Remove the duplicate keyframe, if it existed
+ for duplicate in (keyframe for keyframe in parentframes if keyframe.frame == frame):
+ parentframes.remove(duplicate)
+ break
+
+ # Append the new keyframe then sort them all by frame
+ parentframes.append(newframe)
+ sortedframes = sorted(parentframes, key=lambda keyframe: keyframe.frame)
+ parentframes[:] = sortedframes
+
+ return newframe # Return a reference to the new keyframe, in case it's needed
+
+def ConsolidateKeyframes(parentframes, frame, variables):
+ newframe = Keyframe(frame, variables)
+ parentframes.append(newframe)
+
+ finallist = [] # A truncated list of keyframes
+
+ # Append all the frames which come after the current one to the final list
+ for keyframe in parentframes:
+ if keyframe.frame >= frame:
+ finallist.append(keyframe)
+
+ # Sort the keyframes and give them to the parent object
+ sortedframes = sorted(finallist, key=lambda keyframe: keyframe.frame)
+ parentframes[:] = sortedframes
+
+class Keyframe:
+ def __init__(self, frame = 0, variables = {}):
+ self.frame = frame
+ self.variables = variables
+
+ if not ('interpolationtype' in self.variables):
+ self.variables['interpolationtype'] = INTERPOLATIONTYPE_LINEAR
+
diff --git a/pyignition/obstacles.py b/pyignition/obstacles.py
@@ -0,0 +1,404 @@
+### EXESOFT PYIGNITION ###
+# Copyright David Barker 2010
+#
+# Obstacle objects
+
+import pygame
+from math import sqrt, pow
+from gravity import UNIVERSAL_CONSTANT_OF_MAKE_GRAVITY_LESS_STUPIDLY_SMALL
+import keyframes, interpolate
+from constants import *
+
+
+MAXDIST = 20.0
+
+
+def dotproduct2d(v1, v2):
+ return ((v1[0] * v2[0]) + (v1[1] * v2[1]))
+
+def magnitude(vec):
+ try:
+ return sqrt(vec[0] ** 2 + vec[1] ** 2)
+ except:
+ return 1.0
+
+def magnitudesquared(vec):
+ try:
+ return (vec[0] ** 2 + vec[1] ** 2)
+ except:
+ return 1.0
+
+def normalise(vec):
+ mag = magnitude(vec)
+ return [vec[0] / mag, vec[1] / mag]
+
+
+class Obstacle:
+ def __init__(self, parenteffect, pos, colour, bounce):
+ self.parenteffect = parenteffect
+ self.pos = pos
+ self.colour = colour
+ self.bounce = bounce
+ self.maxdist = MAXDIST # The maximum (square-based, not circle-based) distance away for which forces will still be calculated
+ self.curframe = 0
+ self.keyframes = []
+
+ def Draw(self, display):
+ pass
+
+ def Update(self):
+ self.curframe = self.curframe + 1
+
+ def OutOfRange(self, pos):
+ return (abs(pos[0] - self.pos[0]) > self.maxdist) or (abs(pos[1] - self.pos[1]) > self.maxdist)
+
+ def InsideObject(self, pos, pradius):
+ pass
+
+ def GetResolved(self, pos, pradius): # Get a resolved position for a particle located inside the object
+ pass
+
+ def GetDist(self, pos):
+ return magnitude([pos[0] - self.pos[0], pos[1] - self.pos[1]])
+
+ 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)
+ pass
+
+ 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
+ pass
+
+ def GetForce(self, pos, velocity, pradius = 0.0): # Gets the final (vector) force
+ if self.OutOfRange(pos) or self.bounce == 0.0:
+ return [0.0, 0.0]
+
+ if (pos[0] == self.pos[0]) and (pos[1] == self.pos[1]):
+ return [0.0, 0.0]
+
+ normal = self.GetNormal(pos)
+ 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
+
+ if scalingfactor <= 0.0: # The force should never be attractive, so take any negative value of the scaling factor to be zero
+ return [0.0, 0.0] # A scaling factor of zero always results in zero force
+
+ forcefactor = (self.GetForceFactor(pos, pradius))
+ velmag = magnitude(velocity) # Magnitude of the velocity - multiplied in the force equation
+
+ # 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
+ return [normal[0] * forcefactor * velmag * scalingfactor * self.bounce, normal[1] * forcefactor * velmag * scalingfactor * self.bounce]
+
+ def CreateKeyframe(self):
+ pass
+
+ def SetPos(self, newpos):
+ self.CreateKeyframe(self.curframe, pos = newpos)
+
+ def SetColour(self, newcolour):
+ self.CreateKeyframe(self.curframe, colour = newcolour)
+
+ def SetBounce(self, newbounce):
+ self.CreateKeyframe(self.curframe, bounce = newbounce)
+
+
+class Circle(Obstacle):
+ def __init__(self, parenteffect, pos, colour, bounce, radius):
+ Obstacle.__init__(self, parenteffect, pos, colour, bounce)
+ self.type = "circle"
+ self.radius = radius
+ self.radiussquared = self.radius ** 2
+ self.maxdist = MAXDIST + self.radius
+ self.CreateKeyframe(0, self.pos, self.colour, self.bounce, self.radius)
+
+ def Draw(self, display):
+ offset = self.parenteffect.pos
+ pygame.draw.circle(display, self.colour, (offset[0] + int(self.pos[0]), offset[1] + int(self.pos[1])), int(self.radius))
+
+ def Update(self):
+ 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)
+ self.pos = (newvars['pos_x'], newvars['pos_y'])
+ self.colour = (newvars['colour_r'], newvars['colour_g'], newvars['colour_b'])
+ self.bounce = newvars['bounce']
+ self.radius = newvars['radius']
+
+ Obstacle.Update(self)
+
+ def InsideObject(self, pos, pradius = 0.0):
+ mag = magnitude([pos[0] - self.pos[0], pos[1] - self.pos[1]])
+ return (((mag - pradius) ** 2) < self.radiussquared)
+
+ def GetResolved(self, pos, pradius = 0.0):
+ if pos == self.pos: # If the position is at this obstacle's origin, shift it up a pixel to avoid divide-by-zero errors
+ return self.GetResolved([pos[0], pos[1] - 1])
+
+ vec = [pos[0] - self.pos[0], pos[1] - self.pos[1]]
+ mag = magnitude(vec)
+ nor = [vec[0] / mag, vec[1] / mag]
+
+ # Split the pradius into appropriate components by multiplying the normal vector by pradius
+ pradiusvec = [(nor[0] * pradius), (nor[1] * pradius)]
+
+ correctedvec = [nor[0] * (self.radius), nor[1] * (self.radius)]
+
+ return [self.pos[0] + correctedvec[0] + pradiusvec[0], self.pos[1] + correctedvec[1] + pradiusvec[1]]
+
+ def GetNormal(self, pos):
+ vec = [pos[0] - self.pos[0], pos[1] - self.pos[1]]
+ mag = magnitude(vec)
+
+ return [vec[0] / mag, vec[1] / mag]
+
+ def GetForceFactor(self, pos, pradius = 0.0):
+ nvec = self.GetNormal(pos)
+ tempradius = self.radius
+ vec = [tempradius * nvec[0], tempradius * nvec[1]]
+ newpos = [pos[0] - vec[0], pos[1] - vec[1]]
+
+ distcubed = (abs(pow(float(newpos[0] - self.pos[0]), 3.0)) + abs(pow(float(newpos[1] - self.pos[1]), 3.0))) - (pradius * pradius * pradius)
+ if distcubed <= 1.0:
+ return 1.0
+
+ force = (1.0 / distcubed)
+
+ return force
+
+ def CreateKeyframe(self, frame, pos = (None, None), colour = (None, None, None), bounce = None, radius = None, interpolationtype = INTERPOLATIONTYPE_LINEAR):
+ 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})
+
+ def ConsolidateKeyframes(self):
+ 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})
+
+ def SetRadius(self, newradius):
+ self.CreateKeyframe(self.curframe, radius = newradius)
+
+
+class Rectangle(Obstacle):
+ def __init__(self, parenteffect, pos, colour, bounce, width, height):
+ Obstacle.__init__(self, parenteffect, pos, colour, bounce)
+ self.type = "rectangle"
+ self.width = width
+ self.halfwidth = self.width / 2.0
+ self.height = height
+ self.halfheight = height / 2.0
+ self.maxdist = max(self.halfwidth, self.halfheight) + MAXDIST
+ self.CreateKeyframe(0, self.pos, self.colour, self.bounce, self.width, self.height)
+
+ def Draw(self, display):
+ offset = self.parenteffect.pos
+ 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))
+
+ def Update(self):
+ 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)
+ self.pos = (newvars['pos_x'], newvars['pos_y'])
+ self.colour = (newvars['colour_r'], newvars['colour_g'], newvars['colour_b'])
+ self.bounce = newvars['bounce']
+ self.width = newvars['width']
+ self.halfwidth = self.width / 2.0
+ self.height = newvars['height']
+ self.halfheight = self.height / 2.0
+ self.maxdist = max(self.halfwidth, self.halfheight) + MAXDIST
+
+ Obstacle.Update(self)
+
+ def InsideObject(self, pos, pradius = 0.0):
+ 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))
+
+ def GetResolved(self, pos, pradius = 0.0):
+ if pos == self.pos: # If the position is at this obstacle's origin, shift it up a pixel to avoid divide-by-zero errors
+ return self.GetResolved([pos[0], pos[1] - 1])
+
+ # 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
+ if pos[0] == self.pos[0]: # If it's directly above the centre of the rectangle
+ if pos[1] > self.pos[1]:
+ return [pos[0], self.pos[1] + self.halfheight + pradius]
+ else:
+ return [pos[0], self.pos[1] - (self.halfheight + pradius)]
+ elif pos[1] == self.pos[1]: # If it's directly to one side of the centre of the rectangle
+ if pos[0] > self.pos[0]:
+ return [self.pos[0] + self.halfwidth + pradius, pos[1]]
+ else:
+ return [self.pos[0] - (self.halfwidth + pradius), pos[1]]
+ 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
+ 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
+ else: # If it's in the left or right triangle of the rectangle
+ return [self.pos[0] + ((self.halfwidth + pradius) * ((pos[0] - self.pos[0]) / abs(pos[0] - self.pos[0]))), pos[1]]
+
+ def GetNormal(self, pos):
+ if pos[1] < (self.pos[1] - self.halfheight):
+ return [0, -1]
+ elif pos[1] > (self.pos[1] + self.halfheight):
+ return [0, 1]
+ elif pos[0] < (self.pos[0] - self.halfwidth):
+ return [-1, 0]
+ elif pos[0] > (self.pos[0] + self.halfwidth):
+ return [1, 0]
+ else:
+ vect = [pos[0] - self.pos[0], pos[1] - self.pos[1]]
+ mag = magnitude(vect)
+ return [vect[0] / mag, vect[1] / mag]
+
+ def GetForceFactor(self, pos, pradius = 0.0):
+ nor = self.GetNormal(pos)
+
+ if nor[0] == 0:
+ if (pos[0] > (self.pos[0] - self.halfwidth)) and (pos[0] < (self.pos[0] + self.halfwidth)):
+ r = (abs(pos[1] - self.pos[1]) - self.halfheight) - pradius
+ else:
+ return 0.0
+ elif nor[1] == 0:
+ if (pos[1] > (self.pos[1] - self.halfheight)) and (pos[1] < (self.pos[1] + self.halfheight)):
+ r = (abs(pos[0] - self.pos[0]) - self.halfwidth) - pradius
+ else:
+ return 0.0
+ else:
+ return 1.0
+
+ if r <= 1.0:
+ return 1.0
+
+ return (1.0 / pow(float(r), 3.0))
+
+ def CreateKeyframe(self, frame, pos = (None, None), colour = (None, None, None), bounce = None, width = None, height = None, interpolationtype = INTERPOLATIONTYPE_LINEAR):
+ 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})
+
+ def ConsolidateKeyframes(self):
+ 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})
+
+ def SetWidth(self, newwidth):
+ self.CreateKeyframe(self.curframe, width = newwidth)
+
+ def SetHeight(self, newheight):
+ self.CreateKeyframe(self.curframe, height = newheight)
+
+
+class BoundaryLine(Obstacle):
+ def __init__(self, parenteffect, pos, colour, bounce, normal):
+ Obstacle.__init__(self, parenteffect, pos, colour, bounce)
+ self.type = "boundaryline"
+ self.normal = normalise(normal)
+ self.edgecontacts = []
+ self.hascontacts = False
+ self.storedw, self.storedh = None, None
+ self.curframe = 0
+ self.CreateKeyframe(0, self.pos, self.colour, self.bounce, self.normal)
+
+ def Draw(self, display):
+ offset = self.parenteffect.pos
+
+ W = display.get_width()
+ H = display.get_height()
+
+ if (W != self.storedw) or (H != self.storedh):
+ self.hascontacts = False
+
+ if not self.hascontacts:
+ self.storedw, self.storedh = W, H
+
+ edgecontacts = [] # Where the line touches the screen edges
+
+ if self.normal[0] == 0.0:
+ edgecontacts = [[0, self.pos[1]], [W, self.pos[1]]]
+
+ elif self.normal[1] == 0.0:
+ edgecontacts = [[self.pos[0], 0], [self.pos[0], H]]
+
+ else:
+ pdotn = (self.pos[0] * self.normal[0]) + (self.pos[1] * self.normal[1])
+ reciprocaln0 = (1.0 / self.normal[0])
+ reciprocaln1 = (1.0 / self.normal[1])
+
+ # Left-hand side of the screen
+ pointl = [0, 0]
+ pointl[1] = pdotn * reciprocaln1
+ if (pointl[1] >= 0) and (pointl[1] <= H):
+ edgecontacts.append(pointl)
+
+ # Top of the screen
+ pointt = [0, 0]
+ pointt[0] = pdotn * reciprocaln0
+ if (pointt[0] >= 0) and (pointt[0] <= W):
+ edgecontacts.append(pointt)
+
+ # Right-hand side of the screen
+ pointr = [W, 0]
+ pointr[1] = (pdotn - (W * self.normal[0])) * reciprocaln1
+ if (pointr[1] >= 0) and (pointr[1] <= H):
+ edgecontacts.append(pointr)
+
+ # Bottom of the screen
+ pointb = [0, H]
+ pointb[0] = (pdotn - (H * self.normal[1])) * reciprocaln0
+ if (pointb[0] >= 0) and (pointb[0] <= W):
+ edgecontacts.append(pointb)
+
+ self.edgecontacts = edgecontacts
+ self.hascontacts = True
+
+ tempedgecontacts = []
+
+ for contact in self.edgecontacts:
+ tempedgecontacts.append([offset[0] + contact[0], offset[1] + contact[1]])
+
+ if len(tempedgecontacts) >= 2:
+ pygame.draw.aalines(display, self.colour, True, tempedgecontacts)
+ else:
+ pass # The line must be completely outwith the boundaries of the display
+
+ def Update(self):
+ 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)
+ self.pos = (newvars['pos_x'], newvars['pos_y'])
+ self.colour = (newvars['colour_r'], newvars['colour_g'], newvars['colour_b'])
+ self.bounce = newvars['bounce']
+ oldnormal = self.normal[:]
+ self.normal = [newvars['normal_x'], newvars['normal_y']]
+ if self.normal != oldnormal:
+ self.hascontacts = False
+
+ Obstacle.Update(self)
+
+ def OutOfRange(self, pos):
+ return (self.GetDist(pos) > MAXDIST)
+
+ def InsideObject(self, pos, pradius = 0.0):
+ if pradius == 0.0: # If the particle has no radius, just test whether its centre position has crossed the line
+ return (((float(pos[0] - self.pos[0]) * self.normal[0]) + (float(pos[1] - self.pos[1]) * self.normal[1])) <= 0.0)
+
+ radialnormal = [self.normal[0] * pradius, self.normal[1] * pradius]
+ leftside = [pos[0] + radialnormal[0], pos[1] + radialnormal[1]]
+ rightside = [pos[0] - radialnormal[0], pos[1] - radialnormal[1]]
+
+ return ((((float(leftside[0] - self.pos[0]) * self.normal[0]) + (float(leftside[1] - self.pos[1]) * self.normal[1])) <= 0.0)
+ or (((float(rightside[0] - self.pos[0]) * self.normal[0]) + (float(rightside[1] - self.pos[1]) * self.normal[1])) <= 0.0))
+
+ def GetResolved(self, pos, pradius = 0.0):
+ if pos == self.pos: # If the position is at this obstacle's origin, shift it up a pixel to avoid divide-by-zero errors
+ return self.GetResolved([pos[0], pos[1] - 1])
+
+ dist = abs(self.GetDist(pos, pradius))
+ vec = [dist * self.normal[0], dist * self.normal[1]]
+
+ return [pos[0] + vec[0], pos[1] + vec[1]]
+
+ def GetNormal(self, pos):
+ return self.normal
+
+ def GetDist(self, pos, pradius = 0.0):
+ v = [float(pos[0] - self.pos[0]), float(pos[1] - self.pos[1])]
+ return (v[0] * self.normal[0]) + (v[1] * self.normal[1]) - pradius
+
+ def GetForceFactor(self, pos, pradius = 0.0):
+ r = self.GetDist(pos) - pradius
+
+ if r <= 1.0:
+ return 1.0
+
+ return (1.0 / pow(r, 3.0))
+
+ def CreateKeyframe(self, frame, pos = (None, None), colour = (None, None, None), bounce = None, normal = [None, None], interpolationtype = INTERPOLATIONTYPE_LINEAR):
+ if (normal != [None, None]) and (abs(magnitudesquared(normal) - 1.0) >= 0.3):
+ normal = normalise(normal)
+ 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})
+
+ def ConsolidateKeyframes(self):
+ 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]})
+
+ def SetNormal(self, newnormal):
+ self.CreateKeyframe(self.curframe, normal = newnormal)
diff --git a/pyignition/particles.py b/pyignition/particles.py
@@ -0,0 +1,200 @@
+### EXESOFT PYIGNITION ###
+# Copyright David Barker 2010
+#
+# Particle and ParticleSource objects
+
+
+import keyframes, interpolate, random, math, pygame
+from constants import *
+
+
+class Particle:
+ def __init__(self, parent, initpos, velocity, life, drawtype = DRAWTYPE_POINT, colour = (0, 0, 0), radius = 0.0, length = 0.0, image = None, keyframes = []):
+ self.parent = parent
+ self.pos = initpos
+ self.velocity = velocity
+ self.life = life
+ self.drawtype = drawtype
+ self.colour = colour
+ self.radius = radius
+ self.length = length
+ self.image = image
+
+ self.keyframes = []
+ self.keyframes.extend(keyframes[:])
+ self.curframe = 0
+
+ self.alive = True
+
+ def Update(self):
+ self.pos = [self.pos[0] + self.velocity[0], self.pos[1] + self.velocity[1]]
+
+ if self.life != -1 and self.curframe > self.life:
+ self.alive = False
+ else:
+ if self.life == -1 and self.curframe >= len(self.parent.particlecache): # If the particle has infinite life and has passed the last cached frame
+ 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'])
+ self.radius = self.parent.particlecache[len(self.parent.particlecache) - 1]['radius']
+ self.length = self.parent.particlecache[len(self.parent.particlecache) - 1]['length']
+ else: # Otherwise, get the values for the current frame
+ self.colour = (self.parent.particlecache[self.curframe]['colour_r'], self.parent.particlecache[self.curframe]['colour_g'], self.parent.particlecache[self.curframe]['colour_b'])
+ self.radius = self.parent.particlecache[self.curframe]['radius']
+ self.length = self.parent.particlecache[self.curframe]['length']
+
+ self.curframe = self.curframe + 1
+
+ def Draw(self, display):
+ offset = self.parent.parenteffect.pos
+
+ if (self.pos[0] > 10000) or (self.pos[1] > 10000) or (self.pos[0] < -10000) or (self.pos[1] < -10000):
+ return
+
+ if self.drawtype == DRAWTYPE_POINT: # Point
+ pygame.draw.circle(display, self.colour, (offset[0] + int(self.pos[0]), offset[1] + int(self.pos[1])), 0)
+
+ elif self.drawtype == DRAWTYPE_CIRCLE: # Circle
+ pygame.draw.circle(display, self.colour, (offset[0] + int(self.pos[0]), offset[1] + int(self.pos[1])), int(self.radius))
+
+ elif self.drawtype == DRAWTYPE_LINE:
+ if self.length == 0.0:
+ pygame.draw.circle(display, self.colour, (offset[0] + int(self.pos[0]), offset[1] + int(self.pos[1])), 0)
+
+ else:
+ # Vector = (velocity / mag(velocity)) * length (a line of magnitude 'length' in
+ # direction of velocity); this is calculated as velocity / (mag(velocity) / length)
+ # so that parts consistent for both components in the final calculation are only calculated once
+ velocitymagoverlength = math.sqrt(self.velocity[0]**2 + self.velocity[1]**2) / self.length
+
+ if velocitymagoverlength > 0.0: # Avoid division-by-zero errors by handling lines with zero velocity separately
+ linevec = [(self.velocity[0] / velocitymagoverlength), (self.velocity[1] / velocitymagoverlength)]
+ else:
+ linevec = [self.length, 0.0] # Draw a line pointing to the right
+
+ endpoint = [offset[0] + int(self.pos[0] + linevec[0]), offset[1] + int(self.pos[1] + linevec[1])]
+ pygame.draw.aaline(display, self.colour, (offset[0] + int(self.pos[0]), offset[1] + int(self.pos[1])), endpoint)
+
+ elif self.drawtype == DRAWTYPE_SCALELINE: # Scaling line (scales with velocity)
+ endpoint = [offset[0] + int(self.pos[0] + self.velocity[0]), offset[1] + int(self.pos[1] + self.velocity[1])]
+ pygame.draw.aaline(display, self.colour, (offset[0] + int(self.pos[0]), offset[1] + int(self.pos[1])), endpoint)
+
+ elif self.drawtype == DRAWTYPE_BUBBLE: # Bubble
+ if self.radius >= 1.0:
+ pygame.draw.circle(display, self.colour, (offset[0] + int(self.pos[0]), offset[1] + int(self.pos[1])), int(self.radius), 1)
+ else: # Pygame won't draw circles with thickness < radius, so if radius is smaller than one don't bother trying to set thickness
+ pygame.draw.circle(display, self.colour, (offset[0] + int(self.pos[0]), offset[1] + int(self.pos[1])), int(self.radius))
+
+ elif self.drawtype == DRAWTYPE_IMAGE: # Image
+ size = self.image.get_size()
+ display.blit(self.image, (offset[0] + int(self.pos[0] - size[1]), offset[1] + int(self.pos[1] - size[1])))
+
+ def CreateKeyframe(self, frame, colour = (None, None, None), radius = None, length = None):
+ keyframes.CreateKeyframe(self.keyframes, frame, {'colour_r':colour[0], 'colour_g':colour[1], 'colour_b':colour[2], 'radius':radius, 'length':length})
+
+
+class ParticleSource:
+ 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):
+ self.parenteffect = parenteffect
+ self.pos = pos
+ self.initspeed = initspeed
+ self.initdirection = initdirection
+ self.initspeedrandrange = initspeedrandrange
+ self.initdirectionrandrange = initdirectionrandrange
+ self.particlesperframe = particlesperframe
+ self.particlelife = particlelife
+ self.genspacing = genspacing
+ self.colour = colour
+ self.drawtype = drawtype
+ self.radius = radius
+ self.length = length
+ self.imagepath = imagepath
+ if self.imagepath == None:
+ self.image = None
+ else:
+ self.image = pygame.image.load(self.imagepath).convert_alpha()
+ self.drawtype = drawtype
+
+ self.keyframes = []
+ self.CreateKeyframe(0, self.pos, self.initspeed, self.initdirection, self.initspeedrandrange, self.initdirectionrandrange, self.particlesperframe, self.genspacing)
+ self.particlekeyframes = []
+ self.particlecache = []
+ self.CreateParticleKeyframe(0, colour = self.colour, radius = self.radius, length = self.length)
+ self.curframe = 0
+
+ def Update(self):
+ 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)
+ self.pos = (newvars['pos_x'], newvars['pos_y'])
+ self.initspeed = newvars['initspeed']
+ self.initdirection = newvars['initdirection']
+ self.initspeedrandrange = newvars['initspeedrandrange']
+ self.initdirectionrandrange = newvars['initdirectionrandrange']
+ self.particlesperframe = newvars['particlesperframe']
+ self.genspacing = newvars['genspacing']
+
+ particlesperframe = self.particlesperframe
+
+ if (self.genspacing == 0) or ((self.curframe % self.genspacing) == 0):
+ for i in range(0, int(particlesperframe)):
+ self.CreateParticle()
+
+ self.curframe = self.curframe + 1
+
+ def CreateParticle(self):
+ if self.initspeedrandrange != 0.0:
+ speed = self.initspeed + (float(random.randrange(int(-self.initspeedrandrange * 100.0), int(self.initspeedrandrange * 100.0))) / 100.0)
+ else:
+ speed = self.initspeed
+ if self.initdirectionrandrange != 0.0:
+ direction = self.initdirection + (float(random.randrange(int(-self.initdirectionrandrange * 100.0), int(self.initdirectionrandrange * 100.0))) / 100.0)
+ else:
+ direction = self.initdirection
+ velocity = [speed * math.sin(direction), -speed * math.cos(direction)]
+ 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)
+ self.parenteffect.AddParticle(newparticle)
+
+ def CreateKeyframe(self, frame, pos = (None, None), initspeed = None, initdirection = None, initspeedrandrange = None, initdirectionrandrange = None, particlesperframe = None, genspacing = None, interpolationtype = INTERPOLATIONTYPE_LINEAR):
+ 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})
+
+ def CreateParticleKeyframe(self, frame, colour = (None, None, None), radius = None, length = None, interpolationtype = INTERPOLATIONTYPE_LINEAR):
+ newframe = keyframes.CreateKeyframe(self.particlekeyframes, frame, {'colour_r':colour[0], 'colour_g':colour[1], 'colour_b':colour[2], 'radius':radius, 'length':length, 'interpolationtype':interpolationtype})
+ self.PreCalculateParticles()
+ return newframe
+
+ def GetKeyframeValue(self, keyframe):
+ return keyframe.frame
+
+ def PreCalculateParticles(self):
+ self.particlecache = [] # Clear the cache
+
+ # If the particle has infinite life, interpolate for each frame up until its last keyframe
+ if self.particlelife == -1:
+ particlelife = max(self.particlekeyframes, key = self.GetKeyframeValue).frame
+ else: # Otherwise, interpolate the particle variables for each frame of its life
+ particlelife = self.particlelife
+
+ for i in range(0, particlelife + 1):
+ vars = interpolate.InterpolateKeyframes(i, {'colour_r':0, 'colour_g':0, 'colour_b':0, 'radius':0, 'length':0}, self.particlekeyframes)
+ self.particlecache.append(vars)
+
+ def ConsolidateKeyframes(self):
+ 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})
+
+ def SetPos(self, newpos):
+ self.CreateKeyframe(self.curframe, pos = newpos)
+
+ def SetInitSpeed(self, newinitspeed):
+ self.CreateKeyframe(self.curframe, initspeed = newinitspeed)
+
+ def SetInitDirection(self, newinitdirection):
+ self.CreateKeyframe(self.curframe, initdirection = newinitdirection)
+
+ def SetInitSpeedRandRange(self, newinitspeedrandrange):
+ self.CreateKeyframe(self.curframe, initspeedrandrange = newinitspeedrandrange)
+
+ def SetInitDirectionRandRange(self, newinitdirectionrandrange):
+ self.CreateKeyframe(self.curframe, initdirectionrandrange = newinitdirectionrandrange)
+
+ def SetParticlesPerFrame(self, newparticlesperframe):
+ self.CreateKeyframe(self.curframe, particlesperframe = newparticlesperframe)
+
+ def SetGenSpacing(self, newgenspacing):
+ self.CreateKeyframe(self.curframe, genspacing = newgenspacing)
diff --git a/pyignition/xml.py b/pyignition/xml.py
@@ -0,0 +1,137 @@
+### EXESOFT XML PARSER ###
+# Coopyright David Barker 2010
+#
+# Python XML parser
+
+
+
+class XMLNode:
+ def __init__(self, parent, tag, meta, data, inside):
+ self.tag = tag
+ self.meta = meta
+ self.data = data
+ self.inside = inside
+ self.parent = parent
+ self.children = []
+ self.parsed = False
+
+
+class XMLParser:
+ def __init__(self, data):
+ self.data = data
+ self.meta = {}
+ self.root = None
+
+ def ReadMeta(self):
+ while "<?" in self.data:
+ index = self.data.find("<?") # Start of tag
+ startindex = index + 2 # Start of tag inside
+ endindex = self.data.find("?>", index) # Tag end
+
+ # Get the contents of the angular brackets and split into separate meta tags
+ metaraw = self.data[startindex:endindex].strip()
+ separated = metaraw.split("\" ") # Split like so ('|' = split off):
+ # thingy = "value|" |other = "whatever|" |third = "woo!"
+
+ for splitraw in separated:
+ split = splitraw.split("=")
+
+ # Add it to the dictionary of meta data
+ self.meta[split[0].strip()] = split[1].strip().strip('\"')
+
+ # Remove this tag from the stored data
+ before = self.data[:index]
+ after = self.data[(endindex + 2):]
+ self.data = "".join([before, after])
+
+ def GetTagMeta(self, tag):
+ meta = {}
+
+ metastart = tag.find(" ") + 1
+ metaraw = tag[metastart:]
+ separated = metaraw.split("\" ") # Split like so ('|' = split off):
+ # thingy = "value|" |other = "whatever|" |third = "woo!"
+
+ for splitraw in separated:
+ split = splitraw.split("=")
+
+ # Add it to the dictionary of meta data
+ meta[split[0].strip()] = split[1].strip().strip('\"')
+
+ return meta
+
+ def StripXML(self):
+ # Remove comments
+ while "<!--" in self.data:
+ index = self.data.find("<!--")
+ endindex = self.data.find("-->", index)
+ before = self.data[:index]
+ after = self.data[(endindex + 3):]
+ self.data = "".join([before, after])
+
+ # Remove whitespace
+ self.data = self.data.replace("\n", "").replace("\t", "")
+
+ def GetChildren(self, node):
+ pass
+
+ def GetRoot(self):
+ rootstart = self.data.find("<")
+ rootstartclose = self.data.find(">", rootstart)
+ roottagraw = self.data[(rootstart + 1):rootstartclose]
+
+ rootmeta = {}
+ if len(roottagraw.split("=")) > 1:
+ rootmeta = self.GetTagMeta(roottagraw)
+
+ roottag = roottagraw.strip()
+
+ rootend = self.data.find("</%s" % roottag)
+ rootendclose = self.data.find(">", rootend)
+ rootdata = self.data[rootstart:(rootendclose + 1)].strip()
+ rootinside = self.data[(rootstartclose + 1):rootend]
+
+ self.root = XMLNode(parent = None, tag = roottag, meta = rootmeta, data = rootdata, inside = rootinside)
+
+ def SearchNode(self, node):
+ node.parsed = True
+
+ tempdata = node.inside
+ children = []
+
+ while "<" in tempdata:
+ start = tempdata.find("<")
+ startclose = tempdata.find(">", start)
+ tagraw = tempdata[(start + 1):startclose]
+
+ meta = {}
+ if "=" in tagraw:
+ meta = self.GetTagMeta(tagraw)
+
+ tag = tagraw.split(" ")[0]
+
+ end = tempdata.find("</%s" % tag)
+ endclose = tempdata.find(">", end)
+
+ data = tempdata[start:(endclose + 1)].strip()
+ inside = tempdata[(startclose + 1):end]
+
+ newnode = XMLNode(node, tag, meta, data, inside)
+ children.append(newnode)
+
+ before = tempdata[:start]
+ after = tempdata[(endclose + 1):]
+ tempdata = "".join([before, after])
+
+ node.children = children
+
+ for child in node.children:
+ self.SearchNode(child)
+
+ def Parse(self):
+ self.ReadMeta()
+ self.StripXML()
+ self.GetRoot()
+ self.SearchNode(self.root)
+
+ return self.root