Kirjautuminen

Haku

Tehtävät

Koodit: Python: pyode2d-moduuli

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()

Kommentit

Pekka Karjalainen [07.01.2008 12:17:48]

#

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.)

ZcMander [07.01.2008 17:24:03]

#

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.

Kirjoita kommentti

Muista lukea kirjoitusohjeet.
Tietoa sivustosta