Exemple #1
0
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 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
Exemple #3
0
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 MyApp(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        # Load the environment model.
        self.scene = self.loader.loadModel("models/environment")
        # Reparent the model to render.
        self.scene.reparentTo(self.render)
        # Apply scale and position transforms on the model.
        self.scene.setScale(0.25, 0.25, 0.25)
        self.scene.setPos(-8, 42, 0)

        # Needed for camera image
        self.dr = self.camNode.getDisplayRegion(0)

        # Needed for camera depth image
        winprops = WindowProperties.size(self.win.getXSize(),
                                         self.win.getYSize())
        fbprops = FrameBufferProperties()
        fbprops.setDepthBits(1)
        self.depthBuffer = self.graphicsEngine.makeOutput(
            self.pipe, "depth buffer", -2, fbprops, winprops,
            GraphicsPipe.BFRefuseWindow, self.win.getGsg(), self.win)
        self.depthTex = Texture()
        self.depthTex.setFormat(Texture.FDepthComponent)
        self.depthBuffer.addRenderTexture(self.depthTex,
                                          GraphicsOutput.RTMCopyRam,
                                          GraphicsOutput.RTPDepth)
        lens = self.cam.node().getLens()
        # the near and far clipping distances can be changed if desired
        # lens.setNear(5.0)
        # lens.setFar(500.0)
        self.depthCam = self.makeCamera(self.depthBuffer,
                                        lens=lens,
                                        scene=render)
        self.depthCam.reparentTo(self.cam)

        # TODO: Scene is rendered twice: once for rgb and once for depth image.
        # How can both images be obtained in one rendering pass?

    def get_camera_image(self, requested_format=None):
        """
        Returns the camera's image, which is of type uint8 and has values
        between 0 and 255.
        The 'requested_format' argument should specify in which order the
        components of the image must be. For example, valid format strings are
        "RGBA" and "BGRA". By default, Panda's internal format "BGRA" is used,
        in which case no data is copied over.
        """
        tex = self.dr.getScreenshot()
        if requested_format is None:
            data = tex.getRamImage()
        else:
            data = tex.getRamImageAs(requested_format)
        image = np.frombuffer(
            data, np.uint8)  # use data.get_data() instead of data in python 2
        image.shape = (tex.getYSize(), tex.getXSize(), tex.getNumComponents())
        image = np.flipud(image)
        return image

    def get_camera_depth_image(self):
        """
        Returns the camera's depth image, which is of type float32 and has
        values between 0.0 and 1.0.
        """
        data = self.depthTex.getRamImage()
        depth_image = np.frombuffer(data, np.float32)
        depth_image.shape = (self.depthTex.getYSize(),
                             self.depthTex.getXSize(),
                             self.depthTex.getNumComponents())
        depth_image = np.flipud(depth_image)
        return depth_image
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()
Exemple #6
0
class Panda3dCameraSensor(object):
    def __init__(self,
                 base,
                 color=True,
                 depth=False,
                 size=None,
                 near_far=None,
                 hfov=None,
                 title=None):
        if size is None:
            size = (640, 480)
        if near_far is None:
            near_far = (0.01, 10000.0)
        if hfov is None:
            hfov = 60
        winprops = WindowProperties.size(*size)
        winprops.setTitle(title or 'Camera Sensor')
        fbprops = FrameBufferProperties()
        # Request 8 RGB bits, 8 alpha bits, and a depth buffer.
        fbprops.setRgbColor(True)
        fbprops.setRgbaBits(8, 8, 8, 8)
        fbprops.setDepthBits(24)
        self.graphics_engine = GraphicsEngine(base.pipe)

        window_type = base.config.GetString('window-type', 'onscreen')
        flags = GraphicsPipe.BFFbPropsOptional
        if window_type == 'onscreen':
            flags = flags | GraphicsPipe.BFRequireWindow
        elif window_type == 'offscreen':
            flags = flags | GraphicsPipe.BFRefuseWindow

        self.buffer = self.graphics_engine.makeOutput(base.pipe,
                                                      "camera sensor buffer",
                                                      -100, fbprops, winprops,
                                                      flags)

        if not color and not depth:
            raise ValueError("At least one of color or depth should be True")
        if color:
            self.color_tex = Texture("color_texture")
            self.buffer.addRenderTexture(self.color_tex,
                                         GraphicsOutput.RTMCopyRam,
                                         GraphicsOutput.RTPColor)
        else:
            self.color_tex = None
        if depth:
            self.depth_tex = Texture("depth_texture")
            self.buffer.addRenderTexture(self.depth_tex,
                                         GraphicsOutput.RTMCopyRam,
                                         GraphicsOutput.RTPDepth)
        else:
            self.depth_tex = None

        self.cam = base.makeCamera(self.buffer,
                                   scene=base.render,
                                   camName='camera_sensor')
        self.lens = self.cam.node().getLens()
        self.lens.setFov(hfov)
        self.lens.setFilmSize(
            *size)  # this also defines the units of the focal length
        self.lens.setNearFar(*near_far)

    def observe(self):
        for _ in range(self.graphics_engine.getNumWindows()):
            self.graphics_engine.renderFrame()
        self.graphics_engine.syncFrame()

        images = []

        if self.color_tex:
            data = self.color_tex.getRamImageAs('RGBA')
            if sys.version_info < (3, 0):
                data = data.get_data()
            image = np.frombuffer(data, np.uint8)
            image.shape = (self.color_tex.getYSize(),
                           self.color_tex.getXSize(),
                           self.color_tex.getNumComponents())
            image = np.flipud(image)
            image = image[
                ..., :
                -1]  # remove alpha channel; if alpha values are needed, set alpha bits to 8
            images.append(image)

        if self.depth_tex:
            depth_data = self.depth_tex.getRamImage()
            if sys.version_info < (3, 0):
                depth_data = depth_data.get_data()
            depth_image_size = self.depth_tex.getYSize(
            ) * self.depth_tex.getXSize() * self.depth_tex.getNumComponents()
            if len(depth_data) == 2 * depth_image_size:
                dtype = np.float16
            elif len(depth_data) == 3 * depth_image_size:
                dtype = np.float24
            elif len(depth_data) == 4 * depth_image_size:
                dtype = np.float32
            else:
                raise ValueError(
                    "Depth data has %d bytes but the size of the depth image is %d"
                    % (len(depth_data), depth_image_size))
            depth_image = np.frombuffer(depth_data, dtype)
            depth_image.shape = (self.depth_tex.getYSize(),
                                 self.depth_tex.getXSize(),
                                 self.depth_tex.getNumComponents())
            depth_image = np.flipud(depth_image)
            depth_image = depth_image.astype(
                np.float32, copy=False)  # copy only if necessary
            images.append(depth_image)

        return tuple(images)
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
Exemple #8
0
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
Exemple #9
0
class MyApp(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        # Load the environment model.
        self.setup_environment()
        #self.scene = self.loader.loadModel("models/environment")
        # Reparent the model to render.
        #self.scene.reparentTo(self.render)
        # Apply scale and position transforms on the model.
        #self.scene.setScale(0.25, 0.25, 0.25)
        #self.scene.setPos(-8, 42, 0)

        # Needed for camera image
        self.dr = self.camNode.getDisplayRegion(0)

        # Needed for camera depth image
        winprops = WindowProperties.size(self.win.getXSize(),
                                         self.win.getYSize())
        fbprops = FrameBufferProperties()
        fbprops.setDepthBits(1)
        self.depthBuffer = self.graphicsEngine.makeOutput(
            self.pipe, "depth buffer", -2, fbprops, winprops,
            GraphicsPipe.BFRefuseWindow, self.win.getGsg(), self.win)
        self.depthTex = Texture()
        self.depthTex.setFormat(Texture.FDepthComponent)
        self.depthBuffer.addRenderTexture(self.depthTex,
                                          GraphicsOutput.RTMCopyRam,
                                          GraphicsOutput.RTPDepth)
        lens = self.cam.node().getLens()
        lens.setFov(90.0, 90.0)
        # the near and far clipping distances can be changed if desired
        # lens.setNear(5.0)
        # lens.setFar(500.0)
        self.depthCam = self.makeCamera(self.depthBuffer,
                                        lens=lens,
                                        scene=self.render)
        self.depthCam.reparentTo(self.cam)

        # TODO: Scene is rendered twice: once for rgb and once for depth image.
        # How can both images be obtained in one rendering pass?
        self.render.setAntialias(AntialiasAttrib.MAuto)

    def setup_environment(self):
        # encapsulate some stuff

        # set up ambient lighting
        self.alight = AmbientLight('alight')
        self.alight.setColor(VBase4(0.1, 0.1, 0.1, 1))
        self.alnp = self.render.attachNewNode(self.alight)
        self.render.setLight(self.alnp)

        # set up a point light
        self.plight = PointLight('plight')
        self.plight.setColor(VBase4(0.8, 0.8, 0.8, 1))
        self.plnp = self.render.attachNewNode(self.plight)
        self.plnp.setPos(0, 0, 100)
        self.render.setLight(self.plnp)

        # set up terrain model
        self.terr_material = Material()
        self.terr_material.setShininess(1.0)
        self.terr_material.setAmbient(VBase4(0, 0, 0, 0))
        self.terr_material.setDiffuse(VBase4(1, 1, 1, 1))
        self.terr_material.setEmission(VBase4(0, 0, 0, 0))
        self.terr_material.setSpecular(VBase4(0, 0, 0, 0))

        # general scaling
        self.trrHorzSc = 4.0
        self.trrVertSc = 4.0  # was 4.0

        # Create sky
        #terrctr = self.trrHorzSc*65.0
        #self.setup_skybox(terrctr,800.0,2.0,0.3)

        self.skysphere = self.loader.loadModel("sky-forest/SkySphere.bam")
        self.skysphere.setBin('background', 1)
        self.skysphere.setDepthWrite(0)
        self.skysphere.reparentTo(self.render)

        # Load some textures
        self.grsTxtSc = 5
        self.numTreeTexts = 7

        # ground texture
        self.txtGrass = self.loader.loadTexture('tex/ground005.png')
        self.txtGrass.setWrapU(Texture.WM_mirror)
        self.txtGrass.setWrapV(Texture.WM_mirror)
        self.txtGrass.setMagfilter(Texture.FTLinear)
        self.txtGrass.setMinfilter(Texture.FTLinearMipmapLinear)

        # set up terrain texture stages
        self.TS1 = TextureStage('terrtext')
        self.TS1.setSort(0)
        self.TS1.setMode(TextureStage.MReplace)

        # Set up the GeoMipTerrain
        self.terrain = GeoMipTerrain("myDynamicTerrain")
        img = PNMImage(Filename('tex/bowl_height_map.png'))
        self.terrain.setHeightfield(img)
        self.terrain.setBruteforce(0)
        self.terrain.setAutoFlatten(GeoMipTerrain.AFMMedium)

        # Set terrain properties
        self.terrain.setBlockSize(32)
        self.terrain.setNear(50)
        self.terrain.setFar(500)
        self.terrain.setFocalPoint(self.camera)

        # Store the root NodePath for convenience
        self.root = self.terrain.getRoot()
        self.root.clearTexture()
        self.root.setTwoSided(0)
        self.root.setCollideMask(BitMask32.bit(0))
        self.root.setSz(self.trrVertSc)
        self.root.setSx(self.trrHorzSc)
        self.root.setSy(self.trrHorzSc)
        self.root.setMaterial(self.terr_material)
        self.root.setTexture(self.TS1, self.txtGrass)
        self.root.setTexScale(self.TS1, self.grsTxtSc, self.grsTxtSc)
        offset = 0.5 * img.getXSize() * self.trrHorzSc - 0.5
        self.root.setPos(-offset, -offset, 0)

        self.terrain.generate()
        self.root.reparentTo(self.render)

        # load tree billboards
        self.txtTreeBillBoards = []
        for a in range(self.numTreeTexts):
            fstr = 'trees/tree' + '%03d' % (a + 991)
            self.txtTreeBillBoards.append( \
                self.loader.loadTexture(fstr + '-color.png', fstr + '-opacity.png'))
            self.txtTreeBillBoards[a].setMinfilter(
                Texture.FTLinearMipmapLinear)

        #self.placePlantOnTerrain('trees',300,0,20,20,self.trrHorzSc,self.trrVertSc, \
        #    self.numTreeTexts,self.txtTreeBillBoards,'scene-def/trees.txt')
        self.setup_house()
        self.setup_vehicle()

        self.taskMgr.add(self.skysphereTask, "SkySphere Task")

    def setup_house(self):
        # place farmhouse on terrain
        self.house = ModelNode('house1')
        self.loadModelOntoTerrain(self.render, self.terrain, self.house, 43.0,
                                  0.275, 0.0, 0.0, self.trrHorzSc,
                                  self.trrVertSc, 'models/FarmHouse',
                                  Vec3(0, 0, 0),
                                  Point3(-12.0567, -29.1724, 0.0837742),
                                  Point3(12.2229, 21.1915, 21.3668))

    def setup_vehicle(self):
        # place HMMWV on terrain
        self.hmmwv = ModelNode('hmmwv1')
        self.loadModelOntoTerrain(self.render, self.terrain, self.hmmwv, 33.0,
                                  1.0, 20.0, 24.0, self.trrHorzSc,
                                  self.trrVertSc, 'models/hmmwv',
                                  Vec3(0, -90, 0),
                                  Point3(-1.21273, -2.49153, -1.10753),
                                  Point3(1.21273, 2.49153, 1.10753))

    def setup_skybox(self,
                     terrctr=645.0,
                     boxsz=1000.0,
                     aspect=1.0,
                     uplift=0.0):
        vsz = boxsz / aspect
        self.bckgtx = []
        self.bckgtx.append(self.loader.loadTexture('sky/Back2.png'))
        self.bckgtx.append(self.loader.loadTexture('sky/Right2.png'))
        self.bckgtx.append(self.loader.loadTexture('sky/Front2.png'))
        self.bckgtx.append(self.loader.loadTexture('sky/Left2.png'))
        self.bckgtx.append(self.loader.loadTexture('sky/Up.png'))
        for a in range(4):
            self.bckg = CardMaker('bkcard')
            lr = Point3(0.5 * boxsz, 0.5 * boxsz, -0.5 * vsz)
            ur = Point3(0.5 * boxsz, 0.5 * boxsz, 0.5 * vsz)
            ul = Point3(-0.5 * boxsz, 0.5 * boxsz, 0.5 * vsz)
            ll = Point3(-0.5 * boxsz, 0.5 * boxsz, -0.5 * vsz)
            self.bckg.setFrame(ll, lr, ur, ul)
            self.bckg.setHasNormals(0)
            self.bckg.setHasUvs(1)
            #self.bckg.setUvRange(self.bckgtx[a])
            bkcrd = self.render.attachNewNode(self.bckg.generate())
            bkcrd.setTexture(self.bckgtx[a])
            self.bckgtx[a].setWrapU(Texture.WMClamp)
            self.bckgtx[a].setWrapV(Texture.WMClamp)
            bkcrd.setLightOff()
            bkcrd.setFogOff()
            bkcrd.setHpr(90.0 * a, 0, 0)
            cz = 0.5 * boxsz * uplift
            #print 'set card at:', terrctr,terrctr,cz, ' with points: ', lr,ur,ul,ll
            bkcrd.setPos(terrctr, terrctr, cz)
            self.top = CardMaker('bkcard')
            lr = Point3(0.5 * boxsz, -0.5 * boxsz, 0)
            ur = Point3(0.5 * boxsz, 0.5 * boxsz, 0)
            ul = Point3(-0.5 * boxsz, 0.5 * boxsz, 0)
            ll = Point3(-0.5 * boxsz, -0.5 * boxsz, 0)
            self.top.setFrame(ll, lr, ur, ul)
            self.top.setHasNormals(0)
            self.top.setHasUvs(1)
            #self.top.setUvRange(self.bckgtx[4])
            bkcrd = self.render.attachNewNode(self.bckg.generate())
            bkcrd.setTexture(self.bckgtx[4])
            self.bckgtx[4].setWrapU(Texture.WMClamp)
            self.bckgtx[4].setWrapV(Texture.WMClamp)
            bkcrd.setLightOff()
            bkcrd.setFogOff()
            bkcrd.setHpr(0, 90, 90)
            bkcrd.setPos(terrctr, terrctr, 0.5 * vsz + 0.5 * boxsz * uplift)

    def placePlantOnTerrain(self, itemStr, itemCnt, Mode, typItemWidth,
                            typItemHeight, trrHorzSc, trrVertSc, numTxtTypes,
                            txtList, planFileName):
        # Billboarding plants
        crd = CardMaker('mycard')
        crd.setColor(0.5, 0.5, 0.5, 1)
        ll = Point3(-0.5 * typItemWidth, 0, 0)
        lr = Point3(0.5 * typItemWidth, 0, 0)
        ur = Point3(0.5 * typItemWidth, 0, typItemHeight)
        ul = Point3(-0.5 * typItemWidth, 0, typItemHeight)
        crd.setFrame(ll, lr, ur, ul)
        crd.setHasNormals(False)
        crd.setHasUvs(True)
        # generate/save/load locations
        try:
            plan_data_fp = open(planFileName, 'r')
            item_list = []
            for line in plan_data_fp:
                toks = line.split(',')
                px = float(toks[0].strip(' '))
                py = float(toks[1].strip(' '))
                ang = float(toks[2].strip(' '))
                dht = float(toks[3].strip(' '))
                scl = float(toks[4].strip(' '))
                idx = int(toks[5].strip(' '))
                item_list.append((px, py, ang, dht, scl, idx))
            plan_data_fp.close()
            print 'loaded ', itemStr, ' data file of size:', len(item_list)
        except IOError:
            # generate list and try to save
            item_list = []
            for a in range(itemCnt):
                px = random.randrange(-self.trrHorzSc * 64,
                                      self.trrHorzSc * 64)
                py = random.randrange(-self.trrHorzSc * 64,
                                      self.trrHorzSc * 64)
                ang = 180 * random.random()
                dht = 0.0
                scl = 0.75 + 0.25 * (random.random() + random.random())
                idx = random.randrange(0, numTxtTypes)
                item_list.append([px, py, ang, dht, scl, idx])
            try:
                plan_data_fp = open(planFileName, 'w')
                for c in item_list:
                    print >> plan_data_fp, c[0], ',', c[1], ',', c[2], ',', c[
                        3], ',', c[4], ',', c[5]
                plan_data_fp.close()
                print 'saved ', itemStr, ' data of size: ', len(item_list)
            except IOError:
                print 'unable to store ', itemStr, ' data of size: ', len(
                    item_list)
        # define each plant
        for c in item_list:
            px = c[0]
            py = c[1]
            ang = c[2]
            dht = c[3]
            scl = c[4]
            idx = c[5]
            if idx >= numTxtTypes:
                idx = 0
            if Mode > 0:
                for b in range(Mode):
                    crdNP = self.render.attachNewNode(crd.generate())
                    crdNP.setTexture(txtList[idx])
                    crdNP.setScale(scl)
                    crdNP.setTwoSided(True)
                    ht = self.terrain.getElevation(px / trrHorzSc,
                                                   py / trrHorzSc)
                    crdNP.setPos(px, py, ht * trrVertSc + dht)
                    crdNP.setHpr(ang + (180 / Mode) * b, 0, 0)
                    crdNP.setTransparency(TransparencyAttrib.MAlpha)
                    crdNP.setLightOff()
            else:
                # set up item as defined
                crd.setUvRange(txtList[idx])
                crdNP = self.render.attachNewNode(crd.generate())
                crdNP.setBillboardAxis()
                crdNP.setTexture(txtList[idx])
                crdNP.setScale(scl)
                ht = self.terrain.getElevation(px / trrHorzSc, py / trrHorzSc)
                crdNP.setPos(px, py, ht * trrVertSc)
                crdNP.setTransparency(TransparencyAttrib.MAlpha)
                crdNP.setLightOff()

    def loadModelOntoTerrain(self, render_node, terr_obj, model_obj, hdg, scl,
                             xctr, yctr, terr_horz_sc, terr_vert_sc,
                             model_path, rotA, minP, maxP):
        # load model onto terrain
        hdg_rads = hdg * math.pi / 180.0
        model_obj = self.loader.loadModel(model_path)
        rotAll = rotA
        rotAll.setX(rotAll.getX() + hdg)
        model_obj.setHpr(rotA)
        model_obj.setLightOff()
        # if model changes, these will have to be recomputed
        # minP = Point3(0,0,0)
        # maxP = Point3(0,0,0)
        # model_obj.calcTightBounds(minP,maxP)
        print minP
        print maxP
        htl = []
        maxzofs = -1000.0
        for xi in [minP[0], maxP[0]]:
            for yi in [minP[1], maxP[1]]:
                tx = xctr + scl * xi * math.cos(hdg_rads)
                ty = yctr + scl * yi * math.sin(hdg_rads)
                tht = self.terrain.getElevation(tx / terr_horz_sc,
                                                ty / terr_horz_sc)
                print 'tx=', tx, ', ty=', ty, ', tht=', tht
                htl.append(tht * terr_vert_sc - minP.getZ())
        for hi in htl:
            if hi > maxzofs:
                maxzofs = hi
        print maxzofs
        model_obj.setPos(xctr, yctr, maxzofs)
        model_obj.setHpr(rotAll)
        model_obj.setScale(scl)
        model_obj.reparentTo(render_node)
        return maxzofs, minP, maxP

    def get_camera_image(self, requested_format=None):
        """
        Returns the camera's image, which is of type uint8 and has values
        between 0 and 255.
        The 'requested_format' argument should specify in which order the
        components of the image must be. For example, valid format strings are
        "RGBA" and "BGRA". By default, Panda's internal format "BGRA" is used,
        in which case no data is copied over.
        """
        tex = self.dr.getScreenshot()
        if requested_format is None:
            data = tex.getRamImage()
        else:
            data = tex.getRamImageAs(requested_format)
        image = np.frombuffer(
            data.get_data(),
            np.uint8)  # use data.get_data() instead of data in python 2
        image.shape = (tex.getYSize(), tex.getXSize(), tex.getNumComponents())
        image = np.flipud(image)
        return image

    def get_camera_depth_image(self):
        """
        Returns the camera's depth image, which is of type float32 and has
        values between 0.0 and 1.0.
        """
        data = self.depthTex.getRamImage()
        depth_image = np.frombuffer(data.get_data(), np.float32)
        depth_image.shape = (self.depthTex.getYSize(),
                             self.depthTex.getXSize(),
                             self.depthTex.getNumComponents())
        depth_image = np.flipud(depth_image)
        '''
        
        Surface position can be inferred by calculating backward from the
        depth buffer. Each pixel on the screen represents a ray from the
        camera into the scene, and the depth value in the pixel indicates a
        distance along the ray. Because of this, it is not actually necessary
        to store surface position explicitly - it is only necessary to store
        depth values. Of course, OpenGL does that for free.

        So the framebuffer now needs to store surface normal, diffuse color,
        and depth value (to infer surface position). In practice, most
        ordinary framebuffers can only store color and depth - they don't have
        any place to store a third value. So we need to use a special
        offscreen buffer with an "auxiliary" bitplane. The auxiliary bitplane
        stores the surface normal.

        So then, there's the final postprocessing pass. This involves
        combining the diffuse color texture, the surface normal texture, the
        depth texture, and the light parameters into a final rendered output.
        The light parameters are passed into the postprocessing shader as
        constants, not as textures.

        If there are a lot of lights, things get interesting. You use one
        postprocessing pass per light. Each pass only needs to scan those
        framebuffer pixels that are actually in range of the light in
        question. To traverse only the pixels that are affected by the light,
        just render the illuminated area's convex bounding volume.

        The shader to store the diffuse color and surface normal is trivial.
        But the final postprocessing shader is a little complicated. What
        makes it tricky is that it needs to regenerate the original surface
        position from the screen position and depth value. The math for that
        deserves some explanation.

        We need to take a clip-space coordinate and depth-buffer value
        (ClipX,ClipY,ClipZ,ClipW) and unproject it back to a view-space
        (ViewX,ViewY,ViewZ) coordinate. Lighting is then done in view-space.

        Okay, so here's the math. Panda uses the projection matrix to
        transform view-space into clip-space. But in practice, the projection
        matrix for a perspective camera always contains four nonzero
        constants, and they're always in the same place:
            
        -- here are the non-zero elements of the projection matrix --
        
        A	0	0	0
        0	0	B	1
        0	C	0	0
        0	0	D	0
        
        -- precompute these from above projection matrix --
        '''
        proj = self.cam.node().getLens().getProjectionMat()
        proj_x = 0.5 * proj.getCell(3, 2) / proj.getCell(0, 0)
        proj_y = 0.5 * proj.getCell(3, 2)
        proj_z = 0.5 * proj.getCell(3, 2) / proj.getCell(2, 1)
        proj_w = -0.5 - 0.5 * proj.getCell(1, 2)
        '''
        -- now for each pixel compute viewpoint coordinates --
        
        viewx = (screenx * projx) / (depth + projw)
        viewy = (1 * projy) / (depth + projw)
        viewz = (screeny * projz) / (depth + projw)
        '''
        grid = np.mgrid[0:depth_image.shape[0], 0:depth_image.shape[1]]
        ygrid = np.float32(np.squeeze(
            grid[0, :, :])) / float(depth_image.shape[0] - 1)
        ygrid -= 0.5
        xgrid = np.float32(np.squeeze(
            grid[1, :, :])) / float(depth_image.shape[1] - 1)
        xgrid -= 0.5
        xview = 2.0 * xgrid * proj_x
        zview = 2.0 * ygrid * proj_z
        denom = np.squeeze(depth_image) + proj_w
        xview = xview / denom
        yview = proj_y / denom
        zview = zview / denom
        sqrng = xview**2 + yview**2 + zview**2
        range_image = np.sqrt(sqrng)
        range_image_1 = np.expand_dims(range_image, axis=2)

        return depth_image, range_image_1

    def compute_sample_pattern(self, limg_shape, res_factor):
        # assume velocity is XYZ and we are looking +X up and towards -Z
        pattern = []
        lens = self.cam.node().getLens()
        sx = self.win.getXSize()
        sy = self.win.getYSize()
        ifov_vert = 2.0 * math.tan(
            0.5 * math.radians(lens.getVfov())) / float(sy - 1)
        ifov_horz = 2.0 * math.tan(
            0.5 * math.radians(lens.getHfov())) / float(sx - 1)
        #ifov_vert = lens.getVfov() / float(sy-1)
        #ifov_horz = lens.getHfov() / float(sy-1)
        for ldr_row in range(limg_shape[0]):
            theta = -10.0 - 41.33 * (
                float(ldr_row) / float(limg_shape[0] - 1) - 0.5)
            for ldr_col in range(limg_shape[1]):
                psi = 60.0 * (float(ldr_col) / float(limg_shape[1] - 1) - 0.5)
                cpsi = math.cos(math.radians(psi))
                vert_ang = theta / cpsi
                img_row_flt = (0.5 * float(sy - 1) -
                               (math.tan(math.radians(vert_ang)) / ifov_vert))
                #img_row_flt = 0.5*(sy-1) - (vert_ang / ifov_vert)
                if img_row_flt < 0:
                    print('img_row_flt=%f' % img_row_flt)
                    img_row_flt = 0.0
                if img_row_flt >= sy:
                    print('img_row_flt=%f' % img_row_flt)
                    img_row_flt = float(sy - 1)
                img_col_flt = (0.5 * float(sx - 1) +
                               (math.tan(math.radians(psi)) / ifov_horz))
                #img_col_flt = 0.5*(sx-1) + (psi / ifov_horz)
                if img_col_flt < 0:
                    print('img_col_flt=%f' % img_col_flt)
                    img_col_flt = 0.0
                if img_col_flt >= sx:
                    print('img_col_flt=%f' % img_col_flt)
                    img_col_flt = float(sx - 1)
                pattern.append((ldr_row, ldr_col, img_row_flt, img_col_flt))
        return pattern

    def find_sorted_ladar_returns(self, rangearr, intensarr, ks_m):
        my_range = rangearr.copy()
        my_inten = intensarr.copy()
        '''
        pixels data is organized by:
           [0] starting range of this return
           [1] ending range of this return
           [2] peak range of this return
           [3] total intensity of this return
        '''
        int_mult = len(my_inten)
        pixels = map(
            list,
            zip(my_range.tolist(), my_range.tolist(), my_range.tolist(),
                my_inten.tolist()))
        spix = sorted(pixels, key=lambda x: x[0])
        done = False
        while not done:
            mxpi = len(spix)
            if mxpi > 2:
                mindel = 1e20
                mnidx = None
                for pidx in range(mxpi - 1):
                    rdel = spix[pidx + 1][0] - spix[pidx][1]
                    # must be within ks_m meters in range to merge
                    if (rdel < ks_m) and (rdel < mindel):
                        mindel = rdel
                        mnidx = pidx
                # merge best two returns
                if mnidx is not None:
                    # new range span for testing against neighbors
                    spix[mnidx][1] = spix[mnidx + 1][1]
                    # new peak range is range of max contributor
                    if spix[mnidx + 1][3] > spix[mnidx][3]:
                        spix[mnidx][2] = spix[mnidx + 1][2]
                    # intensity of return is sum of contributors
                    spix[mnidx][3] += spix[mnidx + 1][3]
                    # remove one of the two merged
                    del spix[mnidx + 1]
                else:
                    done = True
            else:
                done = True
        # now eliminate all but max and last returns
        max_idx = None
        max_val = 0.0
        for ci, pix in enumerate(spix):
            if pix[3] > max_val:
                max_val = pix[3] / int_mult
                max_idx = ci
        # if they are the same, return only one
        if spix[-1][3] >= spix[max_idx][3]:
            return [spix[-1]]
        else:
            return [spix[max_idx], spix[-1]]

    def sample_range_image(self, rng_img, int_img, limg_shape, vel_cam, pps,
                           ldr_err, pattern):
        # depth image is set up as 512 x 512 and is 62.5 degrees vertical FOV
        # the center row is vertical, but we want to sample from the
        # region corresponding to HDL-32 FOV: from +10 to -30 degrees
        detailed_sensor_model = False
        fwd_vel = vel_cam[1]
        beam_div = 0.002
        lens = self.cam.node().getLens()
        #sx = self.win.getXSize()
        sy = self.win.getYSize()
        ifov_vert = 2.0 * math.tan(
            0.5 * math.radians(lens.getVfov())) / float(sy - 1)
        #ifov_horz = 2.0*math.tan(0.5*math.radians(lens.getHfov()))/float(sx-1)
        #ifov = math.radians(self.cam.node().getLens().getVfov() / self.win.getYSize())
        sigma = beam_div / ifov_vert
        hs = int(2.0 * sigma + 1.0)
        gprof = gauss_kern(sigma, hs, normalize=False)
        rimg = np.zeros(limg_shape, dtype=np.float32)
        iimg = np.zeros(limg_shape, dtype=np.float32)
        margin = 10.0

        for pidx, relation in enumerate(pattern):

            # get the usual scan pattern sample
            ldr_row, ldr_col, img_row_flt, img_col_flt = relation

            if ((img_row_flt > -margin) and (img_col_flt > -margin)
                    and (img_row_flt < rng_img.shape[0] + margin)
                    and (img_col_flt < rng_img.shape[1] + margin)):

                # within reasonable distance from image limits
                img_row = int(round(img_row_flt))
                img_col = int(round(img_col_flt))

                # motion compensation
                trng = np.float32(rng_img[img_row, img_col])
                if trng > 0.0:
                    # TODO: change this back to False
                    done = True
                    ic = 0
                    while not done:
                        old_trng = trng
                        del_row = pidx * fwd_vel / (ifov_vert * trng * pps)
                        if (abs(del_row) > 1e-1) and (ic < 10):
                            img_row_f = img_row_flt + del_row
                            img_row = int(round(img_row_f))
                            trng = np.float32(rng_img[img_row, img_col])
                            ic += 1
                            if abs(trng - old_trng) < 0.5:
                                done = True
                        else:
                            done = True

                    # simple sensor processing: just sample from large images
                    rimg[ldr_row, ldr_col] = np.float32(rng_img[img_row,
                                                                img_col])
                    iimg[ldr_row, ldr_col] = np.float32(int_img[img_row,
                                                                img_col])

                    if detailed_sensor_model:
                        # detailed model subsamples whole beam width
                        gpatch = copy_patch_centered((img_row, img_col), hs,
                                                     int_img, 0.0)
                        gpatch = np.float32(gpatch)
                        gpatch *= gprof
                        rpatch = copy_patch_centered((img_row, img_col), hs,
                                                     rng_img, 0.0)
                        rpatch = np.squeeze(rpatch)
                        valid = rpatch > 1e-3
                        if np.count_nonzero(valid) > 0:
                            rpatch_ts = rpatch[valid]
                            gpatch_ts = gpatch[valid]
                            returns = self.find_sorted_ladar_returns(
                                rpatch_ts, gpatch_ts, 2.5)
                            # for now we just take first return
                            rimg[ldr_row, ldr_col] = returns[0][2]
                            iimg[ldr_row, ldr_col] = returns[0][3]
                else:
                    rimg[ldr_row, ldr_col] = 0.0
                    iimg[ldr_row, ldr_col] = np.float32(int_img[img_row,
                                                                img_col])

        rimg += ldr_err * np.random.standard_normal(rimg.shape)
        return rimg, iimg

    def skysphereTask(self, task):
        if self.base is not None:
            self.skysphere.setPos(self.base.camera, 0, 0, 0)
            self.terrain.generate()
        return task.cont
Exemple #10
0
class TextureHeightmapBase(Heightmap):
    def __init__(self, name, width, height, height_scale, u_scale, v_scale,
                 median, interpolator):
        Heightmap.__init__(self, name, width, height, height_scale, u_scale,
                           v_scale, median, interpolator)
        self.texture = None
        self.texture_offset = LVector2()
        self.texture_scale = LVector2(1, 1)
        self.tex_id = str(width) + ':' + str(height)

    def reset(self):
        self.texture = None

    def get_texture_offset(self, patch):
        return self.texture_offset

    def get_texture_scale(self, patch):
        return self.texture_scale

        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")
            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.interpolator.get_value(self.texture_peeker, new_x, new_y)
        return height * self.height_scale  # + self.offset

    def create_heightmap(self, shape, callback=None, cb_args=()):
        return self.load(shape, callback, cb_args)

    def load(self, shape, 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.interpolator.configure_texture(self.texture)
            self.do_load(shape, self.heightmap_ready_cb, (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 do_load(self, shape, callback, cb_args):
        pass
Exemple #11
0
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
Exemple #13
0
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))