def transparencyKey(filename): image = PNMImage(GAME+'/textures/effects/'+filename) image.addAlpha() backgroundColor = None for y in range(image.getYSize()): for x in range(image.getXSize()): if backgroundColor == None: backgroundColor = Color(image.getRedVal(x, y), image.getGreenVal(x, y), image.getGreenVal(x, y), 0) if image.getRedVal(x, y) == backgroundColor.R and \ image.getGreenVal(x, y) == backgroundColor.G and \ image.getGreenVal(x, y) == backgroundColor.B: # Transparent image.setAlpha(x, y, 0.0) else: # Opaque image.setAlpha(x, y, 1.0) return image
def transparencyKey(filename): image = PNMImage(GAME + "/textures/effects/" + filename) image.addAlpha() backgroundColor = None for y in range(image.getYSize()): for x in range(image.getXSize()): if backgroundColor == None: backgroundColor = Color(image.getRedVal(x, y), image.getGreenVal(x, y), image.getGreenVal(x, y), 0) if ( image.getRedVal(x, y) == backgroundColor.R and image.getGreenVal(x, y) == backgroundColor.G and image.getGreenVal(x, y) == backgroundColor.B ): # Transparent image.setAlpha(x, y, 0.0) else: # Opaque image.setAlpha(x, y, 1.0) return image
def make_data(self, hmapfile): # open heightmap for reading pixel data heightmap = PNMImage() heightmap.read(Filename(hmapfile)) xs = heightmap.getXSize() ys = heightmap.getYSize() # generate data bi-dimensional array data = [] for x in range(xs): data.append([]) for y in range(ys): # set data dictionary properties # name name = "cell_" + str(x) + "_" + str(y) # height height = (heightmap.getXel(x, ys - y - 1)[0] * 10) if self.retro == True: if height < 1 : height = height / 5 height = int(height) # c and rgb c = [random.random(), random.random(), random.random()] rgb = (int(c[0] * 255), int(c[1] * 255), int(c[2] * 255)) # default texture texture = self.tiles[0]['tex'] texturenum = 0 score = self.tiles[0]['score'] # from rgb we assign tex and score for n in range(len(self.tiles)): if rgb == self.tiles[n]['rgb']: texture = self.tiles[n]['tex'] texturenum = n score = self.tiles[n]['score'] break # set terrain data dictionary data[x].append({'name':name, 'h':height, 'c':c, 'rgb':rgb, 'tex':texture, 'texnum':texturenum, 'score':score}) return data
def __init__(self, image_path, rowPerFace, name=None,\ rows=1, cols=1, scale=1.0,\ twoSided=False, alpha=TRANS_ALPHA,\ repeatX=1, repeatY=1,\ anchorX=ALIGN_CENTER, anchorY=ALIGN_BOTTOM): """ Create a card textured with an image. The card is sized so that the ratio between the card and image is the same. """ global SpriteId self.spriteNum = str(SpriteId) SpriteId += 1 scale *= self.PIXEL_SCALE self.animations = {} self.scale = scale self.repeatX = repeatX self.repeatY = repeatY self.flip = {'x': False, 'y': False} self.rows = rows self.cols = cols self.currentFrame = 0 self.currentAnim = None self.loopAnim = False self.frameInterrupt = True # Create the NodePath if name: self.node = NodePath("Sprite2d:%s" % name) else: self.node = NodePath("Sprite2d:%s" % image_path) # Set the attribute for transparency/twosided self.node.node().setAttrib(TransparencyAttrib.make(alpha)) if twoSided: self.node.setTwoSided(True) # Make a filepath self.imgFile = Filename(image_path) if self.imgFile.empty(): raise IOError, "File not found" # Instead of loading it outright, check with the PNMImageHeader if we can open # the file. imgHead = PNMImageHeader() if not imgHead.readHeader(self.imgFile): raise IOError, "PNMImageHeader could not read file. Try using absolute filepaths" # Load the image with a PNMImage image = PNMImage() image.read(self.imgFile) self.sizeX = image.getXSize() self.sizeY = image.getYSize() # We need to find the power of two size for the another PNMImage # so that the texture thats loaded on the geometry won't have artifacts textureSizeX = self.nextsize(self.sizeX) textureSizeY = self.nextsize(self.sizeY) # The actual size of the texture in memory self.realSizeX = textureSizeX self.realSizeY = textureSizeY self.paddedImg = PNMImage(textureSizeX, textureSizeY) if image.hasAlpha(): self.paddedImg.alphaFill(0) # Copy the source image to the image we're actually using self.paddedImg.blendSubImage(image, 0, 0) # We're done with source image, clear it image.clear() # The pixel sizes for each cell self.colSize = self.sizeX / self.cols self.rowSize = self.sizeY / self.rows # How much padding the texture has self.paddingX = textureSizeX - self.sizeX self.paddingY = textureSizeY - self.sizeY # Set UV padding self.uPad = float(self.paddingX) / textureSizeX self.vPad = float(self.paddingY) / textureSizeY # The UV dimensions for each cell self.uSize = (1.0 - self.uPad) / self.cols self.vSize = (1.0 - self.vPad) / self.rows self.cards = [] self.rowPerFace = rowPerFace for i in range(len(rowPerFace)): card = CardMaker("Sprite2d-Geom") # The positions to create the card at if anchorX == self.ALIGN_LEFT: posLeft = 0 posRight = (self.colSize / scale) * repeatX elif anchorX == self.ALIGN_CENTER: posLeft = -(self.colSize / 2.0 / scale) * repeatX posRight = (self.colSize / 2.0 / scale) * repeatX elif anchorX == self.ALIGN_RIGHT: posLeft = -(self.colSize / scale) * repeatX posRight = 0 if anchorY == self.ALIGN_BOTTOM: posTop = 0 posBottom = (self.rowSize / scale) * repeatY elif anchorY == self.ALIGN_CENTER: posTop = -(self.rowSize / 2.0 / scale) * repeatY posBottom = (self.rowSize / 2.0 / scale) * repeatY elif anchorY == self.ALIGN_TOP: posTop = -(self.rowSize / scale) * repeatY posBottom = 0 card.setFrame(posLeft, posRight, posTop, posBottom) card.setHasUvs(True) self.cards.append(self.node.attachNewNode(card.generate())) self.cards[-1].setH(i * 360 / len(rowPerFace)) # Since the texture is padded, we need to set up offsets and scales to make # the texture fit the whole card self.offsetX = (float(self.colSize) / textureSizeX) self.offsetY = (float(self.rowSize) / textureSizeY) # self.node.setTexScale(TextureStage.getDefault(), self.offsetX * repeatX, self.offsetY * repeatY) # self.node.setTexOffset(TextureStage.getDefault(), 0, 1-self.offsetY) self.texture = Texture() self.texture.setXSize(textureSizeX) self.texture.setYSize(textureSizeY) self.texture.setZSize(1) # Load the padded PNMImage to the texture self.texture.load(self.paddedImg) self.texture.setMagfilter(Texture.FTNearest) self.texture.setMinfilter(Texture.FTNearest) #Set up texture clamps according to repeats if repeatX > 1: self.texture.setWrapU(Texture.WMRepeat) else: self.texture.setWrapU(Texture.WMClamp) if repeatY > 1: self.texture.setWrapV(Texture.WMRepeat) else: self.texture.setWrapV(Texture.WMClamp) self.node.setTexture(self.texture) self.setFrame(0)
def __init__(self, image_path, rowPerFace, name=None,\ rows=1, cols=1, scale=1.0,\ twoSided=False, alpha=TRANS_ALPHA,\ repeatX=1, repeatY=1,\ anchorX=ALIGN_CENTER, anchorY=ALIGN_BOTTOM): """ Create a card textured with an image. The card is sized so that the ratio between the card and image is the same. """ global SpriteId self.spriteNum = str(SpriteId) SpriteId += 1 scale *= self.PIXEL_SCALE self.animations = {} self.scale = scale self.repeatX = repeatX self.repeatY = repeatY self.flip = {'x':False,'y':False} self.rows = rows self.cols = cols self.currentFrame = 0 self.currentAnim = None self.loopAnim = False self.frameInterrupt = True # Create the NodePath if name: self.node = NodePath("Sprite2d:%s" % name) else: self.node = NodePath("Sprite2d:%s" % image_path) # Set the attribute for transparency/twosided self.node.node().setAttrib(TransparencyAttrib.make(alpha)) if twoSided: self.node.setTwoSided(True) # Make a filepath self.imgFile = Filename(image_path) if self.imgFile.empty(): raise IOError, "File not found" # Instead of loading it outright, check with the PNMImageHeader if we can open # the file. imgHead = PNMImageHeader() if not imgHead.readHeader(self.imgFile): raise IOError, "PNMImageHeader could not read file. Try using absolute filepaths" # Load the image with a PNMImage image = PNMImage() image.read(self.imgFile) self.sizeX = image.getXSize() self.sizeY = image.getYSize() # We need to find the power of two size for the another PNMImage # so that the texture thats loaded on the geometry won't have artifacts textureSizeX = self.nextsize(self.sizeX) textureSizeY = self.nextsize(self.sizeY) # The actual size of the texture in memory self.realSizeX = textureSizeX self.realSizeY = textureSizeY self.paddedImg = PNMImage(textureSizeX, textureSizeY) if image.hasAlpha(): self.paddedImg.alphaFill(0) # Copy the source image to the image we're actually using self.paddedImg.blendSubImage(image, 0, 0) # We're done with source image, clear it image.clear() # The pixel sizes for each cell self.colSize = self.sizeX/self.cols self.rowSize = self.sizeY/self.rows # How much padding the texture has self.paddingX = textureSizeX - self.sizeX self.paddingY = textureSizeY - self.sizeY # Set UV padding self.uPad = float(self.paddingX)/textureSizeX self.vPad = float(self.paddingY)/textureSizeY # The UV dimensions for each cell self.uSize = (1.0 - self.uPad) / self.cols self.vSize = (1.0 - self.vPad) / self.rows self.cards = [] self.rowPerFace = rowPerFace for i in range(len(rowPerFace)): card = CardMaker("Sprite2d-Geom") # The positions to create the card at if anchorX == self.ALIGN_LEFT: posLeft = 0 posRight = (self.colSize/scale)*repeatX elif anchorX == self.ALIGN_CENTER: posLeft = -(self.colSize/2.0/scale)*repeatX posRight = (self.colSize/2.0/scale)*repeatX elif anchorX == self.ALIGN_RIGHT: posLeft = -(self.colSize/scale)*repeatX posRight = 0 if anchorY == self.ALIGN_BOTTOM: posTop = 0 posBottom = (self.rowSize/scale)*repeatY elif anchorY == self.ALIGN_CENTER: posTop = -(self.rowSize/2.0/scale)*repeatY posBottom = (self.rowSize/2.0/scale)*repeatY elif anchorY == self.ALIGN_TOP: posTop = -(self.rowSize/scale)*repeatY posBottom = 0 card.setFrame(posLeft, posRight, posTop, posBottom) card.setHasUvs(True) self.cards.append(self.node.attachNewNode(card.generate())) self.cards[-1].setH(i * 360/len(rowPerFace)) # Since the texture is padded, we need to set up offsets and scales to make # the texture fit the whole card self.offsetX = (float(self.colSize)/textureSizeX) self.offsetY = (float(self.rowSize)/textureSizeY) # self.node.setTexScale(TextureStage.getDefault(), self.offsetX * repeatX, self.offsetY * repeatY) # self.node.setTexOffset(TextureStage.getDefault(), 0, 1-self.offsetY) self.texture = Texture() self.texture.setXSize(textureSizeX) self.texture.setYSize(textureSizeY) self.texture.setZSize(1) # Load the padded PNMImage to the texture self.texture.load(self.paddedImg) self.texture.setMagfilter(Texture.FTNearest) self.texture.setMinfilter(Texture.FTNearest) #Set up texture clamps according to repeats if repeatX > 1: self.texture.setWrapU(Texture.WMRepeat) else: self.texture.setWrapU(Texture.WMClamp) if repeatY > 1: self.texture.setWrapV(Texture.WMRepeat) else: self.texture.setWrapV(Texture.WMClamp) self.node.setTexture(self.texture) self.setFrame(0)
class Region(DirectObject.DirectObject): '''Stuff''' def __init__(self): self.tiles = [] self.cities = {} self.accept('loadRegion', self.load) self.accept("updatedTiles", self.updateTiles) self.accept("newCity", self.newCity) self.accept("clickForCity", self.checkCity) self.accept("unfoundCity", self.unfoundCity) self.accept("enterCity", self.enterCity) def load(self, container, name="New Region"): '''Loads a new region, usually from connecting to a server Or starting a new or previously saved region. ''' import base64 self.heightmap = PNMImage() imageString = base64.b64decode(container.heightmap) self.heightmap.read(StringStream(imageString)) self.region_size = (self.heightmap.getXSize() - 1, self.heightmap.getYSize() - 1) position = 0 tileid = 0 total_tiles = self.region_size[0] * self.region_size[1] ranges = [] tiles = [] for tile in container.tiles: tiles.append((tile.id, tile.cityid)) for n in range(len(tiles)): try: ranges.append((tiles[n][0], tiles[n + 1][0] - 1, tiles[n][1])) except: ranges.append((tiles[n][0], total_tiles, tiles[n][1])) for r in ranges: for x in range(r[0], r[1] + 1): #print "r0, r1, x", r[0], r[1], x self.tiles.append(Tile(tileid, r[2])) #print "Len", len(self.tiles) tileid += 1 position = 0 for y in range(self.region_size[1]): for x in range(self.region_size[0]): self.tiles[position].coords = (x, y) position += 1 for city in container.cities: self.newCity(city) messenger.send("generateRegion", [self.heightmap, self.tiles, self.cities, container]) def updateTiles(self, container): x = 0 for tile in container: x += 1 self.tiles[tile.id].cityid = tile.cityid print x, "tiles updated from server." messenger.send("updateRegion", [self.heightmap, self.tiles, self.cities]) def newCity(self, city): self.cities[city.id] = { "name": city.name, "mayor": city.mayor, "funds": city.funds, "population": city.population } def checkCity(self, cell): '''Checks for city in given cell for region gui display''' if not cell: return tile = self.getTile(cell[0], cell[1]) if tile.cityid: messenger.send("showRegionCityWindow", [tile.cityid, self.cities[tile.cityid]]) def getTile(self, x, y): '''Returns tile by coordinate. Thankfully smart enough to find a way to not iterate ''' value = y * self.region_size[0] + x return self.tiles[value] def unfoundCity(self, ident): '''Unfounds a city''' del self.cities[ident] def enterCity(self, ident): '''Processess information needed for graphical elements to enter city view.''' # We need to send list of tiles for terrain manager tiles = [] xsum = 0 ysum = 0 n = 0 for tile in self.tiles: if tile.cityid is ident: tiles.append(tile) # We need to compute center of city to target camera there xsum += tile.coords[0] ysum += tile.coords[1] n += 1 xavg = xsum / n yavg = ysum / n position = (xavg, yavg) # We need to send city info so gui elements can be drawn city = self.cities[ident] messenger.send('enterCityView', [ident, city, position, tiles])
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)
class Region(DirectObject.DirectObject): '''Stuff''' def __init__(self): self.tiles = [] self.cities = {} self.accept('loadRegion', self.load) self.accept("updatedTiles", self.updateTiles) self.accept("newCity", self.newCity) self.accept("clickForCity", self.checkCity) self.accept("unfoundCity", self.unfoundCity) self.accept("enterCity", self.enterCity) def load(self, container, name="New Region"): '''Loads a new region, usually from connecting to a server Or starting a new or previously saved region. ''' import base64 self.heightmap = PNMImage() imageString = base64.b64decode(container.heightmap) self.heightmap.read(StringStream(imageString)) self.region_size = (self.heightmap.getXSize()-1, self.heightmap.getYSize()-1) position = 0 tileid = 0 total_tiles = self.region_size[0] * self.region_size[1] ranges = [] tiles = [] for tile in container.tiles: tiles.append((tile.id, tile.cityid)) for n in range(len(tiles)): try: ranges.append((tiles[n][0], tiles[n+1][0]-1, tiles[n][1])) except: ranges.append((tiles[n][0], total_tiles, tiles[n][1])) for r in ranges: for x in range(r[0], r[1]+1): #print "r0, r1, x", r[0], r[1], x self.tiles.append(Tile(tileid, r[2])) #print "Len", len(self.tiles) tileid += 1 position = 0 for y in range(self.region_size[1]): for x in range(self.region_size[0]): self.tiles[position].coords = (x,y) position += 1 for city in container.cities: self.newCity(city) messenger.send("generateRegion", [self.heightmap, self.tiles, self.cities, container]) def updateTiles(self, container): x = 0 for tile in container: x += 1 self.tiles[tile.id].cityid = tile.cityid print x, "tiles updated from server." messenger.send("updateRegion", [self.heightmap, self.tiles, self.cities]) def newCity(self, city): self.cities[city.id] = {"name": city.name, "mayor": city.mayor, "funds": city.funds, "population": city.population} def checkCity(self, cell): '''Checks for city in given cell for region gui display''' if not cell: return tile = self.getTile(cell[0], cell[1]) if tile.cityid: messenger.send("showRegionCityWindow", [tile.cityid, self.cities[tile.cityid]]) def getTile(self, x, y): '''Returns tile by coordinate. Thankfully smart enough to find a way to not iterate ''' value = y * self.region_size[0] + x return self.tiles[value] def unfoundCity(self, ident): '''Unfounds a city''' del self.cities[ident] def enterCity(self, ident): '''Processess information needed for graphical elements to enter city view.''' # We need to send list of tiles for terrain manager tiles = [] xsum = 0 ysum = 0 n = 0 for tile in self.tiles: if tile.cityid is ident: tiles.append(tile) # We need to compute center of city to target camera there xsum += tile.coords[0] ysum += tile.coords[1] n += 1 xavg = xsum/n yavg = ysum/n position = (xavg, yavg) # We need to send city info so gui elements can be drawn city = self.cities[ident] messenger.send('enterCityView', [ident, city, position, tiles])
class TexturePainter(DirectObject): def __init__(self): self.editTexture = None self.editModel = None self.texturePainterStatus = TEXTURE_PAINTER_STATUS_DISABLED self.paintColor = VBase4D(1,1,1,1) self.paintSize = 10 self.paintEffect = PNMBrush.BEBlend self.paintSmooth = True self.paintMode = TEXTUREPAINTER_FUNCTION_PAINT_POINT self.painter = None # --- creation and destroying of the whole editor --- def enableEditor(self): ''' create the editor change from disabled to enabled''' if self.texturePainterStatus == TEXTURE_PAINTER_STATUS_DISABLED: self.texturePainterStatus = TEXTURE_PAINTER_STATUS_ENABLED self.__enableEditor() else: print "E: TexturePainter.enableEditor: not disabled", self.texturePainterStatus def disableEditor(self): ''' destroy the editor, automatically stop the editor and painting change from enabled to disabled''' # try stopping if more advanced mode #if self.texturePainterStatus == TEXTURE_PAINTER_STATUS_INITIALIZED: # self.stopEditor() if self.texturePainterStatus == TEXTURE_PAINTER_STATUS_ENABLED: self.texturePainterStatus = TEXTURE_PAINTER_STATUS_DISABLED self.__disableEditor() else: print "E: TexturePainter.disableEditor: not enabled", self.texturePainterStatus # --- def startEditor(self, editModel, editTexture, backgroundShader=MODEL_COLOR_SHADER): ''' prepare to paint change from enabled to initialized''' if self.texturePainterStatus == TEXTURE_PAINTER_STATUS_ENABLED: self.texturePainterStatus = TEXTURE_PAINTER_STATUS_INITIALIZED self.__startEditor(editModel, editTexture, backgroundShader) else: print "E: TexturePainter.startEditor: not enabled", self.texturePainterStatus def stopEditor(self): ''' stop painting, automatically stop painting change from initialized to enabled''' #if self.texturePainterStatus == TEXTURE_PAINTER_STATUS_INITIALIZED: # self.stopPaint() if self.texturePainterStatus == TEXTURE_PAINTER_STATUS_INITIALIZED: self.texturePainterStatus = TEXTURE_PAINTER_STATUS_ENABLED return self.__stopEditor() else: print "E: TexturePainter.startEditor: not initialized", self.texturePainterStatus """ # this is not externally callable # --- def startPaint(self): ''' start painting on the model change from initialized to running ''' if self.texturePainterStatus == TEXTURE_PAINTER_STATUS_INITIALIZED: self.texturePainterStatus = TEXTURE_PAINTER_STATUS_RUNNING self.__startPaint() else: print "E: TexturePainter.startPaint: not enabled", self.texturePainterStatus""" def stopPaint(self): ''' stop painting change from running to initialized ''' if self.texturePainterStatus == TEXTURE_PAINTER_STATUS_RUNNING: self.texturePainterStatus = TEXTURE_PAINTER_STATUS_INITIALIZED self.__stopPaint() else: print "E: TexturePainter.stopPaint: not running", self.texturePainterStatus # --- brush settings for painting --- ''' changing brush settings is possible all the time ''' def setBrushSettings(self, color, size, smooth, effect): #print "I: TexturePainter.setBrushSettings", color, size, smooth, effect self.paintColor = color self.paintSize = size self.paintEffect = effect self.paintSmooth = smooth if effect in [PNMBrush.BESet, PNMBrush.BEBlend, PNMBrush.BEDarken, PNMBrush.BELighten]: self.brush = PNMBrush.makeSpot(color, size, smooth, effect) #if self.paintModel: if self.painter: self.painter.setPen(self.brush) def getBrushSettings(self): return self.paintColor,self.paintSize,self.paintSmooth,self.paintEffect def setPaintMode(self, newMode): self.paintMode = newMode # clear last point if mode changed if newMode == TEXTUREPAINTER_FUNCTION_PAINT_POINT or \ newMode == TEXTUREPAINTER_FUNCTION_READ: self.lastPoint = None def getPaintMode(self): return self.paintMode def __enableEditor(self): ''' create the background rendering etc., but the model is not yet defined ''' print "I: TexturePainter.__enableEditor" # the buffer the model with the color texture is rendered into self.modelColorBuffer = None self.modelColorCam = None # the buffer the picked position color is rendered into self.colorPickerBuffer = None self.colorPickerCam = None # create the buffers self.__createBuffer() # when the window is resized, the background buffer etc must be updated. self.accept("window-event", self.__windowEvent) # some debugging stuff self.accept("v", base.bufferViewer.toggleEnable) self.accept("V", base.bufferViewer.toggleEnable) def __disableEditor(self): print "I: TexturePainter.__disableEditor" self.__destroyBuffer() # ignore window-event and debug self.ignoreAll() def __windowEvent(self, win=None): ''' when the editor is enabled, update the buffers etc. when the window is resized ''' print "I: TexturePainter.windowEvent" # with a fixed backgroudn buffer size this is not needed anymore if False: #if self.texturePainterStatus != TEXTURE_PAINTER_STATUS_DISABLED: if self.modelColorBuffer: if WindowManager.activeWindow: # on window resize there seems to be never a active window win = WindowManager.activeWindow.win else: win = base.win if self.modelColorBuffer.getXSize() != win.getXSize() or self.modelColorBuffer.getYSize() != win.getYSize(): '''print " - window resized",\ self.modelColorBuffer.getXSize(),\ win.getXSize(),\ self.modelColorBuffer.getYSize(),\ win.getYSize()''' # if the buffer size doesnt match the window size (window has been resized) self.__destroyBuffer() self.__createBuffer() self.__updateModel() else: print "W: TexturePainter.__windowEvent: no buffer" self.__createBuffer() def __createBuffer(self): ''' create the buffer we render in the background into ''' print "I: TexturePainter.__createBuffer" # the window has been modified if WindowManager.activeWindow: # on window resize there seems to be never a active window win = WindowManager.activeWindow.win else: win = base.win # get the window size self.windowSizeX = win.getXSize() self.windowSizeY = win.getYSize() # create a buffer in which we render the model using a shader self.paintMap = Texture() # 1.5.4 cant handle non power of 2 buffers self.modelColorBuffer = createOffscreenBuffer(-3, TEXTUREPAINTER_BACKGROUND_BUFFER_RENDERSIZE[0], TEXTUREPAINTER_BACKGROUND_BUFFER_RENDERSIZE[1]) #self.windowSizeX, self.windowSizeY) self.modelColorBuffer.addRenderTexture(self.paintMap, GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPColor) self.modelColorCam = base.makeCamera(self.modelColorBuffer, lens=base.cam.node().getLens(), sort=1) # Create a small buffer for the shader program that will fetch the point from the texture made # by the self.modelColorBuffer self.colorPickerImage = PNMImage() self.colorPickerTex = Texture() self.colorPickerBuffer = base.win.makeTextureBuffer("color picker buffer", 2, 2, self.colorPickerTex, True) self.colorPickerScene = NodePath('color picker scene') self.colorPickerCam = base.makeCamera(self.colorPickerBuffer, lens=base.cam.node().getLens(), sort=2) self.colorPickerCam.reparentTo(self.colorPickerScene) self.colorPickerCam.setY(-2) cm = CardMaker('color picker scene card') cm.setFrameFullscreenQuad() pickerCard = self.colorPickerScene.attachNewNode(cm.generate()) loadPicker = NodePath(PandaNode('pointnode')) loadPicker.setShader(Shader.make(COLOR_PICKER_SHADER), 10001) # Feed the paintmap from the paintBuffer to the shader and initial mouse positions self.colorPickerScene.setShaderInput('paintmap', self.paintMap) self.colorPickerScene.setShaderInput('mousepos', 0, 0, 0, 1) self.colorPickerCam.node().setInitialState(loadPicker.getState()) def __destroyBuffer(self): print "I: TexturePainter.__destroyBuffer" if self.modelColorBuffer: # Destroy the buffer base.graphicsEngine.removeWindow(self.modelColorBuffer) self.modelColorBuffer = None # Remove the camera self.modelColorCam.removeNode() del self.modelColorCam self.colorPickerScene.removeNode() del self.colorPickerScene # remove cam self.colorPickerCam.removeNode() del self.colorPickerCam # Destroy the buffer base.graphicsEngine.removeWindow(self.colorPickerBuffer) self.colorPickerBuffer = None del self.colorPickerTex del self.colorPickerImage def __startEditor(self, editModel, editTexture, backgroundShader=MODEL_COLOR_SHADER): print "I: TexturePainter.__startEditor" # this is needed as on startup the editor may not have had a window etc. self.__windowEvent() if not editModel or not editTexture: print "W: TexturePainter.__startEditor: model or texture invalid", editModel, editTexture return False self.editModel = editModel self.editTexture = editTexture self.editImage = None self.backgroundShader = backgroundShader if type(self.editTexture) == Texture: # if the image to modify is a texture, create a pnmImage which we modify self.editImage = PNMImage() # copy the image from the texture to the working layer self.editTexture.store(self.editImage) else: self.editImage = self.editTexture # create the brush for painting self.painter = PNMPainter(self.editImage) self.setBrushSettings( *self.getBrushSettings() ) self.__updateModel() # start edit messenger.send(EVENT_TEXTUREPAINTER_STARTEDIT) for startEvent in TEXTUREPAINTER_START_PAINT_EVENTS: self.accept(startEvent, self.__startPaint) for stopEvent in TEXTUREPAINTER_STOP_PAINT_EVENTS: self.accept(stopEvent, self.__stopPaint) self.modelColorCam.node().copyLens(WindowManager.activeWindow.camera.node().getLens()) taskMgr.add(self.__paintTask, 'paintTask') #modelModificator.toggleEditmode(False) self.isPainting = False def __stopEditor(self): print "I: TexturePainter.__stopEditor" for startEvent in TEXTUREPAINTER_START_PAINT_EVENTS: self.ignore(startEvent) for stopEvent in TEXTUREPAINTER_STOP_PAINT_EVENTS: self.ignore(stopEvent) taskMgr.remove('paintTask') # stop edit end # must be reset before we loose the properties if self.editModel and self.editTexture and self.editImage: try: # hide the model from cam 2 self.editModel.hide(BitMask32.bit(1)) self.editModel = None except: print "E: TexturePainter.__stopEditor: the model has already been deleted" # stop edit messenger.send(EVENT_TEXTUREPAINTER_STOPEDIT) self.editImage = None self.editTexture = None self.painter = None self.brush = None #modelModificator.toggleEditmode(True) def __updateModel(self): if self.editModel: # create a image with the same size of the texture textureSize = (self.editTexture.getXSize(), self.editTexture.getYSize()) # create a dummy node, where we setup the parameters for the background rendering loadPaintNode = NodePath(PandaNode('paintnode')) loadPaintNode.setShader(Shader.make(self.backgroundShader), 10001) loadPaintNode.setShaderInput('texsize', textureSize[0], textureSize[1], 0, 0) # copy the state onto the camera self.modelColorCam.node().setInitialState(loadPaintNode.getState()) # the camera gets a special bitmask, to show/hide models from it self.modelColorCam.node().setCameraMask(BitMask32.bit(1)) if False: # doesnt work, but would be nicer (not messing with the default render state) hiddenNode = NodePath(PandaNode('hiddennode')) hiddenNode.hide(BitMask32.bit(1)) showTroughNode = NodePath(PandaNode('showtroughnode')) showTroughNode.showThrough(BitMask32.bit(1)) self.modelColorCam.node().setTagStateKey('show-on-backrender-cam') self.modelColorCam.node().setTagState('False', hiddenNode.getState()) self.modelColorCam.node().setTagState('True', showTroughNode.getState()) render.setTag('show-on-backrender-cam', 'False') self.editModel.setTag('show-on-backrender-cam', 'True') else: # make only the model visible to the background camera render.hide(BitMask32.bit(1)) self.editModel.showThrough(BitMask32.bit(1)) # --- start the paint tasks --- def __startPaint(self): self.isPainting = True def __stopPaint(self): self.isPainting = False # --- modification tasks --- def __textureUpdateTask(self, task=None): ''' modify the texture using the edited image ''' if type(self.editTexture) == Texture: self.editTexture.load(self.editImage) if task: # task may be None return task.again def __paintTask(self, task): #print "I: TexturePainter.__paintTask:" if not WindowManager.activeWindow or not WindowManager.activeWindow.mouseWatcherNode.hasMouse(): '''print " - abort:", WindowManager.activeWindow if WindowManager.activeWindow: print " - mouse:", WindowManager.activeWindow.mouseWatcherNode.hasMouse()''' return task.cont # update the camera according to the active camera #self.modelColorCam.setMat(render, WindowManager.activeWindow.camera.getMat(render)) mpos = base.mouseWatcherNode.getMouse() x_ratio = min( max( ((mpos.getX()+1)/2), 0), 1) y_ratio = min( max( ((mpos.getY()+1)/2), 0), 1) mx = int(x_ratio*self.windowSizeX) my = self.windowSizeY - int(y_ratio*self.windowSizeY) self.colorPickerScene.setShaderInput('mousepos', x_ratio, y_ratio, 0, 1) if self.colorPickerTex.hasRamImage(): self.colorPickerTex.store(self.colorPickerImage) # get the color below the mousepick from the rendered frame r = self.colorPickerImage.getRedVal(0,0) g = self.colorPickerImage.getGreenVal(0,0) b = self.colorPickerImage.getBlueVal(0,0) # calculate uv-texture position from the color x = r + ((b%16)*256) y = g + ((b//16)*256) if self.isPainting: self.__paintPixel(x,y) self.__textureUpdateTask() else: # this might happen if no frame has been rendered yet since creation of the texture print "W: TexturePainter.__paintTask: colorPickerTex.hasRamMipmapImage() =", self.colorPickerTex.hasRamImage() return task.cont def __paintPixel(self, x, y): ''' paint at x/y with the defined settings ''' imageMaxX = self.editImage.getXSize() imageMaxY = self.editImage.getYSize() def inImage(x,y): ''' is the given x/y position within the image ''' return ((imageMaxX > x >= 0) and (imageMaxY > y >= 0)) # how smooth should be painted if self.paintSmooth: # a smooth brush hardness = 1.0 else: # a hard brush hardness = 0.1 hardness = min(1.0, max(0.05, hardness)) # the paint radius radius = int(round(self.paintSize/2.0)) radiusSquare = float(radius*radius) # a function to get the brush color/strength, depending on the radius def getBrushColor(diffPosX, diffPosY): distance = diffPosX**2 + diffPosY**2 brushStrength = (1 - (min(distance, radiusSquare) / radiusSquare)) / hardness return min(1.0, max(0.0, brushStrength)) if inImage(x,y): if self.paintMode == TEXTUREPAINTER_FUNCTION_PAINT_POINT: if self.paintEffect in [PNMBrush.BESet, PNMBrush.BEBlend, PNMBrush.BEDarken, PNMBrush.BELighten]: # render a spot into the texture self.painter.drawPoint(x, y) elif self.paintEffect in [TEXTUREPAINTER_BRUSH_FLATTEN, TEXTUREPAINTER_BRUSH_SMOOTH, TEXTUREPAINTER_BRUSH_RANDOMIZE]: if self.paintEffect == TEXTUREPAINTER_BRUSH_SMOOTH: # calculate average values data = dict() smoothRadius = 2 for dx in xrange(-radius, radius+1): for dy in xrange(-radius, radius+1): if inImage(x+dx,y+dy): average = VBase4D(0) dividor = 0 for px in xrange(-smoothRadius,smoothRadius+1): for py in xrange(-smoothRadius,smoothRadius+1): if inImage(x+dx+px,y+dy+py): average += self.editImage.getXelA(x+dx+px,y+dy+py) dividor += 1 average /= float(dividor) data[(x+dx,y+dy)] = average # save to image for (px,py), newValue in data.items(): currentValue = self.editImage.getXelA(px,py) diffValue = currentValue - newValue dx = px - x dy = py - y multiplier = getBrushColor(dx, dy) print dx, dy, multiplier '''if self.paintSmooth: multiplier = ((radius-math.fabs(dx))*(radius-math.fabs(dy))) / (radius*radius) else: # not sure if this is correct multiplier = ((radius-math.fabs(dx))*(radius-math.fabs(dy)))''' '''r = currentValue.getX() * (1-multiplier*self.paintColor.getX()) + diffValue.getX() * multiplier*self.paintColor.getX() g = currentValue.getY() * (1-multiplier*self.paintColor.getY()) + diffValue.getY() * multiplier*self.paintColor.getY() b = currentValue.getZ() * (1-multiplier*self.paintColor.getZ()) + diffValue.getZ() * multiplier*self.paintColor.getZ() a = currentValue.getW() * (1-multiplier*self.paintColor.getW()) + diffValue.getW() * multiplier*self.paintColor.getW()''' r = currentValue.getX() - multiplier * diffValue.getX() g = currentValue.getY() - multiplier * diffValue.getY() b = currentValue.getZ() - multiplier * diffValue.getZ() a = currentValue.getW() - multiplier * diffValue.getW() if self.editImage.hasAlpha(): self.editImage.setXelA(px,py,VBase4D(r,g,b,a)) else: self.editImage.setXel(px,py,VBase3D(r,g,b)) #self.editImage.setXelA(x,y,value) if self.paintEffect == TEXTUREPAINTER_BRUSH_FLATTEN: dividor = 0 average = VBase4D(0) for dx in xrange(-radius, radius+1): for dy in xrange(-radius, radius+1): if inImage(x+dx,y+dy): multiplier = getBrushColor(dx, dy) '''if self.paintSmooth: multiplier = ((radius-math.fabs(dx))*(radius-math.fabs(dy))) / (radius*radius) else: multiplier = ((radius-math.fabs(dx))*(radius-math.fabs(dy)))''' dividor += multiplier average += self.editImage.getXelA(x+dx,y+dy) * multiplier average /= dividor for dx in xrange(-radius, radius+1): for dy in xrange(-radius, radius+1): if inImage(x+dx,y+dy): multiplier = getBrushColor(dx, dy) '''if self.paintSmooth: multiplier = ((radius-math.fabs(dx))*(radius-math.fabs(dy))) / (radius*radius) else: # not sure if this is correct multiplier = ((radius-math.fabs(dx))*(radius-math.fabs(dy)))''' currentValue = self.editImage.getXelA(x+dx,y+dy) r = currentValue.getX() * (1-multiplier*self.paintColor.getX()) + average.getX() * multiplier*self.paintColor.getX() g = currentValue.getY() * (1-multiplier*self.paintColor.getY()) + average.getY() * multiplier*self.paintColor.getY() b = currentValue.getZ() * (1-multiplier*self.paintColor.getZ()) + average.getZ() * multiplier*self.paintColor.getZ() a = currentValue.getW() * (1-multiplier*self.paintColor.getW()) + average.getW() * multiplier*self.paintColor.getW() if self.editImage.hasAlpha(): self.editImage.setXelA(x+dx,y+dy,VBase4D(r,g,b,a)) else: self.editImage.setXel(x+dx,y+dy,VBase3D(r,g,b)) elif self.paintEffect == TEXTUREPAINTER_BRUSH_RANDOMIZE: for dx in xrange(-radius, radius+1): for dy in xrange(-radius, radius+1): if inImage(x+dx,y+dy): r = VBase4D(random.random()*self.paintColor.getX()-self.paintColor.getX()/2., random.random()*self.paintColor.getY()-self.paintColor.getY()/2., random.random()*self.paintColor.getZ()-self.paintColor.getZ()/2., random.random()*self.paintColor.getW()-self.paintColor.getW()/2.) multiplier = getBrushColor(dx, dy) '''if self.paintSmooth: multiplier = ((radius-math.fabs(dx))*(radius-math.fabs(dy))) / (radius*radius) else: # not sure if this is correct multiplier = ((radius-math.fabs(dx))*(radius-math.fabs(dy)))''' currentValue = self.editImage.getXelA(x+dx,y+dy) self.editImage.setXelA(x+dx,y+dy,currentValue+r*multiplier) elif self.paintMode == TEXTUREPAINTER_FUNCTION_READ: if inImage(x,y): col = self.editImage.getXelA(x,y) if self.editImage.hasAlpha(): self.paintColor = VBase4D(col[0], col[1], col[2], col[3]) else: self.paintColor = VBase4D(col[0], col[1], col[2], 1.0) messenger.send(EVENT_TEXTUREPAINTER_BRUSHCHANGED) elif self.paintMode == TEXTUREPAINTER_FUNCTION_PAINT_LINE: if self.lastPoint != None: self.painter.drawLine(x, y, self.lastPoint[0], self.lastPoint[1]) elif self.paintMode == TEXTUREPAINTER_FUNCTION_PAINT_RECTANGLE: if self.lastPoint != None: self.painter.drawRectangle(x, y, self.lastPoint[0], self.lastPoint[1]) self.lastPoint = (x,y)
class TexturePainter(DirectObject): def __init__(self): self.editTexture = None self.editModel = None self.texturePainterStatus = TEXTURE_PAINTER_STATUS_DISABLED self.paintColor = VBase4D(1, 1, 1, 1) self.paintSize = 10 self.paintEffect = PNMBrush.BEBlend self.paintSmooth = True self.paintMode = TEXTUREPAINTER_FUNCTION_PAINT_POINT self.painter = None # --- creation and destroying of the whole editor --- def enableEditor(self): ''' create the editor change from disabled to enabled''' if self.texturePainterStatus == TEXTURE_PAINTER_STATUS_DISABLED: self.texturePainterStatus = TEXTURE_PAINTER_STATUS_ENABLED self.__enableEditor() else: print "E: TexturePainter.enableEditor: not disabled", self.texturePainterStatus def disableEditor(self): ''' destroy the editor, automatically stop the editor and painting change from enabled to disabled''' # try stopping if more advanced mode #if self.texturePainterStatus == TEXTURE_PAINTER_STATUS_INITIALIZED: # self.stopEditor() if self.texturePainterStatus == TEXTURE_PAINTER_STATUS_ENABLED: self.texturePainterStatus = TEXTURE_PAINTER_STATUS_DISABLED self.__disableEditor() else: print "E: TexturePainter.disableEditor: not enabled", self.texturePainterStatus # --- def startEditor(self, editModel, editTexture, backgroundShader=MODEL_COLOR_SHADER): ''' prepare to paint change from enabled to initialized''' if self.texturePainterStatus == TEXTURE_PAINTER_STATUS_ENABLED: self.texturePainterStatus = TEXTURE_PAINTER_STATUS_INITIALIZED self.__startEditor(editModel, editTexture, backgroundShader) else: print "E: TexturePainter.startEditor: not enabled", self.texturePainterStatus def stopEditor(self): ''' stop painting, automatically stop painting change from initialized to enabled''' #if self.texturePainterStatus == TEXTURE_PAINTER_STATUS_INITIALIZED: # self.stopPaint() if self.texturePainterStatus == TEXTURE_PAINTER_STATUS_INITIALIZED: self.texturePainterStatus = TEXTURE_PAINTER_STATUS_ENABLED return self.__stopEditor() else: print "E: TexturePainter.startEditor: not initialized", self.texturePainterStatus """ # this is not externally callable # --- def startPaint(self): ''' start painting on the model change from initialized to running ''' if self.texturePainterStatus == TEXTURE_PAINTER_STATUS_INITIALIZED: self.texturePainterStatus = TEXTURE_PAINTER_STATUS_RUNNING self.__startPaint() else: print "E: TexturePainter.startPaint: not enabled", self.texturePainterStatus""" def stopPaint(self): ''' stop painting change from running to initialized ''' if self.texturePainterStatus == TEXTURE_PAINTER_STATUS_RUNNING: self.texturePainterStatus = TEXTURE_PAINTER_STATUS_INITIALIZED self.__stopPaint() else: print "E: TexturePainter.stopPaint: not running", self.texturePainterStatus # --- brush settings for painting --- ''' changing brush settings is possible all the time ''' def setBrushSettings(self, color, size, smooth, effect): #print "I: TexturePainter.setBrushSettings", color, size, smooth, effect self.paintColor = color self.paintSize = size self.paintEffect = effect self.paintSmooth = smooth if effect in [ PNMBrush.BESet, PNMBrush.BEBlend, PNMBrush.BEDarken, PNMBrush.BELighten ]: self.brush = PNMBrush.makeSpot(color, size, smooth, effect) #if self.paintModel: if self.painter: self.painter.setPen(self.brush) def getBrushSettings(self): return self.paintColor, self.paintSize, self.paintSmooth, self.paintEffect def setPaintMode(self, newMode): self.paintMode = newMode # clear last point if mode changed if newMode == TEXTUREPAINTER_FUNCTION_PAINT_POINT or \ newMode == TEXTUREPAINTER_FUNCTION_READ: self.lastPoint = None def getPaintMode(self): return self.paintMode def __enableEditor(self): ''' create the background rendering etc., but the model is not yet defined ''' print "I: TexturePainter.__enableEditor" # the buffer the model with the color texture is rendered into self.modelColorBuffer = None self.modelColorCam = None # the buffer the picked position color is rendered into self.colorPickerBuffer = None self.colorPickerCam = None # create the buffers self.__createBuffer() # when the window is resized, the background buffer etc must be updated. self.accept("window-event", self.__windowEvent) # some debugging stuff self.accept("v", base.bufferViewer.toggleEnable) self.accept("V", base.bufferViewer.toggleEnable) def __disableEditor(self): print "I: TexturePainter.__disableEditor" self.__destroyBuffer() # ignore window-event and debug self.ignoreAll() def __windowEvent(self, win=None): ''' when the editor is enabled, update the buffers etc. when the window is resized ''' print "I: TexturePainter.windowEvent" # with a fixed backgroudn buffer size this is not needed anymore if False: #if self.texturePainterStatus != TEXTURE_PAINTER_STATUS_DISABLED: if self.modelColorBuffer: if WindowManager.activeWindow: # on window resize there seems to be never a active window win = WindowManager.activeWindow.win else: win = base.win if self.modelColorBuffer.getXSize() != win.getXSize( ) or self.modelColorBuffer.getYSize() != win.getYSize(): '''print " - window resized",\ self.modelColorBuffer.getXSize(),\ win.getXSize(),\ self.modelColorBuffer.getYSize(),\ win.getYSize()''' # if the buffer size doesnt match the window size (window has been resized) self.__destroyBuffer() self.__createBuffer() self.__updateModel() else: print "W: TexturePainter.__windowEvent: no buffer" self.__createBuffer() def __createBuffer(self): ''' create the buffer we render in the background into ''' print "I: TexturePainter.__createBuffer" # the window has been modified if WindowManager.activeWindow: # on window resize there seems to be never a active window win = WindowManager.activeWindow.win else: win = base.win # get the window size self.windowSizeX = win.getXSize() self.windowSizeY = win.getYSize() # create a buffer in which we render the model using a shader self.paintMap = Texture() # 1.5.4 cant handle non power of 2 buffers self.modelColorBuffer = createOffscreenBuffer( -3, TEXTUREPAINTER_BACKGROUND_BUFFER_RENDERSIZE[0], TEXTUREPAINTER_BACKGROUND_BUFFER_RENDERSIZE[1] ) #self.windowSizeX, self.windowSizeY) self.modelColorBuffer.addRenderTexture(self.paintMap, GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPColor) self.modelColorCam = base.makeCamera(self.modelColorBuffer, lens=base.cam.node().getLens(), sort=1) # Create a small buffer for the shader program that will fetch the point from the texture made # by the self.modelColorBuffer self.colorPickerImage = PNMImage() self.colorPickerTex = Texture() self.colorPickerBuffer = base.win.makeTextureBuffer( "color picker buffer", 2, 2, self.colorPickerTex, True) self.colorPickerScene = NodePath('color picker scene') self.colorPickerCam = base.makeCamera(self.colorPickerBuffer, lens=base.cam.node().getLens(), sort=2) self.colorPickerCam.reparentTo(self.colorPickerScene) self.colorPickerCam.setY(-2) cm = CardMaker('color picker scene card') cm.setFrameFullscreenQuad() pickerCard = self.colorPickerScene.attachNewNode(cm.generate()) loadPicker = NodePath(PandaNode('pointnode')) loadPicker.setShader(Shader.make(COLOR_PICKER_SHADER), 10001) # Feed the paintmap from the paintBuffer to the shader and initial mouse positions self.colorPickerScene.setShaderInput('paintmap', self.paintMap) self.colorPickerScene.setShaderInput('mousepos', 0, 0, 0, 1) self.colorPickerCam.node().setInitialState(loadPicker.getState()) def __destroyBuffer(self): print "I: TexturePainter.__destroyBuffer" if self.modelColorBuffer: # Destroy the buffer base.graphicsEngine.removeWindow(self.modelColorBuffer) self.modelColorBuffer = None # Remove the camera self.modelColorCam.removeNode() del self.modelColorCam self.colorPickerScene.removeNode() del self.colorPickerScene # remove cam self.colorPickerCam.removeNode() del self.colorPickerCam # Destroy the buffer base.graphicsEngine.removeWindow(self.colorPickerBuffer) self.colorPickerBuffer = None del self.colorPickerTex del self.colorPickerImage def __startEditor(self, editModel, editTexture, backgroundShader=MODEL_COLOR_SHADER): print "I: TexturePainter.__startEditor" # this is needed as on startup the editor may not have had a window etc. self.__windowEvent() if not editModel or not editTexture: print "W: TexturePainter.__startEditor: model or texture invalid", editModel, editTexture return False self.editModel = editModel self.editTexture = editTexture self.editImage = None self.backgroundShader = backgroundShader if type(self.editTexture) == Texture: # if the image to modify is a texture, create a pnmImage which we modify self.editImage = PNMImage() # copy the image from the texture to the working layer self.editTexture.store(self.editImage) else: self.editImage = self.editTexture # create the brush for painting self.painter = PNMPainter(self.editImage) self.setBrushSettings(*self.getBrushSettings()) self.__updateModel() # start edit messenger.send(EVENT_TEXTUREPAINTER_STARTEDIT) for startEvent in TEXTUREPAINTER_START_PAINT_EVENTS: self.accept(startEvent, self.__startPaint) for stopEvent in TEXTUREPAINTER_STOP_PAINT_EVENTS: self.accept(stopEvent, self.__stopPaint) self.modelColorCam.node().copyLens( WindowManager.activeWindow.camera.node().getLens()) taskMgr.add(self.__paintTask, 'paintTask') #modelModificator.toggleEditmode(False) self.isPainting = False def __stopEditor(self): print "I: TexturePainter.__stopEditor" for startEvent in TEXTUREPAINTER_START_PAINT_EVENTS: self.ignore(startEvent) for stopEvent in TEXTUREPAINTER_STOP_PAINT_EVENTS: self.ignore(stopEvent) taskMgr.remove('paintTask') # stop edit end # must be reset before we loose the properties if self.editModel and self.editTexture and self.editImage: try: # hide the model from cam 2 self.editModel.hide(BitMask32.bit(1)) self.editModel = None except: print "E: TexturePainter.__stopEditor: the model has already been deleted" # stop edit messenger.send(EVENT_TEXTUREPAINTER_STOPEDIT) self.editImage = None self.editTexture = None self.painter = None self.brush = None #modelModificator.toggleEditmode(True) def __updateModel(self): if self.editModel: # create a image with the same size of the texture textureSize = (self.editTexture.getXSize(), self.editTexture.getYSize()) # create a dummy node, where we setup the parameters for the background rendering loadPaintNode = NodePath(PandaNode('paintnode')) loadPaintNode.setShader(Shader.make(self.backgroundShader), 10001) loadPaintNode.setShaderInput('texsize', textureSize[0], textureSize[1], 0, 0) # copy the state onto the camera self.modelColorCam.node().setInitialState(loadPaintNode.getState()) # the camera gets a special bitmask, to show/hide models from it self.modelColorCam.node().setCameraMask(BitMask32.bit(1)) if False: # doesnt work, but would be nicer (not messing with the default render state) hiddenNode = NodePath(PandaNode('hiddennode')) hiddenNode.hide(BitMask32.bit(1)) showTroughNode = NodePath(PandaNode('showtroughnode')) showTroughNode.showThrough(BitMask32.bit(1)) self.modelColorCam.node().setTagStateKey( 'show-on-backrender-cam') self.modelColorCam.node().setTagState('False', hiddenNode.getState()) self.modelColorCam.node().setTagState( 'True', showTroughNode.getState()) render.setTag('show-on-backrender-cam', 'False') self.editModel.setTag('show-on-backrender-cam', 'True') else: # make only the model visible to the background camera render.hide(BitMask32.bit(1)) self.editModel.showThrough(BitMask32.bit(1)) # --- start the paint tasks --- def __startPaint(self): self.isPainting = True def __stopPaint(self): self.isPainting = False # --- modification tasks --- def __textureUpdateTask(self, task=None): ''' modify the texture using the edited image ''' if type(self.editTexture) == Texture: self.editTexture.load(self.editImage) if task: # task may be None return task.again def __paintTask(self, task): #print "I: TexturePainter.__paintTask:" if not WindowManager.activeWindow or not WindowManager.activeWindow.mouseWatcherNode.hasMouse( ): '''print " - abort:", WindowManager.activeWindow if WindowManager.activeWindow: print " - mouse:", WindowManager.activeWindow.mouseWatcherNode.hasMouse()''' return task.cont # update the camera according to the active camera #self.modelColorCam.setMat(render, WindowManager.activeWindow.camera.getMat(render)) mpos = base.mouseWatcherNode.getMouse() x_ratio = min(max(((mpos.getX() + 1) / 2), 0), 1) y_ratio = min(max(((mpos.getY() + 1) / 2), 0), 1) mx = int(x_ratio * self.windowSizeX) my = self.windowSizeY - int(y_ratio * self.windowSizeY) self.colorPickerScene.setShaderInput('mousepos', x_ratio, y_ratio, 0, 1) if self.colorPickerTex.hasRamImage(): self.colorPickerTex.store(self.colorPickerImage) # get the color below the mousepick from the rendered frame r = self.colorPickerImage.getRedVal(0, 0) g = self.colorPickerImage.getGreenVal(0, 0) b = self.colorPickerImage.getBlueVal(0, 0) # calculate uv-texture position from the color x = r + ((b % 16) * 256) y = g + ((b // 16) * 256) if self.isPainting: self.__paintPixel(x, y) self.__textureUpdateTask() else: # this might happen if no frame has been rendered yet since creation of the texture print "W: TexturePainter.__paintTask: colorPickerTex.hasRamMipmapImage() =", self.colorPickerTex.hasRamImage( ) return task.cont def __paintPixel(self, x, y): ''' paint at x/y with the defined settings ''' imageMaxX = self.editImage.getXSize() imageMaxY = self.editImage.getYSize() def inImage(x, y): ''' is the given x/y position within the image ''' return ((imageMaxX > x >= 0) and (imageMaxY > y >= 0)) # how smooth should be painted if self.paintSmooth: # a smooth brush hardness = 1.0 else: # a hard brush hardness = 0.1 hardness = min(1.0, max(0.05, hardness)) # the paint radius radius = int(round(self.paintSize / 2.0)) radiusSquare = float(radius * radius) # a function to get the brush color/strength, depending on the radius def getBrushColor(diffPosX, diffPosY): distance = diffPosX**2 + diffPosY**2 brushStrength = ( 1 - (min(distance, radiusSquare) / radiusSquare)) / hardness return min(1.0, max(0.0, brushStrength)) if inImage(x, y): if self.paintMode == TEXTUREPAINTER_FUNCTION_PAINT_POINT: if self.paintEffect in [ PNMBrush.BESet, PNMBrush.BEBlend, PNMBrush.BEDarken, PNMBrush.BELighten ]: # render a spot into the texture self.painter.drawPoint(x, y) elif self.paintEffect in [ TEXTUREPAINTER_BRUSH_FLATTEN, TEXTUREPAINTER_BRUSH_SMOOTH, TEXTUREPAINTER_BRUSH_RANDOMIZE ]: if self.paintEffect == TEXTUREPAINTER_BRUSH_SMOOTH: # calculate average values data = dict() smoothRadius = 2 for dx in xrange(-radius, radius + 1): for dy in xrange(-radius, radius + 1): if inImage(x + dx, y + dy): average = VBase4D(0) dividor = 0 for px in xrange(-smoothRadius, smoothRadius + 1): for py in xrange( -smoothRadius, smoothRadius + 1): if inImage(x + dx + px, y + dy + py): average += self.editImage.getXelA( x + dx + px, y + dy + py) dividor += 1 average /= float(dividor) data[(x + dx, y + dy)] = average # save to image for (px, py), newValue in data.items(): currentValue = self.editImage.getXelA(px, py) diffValue = currentValue - newValue dx = px - x dy = py - y multiplier = getBrushColor(dx, dy) print dx, dy, multiplier '''if self.paintSmooth: multiplier = ((radius-math.fabs(dx))*(radius-math.fabs(dy))) / (radius*radius) else: # not sure if this is correct multiplier = ((radius-math.fabs(dx))*(radius-math.fabs(dy)))''' '''r = currentValue.getX() * (1-multiplier*self.paintColor.getX()) + diffValue.getX() * multiplier*self.paintColor.getX() g = currentValue.getY() * (1-multiplier*self.paintColor.getY()) + diffValue.getY() * multiplier*self.paintColor.getY() b = currentValue.getZ() * (1-multiplier*self.paintColor.getZ()) + diffValue.getZ() * multiplier*self.paintColor.getZ() a = currentValue.getW() * (1-multiplier*self.paintColor.getW()) + diffValue.getW() * multiplier*self.paintColor.getW()''' r = currentValue.getX( ) - multiplier * diffValue.getX() g = currentValue.getY( ) - multiplier * diffValue.getY() b = currentValue.getZ( ) - multiplier * diffValue.getZ() a = currentValue.getW( ) - multiplier * diffValue.getW() if self.editImage.hasAlpha(): self.editImage.setXelA(px, py, VBase4D(r, g, b, a)) else: self.editImage.setXel(px, py, VBase3D(r, g, b)) #self.editImage.setXelA(x,y,value) if self.paintEffect == TEXTUREPAINTER_BRUSH_FLATTEN: dividor = 0 average = VBase4D(0) for dx in xrange(-radius, radius + 1): for dy in xrange(-radius, radius + 1): if inImage(x + dx, y + dy): multiplier = getBrushColor(dx, dy) '''if self.paintSmooth: multiplier = ((radius-math.fabs(dx))*(radius-math.fabs(dy))) / (radius*radius) else: multiplier = ((radius-math.fabs(dx))*(radius-math.fabs(dy)))''' dividor += multiplier average += self.editImage.getXelA( x + dx, y + dy) * multiplier average /= dividor for dx in xrange(-radius, radius + 1): for dy in xrange(-radius, radius + 1): if inImage(x + dx, y + dy): multiplier = getBrushColor(dx, dy) '''if self.paintSmooth: multiplier = ((radius-math.fabs(dx))*(radius-math.fabs(dy))) / (radius*radius) else: # not sure if this is correct multiplier = ((radius-math.fabs(dx))*(radius-math.fabs(dy)))''' currentValue = self.editImage.getXelA( x + dx, y + dy) r = currentValue.getX() * ( 1 - multiplier * self.paintColor.getX() ) + average.getX( ) * multiplier * self.paintColor.getX() g = currentValue.getY() * ( 1 - multiplier * self.paintColor.getY() ) + average.getY( ) * multiplier * self.paintColor.getY() b = currentValue.getZ() * ( 1 - multiplier * self.paintColor.getZ() ) + average.getZ( ) * multiplier * self.paintColor.getZ() a = currentValue.getW() * ( 1 - multiplier * self.paintColor.getW() ) + average.getW( ) * multiplier * self.paintColor.getW() if self.editImage.hasAlpha(): self.editImage.setXelA( x + dx, y + dy, VBase4D(r, g, b, a)) else: self.editImage.setXel( x + dx, y + dy, VBase3D(r, g, b)) elif self.paintEffect == TEXTUREPAINTER_BRUSH_RANDOMIZE: for dx in xrange(-radius, radius + 1): for dy in xrange(-radius, radius + 1): if inImage(x + dx, y + dy): r = VBase4D( random.random() * self.paintColor.getX() - self.paintColor.getX() / 2., random.random() * self.paintColor.getY() - self.paintColor.getY() / 2., random.random() * self.paintColor.getZ() - self.paintColor.getZ() / 2., random.random() * self.paintColor.getW() - self.paintColor.getW() / 2.) multiplier = getBrushColor(dx, dy) '''if self.paintSmooth: multiplier = ((radius-math.fabs(dx))*(radius-math.fabs(dy))) / (radius*radius) else: # not sure if this is correct multiplier = ((radius-math.fabs(dx))*(radius-math.fabs(dy)))''' currentValue = self.editImage.getXelA( x + dx, y + dy) self.editImage.setXelA( x + dx, y + dy, currentValue + r * multiplier) elif self.paintMode == TEXTUREPAINTER_FUNCTION_READ: if inImage(x, y): col = self.editImage.getXelA(x, y) if self.editImage.hasAlpha(): self.paintColor = VBase4D(col[0], col[1], col[2], col[3]) else: self.paintColor = VBase4D(col[0], col[1], col[2], 1.0) messenger.send(EVENT_TEXTUREPAINTER_BRUSHCHANGED) elif self.paintMode == TEXTUREPAINTER_FUNCTION_PAINT_LINE: if self.lastPoint != None: self.painter.drawLine(x, y, self.lastPoint[0], self.lastPoint[1]) elif self.paintMode == TEXTUREPAINTER_FUNCTION_PAINT_RECTANGLE: if self.lastPoint != None: self.painter.drawRectangle(x, y, self.lastPoint[0], self.lastPoint[1]) self.lastPoint = (x, y)