def _createMapTextureCard(self): mapImage = PNMImage(MAP_RESOLUTION, MAP_RESOLUTION) mapImage.fill(*self._bgColor) fgColor = VBase4D(*self._fgColor) for x in xrange(self._mazeHeight): for y in xrange(self._mazeWidth): if self._mazeCollTable[y][x] == 1: ax = float(x) / self._mazeWidth * MAP_RESOLUTION invertedY = self._mazeHeight - 1 - y ay = float(invertedY) / self._mazeHeight * MAP_RESOLUTION self._drawSquare(mapImage, int(ax), int(ay), 10, fgColor) mapTexture = Texture('mapTexture') mapTexture.setupTexture(Texture.TT2dTexture, self._maskResolution, self._maskResolution, 1, Texture.TUnsignedByte, Texture.FRgba) mapTexture.setMinfilter(Texture.FTLinear) mapTexture.load(mapImage) mapTexture.setWrapU(Texture.WMClamp) mapTexture.setWrapV(Texture.WMClamp) mapImage.clear() del mapImage cm = CardMaker('map_cardMaker') cm.setFrame(-1.0, 1.0, -1.0, 1.0) map = self.attachNewNode(cm.generate()) map.setTexture(mapTexture, 1) return map
def _createMapTextureCard(self): mapImage = PNMImage(MAP_RESOLUTION, MAP_RESOLUTION) mapImage.fill(*self._bgColor) fgColor = VBase4D(*self._fgColor) for x in range(self._mazeHeight): for y in range(self._mazeWidth): if self._mazeCollTable[y][x] == 1: ax = float(x) / self._mazeWidth * MAP_RESOLUTION invertedY = self._mazeHeight - 1 - y ay = float(invertedY) / self._mazeHeight * MAP_RESOLUTION self._drawSquare(mapImage, int(ax), int(ay), 10, fgColor) mapTexture = Texture('mapTexture') mapTexture.setupTexture(Texture.TT2dTexture, self._maskResolution, self._maskResolution, 1, Texture.TUnsignedByte, Texture.FRgba) mapTexture.setMinfilter(Texture.FTLinear) mapTexture.load(mapImage) mapTexture.setWrapU(Texture.WMClamp) mapTexture.setWrapV(Texture.WMClamp) mapImage.clear() del mapImage cm = CardMaker('map_cardMaker') cm.setFrame(-1.0, 1.0, -1.0, 1.0) map = self.attachNewNode(cm.generate()) map.setTexture(mapTexture, 1) return map
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(int(size[0] * 0.5), int(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 new(size, color=(255,255,255)): img = PNMImage(*size) if len(color) == 4: img.addAlpha() img.fill(*color) panda_tex = PandaTexture('texture') panda_tex.load(img) return Texture(panda_tex)
def loadSpriteImages(self,file_path,cols,rows,flipx = False,flipy = False): """ Loads an image file containing individual animation frames and returns then in a list of PNMImages inputs: - file_path - cols - rows - flipx - flipy Output: - tuple ( bool , list[PNMImage] ) """ # Make a filepath image_file = Filename(file_path) if image_file .empty(): raise IOError("File not found") return (False, []) # Instead of loading it outright, check with the PNMImageHeader if we can open # the file. img_head = PNMImageHeader() if not img_head.readHeader(image_file ): raise IOError("PNMImageHeader could not read file %s. Try using absolute filepaths"%(file_path)) return (False, []) # Load the image with a PNMImage full_image = PNMImage(img_head.getXSize(),img_head.getYSize()) full_image.alphaFill(0) full_image.read(image_file) if flipx or flipy: full_image.flip(flipx,flipy,False) w = int(full_image.getXSize()/cols) h = int(full_image.getYSize()/rows) images = [] counter = 0 for i in range(0,cols): for j in range(0,rows): sub_img = PNMImage(w,h) sub_img.addAlpha() sub_img.alphaFill(0) sub_img.fill(1,1,1) sub_img.copySubImage(full_image ,0 ,0 ,i*w ,j*h ,w ,h) images.append(sub_img) return (True, images)
def test_pnmimage_add_sub_image(): dst = PNMImage(2, 2) dst.fill(0.5, 0, 0) #adding color to dst #dst_color will store rgb values at each pixel of dst dst_color = ((dst.get_xel(0, 0), dst.get_xel(0, 1)), (dst.get_xel(1, 0), dst.get_xel(1, 1))) src = PNMImage(1, 1) src.fill(0, 0.7, 0) #adding color to src #src_color will store rgb values at each pixel of src src_color = src.get_xel(0, 0) dst.add_sub_image(src, 1, 1, 0, 0, 1, 1) final_color = ((dst.get_xel(0, 0), dst.get_xel(0, 1)), (dst.get_xel(1, 0), dst.get_xel(1, 1))) assert final_color[0][0] == dst_color[0][0] assert final_color[0][1] == dst_color[0][1] assert final_color[1][0] == dst_color[1][0] assert final_color[1][1] == dst_color[1][1] + src_color
def createSequenceNode(self,name,img,cols,rows,scale_x,scale_y,frame_rate): seq = SequenceNode(name) w = int(img.getXSize()/cols) h = int(img.getYSize()/rows) counter = 0 for i in range(0,cols): for j in range(0,rows): sub_img = PNMImage(w,h) sub_img.addAlpha() sub_img.alphaFill(0) sub_img.fill(1,1,1) sub_img.copySubImage(img ,0 ,0 ,i*w ,j*h ,w ,h) # Load the image onto the texture texture = Texture() texture.setXSize(w) texture.setYSize(h) texture.setZSize(1) texture.load(sub_img) texture.setWrapU(Texture.WM_border_color) # gets rid of odd black edges around image texture.setWrapV(Texture.WM_border_color) texture.setBorderColor(LColor(0,0,0,0)) cm = CardMaker(name + '_' + str(counter)) cm.setFrame(-0.5*scale_x,0.5*scale_x,-0.5*scale_y,0.5*scale_y) card = NodePath(cm.generate()) seq.addChild(card.node(),counter) card.setTexture(texture) sub_img.clear() counter+=1 seq.setFrameRate(frame_rate) print "Sequence Node %s contains %i frames of size %s"%(name,seq.getNumFrames(),str((w,h))) return seq
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
imgs[i] = img else: # assume it is a constant value val = float(fImg.getFullpath()) if is_sRGB: print("Converting", getChannelName(i), "to linear") # Convert to linear val = math.pow(val, 2.2) imgs[i] = val print("Size:", size) output = PNMImage(*size) output.setNumChannels(4) output.setColorType(PNMImageHeader.CTFourChannel) output.fill(1.0, 0.0, 0.0) output.alphaFill(1.0) for channel, img in imgs.items(): img print("Filling in", getChannelName(channel), "channel...") if isinstance(img, float): print("Value", img) for x in range(size[0]): for y in range(size[1]): if isinstance(img, float): setChannel(output, x, y, channel, img) else: setChannel(output, x, y, channel, getChannel(img, x, y, 0)) outputFile = Filename(raw_input("Output image: "))
class GPUFFT(DebugObject): """ This is a collection of compute shaders to generate the inverse fft efficiently on the gpu, with butterfly FFT and precomputed weights """ def __init__(self, N, sourceTex, normalizationFactor): """ Creates a new fft instance. The source texture has to specified from the begining, as the shaderAttributes are pregenerated for performance reasons """ DebugObject.__init__(self, "GPU-FFT") self.size = N self.log2Size = int(math.log(N, 2)) self.normalizationFactor = normalizationFactor # Create a ping and a pong texture, because we can't write to the # same texture while reading to it (that would lead to unexpected # behaviour, we could solve that by using an appropriate thread size, # but it works fine so far) self.pingTexture = Texture("FFTPing") self.pingTexture.setup2dTexture( self.size, self.size, Texture.TFloat, Texture.FRgba32) self.pongTexture = Texture("FFTPong") self.pongTexture.setup2dTexture( self.size, self.size, Texture.TFloat, Texture.FRgba32) self.sourceTex = sourceTex for tex in [self.pingTexture, self.pongTexture, sourceTex]: tex.setMinfilter(Texture.FTNearest) tex.setMagfilter(Texture.FTNearest) tex.setWrapU(Texture.WMClamp) tex.setWrapV(Texture.WMClamp) # Pregenerate weights & indices for the shaders self._computeWeighting() # Pre generate the shaders, we have 2 passes: Horizontal and Vertical # which both execute log2(N) times with varying radii self.horizontalFFTShader = BetterShader.loadCompute( "Shader/Water/HorizontalFFT.compute") self.horizontalFFT = NodePath("HorizontalFFT") self.horizontalFFT.setShader(self.horizontalFFTShader) self.horizontalFFT.setShaderInput( "precomputedWeights", self.weightsLookupTex) self.horizontalFFT.setShaderInput("N", LVecBase2i(self.size)) self.verticalFFTShader = BetterShader.loadCompute( "Shader/Water/VerticalFFT.compute") self.verticalFFT = NodePath("VerticalFFT") self.verticalFFT.setShader(self.verticalFFTShader) self.verticalFFT.setShaderInput( "precomputedWeights", self.weightsLookupTex) self.verticalFFT.setShaderInput("N", LVecBase2i(self.size)) # Create a texture where the result is stored self.resultTexture = Texture("Result") self.resultTexture.setup2dTexture( self.size, self.size, Texture.TFloat, Texture.FRgba16) self.resultTexture.setMinfilter(Texture.FTLinear) self.resultTexture.setMagfilter(Texture.FTLinear) # Prepare the shader attributes, so we don't have to regenerate them # every frame -> That is VERY slow (3ms per fft instance) self._prepareAttributes() def getResultTexture(self): """ Returns the result texture, only contains valid data after execute was called at least once """ return self.resultTexture def _generateIndices(self, storageA, storageB): """ This method generates the precompute indices, see http://cnx.org/content/m12012/latest/image1.png """ numIter = self.size offset = 1 step = 0 for i in xrange(self.log2Size): numIter = numIter >> 1 step = offset for j in xrange(self.size): goLeft = (j / step) % 2 == 1 indexA, indexB = 0, 0 if goLeft: indexA, indexB = j - step, j else: indexA, indexB = j, j + step storageA[i][j] = indexA storageB[i][j] = indexB offset = offset << 1 def _generateWeights(self, storage): """ This method generates the precomputed weights """ # Using a custom pi variable should force the calculations to use # high precision (I hope so) pi = 3.141592653589793238462643383 numIter = self.size / 2 numK = 1 resolutionFloat = float(self.size) for i in xrange(self.log2Size): start = 0 end = 2 * numK for b in xrange(numIter): K = 0 for k in xrange(start, end, 2): fK = float(K) fNumIter = float(numIter) weightA = Vec2( math.cos(2.0 * pi * fK * fNumIter / resolutionFloat), -math.sin(2.0 * pi * fK * fNumIter / resolutionFloat)) weightB = Vec2( -math.cos(2.0 * pi * fK * fNumIter / resolutionFloat), math.sin(2.0 * pi * fK * fNumIter / resolutionFloat)) storage[i][k / 2] = weightA storage[i][k / 2 + numK] = weightB K += 1 start += 4 * numK end = start + 2 * numK numIter = numIter >> 1 numK = numK << 1 def _reverseRow(self, indices): """ Reverses the bits in the given row. This is required for inverse fft (actually we perform a normal fft, but reversing the bits gives us an inverse fft) """ mask = 0x1 for j in xrange(self.size): val = 0x0 temp = int(indices[j]) # Int is required, for making a copy for i in xrange(self.log2Size): t = mask & temp val = (val << 1) | t temp = temp >> 1 indices[j] = val def _computeWeighting(self): """ Precomputes the weights & indices, and stores them in a texture """ indicesA = [[0 for i in xrange(self.size)] for k in xrange(self.log2Size)] indicesB = [[0 for i in xrange(self.size)] for k in xrange(self.log2Size)] weights = [[Vec2(0.0) for i in xrange(self.size)] for k in xrange(self.log2Size)] self.debug("Pre-Generating indices ..") self._generateIndices(indicesA, indicesB) self._reverseRow(indicesA[0]) self._reverseRow(indicesB[0]) self.debug("Pre-Generating weights ..") self._generateWeights(weights) # Create storage for the weights & indices self.weightsLookup = PNMImage(self.size, self.log2Size, 4) self.weightsLookup.setMaxval((2 ** 16) - 1) self.weightsLookup.fill(0.0) # Populate storage for x in xrange(self.size): for y in xrange(self.log2Size): indexA = indicesA[y][x] indexB = indicesB[y][x] weight = weights[y][x] self.weightsLookup.setRed(x, y, indexA / float(self.size)) self.weightsLookup.setGreen(x, y, indexB / float(self.size)) self.weightsLookup.setBlue(x, y, weight.x * 0.5 + 0.5) self.weightsLookup.setAlpha(x, y, weight.y * 0.5 + 0.5) # Convert storage to texture so we can use it in a shader self.weightsLookupTex = Texture("Weights Lookup") self.weightsLookupTex.load(self.weightsLookup) self.weightsLookupTex.setFormat(Texture.FRgba16) self.weightsLookupTex.setMinfilter(Texture.FTNearest) self.weightsLookupTex.setMagfilter(Texture.FTNearest) self.weightsLookupTex.setWrapU(Texture.WMClamp) self.weightsLookupTex.setWrapV(Texture.WMClamp) def _prepareAttributes(self): """ Prepares all shaderAttributes, so that we have a list of ShaderAttributes we can simply walk through in the update method, that is MUCH faster than using setShaderInput, as each call to setShaderInput forces the generation of a new ShaderAttrib """ self.attributes = [] textures = [self.pingTexture, self.pongTexture] currentIndex = 0 firstPass = True # Horizontal for step in xrange(self.log2Size): source = textures[currentIndex] dest = textures[1 - currentIndex] if firstPass: source = self.sourceTex firstPass = False index = self.log2Size - step - 1 self.horizontalFFT.setShaderInput("source", source) self.horizontalFFT.setShaderInput("dest", dest) self.horizontalFFT.setShaderInput( "butterflyIndex", LVecBase2i(index)) self._queueShader(self.horizontalFFT) currentIndex = 1 - currentIndex # Vertical for step in xrange(self.log2Size): source = textures[currentIndex] dest = textures[1 - currentIndex] isLastPass = step == self.log2Size - 1 if isLastPass: dest = self.resultTexture index = self.log2Size - step - 1 self.verticalFFT.setShaderInput("source", source) self.verticalFFT.setShaderInput("dest", dest) self.verticalFFT.setShaderInput( "isLastPass", isLastPass) self.verticalFFT.setShaderInput( "normalizationFactor", self.normalizationFactor) self.verticalFFT.setShaderInput( "butterflyIndex", LVecBase2i(index)) self._queueShader(self.verticalFFT) currentIndex = 1 - currentIndex def execute(self): """ Executes the inverse fft once """ for attr in self.attributes: self._executeShader(attr) def _queueShader(self, node): """ Internal method to fetch the ShaderAttrib of a node and store it in the update queue """ sattr = node.getAttrib(ShaderAttrib) self.attributes.append(sattr) def _executeShader(self, sattr): """ Internal method to execute a shader by a given ShaderAttrib """ Globals.base.graphicsEngine.dispatch_compute( (self.size / 16, self.size / 16, 1), sattr, Globals.base.win.get_gsg())
class PlayerBase(DirectObject): def __init__(self): # Player Model setup self.player = Actor("Player", {"Run":"Player-Run", "Sidestep":"Player-Sidestep", "Idle":"Player-Idle"}) self.player.setBlend(frameBlend = True) self.player.setPos(0, 0, 0) self.player.pose("Idle", 0) self.player.reparentTo(render) self.player.hide() self.footstep = base.audio3d.loadSfx('footstep.ogg') self.footstep.setLoop(True) base.audio3d.attachSoundToObject(self.footstep, self.player) # Create a brush to paint on the texture splat = PNMImage("../data/Splat.png") self.colorBrush = PNMBrush.makeImage(splat, 6, 6, 1) CamMask = BitMask32.bit(0) AvBufMask = BitMask32.bit(1) self.avbuf = None if base.win: self.avbufTex = Texture('avbuf') self.avbuf = base.win.makeTextureBuffer('avbuf', 256, 256, self.avbufTex, True) cam = Camera('avbuf') cam.setLens(base.camNode.getLens()) self.avbufCam = base.cam.attachNewNode(cam) dr = self.avbuf.makeDisplayRegion() dr.setCamera(self.avbufCam) self.avbuf.setActive(False) self.avbuf.setClearColor((1, 0, 0, 1)) cam.setCameraMask(AvBufMask) base.camNode.setCameraMask(CamMask) # avbuf renders everything it sees with the gradient texture. tex = loader.loadTexture('gradient.png') np = NodePath('np') np.setTexture(tex, 100) np.setColor((1, 1, 1, 1), 100) np.setColorScaleOff(100) np.setTransparency(TransparencyAttrib.MNone, 100) np.setLightOff(100) cam.setInitialState(np.getState()) #render.hide(AvBufMask) # Setup a texture stage to paint on the player self.paintTs = TextureStage('paintTs') self.paintTs.setMode(TextureStage.MDecal) self.paintTs.setSort(10) self.paintTs.setPriority(10) self.tex = Texture('paint_av_%s'%id(self)) # Setup a PNMImage that will hold the paintable texture of the player self.imageSizeX = 64 self.imageSizeY = 64 self.p = PNMImage(self.imageSizeX, self.imageSizeY, 4) self.p.fill(1) self.p.alphaFill(0) self.tex.load(self.p) self.tex.setWrapU(self.tex.WMClamp) self.tex.setWrapV(self.tex.WMClamp) # Apply the paintable texture to the avatar self.player.setTexture(self.paintTs, self.tex) # team self.playerTeam = "" # A lable that will display the players team self.lblTeam = DirectLabel( scale = 1, pos = (0, 0, 3), frameColor = (0, 0, 0, 0), text = "TEAM", text_align = TextNode.ACenter, text_fg = (0,0,0,1)) self.lblTeam.reparentTo(self.player) self.lblTeam.setBillboardPointEye() # basic player values self.maxHits = 3 self.currentHits = 0 self.isOut = False self.TorsorControl = self.player.controlJoint(None,"modelRoot","Torsor") # setup the collision detection # wall and object collision self.playerSphere = CollisionSphere(0, 0, 1, 1) self.playerCollision = self.player.attachNewNode(CollisionNode("playerCollision%d"%id(self))) self.playerCollision.node().addSolid(self.playerSphere) base.pusher.addCollider(self.playerCollision, self.player) base.cTrav.addCollider(self.playerCollision, base.pusher) # foot (walk) collision self.playerFootRay = self.player.attachNewNode(CollisionNode("playerFootCollision%d"%id(self))) self.playerFootRay.node().addSolid(CollisionRay(0, 0, 2, 0, 0, -1)) self.playerFootRay.node().setIntoCollideMask(0) self.lifter = CollisionHandlerFloor() self.lifter.addCollider(self.playerFootRay, self.player) base.cTrav.addCollider(self.playerFootRay, self.lifter) # Player weapon setup self.gunAttach = self.player.exposeJoint(None, "modelRoot", "WeaponSlot_R") self.color = LPoint3f(1, 1, 1) self.gun = Gun(id(self)) self.gun.reparentTo(self.gunAttach) self.gun.hide() self.gun.setColor(self.color) self.hud = None # Player controls setup self.keyMap = {"left":0, "right":0, "forward":0, "backward":0} # screen sizes self.winXhalf = base.win.getXSize() / 2 self.winYhalf = base.win.getYSize() / 2 self.mouseSpeedX = 0.1 self.mouseSpeedY = 0.1 # AI controllable variables self.AIP = 0.0 self.AIH = 0.0 self.movespeed = 5.0 self.userControlled = False self.accept("Bulet-hit-playerCollision%d" % id(self), self.hit) self.accept("window-event", self.recalcAspectRatio) def runBase(self): self.player.show() self.gun.show() taskMgr.add(self.move, "moveTask%d"%id(self), priority=-4) def stopBase(self): taskMgr.remove("moveTask%d"%id(self)) self.ignoreAll() self.gun.remove() self.footstep.stop() base.audio3d.detachSound(self.footstep) self.player.delete() def setKey(self, key, value): self.keyMap[key] = value def setPos(self, pos): self.player.setPos(pos) def setColor(self, color=LPoint3f(0,0,0)): self.color = color self.gun.setColor(color) c = (color[0], color[1], color[2], 1.0) self.lblTeam["text_fg"] = c def setTeam(self, team): self.playerTeam = team self.lblTeam["text"] = team def shoot(self, shotVec=None): self.gun.shoot(shotVec) if self.hud != None: self.hud.updateAmmo(self.gun.maxAmmunition, self.gun.ammunition) def reload(self): self.gun.reload() if self.hud != None: self.hud.updateAmmo(self.gun.maxAmmunition, self.gun.ammunition) def recalcAspectRatio(self, window): self.winXhalf = window.getXSize() / 2 self.winYhalf = window.getYSize() / 2 def hit(self, entry, color): self.currentHits += 1 # Create a brush to paint on the texture splat = PNMImage("../data/Splat.png") splat = splat * LColorf(color[0], color[1], color[2], 1.0) self.colorBrush = PNMBrush.makeImage(splat, 6, 6, 1) self.paintAvatar(entry) if self.currentHits >= self.maxHits: base.messenger.send("GameOver-player%d" % id(self)) self.isOut = True def __paint(self, s, t): """ Paints a point on the avatar at texture coordinates (s, t). """ x = (s * self.p.getXSize()) y = ((1.0 - t) * self.p.getYSize()) # Draw in color directly on the avatar p1 = PNMPainter(self.p) p1.setPen(self.colorBrush) p1.drawPoint(x, y) self.tex.load(self.p) self.tex.setWrapU(self.tex.WMClamp) self.tex.setWrapV(self.tex.WMClamp) self.paintDirty = True def paintAvatar(self, entry): """ Paints onto an avatar. Returns true on success, false on failure (because there are no avatar pixels under the mouse, for instance). """ # First, we have to render the avatar in its false-color # image, to determine which part of its texture is under the # mouse. if not self.avbuf: return False #mpos = base.mouseWatcherNode.getMouse() mpos = entry.getSurfacePoint(self.player) ppos = entry.getSurfacePoint(render) self.player.showThrough(BitMask32.bit(1)) self.avbuf.setActive(True) base.graphicsEngine.renderFrame() self.player.show(BitMask32.bit(1)) self.avbuf.setActive(False) # Now we have the rendered image in self.avbufTex. if not self.avbufTex.hasRamImage(): print "Weird, no image in avbufTex." return False p = PNMImage() self.avbufTex.store(p) ix = int((1 + mpos.getX()) * p.getXSize() * 0.5) iy = int((1 - mpos.getY()) * p.getYSize() * 0.5) x = 1 if ix >= 0 and ix < p.getXSize() and iy >= 0 and iy < p.getYSize(): s = p.getBlue(ix, iy) t = p.getGreen(ix, iy) x = p.getRed(ix, iy) if x > 0.5: # Off the avatar. return False # At point (s, t) on the avatar's map. self.__paint(s, t) return True def move(self, task): if self is None: return task.done if self.userControlled: if not base.mouseWatcherNode.hasMouse(): return task.cont self.pointer = base.win.getPointer(0) mouseX = self.pointer.getX() mouseY = self.pointer.getY() if base.win.movePointer(0, self.winXhalf, self.winYhalf): p = self.TorsorControl.getP() + (mouseY - self.winYhalf) * self.mouseSpeedY if p <-80: p = -80 elif p > 90: p = 90 self.TorsorControl.setP(p) h = self.player.getH() - (mouseX - self.winXhalf) * self.mouseSpeedX if h <-360: h = 360 elif h > 360: h = -360 self.player.setH(h) else: self.TorsorControl.setP(self.AIP) self.player.setH(self.AIH) forward = self.keyMap["forward"] != 0 backward = self.keyMap["backward"] != 0 if self.keyMap["left"] != 0: if self.player.getCurrentAnim() != "Sidestep" and not (forward or backward): self.player.loop("Sidestep") self.player.setPlayRate(5, "Sidestep") self.player.setX(self.player, self.movespeed * globalClock.getDt()) elif self.keyMap["right"] != 0: if self.player.getCurrentAnim() != "Sidestep" and not (forward or backward): self.player.loop("Sidestep") self.player.setPlayRate(5, "Sidestep") self.player.setX(self.player, -self.movespeed * globalClock.getDt()) else: self.player.stop("Sidestep") if forward: if self.player.getCurrentAnim() != "Run": self.player.loop("Run") self.player.setPlayRate(5, "Run") self.player.setY(self.player, -self.movespeed * globalClock.getDt()) elif backward: if self.player.getCurrentAnim() != "Run": self.player.loop("Run") self.player.setPlayRate(-5, "Run") self.player.setY(self.player, self.movespeed * globalClock.getDt()) else: self.player.stop("Run") if not (self.keyMap["left"] or self.keyMap["right"] or self.keyMap["forward"] or self.keyMap["backward"] or self.player.getCurrentAnim() == "Idle"): self.player.loop("Idle") self.footstep.stop() else: self.footstep.play() return task.cont
map_np = NodePath("map") card = map_np.attach_new_node(cm.generate()) card.set_p(-90) mat = Material() mat.set_base_color((1.0, 1.0, 1.0, 1)) mat.set_emission((1.0, 1.0, 1.0, 1)) mat.set_metallic(1.0) mat.set_roughness(1.0) card.set_material(mat) texture_size = 256 base_color_pnm = PNMImage(texture_size, texture_size) base_color_pnm.fill(0.72, 0.45, 0.2) # Copper base_color_tex = Texture("BaseColor") base_color_tex.load(base_color_pnm) ts = TextureStage('BaseColor') # a.k.a. Modulate ts.set_mode(TextureStage.M_modulate) card.set_texture(ts, base_color_tex) # Emission; Gets multiplied with mat.emission emission_pnm = PNMImage(texture_size, texture_size) emission_pnm.fill(0.0, 0.0, 0.0) emission_tex = Texture("Emission") emission_tex.load(emission_pnm) ts = TextureStage('Emission') ts.set_mode(TextureStage.M_emission) card.set_texture(ts, emission_tex)
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
mat.set_base_color((1.0, 1.0, 1.0, 1)) mat.set_emission((1.0, 1.0, 1.0, 1)) mat.set_metallic(1.0) mat.set_roughness(1.0) card_np.set_material(mat) texture_size = 256 texture_bands_x = 2 texture_bands_y = 2 # Base color, a.k.a. Modulate, a.k.a. albedo map # Gets multiplied with mat.base_color base_color_pnm = PNMImage(texture_size, texture_size) base_color_pnm.fill(0.72, 0.45, 0.2) # Copper base_color_tex = Texture("BaseColor") base_color_tex.load(base_color_pnm) ts = TextureStage('BaseColor') # a.k.a. Modulate ts.set_mode(TextureStage.M_modulate) card_np.set_texture(ts, base_color_tex) # Emission; Gets multiplied with mat.emission emission_pnm = PNMImage(texture_size, texture_size) emission_pnm.fill(0.0, 0.0, 0.0) emission_tex = Texture("Emission") emission_tex.load(emission_pnm) ts = TextureStage('Emission') ts.set_mode(TextureStage.M_emission) card_np.set_texture(ts, emission_tex)