def getTextureRAM(mesh): total_image_area = 0 for cimg in mesh.images: pilimg = cimg.pilimage if pilimg: total_image_area += pilimg.size[0] * pilimg.size[1] * len(pilimg.getbands()) else: # PIL doesn't support DDS, so if loading failed, try and load it as a DDS with panda3d imgdata = cimg.data # if we can't even load the image's data, can't convert if imgdata is None: continue try: from panda3d.core import Texture from panda3d.core import StringStream except ImportError: # if panda3d isn't installed and PIL failed, can't convert continue t = Texture() try: success = t.readDds(StringStream(imgdata)) except: success = 1 if success == 0: # failed to load as DDS, so let's give up continue total_image_area += t.getXSize() * t.getYSize() * 3 return total_image_area
def optimizeTextures(mesh): previous_images = [] for cimg in mesh.images: previous_images.append(cimg.path) pilimg = cimg.pilimage #PIL doesn't support DDS, so if loading failed, try and load it as a DDS with panda3d if pilimg is None: imgdata = cimg.data #if we can't even load the image's data, can't convert if imgdata is None: print("Couldn't load image data", file=sys.stderr) continue try: from panda3d.core import Texture from panda3d.core import StringStream from panda3d.core import PNMImage except ImportError: #if panda3d isn't installed and PIL failed, can't convert print('Tried loading image with PIL and DDS and both failed', file=sys.stderr) continue t = Texture() success = t.readDds(StringStream(imgdata)) if success == 0: #failed to load as DDS, so let's give up print('Tried loading image as DDS and failed', file=sys.stderr) continue #convert DDS to PNG outdata = t.getRamImageAs('RGB').getData() try: im = Image.fromstring('RGB', (t.getXSize(), t.getYSize()), outdata) im.load() except IOError: #Any problem with panda3d might generate an invalid image buffer, so don't convert this print('Problem loading DDS file with PIL', file=sys.stderr) continue pilimg = im if pilimg.format == 'JPEG': #PIL image is already in JPG format so don't convert continue if 'A' in pilimg.getbands(): alpha = numpy.array(pilimg.split()[-1].getdata()) if not numpy.any(alpha < 255): alpha = None #this means that none of the pixels are using alpha, so convert to RGB pilimg = pilimg.convert('RGB') if 'A' in pilimg.getbands(): #save textures with an alpha channel in PNG output_format = 'PNG' output_extension = '.png' output_options = {'optimize':True} else: if pilimg.format != 'RGB': pilimg = pilimg.convert("RGB") #otherwise save as JPEG since it gets output_format = 'JPEG' output_extension = '.jpg' output_options = {'quality':95, 'optimize':True} if cimg.path.lower()[-len(output_extension):] != output_extension: dot = cimg.path.rfind('.') before_ext = cimg.path[0:dot] if dot != -1 else cimg.path while before_ext + output_extension in previous_images: before_ext = before_ext + '-x' cimg.path = before_ext + output_extension previous_images.append(cimg.path) outbuf = StringIO() try: pilimg.save(outbuf, output_format, **output_options) except IOError as ex: print(ex) cimg.data = outbuf.getvalue()
class Typist(object): TARGETS = { 'paper': { 'model': 'paper', 'textureRoot': 'Front', 'scale': Point3(0.85, 0.85, 1), 'hpr' : Point3(0, 0, 0), } } def __init__(self, base, typewriterNP, underDeskClip, sounds): self.base = base self.sounds = sounds self.underDeskClip = underDeskClip self.typeIndex = 0 self.typewriterNP = typewriterNP self.rollerAssemblyNP = typewriterNP.find("**/roller assembly") assert self.rollerAssemblyNP self.rollerNP = typewriterNP.find("**/roller") assert self.rollerNP self.carriageNP = typewriterNP.find("**/carriage") assert self.carriageNP self.baseCarriagePos = self.carriageNP.getPos() self.carriageBounds = self.carriageNP.getTightBounds() self.font = base.loader.loadFont('Harting.ttf', pointSize=32) self.pnmFont = PNMTextMaker(self.font) self.fontCharSize, _, _ = fonts.measureFont(self.pnmFont, 32) print "font char size: ",self.fontCharSize self.pixelsPerLine = int(round(self.pnmFont.getLineHeight())) self.target = None """ panda3d.core.NodePath """ self.targetRoot = None """ panda3d.core.NodePath """ self.paperY = 0.0 """ range from 0 to 1 """ self.paperX = 0.0 """ range from 0 to 1 """ self.createRollerBase() self.tex = None self.texImage = None self.setupTexture() self.scheduler = Scheduler() task = self.base.taskMgr.add(self.tick, 'timerTask') task.setDelay(0.01) def tick(self, task): self.scheduler.tick(globalClock.getRealTime()) return task.cont def setupTexture(self): """ This is the overlay/decal/etc. which contains the typed characters. The texture size and the font size are currently tied together. :return: """ self.texImage = PNMImage(1024, 1024) self.texImage.addAlpha() self.texImage.fill(1.0) self.texImage.alphaFill(1.0) self.tex = Texture('typing') self.tex.setMagfilter(Texture.FTLinear) self.tex.setMinfilter(Texture.FTLinear) self.typingStage = TextureStage('typing') self.typingStage.setMode(TextureStage.MModulate) self.tex.load(self.texImage) # ensure we can quickly update subimages self.tex.setKeepRamImage(True) # temp for drawing chars self.chImage = PNMImage(*self.fontCharSize) def drawCharacter(self, ch, px, py): """ Draw a character onto the texture :param ch: :param px: paperX :param py: paperY :return: the paper-relative size of the character """ h = self.fontCharSize[1] if ch != ' ': # position -> pixel, applying margins x = int(self.tex.getXSize() * (px * 0.8 + 0.1)) y = int(self.tex.getYSize() * (py * 0.8 + 0.1)) # always draw onto the paper, to capture # incremental character overstrikes self.pnmFont.generateInto(ch, self.texImage, x, y) if False: #print ch,"to",x,y,"w=",g.getWidth() self.tex.load(self.texImage) else: # copy an area (presumably) encompassing the character g = self.pnmFont.getGlyph(ord(ch)) cx, cy = self.fontCharSize # a glyph is minimally sized and "moves around" in its text box # (think ' vs. ,), so it has been drawn somewhere relative to # the 'x' and 'y' we wanted. x += g.getLeft() y -= g.getTop() self.chImage.copySubImage( self.texImage, 0, 0, # from x, y, # to cx, cy # size ) self.tex.loadSubImage(self.chImage, x, y) # toggle for a typewriter that uses non-proportional spacing #w = self.paperCharWidth(g.getWidth()) w = self.paperCharWidth() else: w = self.paperCharWidth() return w, h def start(self): self.target = None self.setTarget('paper') self.hookKeyboard() def createRollerBase(self): """ The paper moves such that it is tangent to the roller. This nodepath keeps a coordinate space relative to that, so that the paper can be positioned from (0,0,0) to (0,0,1) to "roll" it along the roller. """ bb = self.rollerNP.getTightBounds() #self.rollerNP.showTightBounds() self.paperRollerBase = self.rollerAssemblyNP.attachNewNode('rollerBase') self.paperRollerBase.setHpr(0, -20, 0) print "roller:",bb rad = abs(bb[0].y - bb[1].y) / 2 center = Vec3(-(bb[0].x+bb[1].x)/2 - 0.03, (bb[0].y-bb[1].y)/2, (bb[0].z+bb[1].z)/2) self.paperRollerBase.setPos(center) def setTarget(self, name): if self.target: self.target.removeNode() # load and transform the model target = self.TARGETS[name] self.target = self.base.loader.loadModel(target['model']) #self.target.setScale(target['scale']) self.target.setHpr(target['hpr']) # put it in the world self.target.reparentTo(self.paperRollerBase) rbb = self.rollerNP.getTightBounds() tbb = self.target.getTightBounds() rs = (rbb[1] - rbb[0]) ts = (tbb[1] - tbb[0]) self.target.setScale(rs.x / ts.x, 1, 1) # apply the texture self.targetRoot = self.target if 'textureRoot' in target: self.targetRoot = self.target.find("**/" + target['textureRoot']) assert self.targetRoot self.targetRoot.setTexture(self.typingStage, self.tex) #self.setupTargetClip() # reset self.paperX = self.paperY = 0. newPos = self.calcPaperPos(self.paperY) self.target.setPos(newPos) self.moveCarriage() def setupTargetClip(self): """ The target is fed in to the typewriter but until we invent "geom curling", it shouldn't be visible under the typewriter under the desk. The @underDeskClip node has a world-relative bounding box, which we can convert to the target-relative bounding box, and pass to a shader that can clip the nodes. """ shader = Shader.make( Shader.SLGLSL, """ #version 120 attribute vec4 p3d_MultiTexCoord0; attribute vec4 p3d_MultiTexCoord1; void main() { gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; gl_TexCoord[0] = p3d_MultiTexCoord0; gl_TexCoord[1] = p3d_MultiTexCoord1; } """, """ #version 120 uniform sampler2D baseTex; uniform sampler2D charTex; const vec4 zero = vec4(0, 0, 0, 0); const vec4 one = vec4(1, 1, 1, 1); const vec4 half = vec4(0.5, 0.5, 0.5, 0); void main() { vec4 baseColor = texture2D(baseTex, gl_TexCoord[0].st); vec4 typeColor = texture2D(charTex, gl_TexCoord[1].st); gl_FragColor = baseColor * typeColor; }""" ) self.target.setShader(shader) baseTex = self.targetRoot.getTexture() print "Base Texture:",baseTex self.target.setShaderInput("baseTex", baseTex) self.target.setShaderInput("charTex", self.tex) def hookKeyboard(self): """ Hook events so we can respond to keypresses. """ self.base.buttonThrowers[0].node().setKeystrokeEvent('keystroke') self.base.accept('keystroke', self.schedTypeCharacter) self.base.accept('backspace', self.schedBackspace) self.base.accept('arrow_up', lambda: self.schedAdjustPaper(-5)) self.base.accept('arrow_up-repeat', lambda: self.schedAdjustPaper(-1)) self.base.accept('arrow_down', lambda:self.schedAdjustPaper(5)) self.base.accept('arrow_down-repeat', lambda:self.schedAdjustPaper(1)) self.base.accept('arrow_left', lambda: self.schedAdjustCarriage(-1)) self.base.accept('arrow_left-repeat', lambda: self.schedAdjustCarriage(-1)) self.base.accept('arrow_right', lambda:self.schedAdjustCarriage(1)) self.base.accept('arrow_right-repeat', lambda:self.schedAdjustCarriage(1)) def paperCharWidth(self, pixels=None): if not pixels: pixels = self.fontCharSize[0] return float(pixels) / self.tex.getXSize() def paperLineHeight(self): return float(self.fontCharSize[1] * 1.2) / self.tex.getYSize() def schedScroll(self): if self.scheduler.isQueueEmpty(): self.schedRollPaper(1) self.schedResetCarriage() def schedBackspace(self): if self.scheduler.isQueueEmpty(): def doit(): if self.paperX > 0: self.schedAdjustCarriage(-1) self.scheduler.schedule(0.01, doit) def createMoveCarriageInterval(self, newX, curX=None): if curX is None: curX = self.paperX here = self.calcCarriage(curX) there = self.calcCarriage(newX) posInterval = LerpPosInterval( self.carriageNP, abs(newX - curX), there, startPos = here, blendType='easeIn') posInterval.setDoneEvent('carriageReset') def isReset(): self.paperX = newX self.base.acceptOnce('carriageReset', isReset) return posInterval def schedResetCarriage(self): if self.paperX > 0.1: self.sounds['pullback'].play() invl = self.createMoveCarriageInterval(0) self.scheduler.scheduleInterval(0, invl) def calcCarriage(self, paperX): """ Calculate where the carriage should be offset based on the position on the paper :param paperX: 0...1 :return: pos for self.carriageNP """ x = (0.5 - paperX) * 0.69 * 0.8 + 0.01 bb = self.carriageBounds return self.baseCarriagePos + Point3(x * (bb[1].x-bb[0].x), 0, 0) def moveCarriage(self): pos = self.calcCarriage(self.paperX) self.carriageNP.setPos(pos) def schedMoveCarriage(self, curX, newX): if self.scheduler.isQueueEmpty(): #self.scheduler.schedule(0.1, self.moveCarriage) invl = self.createMoveCarriageInterval(newX, curX=curX) invl.start() def schedAdjustCarriage(self, bx): if self.scheduler.isQueueEmpty(): def doit(): self.paperX = max(0.0, min(1.0, self.paperX + bx * self.paperCharWidth())) self.moveCarriage() self.scheduler.schedule(0.1, doit) def calcPaperPos(self, paperY): # center over roller, peek out a little z = paperY * 0.8 - 0.5 + 0.175 bb = self.target.getTightBounds() return Point3(-0.5, 0, z * (bb[1].z-bb[0].z)) def createMovePaperInterval(self, newY): here = self.calcPaperPos(self.paperY) there = self.calcPaperPos(newY) posInterval = LerpPosInterval( self.target, abs(newY - self.paperY), there, startPos = here, blendType='easeInOut') posInterval.setDoneEvent('scrollDone') def isDone(): self.paperY = newY self.base.acceptOnce('scrollDone', isDone) return posInterval def schedAdjustPaper(self, by): if self.scheduler.isQueueEmpty(): def doit(): self.schedRollPaper(by) self.scheduler.schedule(0.1, doit) def schedRollPaper(self, by): """ Position the paper such that @percent of it is rolled over roller :param percent: :return: """ def doit(): self.sounds['scroll'].play() newY = min(1.0, max(0.0, self.paperY + self.paperLineHeight() * by)) invl = self.createMovePaperInterval(newY) invl.start() self.scheduler.schedule(0.1, doit) def schedTypeCharacter(self, keyname): # filter for visibility if ord(keyname) == 13: self.schedScroll() elif ord(keyname) >= 32 and ord(keyname) != 127: if self.scheduler.isQueueEmpty(): curX, curY = self.paperX, self.paperY self.typeCharacter(keyname, curX, curY) def typeCharacter(self, ch, curX, curY): newX = curX w, h = self.drawCharacter(ch, curX, curY) newX += w if ch != ' ': # alternate typing sound #self.typeIndex = (self.typeIndex+1) % 3 self.typeIndex = random.randint(0, 2) self.sounds['type' + str(self.typeIndex+1)].play() else: self.sounds['advance'].play() if newX >= 1: self.sounds['bell'].play() newX = 1 self.schedMoveCarriage(self.paperX, newX) # move first, to avoid overtype self.paperX = newX
class HeightmapPatch: cachable = True def __init__(self, parent, x0, y0, x1, y1, width, height, scale=1.0, coord=TexCoord.Cylindrical, face=-1, border=1): self.parent = parent self.x0 = x0 self.y0 = y0 self.x1 = x1 self.y1 = y1 self.scale = scale self.width = width self.height = height self.coord = coord self.face = face self.border = border self.r_width = self.width + self.border * 2 self.r_height = self.height + self.border * 2 self.r_x0 = self.x0 - float( self.border) / self.width * (self.x1 - self.x0) self.r_x1 = self.x1 + float( self.border) / self.width * (self.x1 - self.x0) self.r_y0 = self.y0 - float( self.border) / self.height * (self.y1 - self.y0) self.r_y1 = self.y1 + float( self.border) / self.height * (self.y1 - self.y0) self.dx = self.r_x1 - self.r_x0 self.dy = self.r_y1 - self.r_y0 self.lod = None self.patch = None self.heightmap_ready = False self.texture = None self.texture_peeker = None self.callback = None self.cloned = False self.texture_offset = LVector2() self.texture_scale = LVector2(1, 1) self.min_height = None self.max_height = None self.mean_height = None @classmethod def create_from_patch(cls, noise, parent, x, y, scale, lod, density, coord=TexCoord.Cylindrical, face=-1): #TODO: Should be move to Patch/Tile if coord == TexCoord.Cylindrical: r_div = 1 << lod s_div = 2 << lod x0 = float(x) / s_div y0 = float(y) / r_div x1 = float(x + 1) / s_div y1 = float(y + 1) / r_div elif coord == TexCoord.NormalizedCube or coord == TexCoord.SqrtCube: div = 1 << lod r_div = div s_div = div x0 = float(x) / div y0 = float(y) / div x1 = float(x + 1) / div y1 = float(y + 1) / div else: div = 1 << lod r_div = div s_div = div size = 1.0 / (1 << lod) x0 = (x) * scale y0 = (y) * scale x1 = (x + size) * scale y1 = (y + size) * scale patch = cls(noise, parent, x0, y0, x1, y1, width=density, height=density, scale=scale, coord=coord, face=face, border=1) patch.patch_lod = lod patch.lod = lod patch.lod_scale_x = scale / s_div patch.lod_scale_y = scale / r_div patch.density = density patch.x = x patch.y = y return patch def copy_from(self, heightmap_patch): self.cloned = True self.lod = heightmap_patch.lod self.texture = heightmap_patch.texture self.texture_peeker = heightmap_patch.texture_peeker self.heightmap_ready = heightmap_patch.heightmap_ready self.min_height = heightmap_patch.min_height self.max_height = heightmap_patch.max_height self.mean_height = heightmap_patch.mean_height def calc_sub_patch(self): self.copy_from(self.parent_heightmap) delta = self.patch.lod - self.lod scale = 1 << delta if self.patch.coord != TexCoord.Flat: x_tex = int(self.x / scale) * scale y_tex = int(self.y / scale) * scale x_delta = float(self.x - x_tex) / scale y_delta = float(self.y - y_tex) / scale else: x_tex = int(self.x * scale) / scale y_tex = int(self.y * scale) / scale x_delta = float(self.x - x_tex) y_delta = float(self.y - y_tex) self.texture_offset = LVector2(x_delta, y_delta) self.texture_scale = LVector2(1.0 / scale, 1.0 / scale) def is_ready(self): return self.heightmap_ready def set_height(self, x, y, height): pass def get_height(self, x, y): if self.texture_peeker is None: print("No peeker", self.patch.str_id(), self.patch.instance_ready) traceback.print_stack() return 0.0 new_x = x * self.texture_scale[0] + self.texture_offset[0] * self.width new_y = y * self.texture_scale[1] + self.texture_offset[1] * self.height new_x = min(new_x, self.width - 1) new_y = min(new_y, self.height - 1) height = self.parent.interpolator.get_value(self.texture_peeker, new_x, new_y) #TODO: This should be done in PatchedHeightmap.get_height() return height * self.parent.height_scale # + self.parent.offset def get_height_uv(self, u, v): return self.get_height(u * self.width, v * self.height) def load(self, patch, callback, cb_args=()): if self.texture is None: self.texture = Texture() self.texture.set_wrap_u(Texture.WMClamp) self.texture.set_wrap_v(Texture.WMClamp) self.parent.interpolator.configure_texture(self.texture) self.do_load(patch, callback, cb_args) else: if callback is not None: callback(self, *cb_args) def heightmap_ready_cb(self, texture, callback, cb_args): if texture is not None: self.texture = texture #print("READY", self.patch.str_id(), texture, self.texture) self.texture_peeker = texture.peek() # if self.texture_peeker is None: # print("NOT READY !!!") self.heightmap_ready = True data = self.texture.getRamImage() #TODO: should be completed and refactored signed = False component_type = texture.getComponentType() if component_type == Texture.T_float: buffer_type = numpy.float32 scale = 1.0 elif component_type == Texture.T_unsigned_byte: if signed: buffer_type = numpy.int8 scale = 128.0 else: buffer_type = numpy.uint8 scale = 255.0 elif component_type == Texture.T_unsigned_short: if signed: buffer_type = numpy.int16 scale = 32768.0 else: buffer_type = numpy.uint16 scale = 65535.0 if sys.version_info[0] < 3: buf = data.getData() np_buffer = numpy.fromstring(buf, dtype=buffer_type) else: np_buffer = numpy.frombuffer(data, buffer_type) np_buffer.shape = (self.texture.getYSize(), self.texture.getXSize(), self.texture.getNumComponents()) self.min_height = np_buffer.min() / scale self.max_height = np_buffer.max() / scale self.mean_height = np_buffer.mean() / scale else: if self.parent_heightmap is not None: self.calc_sub_patch() else: print("Make default texture for heightmap") texture = Texture() texture.setup_2d_texture(1, 1, Texture.T_float, Texture.F_r32) texture.set_clear_color(LColor(0, 0, 0, 0)) texture.make_ram_image() self.heightmap_ready_cb(texture, None, None) if callback is not None: callback(self, *cb_args) def do_load(self, patch, callback, cb_args): pass
def getMipMaps(mesh): mipmaps = {} for effect in mesh.effects: for prop in effect.supported: propval = getattr(effect, prop) if isinstance(propval, collada.material.Map): image_name = propval.sampler.surface.image.path image_data = propval.sampler.surface.image.data try: im = Image.open(StringIO(image_data)) im.load() except IOError: from panda3d.core import Texture from panda3d.core import StringStream from panda3d.core import PNMImage #PIL failed, so lets try DDS reader with panda3d t = Texture(image_name) success = t.readDds(StringStream(image_data)) if success == 0: raise FilterException("Failed to read image file %s" % image_name) #convert DDS to PNG outdata = t.getRamImageAs('RGBA').getData() try: im = Image.fromstring('RGBA', (t.getXSize(), t.getYSize()), outdata) im.load() except IOError: raise FilterException("Failed to read image file %s" % image_name) #Keep JPG in same format since JPG->PNG is pretty bad if im.format == 'JPEG': output_format = 'JPEG' output_extension = 'jpg' output_options = {'quality': 95, 'optimize':True} else: output_format = 'PNG' output_extension = 'png' output_options = {'optimize':True} #store a copy to the original image so we can resize from it directly each time orig_im = im width, height = im.size #round down to power of 2 width = int(math.pow(2, int(math.log(width, 2)))) height = int(math.pow(2, int(math.log(height, 2)))) pil_images = [] while True: im = orig_im.resize((width, height), Image.ANTIALIAS) pil_images.insert(0, im) if width == 1 and height == 1: break width = max(width / 2, 1) height = max(height / 2, 1) tar_buf = StringIO() tar = tarfile.TarFile(fileobj=tar_buf, mode='w') cur_offset = 0 byte_ranges = [] for i, pil_img in enumerate(pil_images): buf = StringIO() pil_img.save(buf, output_format, **output_options) file_len = buf.tell() cur_name = '%dx%d.%s' % (pil_img.size[0], pil_img.size[1], output_extension) tar_info = tarfile.TarInfo(name=cur_name) tar_info.size=file_len buf.seek(0) tar.addfile(tarinfo=tar_info, fileobj=buf) #tar files have a 512 byte header cur_offset += 512 file_start = cur_offset byte_ranges.append({'offset':file_start, 'length':file_len, 'width':pil_img.size[0], 'height':pil_img.size[1]}) #file lengths are rounded up to nearest 512 multiple file_len = 512 * ((file_len + 512 - 1) / 512) cur_offset += file_len tar.close() mipmaps[propval.sampler.surface.image.path] = (tar_buf.getvalue(), byte_ranges) return mipmaps
class ShaderHeightmapPatch(HeightmapPatch): tex_generators = {} cachable = False def __init__(self, noise, parent, x0, y0, x1, y1, width, height, scale=1.0, coord=TexCoord.Cylindrical, face=0, border=1): HeightmapPatch.__init__(self, parent, x0, y0, x1, y1, width, height, scale, coord, face, border) self.shader = None self.texture = None self.texture_peeker = None self.noise = noise self.tex_generator = None self.callback = None self.cloned = False self.texture_offset = LVector2() self.texture_scale = LVector2(1, 1) self.min_height = None self.max_height = None self.mean_height = None def copy_from(self, heightmap_patch): self.cloned = True self.lod = heightmap_patch.lod self.texture = heightmap_patch.texture self.texture_peeker = heightmap_patch.texture_peeker self.heightmap_ready = heightmap_patch.heightmap_ready self.min_height = heightmap_patch.min_height self.max_height = heightmap_patch.max_height self.mean_height = heightmap_patch.mean_height def get_height(self, x, y): if self.texture_peeker is None: print("No peeker", self.patch.str_id(), self.patch.instance_ready) traceback.print_stack() return 0.0 new_x = x * self.texture_scale[0] + self.texture_offset[0] * self.width new_y = ( (self.height - 1) - y) * self.texture_scale[1] + self.texture_offset[1] * self.height new_x = min(new_x, self.width - 1) new_y = min(new_y, self.height - 1) height = self.parent.interpolator.get_value(self.texture_peeker, new_x, new_y) #TODO: This should be done in PatchedHeightmap.get_height() return height * self.parent.height_scale # + self.parent.offset def generate(self, callback, cb_args=()): if self.texture is None: self.texture = Texture() self.texture.set_wrap_u(Texture.WMClamp) self.texture.set_wrap_v(Texture.WMClamp) self.parent.interpolator.configure_texture(self.texture) self._make_heightmap(callback, cb_args) else: if callback is not None: callback(self, *cb_args) def heightmap_ready_cb(self, texture, callback, cb_args): #print("READY", self.patch.str_id()) self.texture_peeker = self.texture.peek() # if self.texture_peeker is None: # print("NOT READY !!!") self.heightmap_ready = True data = self.texture.getRamImage() if sys.version_info[0] < 3: buf = data.getData() np_buffer = numpy.fromstring(buf, dtype=numpy.float32) else: np_buffer = numpy.frombuffer(data, numpy.float32) np_buffer.shape = (self.texture.getYSize(), self.texture.getXSize(), self.texture.getNumComponents()) self.min_height = np_buffer.min() self.max_height = np_buffer.max() self.mean_height = np_buffer.mean() if callback is not None: callback(self, *cb_args) def _make_heightmap(self, callback, cb_args): if not self.width in ShaderHeightmapPatch.tex_generators: ShaderHeightmapPatch.tex_generators[self.width] = GeneratorPool( settings.patch_pool_size) if settings.encode_float: texture_format = Texture.F_rgba else: texture_format = Texture.F_r32 ShaderHeightmapPatch.tex_generators[self.width].make_buffer( self.width, self.height, texture_format) tex_generator = ShaderHeightmapPatch.tex_generators[self.width] if self.shader is None: self.shader = NoiseShader(coord=self.coord, noise_source=self.noise, noise_target=FloatTarget(), offset=(self.x0, self.y0, 0.0), scale=(self.lod_scale_x, self.lod_scale_y, 1.0)) self.shader.global_frequency = self.parent.global_frequency self.shader.global_scale = self.parent.global_scale self.shader.create_and_register_shader(None, None) tex_generator.generate(self.shader, self.face, self.texture, self.heightmap_ready_cb, (callback, cb_args))