NoUserOrg

Reto Spoerri : Game Design : Zürich

Wall Generator (~2007)

Filed in: Portfolio.SparetimeWallGenerator · Modified on : Fri, 31 Jul 09

Screenshots

source

result

other samples

Sourcecode

#-------------------------------------------------------------------------------
# WALLGENERATOR
#
# by Reto Spoerri (rspoerri@nouser.org)  30.6.2008
#
# Converts 2D informations (like images) into 3d landscapes.
#-------------------------------------------------------------------------------
#   Copyright (C) 2008  Reto Spoerri
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#-------------------------------------------------------------------------------



import random, sys
class randomMap:
  def __init__( self, size ):
    self.size = size
    self.grayBgColor = 0
    self.rgbBgColor = [0,0,0,0]

    self.grayData = dict()
    self.rgbData = dict()

  def getGray( self, x, y ):
    if (self.size[0] > x > -1) and (self.size[1] > y > -1):
      # inside the image
      if (x,y) not in self.grayData:
        rand = 20-round( random.random() * 5 )
        self.grayData[(x,y)] = rand 
      return self.grayData[(x,y)]
    # outside the image
    return self.grayBgColor

  def getRGBA( self, x, y ):
    if (self.size[0] > x > -1) and (self.size[1] > y > -1):
      # inside the image
      if (x,y) not in self.rgbData:
        r = random.random()
        g = random.random()
        b = random.random()
        a = random.random()
        self.rgbData[(x,y)] = [r,g,b,a]
      return self.rgbData[(x,y)]
    # outside the image
    return self.rgbBgColor

try:
  from PIL import Image
except:
  print "wallgenerator requires PIL"

class pilMap:
  def __init__( self, image ):
    self.image = Image.open(image)
    self.size = self.image.size
    self.grayBgColor = -0.01
    self.rgbBgColor = [0,0,0,0]

  def getGray( self, x, y ):
    x=self.size[0]-x
    #y=self.size[1]-y
    if (self.size[0] > x > -1) and (self.size[1] > y > -1):
      data = self.image.getpixel( (x,y) )
      sum = 0
      if type(data) == list or type(data) == tuple:
        for d in data:
          sum += d / 255. * 1./len(data)
      return (1-sum) * 10.0
    # outside the image
    return self.grayBgColor

  def getRGBA( self, x, y ):
    x=self.size[0]-x
    #y=self.size[1]-y
    if (self.size[0] > x > -1) and (self.size[1] > y > -1):
      data = self.image.getpixel( (x,y) )
      newData = list()
      for i in xrange(4):
        try:
          newData.append( data[i] / 255.0 )
        except:
          newData.append( self.rgbBgColor[i] )
      return newData
    return self.rgbBgColor



from pandac.PandaModules import GeomVertexFormat, GeomVertexData\
, GeomVertexWriter, Geom, GeomTriangles, GeomNode, ColorAttrib
class vtxGeneratorClass:
  def __init__( self ):
    format = GeomVertexFormat.getV3n3c4t2()

    self.vdata = GeomVertexData('name', format, Geom.UHStatic)

    self.vertex   = GeomVertexWriter(self.vdata, 'vertex')
    self.normal   = GeomVertexWriter(self.vdata, 'normal')
    self.color    = GeomVertexWriter(self.vdata, 'color')
    self.texcoord = GeomVertexWriter(self.vdata, 'texcoord')

    self.triangles = list()

  def addSquare( self, in_position, in_color
                , normal=[(0,0,1),(0,0,1),(0,0,1),(0,0,1)]
                , texCoord = [[1,0],[1,1],[0,1],[0,0]] ):

    row = self.vertex.getWriteRow()

    for i in xrange(4):
      n = Vec3( *normal[i] )
      n.normalize()
      normal[i] = [n.getX(), n.getY(), n.getZ()]

      self.vertex.addData3f(*in_position[i])
      self.normal.addData3f(*normal[i])
      self.color.addData4f(*in_color[i])
      self.texcoord.addData2f(*texCoord[i])

    self.triangles.extend( ((row+0,row+1,row+2), (row+0,row+2,row+3)) )

  def generate( self ):
    prim = GeomTriangles(Geom.UHStatic)

    for tri in self.triangles:
      prim.addVertex(tri[0])
      prim.addVertex(tri[1])
      prim.addVertex(tri[2])
      prim.closePrimitive()

    geom = Geom(self.vdata)
    geom.addPrimitive(prim)

    node = GeomNode('gnode')
    node.addGeom(geom)

    # if this is not set, the vertex color information is discarded on flatten
    node.setAttrib(ColorAttrib.makeVertex())

    return node

  def reset( self ):
    self.triangles = list()

XMIN = 0
XMAX = 2
YMIN = -1
YMAX = 1

DRAWSINGLEQUADS = False
DRAWOPTIMIZEDQUADS = True
COLORMAPBIGGER = False

from pandac.PandaModules import NodePath
class analyzeMap( NodePath ):
  def __init__( self, heightMap, colorMap ):
    NodePath.__init__( self, 'analyzeMap' )

    # vertex generators for x lines and nodes saving the geoms
    self.xWalls = list()
    self.xVtxGen = vtxGeneratorClass() #list()

    # vertex generators for y lines and nodes saving the geoms
    self.yWalls = list()
    self.yVtxGen = vtxGeneratorClass() #list()

    self.topVtxGen = vtxGeneratorClass()

    if True:
      # create the x walls
      print "calculate x walls (%i):" % (heightMap.size[1]+XMAX+1-XMIN),
      for y in xrange( YMIN, heightMap.size[1]+YMAX+1 ):
        sys.stdout.write( "*" )
        sys.stdout.flush()
        prevHeight = [ heightMap.getGray( XMIN, XMIN ), 0 ]
        prevColors = [ colorMap.getRGBA( XMIN, YMIN )
                     , colorMap.getRGBA( XMIN, YMIN-1 )]
        start = XMIN
        for x in xrange( XMIN, heightMap.size[0]+XMAX ):
          heightBase = heightMap.getGray( x, y-1 )
          heightDiff  = heightMap.getGray( x, y ) - heightBase
          curColors = [ colorMap.getRGBA( x, y   )
                      , colorMap.getRGBA( x, y-1 ) ]
          if prevHeight != [heightBase, heightDiff] \
            or prevColors != curColors:
            # start a new wall if the height or the color has changed
            if prevHeight[1] != 0:
              # only store the wall if it is not of height 0
              wall = [[start,y-1], [x-1,y-1], prevHeight[0], prevHeight[1]]
              self.yWalls.append( wall )
            prevHeight = [heightBase, heightDiff]
            prevColors = curColors
            start = x-1
      print
      print "render x walls (%i):" % len(self.yWalls)
      for [[sx,sy], [ex,ey], heightBase, heightDiff] in self.yWalls:
        sys.stdout.write( "*" )
        sys.stdout.flush()
        position =  [ [ sx, sy, heightBase+heightDiff ]
                    , [ sx, sy, heightBase ]
                    , [ ex, sy, heightBase ]
                    , [ ex, sy, heightBase+heightDiff ] ]
        if COLORMAPBIGGER:
          color = [ colorMap.getRGBA(sx,sy)
                  , colorMap.getRGBA(sx,ey)
                  , colorMap.getRGBA(ex,ey)
                  , colorMap.getRGBA(ex,sy) ]
        else:
          if heightDiff < 0:
            c = colorMap.getRGBA(ex,ey)
          else:
            c = colorMap.getRGBA(ex,ey+1)
          #c = [0,0,0,0]  # TESTING
          color = [c,c,c,c]
        n = (0,-heightDiff,0)
        normal = [n,n,n,n]
        self.yVtxGen.addSquare( position, color, normal )
      self.attachNewNode( self.yVtxGen.generate() )
      print

    if True:
      # create the y walls
      print "calculate y walls (%i):" % (heightMap.size[0]+YMAX+1-YMIN),
      for x in xrange( XMIN, heightMap.size[0]+XMAX+1 ):
        sys.stdout.write( "*" )
        sys.stdout.flush()
        prevHeight = [ heightMap.getGray( XMIN, YMIN ), 0 ]
        prevColors = [ colorMap.getRGBA( XMIN, YMIN )
                     , colorMap.getRGBA( XMIN-1, YMIN )]
        start = YMIN
        for y in xrange( YMIN, heightMap.size[1]+YMAX ):
          curHeight = heightMap.getGray( x-1, y )
          curDiff  = heightMap.getGray( x, y ) - curHeight
          curColors = [ colorMap.getRGBA( x  , y )
                      , colorMap.getRGBA( x-1, y ) ]
          if prevHeight != [curHeight, curDiff] \
            or prevColors != curColors: # start a new wall if the height or the color has changed
            #debug tests
            #if prevColor != curColor:
            #  print "colors differ", prevColor, curColor
            if prevHeight[1] != 0: # only store the wall if it is not of height 0
              wall = [[x-1,start], [x-1,y-1], prevHeight[0], prevHeight[1]]
              self.xWalls.append( wall )
            prevHeight = [curHeight, curDiff]
            prevColors = curColors
            start = y-1
      print
      print "render y walls (%i):" % len(self.xWalls)
      for [[sx,sy], [ex,ey], heightBase, heightDiff] in self.xWalls:
        sys.stdout.write( "*" )
        sys.stdout.flush()
        position =  [ [ sx, ey, heightBase ]
                    , [ sx, sy, heightBase ]
                    , [ ex, sy, heightBase+heightDiff ]
                    , [ ex, ey, heightBase+heightDiff ] ]
        if COLORMAPBIGGER:
          color = [ colorMap.getRGBA(ex,ey)
                  , colorMap.getRGBA(ex,sy)
                  , colorMap.getRGBA(sx,sy)
                  , colorMap.getRGBA(sx,ey) ]
        else:
          if heightDiff < 0:
            c = colorMap.getRGBA(ex,ey)
          else:
            c = colorMap.getRGBA(ex+1,ey)
          #c = [0,0,0,0]
          color = [c,c,c,c]
        n = (-heightDiff,0,0)
        normal = [n,n,n,n]
        self.xVtxGen.addSquare( position, color, normal )
      self.attachNewNode( self.xVtxGen.generate() )
      print

    if False: # create a tile for each square
      # create the roofs's
      print "create simple floor (%i):" % (heightMap.size[0]+XMAX-XMIN),
      for x in xrange( XMIN, heightMap.size[0]+XMAX ):
        sys.stdout.write( "*" )
        sys.stdout.flush()
        for y in xrange( YMIN, heightMap.size[1]+YMAX ):
          h = heightMap.getGray( x, y )
          position =  [ [ x-1, y  , h ]
                      , [ x-1, y-1, h ]
                      , [ x  , y-1, h ]
                      , [ x  , y  , h ] ]
          if COLORMAPBIGGER:
            color = [ colorMap.getRGBA(x-1,y  )
                    , colorMap.getRGBA(x-1,y-1)
                    , colorMap.getRGBA(x  ,y-1)
                    , colorMap.getRGBA(x  ,y  ) ]
          else:
            color = [ colorMap.getRGBA(x,y)
                    , colorMap.getRGBA(x,y)
                    , colorMap.getRGBA(x,y)
                    , colorMap.getRGBA(x,y) ]
          n = (0,0,1)
          normal = [n,n,n,n]
          self.topVtxGen.addSquare( position, color, normal )
      self.attachNewNode( self.topVtxGen.generate() )
      print

    if True: # create highly optimized floor tiles
      done = list()
      print "create optimized floor (%i):" % (heightMap.size[0]+XMAX-XMIN),
      for sX in xrange( XMIN, heightMap.size[0]+XMAX ):
        sys.stdout.write( "*" )
        sys.stdout.flush()
        for sY in xrange( YMIN, heightMap.size[1]+YMAX ):
          if (sX, sY) not in done:
            h = heightMap.getGray(sX, sY)
            c = colorMap.getRGBA(sX, sY)
            dYMax = heightMap.size[1]+YMAX-sY
            solutions = [(1,sX,0,sY,0,h)]
            for dX in xrange( 0, heightMap.size[0]+XMAX-sX ):
              for dY in xrange( 0, dYMax ):
                if heightMap.getGray(sX+dX, sY+dY) == h \
                  and colorMap.getRGBA(sX+dX, sY+dY) == c \
                  and not (sX+dX, sY+dY) in done:
                  solutions.append( ((dX+1)*(dY+1), sX,dX, sY,dY, h) )
                else:
                  dYMax = dY
                  break
              dY = 0
              if heightMap.getGray(sX+dX, sY+dY) == h \
                and colorMap.getRGBA(sX+dX, sY+dY) == c \
                and not (sX+dX, sY+dY) in done:
                solutions.append( ((dX+1)*(dY+1), sX,dX, sY,dY, h) )
              else:
                break
            solutions.sort()
            solutions.reverse()

            a,sX,dX,sY,dY,h = solutions[0]
            for x in xrange(sX,sX+dX+1):
              for y in xrange(sY,sY+dY+1):
                done.append( (x,y) )

            position =  [ [ sX-1   , (sY+dY), h ]
                        , [ sX-1   , sY-1   , h ]
                        , [ (sX+dX), sY-1   , h ]
                        , [ (sX+dX), (sY+dY), h ] ]
            c = colorMap.getRGBA(sX,sY)
            color = [ c,c,c,c ]
            n = (0,0,1)
            normal = [n,n,n,n]
            self.topVtxGen.addSquare( position, color, normal )

      self.attachNewNode( self.topVtxGen.generate() )

    self.flattenStrong()

import sys
from direct.showbase.DirectObject import DirectObject
from pandac.PandaModules import Mat4, Vec3
class keyHandlerClass( DirectObject ):
  def __init__( self, radius ):
    self.radius = radius

    self.wireframeMode = 0
    self.toggleWireframe()
    self.accept( 'w', self.toggleWireframe )

    self.initLight( radius )
    self.lightMode = False
    self.toggleLight()
    self.accept( 'l', self.toggleLight)

    self.accept( 'r', self.resetCam )
    self.accept( 'escape', sys.exit )

  def toggleWireframe( self ):
    self.wireframeMode = not self.wireframeMode
    if self.wireframeMode:
      render.clearRenderMode()
      render.setShaderAuto()
    else:
      render.setRenderModeWireframe()
      render.setShaderOff()

  def initLight( self, radius ):
    plightCenter = NodePath( 'plightCenter' )
    plightCenter.setPos( 0,0,radius)
    plightCenter.reparentTo( render )
    plightCenter.hprInterval(6, Vec3(360, 0, 0)).loop() 

    # dummy to show light position
    if True:
      plightDummy = loader.loadModel( 'models/misc/sphere.egg' )
    else:
      plightDummy = NodePath( 'dummy' )
    plightDummy.setPos(radius, 0, 0)
    plightDummy.reparentTo( plightCenter )

    plight = PointLight('plight')
    plight.setColor(VBase4(0.6, 0.6, 0.6, 1))
    self.plnp = plightDummy.attachNewNode(plight)
    render.setLight(self.plnp)

    alight = AmbientLight('alight')
    alight.setColor(VBase4(0.2, 0.2, 0.2, 1))
    self.alnp = render.attachNewNode(alight)
    render.setLight(self.alnp)

  def toggleLight( self ):
    self.lightMode = not self.lightMode
    if self.lightMode:
      render.setLightOff()
    else:
      render.setLight(self.plnp)
      render.setLight(self.alnp)

  def resetCam( self ):
    position = Vec3(0,0,self.radius*3)
    rotation = Vec3(0,0,0)
    camera.setPos(position)
    camera.lookAt(render)
    camera.setHpr(camera, rotation)
    cameraMat=Mat4(camera.getMat())
    cameraMat.invertInPlace()
    base.mouseInterfaceNode.setMat(cameraMat)
    #base.camera.setHpr( 0,0,0 )
    #base.camera.setPos( 0,0,0 )


from pandac.PandaModules import PointLight, AmbientLight, DirectionalLight\
, VBase4, Vec3
MAPSIZE=[64,64]
if __name__ == '__main__':
  import direct.directbase.DirectStart
  #a = analyzeMap(pilMap('panda.jpg'), pilMap('panda.jpg'))
  a = analyzeMap(pilMap('sml.png'), pilMap('smlc.png'))
  a.reparentTo( render )
  a.setHpr( 180,0,0 )
  a.setPos( -a.getBounds().getCenter() )

  if False:
    zeropointDummy = loader.loadModel( 'models/misc/sphere.egg' )
    zeropointDummy.reparentTo( render )
    zeropointDummy.setScale( 0.1 )

  a.writeBamFile( 'out.bam' )

  radius = a.getBounds().getRadius()
  keyHandler = keyHandlerClass( radius )

  run()