Donald May Freelance programmer

Explosion Simulator

Introduction
For a course in Classical Mechanics I decided to try my hand at building an explosion simulator in Visual Python and TKinter.

The idea behind the project was to illustrate rigid body mechanics with breakable constraints and explosive forces. What all of this means is that I wanted to be able to see what would happen to a rigid body if an explosive force was applied to it.

The configuration I chose was to construct a grid of particles on a two dimensional plane and then construct a lattice of springs between all the adjacent particles. This would hopefully result in a stable structure and it could be generated procedurally relatively easily.

Eventually I decided that the project should have two modes to demonstrate explosive forces. The first mode is a click to explode mode where an explosive force is triggered where the user clicks. The second mode has a sphere to represent the current explosion origin that is adjusted using arrow keys and Page Up/Page Down.

Visual Python was used for this simulation because it provides a simple way to create 3D geometry and because the course professor liked Visual Python. TKinter was added to allow the use of a configuration dialog for adjusting parameters of the simulation.

Since Visual Python and TKinter both have control loops, it was necessary to use the threading capacity of Python in order to run both Python and TKinter simultaneously.

Theory
The main mathematics behind this simulation is two fold. The first step was to figure out how to represent an explosive force. I chose to represent the force as a 1/r^2 law, where the amplitude of the explosive force would be provided by some explosive constant A. This would give the force as F=A/r^2. Using Newton's second law of motion F=ma we can determine our acceleration to be: a= A/(m*r^2). This would provide a magnitude for the explosive force but our acceleration also needed a direction and so I chose to multiply by a normalized vector between the explosive origin and the particle.

Now I needed an integration technique that could take this acceleration and provide me the position of the particles. To do that I chose to use a velocity verlet integration algorithm. The wikipedia entry Verlet_integration proved helpful in providing the mathematics behind this implementation.

For the constraints between the particles I found that it was necessary to introduce a velocity correction term into the simulation (since we are using velocity verlet after all) which is documented in my source code below.

Code

#Explosion demo
#By: Donald May
#May 02, 2010

#This program was written for a course in Classical Mechanics during the Spring 2010 term at Texas A&M University- Commerce
#The purpose of this program is to demonstrate the effects of explosive forces and breaking constraints on rigid bodies

from Tkinter import *
from visual import *
import thread
import threading
import sys

scene.title = "Explosion Simulation"
scene.autoscale = 0
scene.autocenter = 1

CLICK_EXPLODE_MODE = 0
BOMB_EXPLODE_MODE = 1

class Particle:
def __init__(self, visobj, mass):
self.visible_obj = visobj
self.locked = False
self.start_pos = vector(self.visible_obj.pos)
self.reset_pos()
self.mass = mass

def get_visible_obj(self):
return self._visible_obj

def set_mass(self, value):
self._mass = value

def reset_pos(self):
self.prev_pos = vector(self.start_pos)
self.cur_pos = vector(self.prev_pos)
self.visible_obj.pos = self.cur_pos
self.velocity = vector(0.0,0.0,0.0)
self.accel = vector(0.0,0.0,0.0)
self.inst_accel = vector(0.0,0.0,0.0)

def destroy(self):
self.visible_obj.visible = False
del self.visible_obj
self.visible_obj = None

def clear_color(self):
if self.visible_obj is not None and not self.locked:
self.visible_obj.color = color.red

#The logic behind calculating an explosive force is that an explosive force obeys a 1/r^2 law.
#We construct a formula for the force A/r^2 where A is some explosive constant by constant absorption
#The acceleration is then given using F=ma as a=A/(m*r^2) which gives our acceleration magnitude
#Our acceleration vector is given by finding the unit vector in which our acceleration is to take place
#using the difference between our particle and the explosive position

#The colorization formula shown below is a simple linear interpolation between red and blue colors
#The purpose is simply to show the intensity of the force felt by the particle as a function of the 1/r^2 rate
def calculate_explosive_force(self, explosive_pos, explosive_const):
if self.visible_obj is not None and not self.locked:
diff = self.cur_pos - explosive_pos
if mag(diff)>0.0:
self.inst_accel = norm(diff)*(explosive_const/(self.mass*math.pow(mag(diff), 2.0)))
else:
self.inst_accel = vector(0.0, 0.0, 0.0)

c = mag(norm(diff)/math.pow(mag(diff),2.0))
r = 1.0-5.0*c
b = 5.0*c
g = 0
self.visible_obj.color = (r,g,b)

#This method is based on a velocity verlet integration scheme from:
#http://en.wikipedia.org/wiki/Verlet_integration
#I have added code (the if statements) to check boundries and provide elastic collisions
#a in this formula is the "interaction potential" where we take our explosion acceleration
#and add in our gravitational acceleration.
def update(self, gravity = 0.0, fric = 0.5, min_bound = -10, max_bound = 10):
if self.visible_obj is not None:
delta_time = 1.0
a = self.inst_accel + vector(0.0, -gravity, 0.0) / self.mass
self.cur_pos = self.prev_pos + ((self.velocity * delta_time) + ((self.accel * math.pow(delta_time,2.0))/2.0))

min_bounds = vector(min_bound, min_bound, min_bound)
max_bounds = vector(max_bound, max_bound, max_bound)

if self.cur_pos.x < min_bounds.x:
self.cur_pos.x = min_bounds.x
self.velocity.x = abs(self.velocity.x) * fric
if self.cur_pos.x > max_bounds.x:
self.cur_pos.x = max_bounds.x
self.velocity.x = -abs(self.velocity.x) * fric
if self.cur_pos.y < min_bounds.y:
self.cur_pos.y = min_bounds.y
self.velocity.y = abs(self.velocity.y) * fric
if self.cur_pos.y > max_bounds.y:
self.cur_pos.y = max_bounds.y
self.velocity.y = -abs(self.velocity.y) * fric
if self.cur_pos.z < min_bounds.z:
self.cur_pos.z = min_bounds.z
self.velocity.z = abs(self.velocity.z) * fric
if self.cur_pos.z > max_bounds.z:
self.cur_pos.z = max_bounds.z
self.velocity.z = -abs(self.velocity.z) * fric

self.visible_obj.pos = self.cur_pos
half_vel = (self.velocity + (self.accel * delta_time) / 2.0)
self.velocity = (half_vel + (a * delta_time) / 2.0)
self.prev_pos = vector(self.cur_pos)
self.accel = vector(self.inst_accel)
self.inst_accel = vector(0.0,0.0,0.0)

class ParticleLink:
def __init__(self, parent_particle, adjacent_particle, radius, break_len, color):
self.parent_particle = parent_particle
self.adjacent_particle = adjacent_particle
self.radius = radius
self.break_len = break_len
self.color = color
self.link_curve = curve(pos = [self.parent_particle.cur_pos, self.adjacent_particle.cur_pos], radius = self.radius, color = self.color)

#This method of constraint solution is also taken from:
#http://en.wikipedia.org/wiki/Verlet_integration
#However the constraint technique had to be modified for velocity verlet
#The correction term in the velocity is based on a formula of my own derivation
def resolve_constraints(self):
rv = self.parent_particle.start_pos - self.adjacent_particle.start_pos
r = rv.mag

x1 = self.parent_particle.cur_pos
x2 = self.adjacent_particle.cur_pos
ox1 = vector(x1)
ox2 = vector(x2)

d1 = x2 - x1
d2 = d1.mag

if d2 > 0:
d3 = (d2 - r) / d2

if not self.parent_particle.locked:
x1 += ((1/2.0)*(d1*d3))
if not self.adjacent_particle.locked:
x2 -= ((1/2.0)*(d1*d3))

self.parent_particle.cur_pos = x1
self.parent_particle.velocity += (x1 - ox1)
self.parent_particle.prev_pos = x1
self.adjacent_particle.cur_pos = x2
self.adjacent_particle.velocity += (x2 - ox2)
self.adjacent_particle.prev_pos = x2

def update(self):
if self.link_curve is not None and self.parent_particle is not None and self.adjacent_particle is not None:
self.link_curve.pos[0] = self.parent_particle.visible_obj.pos
self.link_curve.pos[1] = self.adjacent_particle.visible_obj.pos
if self.parent_particle is None or self.adjacent_particle is None:
self.destroy_link()

def destroy_link(self):
if self.link_curve is not None:
self.link_curve.visible = False
del self.link_curve
self.link_curve = None

#The logic here is that if a particle constraint (a link) exceeds a certain breaking threshold
#then we should have the constraint break so that objects can fracture. We simply need to figure out
#how long the constraint should be compared to how long it is. If the value of the length is greater than
#a certain tolerance than we need to remove the constraint
def should_break(self):
rv = self.parent_particle.start_pos - self.adjacent_particle.start_pos
r = rv.mag
x1 = self.parent_particle.cur_pos
x2 = self.adjacent_particle.cur_pos
d1 = x2 - x1
d2 = d1.mag

if d2 > r * self.break_len:
return True

return False

class ExplosionApp:
def __init__(self):
self.radius = 0.1
self.gridx = 0
self.gridy = 0
self.gridrows = 10
self.gridcols = 10
self.gridwid = 0.5
self.gridhei = 0.5
self.mass = 2.0
self.link_radius = 0.01
self.break_len = 1.65
self.explosive_const = 0.5
self.zaxis = 0
self.gravity = 0
self.fric_coeff = 0.5
self.min_bound = -10.0
self.max_bound = 10.0
self.explode_mode = CLICK_EXPLODE_MODE
self.explosive_pos = vector(self.gridx , self.gridy, 0)
self.instruction_text = label(pos = (0, 0, 0), text = "Welcome to Donald's Explosion Simulator!", box = False)
self.instruction_text2= label(pos = (0, -2, 0), text = "R resets scene, L locks points, and C clears force colorization", box = False)
self.instruction_text3 = label(pos = (0, -4, 0), text = "Simulation is stopped while this text is visible!", box = False)
self.links = []
self.init_scene()
self.build_springs()
self.should_reset = False
self.should_rebuild = False
self.should_exit = False
self.running = False

def init_scene(self):
scene.autocenter = 1
self.explode_origin = sphere(radius = self.explosive_const , pos = self.explosive_pos, color = color.cyan)

self.set_explode_mode(self.explode_mode)
#Here we construct our grid of particles using a simple grid formula involving multiplication.
self.particle_grid = []
for y in range(0,self.gridrows):
for x in range(0,self.gridcols):
ball = sphere(radius = self.radius, pos = (self.gridx+self.gridwid*x-(self.gridrows*self.gridwid)/2, self.gridy+self.gridhei*y-(self.gridcols*self.gridhei)/2, 0), color = color.red)
part = Particle(ball, self.mass)
self.particle_grid.append(part)

#Building our spring lattice involves figuring out the current particle position and finding all adjacent particles.
#If we find an adjacent particle than we create a link between them which is stored as a list of particle links.
def build_springs(self):
cur_index = 0
for part in self.particle_grid:
cur_x = cur_index % self.gridcols
cur_y = cur_index / self.gridcols

for i in range(-1,2):
for j in range(-1,2):
if i != 0 or j != 0:
adjacent_part = self.get_particle(cur_x+i,cur_y+j)
if adjacent_part is not None and self.no_link_has_particles(part, adjacent_part):
self.links.append(ParticleLink(part, adjacent_part, self.link_radius, self.break_len, color.green))
cur_index += 1

#This is a bit of code to prevent duplicant links between the same particles (A->B and B->A would result in two links, we want one)
def no_link_has_particles(self, particle, adjacent_part):
for link in self.links:
if (link.parent_particle is particle and link.adjacent_particle is adjacent_part) or (link.parent_particle is adjacent_part and link.adjacent_particle is particle):
return False
return True

def get_particle(self,x,y):
if x >= 0 and y >= 0 and x < self.gridcols and y < self.gridrows:
return self.particle_grid[y * self.gridcols + x]
else:
return None

def set_explode_mode(self, explode_mode):
self.explode_mode = explode_mode
if self.explode_mode == CLICK_EXPLODE_MODE:
self.explode_origin.visible = False
elif self.explode_mode == BOMB_EXPLODE_MODE:
self.explode_origin.visible = True

def destroy_all(self):
link_delete_lst = []
for link in self.links:
link.destroy_link()
link_delete_lst.append(link)

for del_link in link_delete_lst:
self.links.remove(del_link)

part_delete_lst = []
for part in self.particle_grid:
part.destroy()
part_delete_lst.append(part)

for del_part in part_delete_lst:
self.particle_grid.remove(del_part)

self.explode_origin.visible = False
del self.explode_origin

#Locked objects should not move so we clear velocity, and all accelerations
def lock_obj(self, target_obj):
for part in self.particle_grid:
if part.visible_obj == target_obj:
if not part.locked:
part.visible_obj.color = color.yellow
part.locked = True
part.velocity = vector(0.0, 0.0, 0.0)
part.accel = vector(0.0, 0.0, 0.0)
part.inst_accel = vector(0.0, 0.0, 0.0)
else:
part.visible_obj.color = color.red
part.locked = False

def update(self):
if self.running:
for i in range(0,2):
for link in self.links:
link.resolve_constraints()

for part in self.particle_grid:
if not part.locked:
part.update(self.gravity, self.fric_coeff, self.min_bound, self.max_bound)
else:
part.velocity = vector(0.0, 0.0, 0.0)
part.accel = vector(0.0, 0.0, 0.0)
part.inst_accel = vector(0.0, 0.0, 0.0)
part.update(0, self.fric_coeff, self.min_bound, self.max_bound)

destroy_list = []
for link in self.links:
link.update()
if link.should_break():
link.destroy_link()
destroy_list.append(link)

for del_link in destroy_list:
self.links.remove(del_link)

if self.should_reset:
self.reset_pos()
self.should_reset = False

if self.should_rebuild:
self.destroy_all()
self.init_scene()
self.build_springs()
self.should_rebuild = False

def set_force(self, forcepos):
if self.running:
for part in self.particle_grid:
part.calculate_explosive_force(forcepos, self.explosive_const)

def reset_pos(self):
for part in self.particle_grid:
part.reset_pos()

self.build_springs()

def clear_color(self):
for part in self.particle_grid:
part.clear_color()

class MainApp():
#All of this mess is constructing our control panel for the simulation
def __init__(self,root_win, ex_app, pupdate_event):
self.root_win = root_win
self.ex_app = ex_app
self.pupdate_event = pupdate_event
self.frame = Frame(root_win,width=300,height=200)
self.frame.grid(sticky=N+S+E+W)

top = self.frame.winfo_toplevel()
top.rowconfigure(0, weight = 1)
top.columnconfigure(0, weight = 1)

self.row_label = Label(self.frame, text = "Rows")
self.row_label.grid(column = 0, row = 0, columnspan = 1, sticky = N+S+E+W)

self.col_label = Label(self.frame, text = "Columns")
self.col_label.grid(column = 1, row = 0, columnspan = 1, sticky = N+S+E+W)

self.row_string = StringVar()
self.row_entry = Entry(self.frame, width = 5, textvariable = self.row_string)
self.row_string.set(str(self.ex_app.gridrows))
self.row_entry.grid(column = 0,row = 1)

self.col_string = StringVar()
self.col_entry = Entry(self.frame, width = 5, textvariable = self.col_string)
self.col_string.set(str(self.ex_app.gridcols))
self.col_entry.grid(column = 1, row = 1)

self.gw_label = Label(self.frame, text = "Grid Width")
self.gw_label.grid(column = 0, row = 2, columnspan = 1, sticky = N+S+E+W)

self.gh_label = Label(self.frame, text = "Grid Height")
self.gh_label.grid(column = 1, row = 2, columnspan = 1, sticky = N+S+E+W)

self.gw_string = StringVar()
self.gw_entry = Entry(self.frame, width = 5, textvariable = self.gw_string)
self.gw_string.set(str(self.ex_app.gridwid))
self.gw_entry.grid(column = 0, row = 3)

self.gh_string = StringVar()
self.gh_entry = Entry(self.frame, width = 5, textvariable = self.gh_string)
self.gh_string.set(str(self.ex_app.gridhei))
self.gh_entry.grid(column = 1, row = 3)

self.mass_label = Label(self.frame, text = "Mass")
self.mass_label.grid(column = 0, row = 4, columnspan = 1, sticky = N+S+E+W)

self.radius_label = Label(self.frame, text = "Radius")
self.radius_label.grid(column = 1, row = 4, columnspan = 1, sticky = N+S+E+W)

self.mass_string = StringVar()
self.mass_entry = Entry(self.frame, width = 5, textvariable = self.mass_string)
self.mass_string.set(str(self.ex_app.mass))
self.mass_entry.grid(column = 0, row = 5)

self.radius_string = StringVar()
self.radius_entry = Entry(self.frame, width = 5, textvariable = self.radius_string)
self.radius_string.set(str(self.ex_app.radius))
self.radius_entry.grid(column = 1, row = 5)

self.break_label = Label(self.frame, text = "Breaking Length")
self.break_label.grid(column = 0, row = 6, columnspan = 1, sticky = N+S+E+W)

self.linkrad_label = Label(self.frame, text = "Link Radius")
self.linkrad_label.grid(column = 1, row = 6, columnspan = 1, sticky = N+S+E+W)

self.break_string = StringVar()
self.break_entry = Entry(self.frame, width = 5, textvariable = self.break_string)
self.break_string.set(str(self.ex_app.break_len))
self.break_entry.grid(column = 0, row = 7)

self.linkrad_string = StringVar()
self.linkrad_entry = Entry(self.frame, width = 5, textvariable = self.linkrad_string)
self.linkrad_string.set(str(self.ex_app.link_radius))
self.linkrad_entry.grid(column = 1, row = 7)

self.build_button = Button(self.frame, text = "Build Scene", command = self.build_scene)
self.build_button.grid(column = 0, row = 8)

self.reset_button = Button(self.frame, text = "Reset Scene", command = self.reset_scene)
self.reset_button.grid(column = 1, row = 8)

self.force_label = Label(self.frame, text = "Force Mode")
self.force_label.grid(column = 0, row = 9, columnspan = 2, sticky = N+S+E+W)

self.selected_mode = DoubleVar()
self.selected_mode.set(CLICK_EXPLODE_MODE)

self.click_explode_mode = Radiobutton(self.frame, text = "Mouse Mode", command = self.mode_change, variable = self.selected_mode, value = CLICK_EXPLODE_MODE)
self.click_explode_mode.grid(column = 0, row = 10, sticky = N+S+E+W)

self.bomb_explode_mode = Radiobutton(self.frame, text = "Bomb Mode", command = self.mode_change, variable = self.selected_mode, value = BOMB_EXPLODE_MODE)
self.bomb_explode_mode.grid(column = 1, row = 10, sticky = N+S+E+W)

self.zaxis_label = Label(self.frame, text = "Z-Axis")
self.zaxis_label.grid(column = 0, row = 11, columnspan = 1, sticky = N+S+E+W)

self.fconst_label = Label(self.frame, text = "Force Constant")
self.fconst_label.grid(column = 1, row = 11, columnspan = 1, sticky = N+S+E+W)

self.zaxis_string = StringVar()
self.zaxis_entry = Entry(self.frame, width = 5, textvariable = self.zaxis_string)
self.zaxis_string.set(str(self.ex_app.zaxis))
self.zaxis_entry.bind("", self.zaxis_handler)
self.zaxis_entry.grid(column = 0, row = 12)

self.fconst_string = StringVar()
self.fconst_entry = Entry(self.frame, width = 5, textvariable = self.fconst_string)
self.fconst_string.set(str(self.ex_app.explosive_const))
self.fconst_entry.bind("", self.fconst_handler)
self.fconst_entry.grid(column = 1, row = 12)

self.gravity_label = Label(self.frame, text = "Gravity Controls")
self.gravity_label.grid(column = 0, row = 13, columnspan = 1, sticky = N+S+E+W)

self.fric_label = Label(self.frame, text = "Friction Constant")
self.fric_label.grid(column = 1, row = 13, columnspan = 1, sticky = N+S+E+W)

self.gravity_string = StringVar()
self.gravity_entry = Entry(self.frame, width = 5, textvariable = self.gravity_string)
self.gravity_string.set(str(self.ex_app.gravity))
self.gravity_entry.bind("", self.gravity_handler)
self.gravity_entry.grid(column = 0, row = 14)

self.fric_string = StringVar()
self.fric_entry = Entry(self.frame, width = 5, textvariable = self.fric_string)
self.fric_string.set(str(self.ex_app.fric_coeff))
self.fric_entry.bind("", self.fric_handler)
self.fric_entry.grid(column = 1, row = 14)

self.min_label = Label(self.frame, text = "Min Bounds")
self.min_label.grid(column = 0, row = 15)

self.max_label = Label(self.frame, text = "Max Bounds")
self.max_label.grid(column = 1, row = 15)

self.min_string = StringVar()
self.min_entry = Entry(self.frame, width = 5, textvariable = self.min_string)
self.min_string.set(str(self.ex_app.min_bound))
self.min_entry.bind("", self.min_handler)
self.min_entry.grid(column = 0, row = 16)

self.max_string = StringVar()
self.max_entry = Entry(self.frame, width = 5, textvariable = self.max_string)
self.max_string.set(str(self.ex_app.max_bound))
self.max_entry.bind("", self.max_handler)
self.max_entry.grid(column = 1, row = 16)

self.start_button = Button(self.frame, text = "Start Simulation", command = self.start_simulation)
self.start_button.grid(column = 0, row = 17)

self.stop_button = Button(self.frame, text = "Stop Simulation", command = self.stop_simulation)
self.stop_button.grid(column = 1, row = 17)

self.create_menus(root_win)

def create_menus(self, root):
self.main_menu = Menu(root, tearoff = 0)
self.program_menu = Menu(root, tearoff = 0)
self.program_menu.add_command(label = "Exit", command = self.quit_app)
self.main_menu.add_cascade(label = "Program", menu = self.program_menu)
root.config(menu = self.main_menu)

def build_scene(self):
self.pupdate_event.clear()
self.ex_app.gridrows = int(self.row_string.get())
self.ex_app.gridcols = int(self.col_string.get())
self.ex_app.gridwid = double(self.gw_string.get())
self.ex_app.gridhei = double(self.gh_string.get())
self.ex_app.mass = double(self.mass_string.get())
self.ex_app.radius = double(self.radius_string.get())
self.ex_app.break_len = double(self.break_string.get())
self.ex_app.link_radius = double(self.linkrad_string.get())
self.ex_app.should_rebuild = True
self.pupdate_event.set()

def mode_change(self):
sel_mode = self.selected_mode.get()
self.pupdate_event.clear()
self.ex_app.set_explode_mode(sel_mode)
self.pupdate_event.set()

def zaxis_handler(self, event):
self.pupdate_event.clear()
self.ex_app.zaxis = double(self.zaxis_string.get())
self.pupdate_event.set()

def fconst_handler(self, event):
self.pupdate_event.clear()
self.ex_app.explosive_const = double(self.fconst_string.get())
self.ex_app.explode_origin.radius = self.ex_app.explosive_const
self.pupdate_event.set()

def gravity_handler(self, event):
self.pupdate_event.clear()
scene.autocenter = 0
scene.autoscale = 0
self.ex_app.gravity = double(self.gravity_string.get())
self.pupdate_event.set()

def fric_handler(self, event):
self.pupdate_event.clear()
self.ex_app.fric_coeff = double(self.fric_string.get())
self.pupdate_event.set()

def min_handler(self, event):
self.pupdate_event.clear()
self.ex_app.min_bound = double(self.min_string.get())
self.pupdate_event.set()

def max_handler(self, event):
self.pupdate_event.clear()
self.ex_app.max_bound = double(self.max_string.get())
self.pupdate_event.set()

def start_simulation(self):
self.pupdate_event.clear()
self.ex_app.running = True
self.ex_app.instruction_text.visible = False
self.ex_app.instruction_text2.visible = False
self.ex_app.instruction_text3.visible = False
self.pupdate_event.set()

def stop_simulation(self):
self.pupdate_event.clear()
self.ex_app.running = False
self.ex_app.instruction_text.visible = True
self.ex_app.instruction_text2.visible = True
self.ex_app.instruction_text3.visible = True
self.pupdate_event.set()

def quit_app(self):
self.pupdate_event.clear()
self.ex_app.should_exit = True
self.root_win.destroy()
self.pupdate_event.set()
#exit()

def reset_scene(self):
self.pupdate_event.clear()
self.ex_app.should_reset = True
self.pupdate_event.set()

#Visual Python and TKinter both have control loops
#We need multithreading to avoid one blocking the other!

def ControlThread(ex_app = None, pupdate_event = None):
control_win=Tk()
control_win.title("Controls")

main_app = MainApp(control_win, ex_app, pupdate_event)

control_win.mainloop()

update_event = threading.Event()
update_event.set()

explode_app = ExplosionApp()

main_thread = thread.start_new_thread(ControlThread,(explode_app,update_event))

move_speed = 0.1

while not explode_app.should_exit:
update_event.wait()
explode_app.update()
#We click and we are in the explode to click mode so let's have our explosion application
#calculate an explosive force from our mouse position at the z-axis we specify.
if scene.mouse.clicked:
m = scene.mouse.getclick()
if explode_app.explode_mode == CLICK_EXPLODE_MODE:
scene.autocenter = 0
explode_app.set_force(vector(scene.mouse.pos.x,scene.mouse.pos.y,explode_app.zaxis))

if scene.kb.keys:
key = scene.kb.getkey()

if key == 'r':
explode_app.reset_pos()
elif key == 'c':
explode_app.clear_color()
elif key == 'l':
find_obj = scene.mouse.pick
explode_app.lock_obj(find_obj)

#For bomb mode we need to be able to steer the bomb around in three dimensions.
#When we press space we make the whole thing explode!
if explode_app.explode_mode == BOMB_EXPLODE_MODE:
mx = 0
my = 0
mz = 0

if key == 'right':
mx = move_speed
elif key == 'left':
mx = -move_speed
elif key == 'up':
mz = -move_speed
elif key == 'down':
mz = move_speed
elif key == 'page up':
my = move_speed
elif key == 'page down':
my = -move_speed
elif key == ' ':
scene.autocenter = 0
explode_app.set_force(explode_app.explode_origin.pos)

if mx is not 0 or my is not 0 or mz is not 0:
scene.autocenter = 0
explode_app.explode_origin.pos += vector(mx, my, mz)

#This controls the frame rate of the simulation
#Comment added so program does not contain (666) lines!
rate(100)

Screen shots

Download
Download project file

Comments (0) Trackbacks (0)

No comments yet.


Leave a comment

(required)

No trackbacks yet.