Kirjoittaja: ZcMander
Kirjoitettu: 07.01.2008 – 07.01.2008
Tagit: kirjaston käyttö, koodi näytille, vinkki
PyODE on siitä "mielenkiintoinen" kirjasto, koska kun sillä tekee bodyn/jointin/geomin (ode.Body/ode.Joint/ode.GeomObject) niin se pitää aina säilyttää jossain muuttujassa, koska muuten pythonin roskienkeruu tuhoaa sen(luullakseni näin, koska ne ei vaan toimi, ellei ne ole jossain muuttujasa).
Tein yksinkertaisen luokan, jossa jokaisella kappaleella olisi oma ryhmä ja sitten törmäystarkistuksen kohdalla tarkistettaisiin missä ryhmässä törmäyksen kappaleet ovat ja tehtäisiin törmäys sen mukaan.
Tämä pienimuotoinen kirjasto sisätlää neljä luokkaa pyode2d, Physis2D, Handlers ja Handler.
pyode2d on todella yksinkertainen luokka joka luo geomin/bodyn tai jointin, mutta se myös käsittelee törmäyksen ja pitää törmäykseen tarvittavaa contactgrouppia sekä oden world ja spacet sisällään.
Physics2D on luokka joka kapseloi sisälleen kaikki jointit ja geomit (geomit sen takia, koska sillä on getBody, mutta ode.Body:stä ei saa geomia ulos). Jointelle annetaan nimi, jotta niitä voi helposti muistettavalla nimellä hakea. Geomeit on sen sijaan ryhmitelty sisäkkäisillä dictionaryillä.
Handlers hoitaa törmäysten käsittelyn, siihen voi lisätä käsittelijöitä ja handlers käy nämä käsittelijät läpi ja kutsuu niitä, jos ne ryhmään sopii.
Handler-luokka on itse törmäyksen käsitteliä, ja tästä luokasta voi helposti periä ja muokata oma käsittelijä jollekki ryhmälle.
Koodista tuli hieman pitkähkö, mutta help(pyode2d) pitäisi auttaa kirjaston käyttämisessä.
# -*- coding: utf-8 -*-
#Tekijä: ZcMander
#Luotu: 30.4.07
#Modifed: 2.1.08
import ode
import re
import logging
class pyOde2d:
_world = ode.World()
_contactgroup = ode.JointGroup()
_space = ode.Space()
def set_collision_handler(self, collidecallback):
"""Asettaa törmäyksenkäsittelijän"""
self._callback = collidecallback
def set_gravity(self, gravity):
"""Asettaa gravitaatiovoiman"""
self._world.setGravity([gravity[0], gravity[1], 0])
def create_body(self, pos):
"""Luo kappaleen"""
body = ode.Body(self._world)
body.setPosition((pos[0], pos[1], 0))
return body
def create_sphere(self, radius):
"""Palauttaa pallon"""
return ode.GeomSphere(self._space, float(radius))
def create_plane(self, normal, dist):
"""Luo loppumattoman maan"""
return ode.GeomPlane(self._space, [normal[0], normal[1], 0], dist)
def create_box(self, lenghts):
"""Luo laatikon"""
l = lenghts[:]
l.append(20)
return ode.GeomBox(self._space, l)
def create_polygon(self, pointlist):
"""
Luo monikulmion kolmiulotteisen mallin
0 1
/¨¨¨/|
3+---+2|
| | |
|___|/
Yksinkertainen kuutio, tästä luodaan oikeasti vain sivut, koska
kahdessa ulottuviidessa ei tarvitse törmäystarkistusta syvyyssuuntaan
"""
#FIXME: Älä käytä Trimeshiä!!!
#Trimeshi on kuulemma erittäin keskeneräinen tjsp.
# ja ei välttämättä toimi hyvin esim. törmäystentarkistuksissa
#Että ei vahinggossakaan jyrätä annettua pointlistiä
pointlist = pointlist[:]
#Pointlisti, mutta kolmiulotteisena
real_pointlist = []
#Luodaan kolmiulotteinen malli, joten ensin verteksit, jotka on lähempänä
for point in pointlist:
point = point[:] #Ei haluta jyrätä alkuperäisiä tietoja
point.append(float(-10))
real_pointlist.append(point)
#Sen jälkeen lisätään malliin takana olevat verteksit
for point in pointlist:
point = point[:]
point.append(float(10))
real_pointlist.append(point)
#Luodaan kaksi kolmiota, koska trimesh ei nimensämukaan osaa neliöitä
trindexes = [[0,1,2],[0,2,3]]
#Ei välttämättä tarvitse, mutta luodaan toisinpäin kolmiot, joten
#keskelle muodostuu risti
trindexes.extend([[2,0,1], [3,1,2]])
#Luodaan facet
faces = []
for i in range(len(pointlist)-1):
#Vain helpotaakseen kirjoitusta, ettei tarvi pitkää muuttujan nimeä
#kirjoittaa moneen kertaan
b = pointlist
#Neliö koostuu neljästä verteksistä (eli pisteestä), ensimmäiset kaksi
#ovat edessä ja loput kaksi ovat takaa, vasemmalta oikealle kummatkin,
#Katso ylhäällä olevan kuutio numerot, ne ovat tämän listan indexejä
a = [float(i), float(len(b)+i), float(len(b)+i+1), float(i+1)]
#Käydään kolmiot läpi
for indexes in trindexes:
#Ja lisätään listaan
faces.append([ a[indexes[0]], a[indexes[1]], a[indexes[2]] ])
#Luodaan käsin vielä viimeinenkin sivu
a = [len(b)-1, len(b)*2-1, len(b), 0]
#Käydään kolmiot läpi
for indexs in trindexes:
#Ja lisätään listaan
faces.append([ a[indexs[0]], a[indexs[1]], a[indexs[2]] ])
#Nyt voidaan luoda itse trimesh-data,
#johon verteksien ja facet tallennetaan,
meshdata = ode.TriMeshData()
meshdata.build(real_pointlist, faces)
#Luodaan geometrinen malli ja palautetaan se
return ode.GeomTriMesh(meshdata, self._space)
def create_joint(self, body1, body2, anchor_pos=None):
"""Palauttaa pallo-jointin"""
j = ode.FixedJoint(self._world)
j.attach(body1, body2)
j.setFixed()
#if anchor_pos != None:
# j.setAnchor((anchor_pos[0],anchor_pos[1],0))
return j
def step(self, fps):
"""Askeleen eteenpäin fysiikan simuloinnissa"""
self._contactgroup.empty()
self._space.collide((self._world, self._contactgroup), self._callback)
self._world.step(0.4/fps)
def get_world(self):
"""Palauttaa maailman"""
return self._world
def get_space(self):
"""Palauttaa maailman"""
return self._space
def get_contactgroup(self):
"""Palauttaa listan kaikista törmäyksistä"""
return self._contactgroup
def set_collision_handler(self, callback):
"""Asettaa törmäyksen käsittelijän"""
self._callback = callback
class Handler:
def __init__(self, group1, group2):
self._matching_res = []
groups = [group1, group2]
for i in groups:
for a in groups:
if i != a:
self._matching_res.append(i + "_" + a)
if group1 == group2:
self._matching_res.append(group1 + "_" + group2)
def match(self, **kwargs):
"""Käy läpi omat säännölliset lausekkeet ja suorittaa törmäyksen
käsittelyn"""
matched = False
for match_re in self._matching_res:
if re.match(match_re, kwargs["rule"]):
args = kwargs.copy()
del args["rule"]
self.handle(**args)
return True
return False
def handle(self, contactgroup, world, body1, body2):
"""
contactgroup = ode.ContactGroup
world = ode.World
body1, body2 = dict
Käsittelee törmäyksen
"""
geom1 = body1["geom"]
geom2 = body2["geom"]
contacts = ode.collide(geom1, geom2)
for c in contacts:
c.setMu(0)
c.setBounce(0.5)
j = ode.ContactJoint(world, contactgroup, c)
j.attach(body1["body"], body2["body"])
class Handlers:
"""Huolehtii törmäyksen käsittelijöistä ja suorittaa käsittelijät kun
törmäys tulee"""
def __init__(self):
self._log = logging.getLogger(self.__class__.__name__)
self._handlers = {} # {handlername: Handler}
def add(self, name, handler):
"""
name = string käsittelijän nimi
handler = func käsittelijän funktio
Lisää käsittelijän
"""
self._log.debug("Adding handler %s" % name)
if self._handlers.has_key(name):
raise NameError, "Handler's name exists"
self._handlers[name] = handler
def remove(self, name):
"""
name = string käsittelijän nimi
Poistaa käsittelijän nimen perusteella
"""
self._log.debug("Removing handler %s" % name)
if self._handlers.has_key(name):
del self._handlers[name]
else:
raise NameError, "Handler's name not found"
def collision(self, **kwargs):
"""
kwargs = dict Nämä välitetään itse käsittelijälle
Käy käsittelijät läpi ja suorittaa ne
"""
rules = []
for group in kwargs["body1"]["groups"][::-1]:
for group2 in kwargs["body2"]["groups"][::-1]:
rules.append([group, group2])
rules.append(["all", "all"])
#Käydään käsittelijät läpi ja lisätään ohitettavien listaan, jotta
#samaa käsittelijää ei ajettais kahta kertaa saman törmäyksen aikana
skiphandler = []
for rule in rules:
r = "_".join( [str(rule[0]), str(rule[1])] )
kwargs["rule"] = r
for handler in self._handlers:
if handler not in skiphandler:
if self._handlers[handler].match(**kwargs):
skiphandler.append(handler)
class Physics2D:
"""
Huolehtii kappaleista ja niiden välisistä yhteyksistä (joints),
lisäksi toimii pyOde2d-luokan frontendinä sekä huolentii törmäyksistä
"""
def __init__(self, gravity=None, custom_handler=None):
"""
gravity = list(2) painovoima kaksiulotteisesti
custom_handler = func funktio törmäysten käsittelyyn
Alustaa pyode2d:n ja asettaa törmäystenkäsittelijän,
tyhjentää kappale ja jointtilistan.
"""
self._log = logging.getLogger(self.__class__.__name__)
self._physics = pyOde2d()
#Jos annettiin painovoima
if gravity != None:
self._physics.set_gravity(gravity)
#Jos on annettu oma törmäysten käsittelijä,niin käytetään sitä
self._physics.set_collision_handler(self._collision_handler)
if custom_handler == None:
self.handler = Handlers()
self._collision_handler = self.handler.collision
else:
self._collision_handler = custom_handler
self._joints = {} # {jointname: ode.Joint}
self._geoms = {} # {category:
# {
# category2:
# {
# geomname: ode.Geom,
# category3:
# {
# geomname: ode.Geom
# }
# },
# geomname: ode.Geom,
# geomname: ode.Geom
# }
def _collision_handler(self, args, geom1, geom2):
"""
args = list Contactgroup ja world listassa
geom1, geom2 = ode.GeomObject Kappaleet, joihin törmäys kohdistuu
Esikäsittelee törmäykset
"""
#Tämä dicti annetaan varsinaiselle käsittelijälle
kwargs = {}
#Lisätään world ja contactgroup siihen
world, contactgroup = args
kwargs["world"] = world
kwargs["contactgroup"] = contactgroup
#Haetaan geomin body ja groupit ja lisätään dictiin
geoms = [geom1, geom2]
for i in range(len(geoms)):
geom = geoms[i]
body = geom.getBody()
if geom != None:
name = self.get_geomname(geom)
else:
name = "World"
groups = self.get_geomgroups(geom)
body_args = {
"geom": geom,
"groups": groups,
"body": body,
"name": name
}
kwargs["body" + str(i+1)] = body_args
#for arg in kwargs:
# if type(kwargs[arg]) == dict:
# print arg, "{"
# for index in kwargs[arg]:
# print " ", index, "=", kwargs[arg][index]
# print "}"
# else:
# print arg, "=", kwargs[arg]
#print
self._collision_handler(**kwargs)
def _create_group_tree(self, groups, tree=None):
"""
groups = list luotavat ryhmöt
tree = dictionary rekursiota varten
Luo kategoriat jos niitä ei ole jo olemassa
"""
if tree == None:
tree = self._geoms
if not tree.has_key(groups[0]):
tree[groups[0]] = {}
if len(groups) > 1:
self._create_group_tree(groups[1:], tree[groups[0]])
def _get_geomgroups(self, geom, geomname, tree=None, groups=None):
"""
geom = ode.GeomObject haettava kappale
geomname = string haettavan kappaleen nimi
tree = list haettava lista
groups = list löydetyt ryhmät
Etsii kappaleen ryhmät, jätä tree ja geom antamatta
"""
if tree == None:
tree = self._geoms
if groups == None:
groups = []
for index in tree:
if geom == tree[index] and geomname == index:
break
else:
if type(tree[index]) == dict and self._get_geomname(geom, tree[index]):
groups.append(index)
self._get_geomgroups(geom, geomname, tree[index], groups)
return groups
def _get_geomname(self, geom, tree=None):
"""
name = string haettavan kappaleen nimi
tree = dict rekursiota varten
Palauttaa kappaleen kappaleen nimen perusteella
"""
if tree == None:
tree = self._geoms
# r niinkuin return
r = None
for index in tree:
if tree[index] == geom:
r = index
if type(tree[index]) == dict:
r = self._get_geomname(geom, tree[index])
if r != None:
break
return r
def _remove_object(self, name, tree=None):
"""
name = string poistettavan kappaleen nimi
tree = dict dictionary, josta poistettavaa kappaletta etsitään
Poistaa kappaleen
"""
if tree == None:
tree = self._geoms
for index in tree:
if type(tree[index]) != dict and index == name:
del tree[index]
break
elif type(tree[index]) == dict:
self._remove_object(name, tree[index])
def make_joint(self, name, joint, geom1=None, geom2=None, anchor=None):
"""
name = string jointin nimi
joint = string jointin tyypin nimi (Katso pyOde:n API)
geom1, geom2 = ode.GeomObject kappaleet joiden välille jointti tulee
Luo jointin kahden kappaleen välille, jonka jälkeen lisää sen
jointti-listaan. Joint on sama kuin pyOde:n API:ssa, mutta ilman
Joint-loppua
"""
#Jos annettiin ode.Joint niin käytetään sitä suoraan
if type(joint) == ode.Joint:
body1 = joint.getBody(0)
body2 = joint.getBody(1)
geom1 = self.get_geom_by_body(body1)
geom2 = self.get_geom_by_body(body2)
else:
jointtype, joint = joint, None
jointtype += "Joint" #Ei tarvitse kirjoittaa Joint-loppua
#Geomit listaan, jotta voidaan käydä läpi helposti
geoms = [geom1, geom2]
#Jos ei ode.Jointtia annettu
if not joint:
#Tarkistetaan että jointin tyyppi on olemassa
if not hasattr(ode, jointtype):
raise TypeError, "Joint type not found"
#Ja luodaan se
joint = getattr(ode, jointtype)(self._physics.get_world())
#ode.Joint.attach tarvii bodyt, eikä geomeja, joten haetaan
#bodyt geomin perusteella
bodies = []
for geom in geoms:
#Jos geom on None, niin se tarkoittaa environment:ia
if geom != None:
bodies.append(geom.getBody())
else:
bodies.append(None)
#Ja liitetään ne jointtiin
joint.attach(*bodies)
geomnames = {}
#Haetaan nimet
for geom in geoms:
if geom == None:
geomnames[geom] = "Environment"
else:
geomnames[geom] = str(self.get_geomname(geom))
#Mudostetaan jointille nimi
jointname = '_'.join(geomnames.values()) + "_" + name
#Tarkistetaan, ettei jointin nimi ole käytössä ja
#lisäätään dictionaryyn
if not self._joints.has_key(jointname):
self._joints[jointname] = joint
else:
raise NameError, "Joint name already in use"
return jointname
def add_object(self, name, geomtype, body=False, groups=None,
bodymass=None, pos=[0,0], fixed=False, **kwargs):
"""
name = string kappale tunnistetaan nimellä
geomtype = string, kappaleen tyyppi esim. box, sphere
body = bool, None luodaanko body
group = list lista sisäkkäisista ryhmistä
bodymass = float, ode.Mass, None kappaleen massa
pos = tuple(2), list(2) kappaleen paikka
fixed = bool, laikkuuka kappale ikinä mihinkään
**kwargs = dict, lisäparametreja geom:n luomiselle
(kuten meshdata polygonille)
Luo kappaleen annettujen tietojen perusteella ja lisää sen lopuksi
omaan listaansa
"""
self._log.debug("Adding object called %s" % (name))
#Aloitetaan geom:n luomisella
geom = getattr(self._physics, "create_" + geomtype)(**kwargs)
#Jatketaan bodyn luomisella
if body:
if geomtype == "box":
body = ode.Body(self._physics.get_world())
body.setPosition((pos[0], pos[1], -10))
else:
body = self._physics.create_body(pos)
geom.setBody(body)
#Asetetaan massa suoraan, jos on ode.Mass
if type(bodymass) == ode.Mass:
body.setMass(bodymass)
#Jos int, niin luodaan massa ja asetetaan kokonaispainoksi
elif type(bodymass) == float:
m = ode.Mass()
#Muille tyypeille ei voi antaa kokonaispainoa kuin sphere ja box
if geomtype == "sphere":
m.setSphereTotal(bodymass, kwargs["radius"])
elif geomtype == "box":
args = [bodymass]
args.extend(*kwargs["lenghts"])
m.setBoxTotal(*args)
elif geomtype != None:
self._log.warning("Bodymass is set, but wasn't box or sphere")
m = None
#Jos massaa ei ole niin, sitä ei myöskään voida asettaa
if m != None:
body.setMass(m)
#Luodaan kategoriat geomille, jos niitä ei ole
self._create_group_tree(groups)
#Haetaan kategoriaviidakon keskeltä haluttu kategoria
curpos = self._geoms
groups = groups[:]
while len(groups) != 0:
for group in curpos:
if type(curpos[group]) == dict:
if group == groups[0]:
curpos = curpos[group]
groups = groups[1:]
break #Break sen takia, ettei for mene solmuun kun läpi-
#käytävää listaa muokattiin
#Vaikka muuttujan nimi on _bodies, voi seassa olla geomejakin
#if body:
# curpos[name] = body
#else:
curpos[name] = geom
#Jointit lopuksi
#Jos tuli parametrina jointteja
if kwargs.has_key("joints"):
for key in kwargs["joints"]:
value = kwargs["joints"][key]
self.make_joint(key, value)
#Luodaan 2d-jointti, jottei kappale vajoa syvyyksiin
self.make_joint("2djoint", "Plane2D", geom, None)
#Jos haluttiin kappaleen pysyvän paikallaan
if fixed:
name = self.make_joint("fixed", "Fixed", geom, None)
j = self.get_joint(name)
j.setFixed()
def remove_object(self, name, tree=None):
self._log.debug("Removing object called %s" % (name))
self._remove_object(name)
def step(self, size):
"""
size = float Ehm, timestep? Kiitokset PyOde:n dokumentaatiolle
Menee muuttujan size verran eteenpäin simulaatiossa
"""
self._physics.step(size)
def get_geomname(self, geom):
"""
geom = ode.GeomObject
Etsii nimen kappaleen perusteella
Palauttaa: string, None haetun geomin nimi tai None jos ei löytynyt
"""
name = self._get_geomname(geom)
return name
def get_geom(self, name, tree=None):
"""
name = string haettavan kappaleen nimi
tree = dict
Hakee kappaleen nimen perusteella
Palauttaa: ode.GeomObject, None haettu kappale, None, jos ei löytynyt
"""
if tree == None:
tree = self._geoms
# r niinkuin return
r = None
for index in tree:
if index == name and type(tree[index]) != dict:
r = tree[index]
if type(tree[index]) == dict:
r = self.get_geom(name, tree[index])
if r != None:
break
return r
def get_geom_by_body(self, body, tree=None):
"""
body = ode.Body haettava kappale
tree = dict rekursiota varten
Palauttaa kappaleen kappaleen nimen perusteella
"""
if tree == None:
tree = self._geoms
# r niinkuin return
r = None
for index in tree:
if tree[index].getBody() == body:
r = index
if type(tree[index]) == dict:
r = self._get_geomname(body, tree[index])
if r != None:
break
return r
def get_geomgroups(self, geom):
"""
geom = ode.GeomObject haettava kappale
Hakee kappaleen ryhmät
"""
name = self.get_geomname(geom)
return self._get_geomgroups(geom, name)
def get_joint(self, joint):
"""
joint = ode.Joint haettava jointti
Palauttaa jointin jointin nimen perusteella
"""
for name in self._joints:
if joint == name:
return self._joints[name]
return None
def print_geoms(self, tree=None, depth=0):
"""Tulostaa geomi hierarkian"""
if tree == None:
tree = self._geoms
if tree == {}:
return
for index in tree:
t = type(tree[index])
print " "*depth,
if t == dict:
print index
self.print_geoms(tree[index], depth+1)
if t != dict:
print index, ":", tree[index],
print# -*- coding: utf-8 -*-
import sys
import math
#Tarkistetaan, jos halutaan piirrettävän ilman opengllää
opengl = True
if len(sys.argv) > 1 and sys.argv[1] == "--no-opengl":
opengl = False
import pyode2d
import pygame
if opengl:
from OpenGL.GL import *
from OpenGL.GLU import *
class Renderer:
def __init__(self, bgcolor=[128,64,255]):
pygame.init()
self._bgcolor = bgcolor
def set_display(self, resolution, fullscreen=False, flags=0):
"""
resolution = list asetettava resoluutio
fullscreen = bool onko kokoruudussa
flags = int muita pygame:n flagejä
Alustaa näytön lähimmälle sopivalle resoluutiolle
"""
flags = flags | pygame.DOUBLEBUF
if fullscreen:
flags = flags | pygame.FULLSCREEN
pygame.display.set_mode(resolution, flags)
def start(self):
"""Aloittaa piirtämisen"""
pygame.display.get_surface().fill(self._bgcolor)
def draw(self, shape, **kwargs):
"""
shape = string objectin muoto
kwargs = dict muoto-kohtaiset asetukset, kuten sphere:llä radius
Piirtää objectin muodon mukaan
"""
getattr(self, "_draw_" + shape)(**kwargs)
def end(self):
"""Lopettaa piirtämisen"""
pygame.display.flip()
def _draw_sphere(self, pos, radius, color, **kwargs):
pos = list(pos)
pos[0] += radius/2
pos[1] += radius/2
pygame.draw.circle(pygame.display.get_surface(), color, pos, radius)
def _draw_plane(self, **kwargs):
pass
def _draw_box(self, pos, color, lenghts, **kwargs):
rect = pygame.Rect(pos, lenghts)
pygame.draw.rect(pygame.display.get_surface(), color, rect)
class GLRenderer(Renderer):
def _convert_color(self, color):
return color[0]/255.0, color[1]/255.0, color[2]/255.0
def set_display(self, resolution, fullscreen=False):
Renderer.set_display(self, resolution, fullscreen, pygame.OPENGL)
c = self._convert_color(self._bgcolor)
glClearColor(c[0], c[1], c[2], 1.0)
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, resolution[0], 0, resolution[1]);
glMatrixMode(GL_MODELVIEW);
glDisable(GL_DEPTH_TEST)
def start(self):
glClear(GL_COLOR_BUFFER_BIT)
def _draw_cross(self, pos):
glLoadIdentity()
glColorf((1,0,0))
glTranslatef(pos[0], pos[1], 0)
glBegin(GL_LINES)
glVertex2f(-10,0)
glVertex2f( 10,0)
glEnd()
glBegin(GL_LINES)
glVertex2f(0,-10)
glVertex2f(0, 10)
glEnd()
def _start_draw(self, pos, color, type, rotation=None):
glLoadIdentity()
glColorf((1,0,0))
glBegin(GL_POINTS)
glVertex2f(*pos)
glEnd()
glTranslatef(pos[0], pos[1], 0)
if rotation:
R = rotation
rot = [R[0], R[3], R[6], 0,
R[1], R[4], R[7], 0,
R[2], R[5], R[8], 0,
0, 0, 0, 1]
glMultMatrixf(rot)
glColorf(self._convert_color(color))
glBegin(type)
def _draw_box(self, pos, color, lenghts, rotation, **kwargs):
self._start_draw(pos, color, GL_QUADS, rotation)
w,h = lenghts
glVertex2f(-w/2, -h/2)
glVertex2f( w/2, -h/2)
glVertex2f( w/2, h/2)
glVertex2f(-w/2, h/2)
glEnd()
#self._draw_cross(pos)
def _draw_sphere(self, pos, radius, color, **kwargs):
self._start_draw(pos, color, GL_TRIANGLE_FAN)
glVertex2f(0,0)
for i in range(360):
glVertex2f(math.cos(math.degrees(i))*radius,
math.sin(math.degrees(i))*radius)
glEnd()
#self._draw_cross(pos)
class Obj:
"""Luokka, joka yhdistää piirtämisen ja fysiikan"""
def __init__(self, physics2d, renderer, color=[0,0,0], **kwargs):
self._kwargs = kwargs
self._renderer = renderer
self._color = color
self._physics2d = physics2d
self._physics2d.add_object(**kwargs)
def draw(self):
"""Piirtää kappaleen renderöijällä"""
args = self._kwargs.copy()
shape = self._kwargs["geomtype"]
geom = self._physics2d.get_geom(self._kwargs["name"])
body = geom.getBody()
if body != None:
args["pos"] = body.getPosition()[:-1]
args["rotation"] = body.getRotation()
args["color"] = self._color
self._renderer.draw(shape, **args)
def add_force(self, f):
geom = self._physics2d.get_geom(self._kwargs["name"])
body = geom.getBody()
body.addForce((f[0], f[1], 0))
def print_debug(self):
print self._kwargs["name"],
print self._physics2d.get_geom(self._kwargs["name"]).getPosition()
def main():
global opengl
#Luodaan fysiikat
physics = pyode2d.Physics2D([0, -0.01])
handler = pyode2d.Handler("all", "all")
handler2 = pyode2d.Handler("box", "box")
physics.handler.add("alltoall", handler)
physics.handler.add("boxtobox", handler2)
#Luodaan piirtäjä
if opengl:
render = GLRenderer()
else:
render = Renderer()
render.set_display((800,600))
#Luodaan muutama objekti
objects = []
obj = Obj(physics, render,
name = "sphere1",
geomtype = "sphere",
body = True,
groups = ["spheres"],
bodymass = 0.0001,
pos = [400,300],
radius = 100,
fixed = True,
color = [255,255,255])
obj2 = Obj(physics, render,
name = "box1",
geomtype = "box",
lenghts = [100,100],
groups = ["controlable"],
body = True,
pos = [200, 200],
fixed = False,
color = [0,0,0])
obj3 = Obj(physics, render,
name = "box2",
geomtype = "box",
lenghts = [100, 100],
body = True,
groups = ["box2"],
pos = [400, 600],
fixed = False,
color = [128,128,128])
obj4 = Obj(physics, render,
name = "sphere2",
geomtype = "sphere",
radius = 50,
body = True,
groups = ["spheres"],
pos = [400, 800],
fixed = False,
color = [255,128,255])
obj6 = Obj(physics, render,
name = "floor",
geomtype = "plane",
dist = 0,
normal = [0, 1],
body = False,
groups = ["walls", "floor"],
pos = [0, 600],
fixed = True,
color = [128,255,64])
objects.append(obj)
objects.append(obj2)
objects.append(obj3)
objects.append(obj4)
objects.append(obj6)
done = False
while not done:
for e in pygame.event.get():
#Jos painettiin esciä tai suljetaan ikkuna
if e.type == pygame.QUIT or \
( e.type == pygame.KEYDOWN and e.key == pygame.K_ESCAPE ):
done = True
elif e.type == pygame.KEYDOWN and e.key == pygame.K_SPACE:
pass
if e.type == pygame.KEYDOWN:
if e.key == pygame.K_UP:
objects[1].add_force((0,1))
if e.key == pygame.K_DOWN:
objects[0].add_force((0,-1))
if e.key == pygame.K_LEFT:
objects[1].add_force((-1,0))
if e.key == pygame.K_RIGHT:
objects[1].add_force((1,0))
physics.step(1)
#for obj in objects:
# obj.print_debug()
render.start()
for obj in objects:
obj.draw()
render.end()
if __name__ == '__main__': main()Kannattaisi mielestäni mainita selvästi, mitkä ovat perus-Pythonin ulkopuoliset kirjastot, joita tämä koodivinkki vaatii. Sen voi kai mainita täällä kommentissakin.
PyODE: http://pyode.sourceforge.net/
PyGame: http://pygame.org/
Nämä on tietääkseni helppo asentaa, joten aloittelevakin Pythonisti pääsee asennuksen jälkeen nopsasti kokeilemaan koodia. (Toivottavasti tuossa oli kaikki vaatimukset.)
Juuh, ne unohtuikin mainita, eli yllä olevan listan lisäksi:
PyOpenGL: http://pyopengl.sourceforge.net/
Jos ei halua opengl:lää asentaa (tai onnistu asentamaan) niin esimerkin voi ajaa myös pelkällä pygamella:
python exmaple.py --no-opengl
Mutta pygamella piirto on himean vajaata, koska kappaleiden asentoja(rotation) ei oteta huomioon.