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






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