def getWaterSurface(manager, polycount=50000, size=(512, 512)): # Get cache directory... cacheDir = manager.get("paths").getConfig().find("cache").get("path") # Check if the data required already exists... cachedWaterSurface = "%s/plane-%dx%d-%dk.bam" % (cacheDir, size[0], size[1], int(polycount / 1000)) try: return loader.loadModel(cachedWaterSurface) except: pass # Make cache directory if needed... if not os.path.isdir(cacheDir): os.mkdir(cacheDir) # Put in an image... img = PNMImage(*size) img.makeGrayscale() img.fill(0, 0, 0) img.write("%s/black-%dx%d.png" % (cacheDir, size[0], size[1])) # Put in a mesh... ht = HeightfieldTesselator("plane") assert ht.setHeightfield(Filename("%s/black-%dx%d.png" % (cacheDir, size[0], size[1]))) ht.setPolyCount(polycount) ht.setFocalPoint(size[0] * 0.5, size[1] * 0.5) node = ht.generate() node.setPos(-0.5 * size[0], 0.5 * size[1], 0) node.flattenLight() node.writeBamFile(cachedWaterSurface) return node
def getWaterSurface(manager, polycount=50000, size=(512, 512)): # Get cache directory... cacheDir = manager.get('paths').getConfig().find('cache').get('path') # Check if the data required already exists... cachedWaterSurface = "%s/plane-%dx%d-%dk.bam" % ( cacheDir, size[0], size[1], int(polycount / 1000)) try: return loader.loadModel(cachedWaterSurface) except: pass # Make cache directory if needed... if not os.path.isdir(cacheDir): os.mkdir(cacheDir) # Put in an image... img = PNMImage(*size) img.makeGrayscale() img.fill(0, 0, 0) img.write("%s/black-%dx%d.png" % (cacheDir, size[0], size[1])) # Put in a mesh... ht = HeightfieldTesselator("plane") assert ht.setHeightfield( Filename("%s/black-%dx%d.png" % (cacheDir, size[0], size[1]))) ht.setPolyCount(polycount) ht.setFocalPoint(size[0] * 0.5, size[1] * 0.5) node = ht.generate() node.setPos(-0.5 * size[0], 0.5 * size[1], 0) node.flattenLight() node.writeBamFile(cachedWaterSurface) return node
def setupHeightfield( self ): # Prep terrain textures #coverTextureFile = "data/textures/ground/green.jpg" #self.mCoverTexture = loader.loadTexture(coverTextureFile) # Setup heightfield self.mHeightFieldTesselator = HeightfieldTesselator("Heightfield") #fName = "data/textures/ground/heightfield.png" #self.mHeightFieldTesselator.setPolyCount(10000) #fileObj = Filename(fName) self.mTerrainVScale = self.mTerrainUScale = 1.0/1024.0 myImage=PNMImage(256,256) myImage.makeGrayscale() p = Perlin(numberOfOctaves = 10, persistance = 0.65, smooth = False) for y in range(0,256): for x in range(0,256): i = p.noise2D(float(x)/256.0,float(y)/256.0) myImage.setGray(x, y, abs(i)) bigImage=PNMImage(1024, 1024) bigImage.gaussianFilterFrom(1.0, myImage) fileObj = Filename("data/textures/ground/myHeightfield.png") bigImage.write(fileObj) #myTexture = Texture() #myTexture.load(bigImage) self.mHeightFieldTesselator.setHeightfield(fileObj) self.mTerrainHeight = MAPSIZE/10 self.mHeightFieldTesselator.setVerticalScale(self.mTerrainHeight) self.mHorizontalScale = MAPSIZE/1024.0 self.mHeightFieldTesselator.setHorizontalScale(self.mHorizontalScale) self.mHeightFieldNode = None # self.tex0 = loader.loadTexture( 'models/textures/ground/schachbrett.png' ) self.tex0 = loader.loadTexture( 'data/textures/ground/mud-tile.png' ) #self.tex1 = loader.loadTexture( 'data/models/textures/ground/green.jpg' ) #self.tex2 = loader.loadTexture( 'data/models/textures/ground/grey-green-leaves.jpg' ) #self.ts0 = TextureStage( 'dirt' ) #self.ts1 = TextureStage( 'fungus' ) #self.ts2 = TextureStage( 'grass' ) self.updateHeightField()
def run(self): size = 256 pb = Perlin.Perlin( persistance = 0.500, smooth = False, seed = random.random() ) myImage2 = pb.imgNoise2D(size,True) myImage=PNMImage(size,size) myImage.makeGrayscale() myImage.setMaxval( (2<<16)-1 ) myImage.fill(0.5) line = lineDrawer.LineDrawer(myImage,(42,180),(13,253),13) for x in range(size): for y in range(size): gray = myImage.getGray(x,y) - 0.5 gray = gray + (myImage2.getGray(x,y) - 0.5) myImage.setGray(x,y,gray + 0.5) self.myTexture.load(myImage)
def flattenArea( self ): tilePos = (500,500) tileSize = (4000,4000) imgTilePos = self.world2MapPos(tilePos) imgTileSize = self.world2MapPos( (tilePos[0] + tileSize[0], tilePos[1] + tileSize[1]) ) imgTileSize = (imgTileSize[0] - imgTilePos[0], imgTileSize[1] - imgTilePos[1]) tileSquare = PNMImage(Filename("tile.png")) tileStamp = PNMImage(int(imgTileSize[0] * (5/3)),int(imgTileSize[1] * (5/3))) tileStamp.makeGrayscale() tileStamp.addAlpha() tileStamp.gaussianFilterFrom(1, tileSquare) count = 4 total = 0.0 selectXLow = int(imgTilePos[0] + imgTileSize[0] * 0.25) selectXHigh = int(imgTilePos[0] + imgTileSize[0] * 0.75) selectYLow = int(imgTilePos[1] + imgTileSize[1] * 0.25) selectYHigh = int(imgTilePos[1] + imgTileSize[1] * 0.75) total += self.myImage.getGray(selectXLow,selectYLow) total += self.myImage.getGray(selectXLow,selectYLow) total += self.myImage.getGray(selectXHigh,selectYHigh) total += self.myImage.getGray(selectXHigh,selectYHigh) average = total/count tileStamp.fill(average) edgeWidth = imgTilePos[0]*(1/3) self.myImage.blendSubImage(tileStamp, int( imgTilePos[0]-edgeWidth), int( imgTilePos[1]-edgeWidth), 0, 0, int(imgTileSize[0]*( 5/3 ) ), int(imgTileSize[1]*( 5/3 ) ), 1)
def imgNoise2D(self, size = 512, autoOctave = False): myImage=PNMImage(1,1) myImage.makeGrayscale() myImage.setMaxval( (2<<16)-1 ) myImage.fill(0.5) octaves = self.numberOfOctaves if autoOctave == True: octaves = int(math.log(size,2)) self.pNoise = range(0,octaves) random.seed(self.seed) for i in range(1,octaves): self.pNoise[i] = PerlinNoise2( 1, 1, 256, random.randint(1,10000)) for o in range(1,octaves): freq = 2**o oldImage = myImage myImage = PNMImage(freq+1,freq+1) myImage.makeGrayscale() myImage.setMaxval( (2<<16)-1 ) myImage.gaussianFilterFrom(1.0, oldImage) for x in range(0,freq): for y in range(0,freq): newNoise = (self.pNoise[o].noise( x, y)*(self.persistance**o)) / 2 myImage.setGray(x,y, myImage.getGray(x,y) + newNoise)#*32) for i in range(0,freq+1): myImage.setGray(i,freq, myImage.getGray(i%freq,0)) myImage.setGray(freq,i, myImage.getGray(0,i%freq)) oldImage = myImage myImage = PNMImage(size,size) myImage.makeGrayscale() myImage.setMaxval( (2<<16)-1 ) myImage.gaussianFilterFrom(1.0, oldImage) return myImage
class Heightfield(DirectObject):# threading.Thread): #def __init__(self): # threading.Thread.__init__(self)#, name="test") def __init__( self ): '''try: import psyco psyco.full() except ImportError: pass''' self.ts0 = TextureStage( 'dirtL0' ) self.ts1 = TextureStage( 'dirtL1' ) self.ts2 = TextureStage( 'dirtL3' ) self.tex0 = loader.loadTexture( 'mud-tile.png' ) self.mTerrainHeight = MAPSIZE*3 self.mHorizontalScale = MAPSIZE/TEXSIZE size = int(TEXSIZE) + 1 pb = Perlin.Perlin( persistance = 0.500, smooth = False, seed = random.random() ) myImage2 = pb.imgNoise2D(size,True) self.myImage=PNMImage(size,size) self.myImage.makeGrayscale() self.myImage.setMaxval( (2<<16)-1 ) line = lineDrawer.LineDrawer(self.myImage,(42,180),(13,240),30) for x in range(size): for y in range(size): if self.myImage.getGray(x,y) > myImage2.getGray(x,y): gray = self.myImage.getGray(x,y) - 0.5 else: gray = myImage2.getGray(x,y) - 0.5 self.myImage.setGray(x,y,gray + 0.5) #size = int(TEXSIZE) + 1 #randSeed = random.random() #p1 = Perlin.Perlin( persistance = 0.500, smooth = False, seed = randSeed ) #self.myImage = p1.imgNoise2D(size,True) self.terrain1 = GeoMipTerrain("myTerrain1") self.terrain2 = GeoMipTerrain("myTerrain2") self.setupHeightfield(self.terrain1) self.setupHeightfield(self.terrain2) self.terrain1.getRoot().reparentTo(render) self.terrain2.getRoot().reparentTo(render) self.accept( "g", self.flattenArea) self.accept( "u", self.updateWithNewImage) def setupHeightfield( self , terrain): terrain.setHeightfield(self.myImage) terrain.setBruteforce(True) terrain.setBlockSize(64) terrain.setNear(128) terrain.setFar(512) terrain.setFocalPoint(base.camera.getPos(render)) taskMgr.add(self.updateTask, "update") mHeightFieldNode = terrain.getRoot() mHeightFieldNode.setPos( -MAPSIZE/2, -MAPSIZE/2, - self.mTerrainHeight/2) mHeightFieldNode.setSx(self.mHorizontalScale) mHeightFieldNode.setSy(self.mHorizontalScale) mHeightFieldNode.setSz(self.mTerrainHeight) terrain.generate() scale = 1.0 mHeightFieldNode.setTexScale( self.ts0, scale, scale ) mHeightFieldNode.setTexture( self.ts0, self.tex0, 1 ) scale = 32.0 mHeightFieldNode.setTexScale( self.ts1, scale, scale ) mHeightFieldNode.setTexture( self.ts1, self.tex0, 1 ) scale = 128.0 mHeightFieldNode.setTexScale( self.ts2, scale, scale ) mHeightFieldNode.setTexture( self.ts2, self.tex0, 1 ) def flattenArea( self ): tilePos = (500,500) tileSize = (4000,4000) imgTilePos = self.world2MapPos(tilePos) imgTileSize = self.world2MapPos( (tilePos[0] + tileSize[0], tilePos[1] + tileSize[1]) ) imgTileSize = (imgTileSize[0] - imgTilePos[0], imgTileSize[1] - imgTilePos[1]) tileSquare = PNMImage(Filename("tile.png")) tileStamp = PNMImage(int(imgTileSize[0] * (5/3)),int(imgTileSize[1] * (5/3))) tileStamp.makeGrayscale() tileStamp.addAlpha() tileStamp.gaussianFilterFrom(1, tileSquare) count = 4 total = 0.0 selectXLow = int(imgTilePos[0] + imgTileSize[0] * 0.25) selectXHigh = int(imgTilePos[0] + imgTileSize[0] * 0.75) selectYLow = int(imgTilePos[1] + imgTileSize[1] * 0.25) selectYHigh = int(imgTilePos[1] + imgTileSize[1] * 0.75) total += self.myImage.getGray(selectXLow,selectYLow) total += self.myImage.getGray(selectXLow,selectYLow) total += self.myImage.getGray(selectXHigh,selectYHigh) total += self.myImage.getGray(selectXHigh,selectYHigh) average = total/count tileStamp.fill(average) edgeWidth = imgTilePos[0]*(1/3) self.myImage.blendSubImage(tileStamp, int( imgTilePos[0]-edgeWidth), int( imgTilePos[1]-edgeWidth), 0, 0, int(imgTileSize[0]*( 5/3 ) ), int(imgTileSize[1]*( 5/3 ) ), 1) def getCurrentTerrain(self): if self.terrain2.getRoot().isHidden(): return self.terrain1 else: return self.terrain2 def getHiddenTerrain(self): if self.terrain1.getRoot().isHidden(): return self.terrain1 else: return self.terrain2 def updateWithNewImage(self): posX = base.camera.getX() + MAPSIZE/2 posY = base.camera.getY() + MAPSIZE/2 if self.terrain2.getRoot().isHidden(): self.terrain2.setHeightfield(self.myImage) self.terrain2.setFocalPoint(posX, posY) if Thread.isThreadingSupported(): thread.start_new_thread(self.updateWithNewImageThread,(self.terrain2,1)) else: self.updateWithNewImageThread(self.terrain2) self.terrain1.getRoot().hide() self.terrain2.getRoot().show() print "done" else: self.terrain1.setHeightfield(self.myImage) self.terrain1.setFocalPoint(posX, posY) if Thread.isThreadingSupported(): thread.start_new_thread(self.updateWithNewImageThread,(self.terrain1,1)) else: self.updateWithNewImageThread(self.terrain1) self.terrain2.getRoot().hide() self.terrain1.getRoot().show() print "done2" def updateWithNewImageThread(self,terrain,blag=1): terrain.update() def updateTask(self,task): posX = base.camera.getX(render) + MAPSIZE/2 posY = base.camera.getY(render) + MAPSIZE/2 self.getCurrentTerrain().setFocalPoint(posX, posY) self.getCurrentTerrain().update() return task.cont def world2MapPos( self, in_pos ): result = (0,0) if abs(in_pos[0]) <= MAPSIZE/2.0 and abs(in_pos[1]) <= MAPSIZE/2.0: posX = (in_pos[0] + MAPSIZE/2.0) / self.mHorizontalScale posY = (in_pos[1] + MAPSIZE/2.0) / self.mHorizontalScale result = (posX,posY) return result def get_elevation( self, in_pos ): result = 0 if abs(in_pos[0]) <= MAPSIZE/2.0 and abs(in_pos[1]) <= MAPSIZE/2.0: posX = (in_pos[0] + MAPSIZE/2.0) / self.mHorizontalScale posY = (in_pos[1] + MAPSIZE/2.0) / self.mHorizontalScale result = (self.getCurrentTerrain().getElevation(posX ,posY ) * self.mTerrainHeight) - self.mTerrainHeight/2 return result
class PerlinTest(DirectObject): def __init__(self): self.size = 64 self.p = Perlin.Perlin(numberOfOctaves=10, persistance=0.75, smooth=False) self.myImage = PNMImage(self.size, self.size) self.myImage.makeGrayscale() self.myTexture = Texture() self.myImage.fill(0.5) self.myTexture.load(self.myImage) self.imageObject = OnscreenImage(image=self.myTexture, pos=(0, 0, 0)) self.myList = [None] * (self.size) for a in range(self.size): self.myList[a] = [0.5] * (self.size) taskMgr.add(self.noiseTaskVerySmart, "perlinNoiseTask") self.startTime = time() self.noiseTaskVerySmart() self.accept("arrow_up", self.run) self.i = [None] * (self.size + 1) for x in range(0, self.size + 1): self.i[x] = [None] * (self.size + 1) def run(self): for a in range(self.size): self.myList[a] = [0.5] * (self.size) self.startTime = time() taskMgr.add(self.noiseTaskVerySmart, "perlinNoiseTask") def noiseTask(self, Task=None): numLines = 8 if Task == None: y = 0 else: y = Task.frame * numLines for yNum in range(0, numLines): for x in range(0, self.size): i = self.p.noise2D(float(x) / self.size, float(y + yNum) / self.size) self.myImage.setGray(x, y + yNum, (i + 1.0) / 2) self.myTexture.load(self.myImage) if Task != None: if self.size >= y + numLines: return Task.cont else: self.myTexture.load(self.myImage) print time() - self.startTime return Task.done def noiseTaskSmart(self, Task=None): if Task == None: o = 0 else: o = Task.frame p = Perlin.Perlin(numberOfOctaves=1, smooth=False, seed=0) freq = 2 ** o for x in range(0, freq + 1): for y in range(0, freq + 1): self.i[x][y] = p.intNoise2D(x * freq, y * freq) for y in range(0, self.size): for x in range(0, self.size): intX = (x * freq) / self.size fraX = (float(x) * freq) / self.size - intX intY = (y * freq) / self.size i1 = p.linearInterpolate(self.i[intX][intY], self.i[intX + 1][intY], fraX) i2 = p.linearInterpolate(self.i[intX][intY + 1], self.i[intX + 1][intY + 1], fraX) interNoise = p.linearInterpolate(i1, i2, (float(y) * freq) / self.size - intY) self.myList[x][y] += interNoise * (0.75 ** o) / 2 self.myImage.setGray(x, y, self.myList[x][y]) self.myTexture.load(self.myImage) if Task != None: if freq < self.size: return Task.cont else: print time() - self.startTime return Task.done def noiseTaskVerySmart(self, Task=None): if Task == None: o = 0 else: o = Task.frame p = Perlin.Perlin(numberOfOctaves=1, smooth=False, seed=0) freq = 2 ** o self.oldImage = self.myImage self.myImage = PNMImage(freq + 1, freq + 1) self.myImage.makeGrayscale() self.myImage.gaussianFilterFrom(1.0, self.oldImage) for x in range(0, freq + 1): for y in range(0, freq + 1): self.myImage.setGray( x, y, self.myImage.getGray(x, y) + p.intNoise2D(x * freq, y * freq) * (0.75 ** o) / 2 ) self.myTexture.load(self.myImage) if Task != None: if freq < self.size: return Task.cont else: print time() - self.startTime return Task.done
class TerrainTile(GeoMipTerrain): """TerrainTiles are the building blocks of a terrain.""" def __init__(self, terrain, x, y): """Builds a Tile for the terrain at input coordinates. Important settings are used directly from the terrain. This allows for easier setting changes, and reduces memory overhead. x and y parameters give the appropriate world coordinates of this tile. """ self.terrain = terrain self.xOffset = x self.yOffset = y self.heightMapDetail = 1 # higher means greater detail self.name = "ID" + str(terrain.id) + "_X" + str(x) + "_Y" + str(y) GeoMipTerrain.__init__(self, name=self.name) self.image = PNMImage() #self.setAutoFlatten(GeoMipTerrain.AFMOff) self.setFocalPoint(self.terrain.focus) self.setAutoFlatten(GeoMipTerrain.AFMOff) self.getRoot().setPos(x, y, 0) if self.terrain.bruteForce: GeoMipTerrain.setBruteforce(self, True) GeoMipTerrain.setBlockSize( self, self.terrain.heightMapSize * self.heightMapDetail) else: GeoMipTerrain.setBlockSize(self, self.terrain.blockSize / 2) #self.setBorderStitching(1) self.setNear(self.terrain.near) self.setFar(self.terrain.far) def update(self): """Updates the GeoMip to use the correct LOD on each block.""" #logging.info("TerrainTile.update()") GeoMipTerrain.update(self) @pstat def updateTask(self, task): """Updates the GeoMip to use the correct LOD on each block.""" self.update() return task.again #@pstat def setHeightField(self, filename): """Set the GeoMip heightfield from a heightmap image.""" GeoMipTerrain.setHeightfield(self, filename) @pstat def generate(self): GeoMipTerrain.generate(self) @pstat def setHeight(self): """Sets the height field to match the height map image.""" self.setHeightField(self.image) @pstat def makeHeightMap(self): """Generate a new heightmap image. Panda3d GeoMipMaps require an image from which to build and update their height field. This function creates the correct image using the tile's position and the Terrain's getHeight() function. """ if SAVED_HEIGHT_MAPS: fileName = "maps/height/" + self.name + ".png" self.getRoot().setTag('EditableTerrain', '1') if self.image.read(Filename(fileName)): logging.info("read heightmap from " + fileName) return heightMapSize = self.terrain.tileSize * self.heightMapDetail + 1 self.image = PNMImage(heightMapSize, heightMapSize, 1, 65535) ySize = self.image.getYSize() - 1 getHeight = self.terrain.getHeight setGray = self.image.setGray xo = self.xOffset yo = self.yOffset d = self.heightMapDetail for x in range(self.image.getXSize()): for y in range(ySize + 1): height = getHeight(x / d + xo, y / d + yo) # feed pixel into image # why is it necessary to invert the y axis I wonder? setGray(x, ySize - y, height) #self.postProcessImage() if SAVED_HEIGHT_MAPS: fileName = "maps/height/" + self.name + ".png" logging.info("saving heightmap to " + fileName) self.image.write(Filename(fileName)) def postProcessImage(self): """Perform filters and manipulations on the heightmap image.""" #self.image.gaussianFilter() def setWireFrame(self, state): self.getRoot().setRenderModeWireframe() def makeSlopeMap(self): self.slopeMap = PNMImage() if SAVED_SLOPE_MAPS: fileName = "maps/slope/" + self.name + ".png" if self.slopeMap.read(Filename(fileName)): logging.info("read slopemap from " + fileName) return self.slopeMap = PNMImage(self.terrain.heightMapSize, self.terrain.heightMapSize) self.slopeMap.makeGrayscale() self.slopeMap.setMaxval(65535) size = self.slopeMap.getYSize() getNormal = self.getNormal setGray = self.slopeMap.setGray for x in range(size): for y in range(size): #note getNormal works at the same resolution as the heightmap normal = getNormal(x, y) # feed pixel into image # why is it necessary to invert the y axis I wonder? #logging.info( normal) normal.z /= self.terrain.getSz() normal.normalize() slope = 1.0 - normal.dot(Vec3(0, 0, 1)) setGray(x, y, slope) if SAVED_SLOPE_MAPS: fileName = "maps/slope/" + self.name + ".png" logging.info("saving slopemap to " + fileName) self.slopeMap.write(Filename(fileName)) def createGroups(self): self.statics = self.getRoot().attachNewNode(self.name + "_statics") self.statics.setSz(1.0 / self.terrain.getSz()) self.statics.setSx(1.0 / self.terrain.getSx()) self.statics.setSy(1.0 / self.terrain.getSy()) self.statics.setShaderAuto() @pstat def make(self): """Build a finished renderable heightMap.""" # apply shader #logging.info( "applying shader") self.terrain.texturer.apply(self.getRoot()) # detail settings #self.getRoot().setSx(1.0 / self.heightMapDetail) #self.getRoot().setSy(1.0 / self.heightMapDetail) #logging.info( "making height map") self.makeHeightMap() #logging.info( "setHeight()") self.setHeight() #self.getRoot().setSz(self.maxHeight) #http://www.panda3d.org/forums/viewtopic.php?t=12054 self.calcAmbientOcclusion() #logging.info( "generate()") self.generate() self.getRoot().setCollideMask(BitMask32.bit(1)) #self.makeSlopeMap() #logging.info( "createGroups()") self.createGroups() self.terrain.populator.populate(self)
class TerrainTile(GeoMipTerrain): """TerrainTiles are the building blocks of a terrain.""" def __init__(self, terrain, x, y): """Builds a Tile for the terrain at input coordinates. Important settings are used directly from the terrain. This allows for easier setting changes and reduces memory overhead. x and y parameters give the appropriate world coordinates of this tile. """ self.terrain = terrain self.xOffset = x self.yOffset = y self.heightMapDetail = 1 # higher means greater detail self.name = "ID" + str(terrain.id) + "_X" + str(x) + "_Y" + str(y) GeoMipTerrain.__init(self, name=self.name) self.image = PNImage() #self.setAutoFlatten(GeoMipTerrain.AFMOff self.setFocalPoint(self.terrain.focus) self.setAutoFlatten(GeoMipTerrain.AFMOff) self.getRoot().setPos(x, y, 0) if self.terrain.bruteForce: GeoMipTerrain.setBruteForce(self, True) GeoMipTerrain.setBlockSize(self, self.terrain.heightMapSize * self.heightMapDetail) else: GeoMipTerrain.setBlockSize(self, self.terrain.blockSize/2) #self.setBorderStitching(1) self.setNear(self.terrain.near) self.setFar(self.terrain.far) def update(self): """Updates the GeoMip to use the correct LOD on each block.""" #logging.info("TerrainTile.update()") GeoMipTerrain.update(self) @pstat def updateTask(self, task): """Updates the GeoMip to use the correct LOD on each block.""" self.update() return task.again #@pstat def setHeightField(self, filename): "Set the GeoMip heightfield from a heightmap image.""" GeoMipTerrain.setHeightfield(self, filename) @pstat def generate(self): GeoMipTerrain.generate(self) @pstat def setHeight(self): """Sets the height field to match the height map image.""" self.setHeightField(self.image) @pstat def makeHeightMap(self): """Generate a new heightmap image. Panda3d GeoMipMaps require an image from which to build and update their height field. This function creates the correct image using the tile's position and the Terrain's getHeight() function. """ if SAVED_HEIGHT_MAPS: fileName = "maps/height/" + self.name + ".png" self.getRoot().setTag('EditableTerrain', '1') if self.image.read(Filename(fileName)): logging.info( "read heightmap from " + fileName) return heightMapSize = self.terrain.tileSize * self.heightMapDetail + 1 self.image = PNMImage(heightMapSize, heightMapSize, 1, 65535) ySize = self.image.getYSize() - 1 getHeight = self.terrain.getHeight setGray = self.image.setGray xo = self.xOffset yo = self.yOffset d = self.heightMapDetail for x in range(self.image.getXSize()): for y in range(ySize + 1): height = getHeight(x / d + xo, y / d + yo) # feed pixel into image # why is it necessary to invert the y axis I wonder? setGray(x, ySize - y, height) #self.postProcessImage() if SAVED_HEIGHT_MAPS: fileName = "maps/height/" + self.name + ".png" logging.info( "saving heightmap to " + fileName) self.image.write(Filename(fileName)) def postProcessImage(self): """Perform filters and manipulations on the heightmap image.""" #self.image.gaussianFilter() def setWireFrame(self, state): self.getRoot().setRenderModeWireframe() def makeSlopeMap(self): self.slopeMap = PNMImage() if SAVED_SLOPE_MAPS: fileName = "maps/slope/" + self.name + ".png" if self.slopeMap.read(Filename(fileName)): logging.info( "read slopemap from " + fileName) return self.slopeMap = PNMImage(self.terrain.heightMapSize, self.terrain.heightMapSize) self.slopeMap.makeGrayscale() self.slopeMap.setMaxval(65535) size = self.slopeMap.getYSize() getNormal = self.getNormal setGray = self.slopeMap.setGray for x in range(size): for y in range(size): #note getNormal works at the same resolution as the heightmap normal = getNormal(x, y) # feed pixel into image # why is it necessary to invert the y axis I wonder? #logging.info( normal) normal.z /= self.terrain.getSz() normal.normalize() slope = 1.0 - normal.dot(Vec3(0, 0, 1)) setGray(x, y, slope) if SAVED_SLOPE_MAPS: fileName = "maps/slope/" + self.name + ".png" logging.info( "saving slopemap to " + fileName) self.slopeMap.write(Filename(fileName)) def createGroups(self): self.statics = self.getRoot().attachNewNode(self.name + "_statics") self.statics.setSz(1.0 / self.terrain.getSz()) self.statics.setSx(1.0 / self.terrain.getSz()) self.statics.setSy(1.0 / self.terrain.getSy()) self.statics.setShaderAuto() @pstat def make(self): """Build a finished renderable heightMap.""" # apply shader #logging.info( "applying shader") self.terrain.texturer.apply(self.getRoot()) # detail settings #self.getRoot().setSz(1.0 / self.heightMapDetail) #self.getRoot().setSy(1.0 / self.heightMapDetail) #logging.info( "making height map") self.makeHeightMap() #logging.info( "setHeight()") self.setHeight() #self.getRoot().setSz(self.maxHeight) #http://www.panda3d.org/forums/viewtopic.php?=t=12054 self.calcAmbientOcclusion() #loggin.info( "generate()") self.generate() self.getRoot().setCollideMask(BitMask32.bit(1)) #self.makeSlopeMap() #logging.info( "createGroups()") self.createGroups() self.terrain.populator.populate(self)