Beispiel #1
0
    def __init__(self, scene, suncgDatasetRoot=None, debug=False, objectMode='box',
                 agentRadius=0.1, agentHeight=1.6, agentMass=60.0, agentMode='capsule'):

        super(Panda3dBulletPhysics, self).__init__()

        self.__dict__.update(scene=scene, suncgDatasetRoot=suncgDatasetRoot, debug=debug, objectMode=objectMode,
                             agentRadius=agentRadius, agentHeight=agentHeight, agentMass=agentMass, agentMode=agentMode)

        if suncgDatasetRoot is not None:
            self.modelCatMapping = ModelCategoryMapping(os.path.join(
                suncgDatasetRoot, "metadata", "ModelCategoryMapping.csv"))
        else:
            self.modelCatMapping = None

        self.bulletWorld = BulletWorld()
        self.bulletWorld.setGravity(Vec3(0, 0, -9.81))

        if debug:
            debugNode = BulletDebugNode('physic-debug')
            debugNode.showWireframe(True)
            debugNode.showConstraints(False)
            debugNode.showBoundingBoxes(True)
            debugNode.showNormals(False)
            self.debugNodePath = self.scene.scene.attachNewNode(debugNode)
            self.debugNodePath.show()
            self.bulletWorld.setDebugNode(debugNode)
        else:
            self.debugNodePath = None

        self._initLayoutModels()
        self._initAgents()
        self._initObjects()

        self.scene.worlds['physics'] = self
    def __init__(self,
                 scene,
                 suncgDatasetRoot,
                 size=(512, 512),
                 mode='offscreen',
                 zNear=0.1,
                 zFar=1000.0,
                 fov=40.0,
                 cameraTransform=None):

        # Off-screen buffers are not supported in OSX
        if sys.platform == 'darwin':
            mode = 'onscreen'

        super(Panda3dSemanticsRenderer, self).__init__()

        self.__dict__.update(scene=scene,
                             suncgDatasetRoot=suncgDatasetRoot,
                             size=size,
                             mode=mode,
                             zNear=zNear,
                             zFar=zFar,
                             fov=fov,
                             cameraTransform=cameraTransform)

        self.categoryMapping = ModelCategoryMapping(
            os.path.join(self.suncgDatasetRoot, 'metadata',
                         'ModelCategoryMapping.csv'))

        self.cameraMask = BitMask32.bit(1)
        self.graphicsEngine = GraphicsEngine.getGlobalPtr()
        self.loader = Loader.getGlobalPtr()
        self.graphicsEngine.setDefaultLoader(self.loader)

        self._initModels()

        selection = GraphicsPipeSelection.getGlobalPtr()
        self.pipe = selection.makeDefaultPipe()
        logger.debug('Using %s' % (self.pipe.getInterfaceName()))

        # Attach a camera to every agent in the scene
        self.cameras = []
        for agentNp in self.scene.scene.findAllMatches('**/agents/agent*'):
            camera = agentNp.attachNewNode(ModelNode('camera-semantics'))
            if self.cameraTransform is not None:
                camera.setTransform(cameraTransform)
            camera.node().setPreserveTransform(ModelNode.PTLocal)
            self.cameras.append(camera)

            # Reparent node below the existing physic node (if any)
            physicsNp = agentNp.find('**/physics')
            if not physicsNp.isEmpty():
                camera.reparentTo(physicsNp)

        self.rgbBuffers = dict()
        self.rgbTextures = dict()

        self._initRgbCapture()

        self.scene.worlds['render-semantics'] = self
Beispiel #3
0
    def getDimensionsFromModelId(modelId, modelInfoFilename, modelCatFilename):

        modelInfo = ModelInformation(modelInfoFilename)
        modelCat = ModelCategoryMapping(modelCatFilename)

        refModelDimensions = None
        otherSimilarDimensions = []
        refModelId = str(modelId)
        refCategory = modelCat.getCoarseGrainedCategoryForModelId(refModelId)
        for modelId in modelInfo.model_info.keys():
            category = modelCat.getCoarseGrainedCategoryForModelId(modelId)
            if refCategory == category:
                info = modelInfo.getModelInfo(modelId)

                # FIXME: handle the general case where for the front vector, do not ignore
                # NOTE: SUNCG is using the Y-up coordinate system
                frontVec = info['front']
                if np.count_nonzero(frontVec) > 1 or not np.array_equal(
                        frontVec, [0, 0, 1]):
                    continue

                width, height, depth = info['aligned_dims'] / 100.0  # cm to m
                otherSimilarDimensions.append([width, height, depth])

                if refModelId == modelId:
                    refModelDimensions = np.array([width, height, depth])

        otherSimilarDimensions = np.array(otherSimilarDimensions)
        logger.debug('Number of similar objects found in dataset: %d' %
                     (otherSimilarDimensions.shape[0]))

        # Volume statistics (assume a gaussian distribution)
        # XXX: use a more general histogram method to define the categories,
        # rather than simply comparing the deviation to the mean
        refVolume = np.prod(refModelDimensions)
        otherVolumes = np.prod(otherSimilarDimensions, axis=-1)
        mean = np.mean(otherVolumes)
        std = np.std(otherVolumes)

        # Compare the deviation to the mean
        overallSizeTag = None
        diff = refVolume - mean
        for tag, threshold in DimensionTable.overallSizeTable:
            if threshold >= 0.0:
                if diff > threshold * std:
                    overallSizeTag = tag
            else:
                if diff < threshold * std:
                    overallSizeTag = tag

        if overallSizeTag is None:
            overallSizeTag = 'normal'

        return overallSizeTag
Beispiel #4
0
    def __init__(self, scene, suncgDatasetRoot):

        super(SuncgSemantics, self).__init__()

        self.__dict__.update(scene=scene, suncgDatasetRoot=suncgDatasetRoot)

        self.categoryMapping = ModelCategoryMapping(
            os.path.join(self.suncgDatasetRoot, 'metadata',
                         'ModelCategoryMapping.csv'))

        self._initLayoutModels()
        self._initAgents()
        self._initObjects()

        self.scene.worlds['semantics'] = self
Beispiel #5
0
    def __init__(self, scene, suncgDatasetRoot):
        self.scene = scene
        self.suncgDatasetRoot = suncgDatasetRoot

        if suncgDatasetRoot is not None:
            self.categoryMapping = ModelCategoryMapping(
                os.path.join(self.suncgDatasetRoot, 'metadata',
                             'ModelCategoryMapping.csv'))
        else:
            self.categoryMapping = None

        self._initLayoutModels()
        self._initAgents()
        self._initObjects()

        self.scene.worlds['semantics'] = self
Beispiel #6
0
 def testInit(self):
     _ = ModelCategoryMapping(
         os.path.join(TEST_SUNCG_DATA_DIR, "metadata",
                      "ModelCategoryMapping.csv"))
Beispiel #7
0
class SuncgSemantics(World):
    
    # XXX: is not a complete list of movable objects, and the list is redundant with the one for physics
    movableObjectCategories = ['table', 'dressing_table', 'sofa', 'trash_can', 'chair', 'ottoman', 'bed']
    
    def __init__(self, scene, suncgDatasetRoot):

        super(SuncgSemantics, self).__init__()
        
        self.__dict__.update(scene=scene, suncgDatasetRoot=suncgDatasetRoot)
        
        self.categoryMapping = ModelCategoryMapping(
            os.path.join(
                self.suncgDatasetRoot,
                'metadata',
                'ModelCategoryMapping.csv')
        )

        self._initLayoutModels()
        self._initAgents()
        self._initObjects()
    
        self.scene.worlds['semantics'] = self

    def _initLayoutModels(self):
        
        # Load layout objects as meshes
        for _ in self.scene.scene.findAllMatches('**/layouts/object*/model*'):
            # Nothing to do
            pass
    
    def _initAgents(self):
    
        # Load agents
        for _ in self.scene.scene.findAllMatches('**/agents/agent*'):
            # Nothing to do
            pass
            
    def _initObjects(self):
    
        # Load objects
        for model in self.scene.scene.findAllMatches('**/objects/object*/model*'):
            modelId = model.getParent().getTag('model-id')
            
            objNp = model.getParent()
            
            semanticsNp = objNp.attachNewNode('semantics')
            
            # Categories
            coarseCategory = self.categoryMapping.getCoarseGrainedCategoryForModelId(modelId)
            semanticsNp.setTag('coarse-category', coarseCategory)
            
            fineCategory = self.categoryMapping.getFineGrainedCategoryForModelId(modelId)
            semanticsNp.setTag('fine-category', fineCategory)
            
            # Estimate mass of object based on volumetric data and default material density
            objVoxFilename = os.path.join(self.suncgDatasetRoot, 'object_vox', 'object_vox_data', modelId, modelId + '.binvox')
            voxelData = ObjectVoxelData.fromFile(objVoxFilename)
            volume = voxelData.getFilledVolume()
            semanticsNp.setTag('volume', str(volume))
            
            #XXX: we could get information below from physic node (if any)
            if coarseCategory in self.movableObjectCategories:
                semanticsNp.setTag('movable', 'true')
            else:
                semanticsNp.setTag('movable', 'false')
                
            #TODO: add mass information
            
            # Color information
            basicColors = MaterialColorTable.getColorsFromObject(objNp, mode='basic')
            semanticsNp.setTag('basic-colors', ",".join(basicColors))
            
            advancedColors = MaterialColorTable.getColorsFromObject(objNp, mode='advanced')
            semanticsNp.setTag('advanced-colors', ",".join(advancedColors))

            # Material information
            materials = MaterialTable.getMaterialNameFromObject(objNp)
            semanticsNp.setTag('materials', ",".join(materials))
            
            # Object size information
            modelInfoFilename = os.path.join(self.suncgDatasetRoot,
                                            'metadata',
                                            'models.csv')
            modelCatFilename = os.path.join(self.suncgDatasetRoot,
                                            'metadata',
                                            'ModelCategoryMapping.csv')
            overallSize = DimensionTable.getDimensionsFromModelId(modelId, modelInfoFilename, modelCatFilename)
            semanticsNp.setTag('overall-size', str(overallSize))
            
    def describeObject(self, obj):
        
        semanticsNp = obj.find('**/semantics')
        if not semanticsNp.isEmpty():
            
            items = []
            
            sizeDescription = semanticsNp.getTag('overall-size')
            if sizeDescription == 'normal':
                sizeDescription = ''
            items.append(sizeDescription)

            colorDescription = semanticsNp.getTag('advanced-colors')
            items.append(colorDescription)
     
            categoryDescription = semanticsNp.getTag('fine-category')
            categoryDescription = categoryDescription.replace("_", " ")
            items.append(categoryDescription)
     
            materialDescription = semanticsNp.getTag('materials')
            materialDescription = 'made of ' + materialDescription
            items.append(materialDescription)
     
            desc = " ".join(items)    
        else:
            desc = ""
        
        return desc
    
    def step(self, dt):
        #Nothing to do yet here
        pass
Beispiel #8
0
class Panda3dBulletPhysics(World):

    # NOTE: the model ids of objects that correspond to opened doors. They will be ignored for collisions.
    openedDoorModelIds = [
        '122',
        '133',
        '214',
        '246',
        '247',
        '361',
        '73',
        '756',
        '757',
        '758',
        '759',
        '760',
        '761',
        '762',
        '763',
        '764',
        '765',
        '768',
        '769',
        '770',
        '771',
        '778',
        '779',
        '780',
        's__1762',
        's__1763',
        's__1764',
        's__1765',
        's__1766',
        's__1767',
        's__1768',
        's__1769',
        's__1770',
        's__1771',
        's__1772',
        's__1773',
    ]

    # FIXME: is not a complete list of movable objects
    movableObjectCategories = [
        'table', 'dressing_table', 'sofa', 'trash_can', 'chair', 'ottoman',
        'bed'
    ]

    # For more material, see table: http://www.ambrsoft.com/CalcPhysics/Density/Table_2.htm
    defaultDensity = 1000.0  # kg/m^3

    # For more coefficients, see table: https://www.thoughtspike.com/friction-coefficients-for-bullet-physics/
    defaultMaterialFriction = 0.7

    defaultMaterialRestitution = 0.5

    def __init__(self,
                 scene,
                 suncgDatasetRoot=None,
                 debug=False,
                 objectMode='box',
                 agentRadius=0.1,
                 agentHeight=1.6,
                 agentMass=60.0,
                 agentMode='capsule'):

        super(Panda3dBulletPhysics, self).__init__()

        self.__dict__.update(scene=scene,
                             suncgDatasetRoot=suncgDatasetRoot,
                             debug=debug,
                             objectMode=objectMode,
                             agentRadius=agentRadius,
                             agentHeight=agentHeight,
                             agentMass=agentMass,
                             agentMode=agentMode)

        if suncgDatasetRoot is not None:
            self.modelCatMapping = ModelCategoryMapping(
                os.path.join(suncgDatasetRoot, "metadata",
                             "ModelCategoryMapping.csv"))
        else:
            self.modelCatMapping = None

        self.bulletWorld = BulletWorld()
        self.bulletWorld.setGravity(Vec3(0, 0, -9.81))

        if debug:
            debugNode = BulletDebugNode('physic-debug')
            debugNode.showWireframe(True)
            debugNode.showConstraints(False)
            debugNode.showBoundingBoxes(True)
            debugNode.showNormals(False)
            self.debugNodePath = self.scene.scene.attachNewNode(debugNode)
            self.debugNodePath.show()
            self.bulletWorld.setDebugNode(debugNode)
        else:
            self.debugNodePath = None

        self._initLayoutModels()
        self._initAgents()
        self._initObjects()

        self.scene.worlds['physics'] = self

    def destroy(self):
        # Nothing to do
        pass

    def _initLayoutModels(self):

        # Load layout objects as meshes
        for model in self.scene.scene.findAllMatches(
                '**/layouts/object*/model*'):

            shape, transform = getCollisionShapeFromModel(
                model, mode='mesh', defaultCentered=False)

            node = BulletRigidBodyNode('physics')
            node.setMass(0.0)
            node.setFriction(self.defaultMaterialFriction)
            node.setRestitution(self.defaultMaterialRestitution)
            node.setStatic(True)
            node.addShape(shape)
            node.setDeactivationEnabled(True)
            node.setIntoCollideMask(BitMask32.allOn())
            self.bulletWorld.attach(node)

            # Attach the physic-related node to the scene graph
            physicsNp = model.getParent().attachNewNode(node)
            physicsNp.setTransform(transform)

            if node.isStatic():
                model.getParent().setTag('physics-mode', 'static')
            else:
                model.getParent().setTag('physics-mode', 'dynamic')

            # Reparent render and acoustics nodes (if any) below the new physic node
            #XXX: should be less error prone to just reparent all children (except the hidden model)
            renderNp = model.getParent().find('**/render')
            if not renderNp.isEmpty():
                renderNp.reparentTo(physicsNp)
            acousticsNp = model.getParent().find('**/acoustics')
            if not acousticsNp.isEmpty():
                acousticsNp.reparentTo(physicsNp)

            # NOTE: we need this to update the transform state of the internal bullet node
            physicsNp.node().setTransformDirty()

            # Validation
            assert np.allclose(
                mat4ToNumpyArray(physicsNp.getNetTransform().getMat()),
                mat4ToNumpyArray(model.getNetTransform().getMat()),
                atol=1e-6)

    def _initAgents(self):

        # Load agents
        for agent in self.scene.scene.findAllMatches('**/agents/agent*'):

            transform = TransformState.makeIdentity()
            if self.agentMode == 'capsule':
                shape = BulletCapsuleShape(
                    self.agentRadius, self.agentHeight - 2 * self.agentRadius)
            elif self.agentMode == 'sphere':
                shape = BulletCapsuleShape(self.agentRadius,
                                           2 * self.agentRadius)

            # XXX: use BulletCharacterControllerNode class, which already handles local transform?
            node = BulletRigidBodyNode('physics')
            node.setMass(self.agentMass)
            node.setStatic(False)
            node.setFriction(self.defaultMaterialFriction)
            node.setRestitution(self.defaultMaterialRestitution)
            node.addShape(shape)
            self.bulletWorld.attach(node)

            # Constrain the agent to have fixed position on the Z-axis
            node.setLinearFactor(Vec3(1.0, 1.0, 0.0))

            # Constrain the agent not to be affected by rotations
            node.setAngularFactor(Vec3(0.0, 0.0, 0.0))

            node.setIntoCollideMask(BitMask32.allOn())
            node.setDeactivationEnabled(True)

            # Enable continuous collision detection (CCD)
            node.setCcdMotionThreshold(1e-7)
            node.setCcdSweptSphereRadius(0.50)

            if node.isStatic():
                agent.setTag('physics-mode', 'static')
            else:
                agent.setTag('physics-mode', 'dynamic')

            # Attach the physic-related node to the scene graph
            physicsNp = NodePath(node)
            physicsNp.setTransform(transform)

            # Reparent all child nodes below the new physic node
            for child in agent.getChildren():
                child.reparentTo(physicsNp)
            physicsNp.reparentTo(agent)

            # NOTE: we need this to update the transform state of the internal bullet node
            physicsNp.node().setTransformDirty()

            # Validation
            assert np.allclose(
                mat4ToNumpyArray(physicsNp.getNetTransform().getMat()),
                mat4ToNumpyArray(agent.getNetTransform().getMat()),
                atol=1e-6)

    def _initObjects(self):

        # Load objects
        for model in self.scene.scene.findAllMatches(
                '**/objects/object*/model*'):
            modelId = model.getParent().getTag('model-id')

            # XXX: we could create BulletGhostNode instance for non-collidable objects, but we would need to filter out the collisions later on
            if not modelId in self.openedDoorModelIds:

                shape, transform = getCollisionShapeFromModel(
                    model, self.objectMode, defaultCentered=True)

                node = BulletRigidBodyNode('physics')
                node.addShape(shape)
                node.setFriction(self.defaultMaterialFriction)
                node.setRestitution(self.defaultMaterialRestitution)
                node.setIntoCollideMask(BitMask32.allOn())
                node.setDeactivationEnabled(True)

                if self.suncgDatasetRoot is not None:

                    # Check if it is a movable object
                    category = self.modelCatMapping.getCoarseGrainedCategoryForModelId(
                        modelId)
                    if category in self.movableObjectCategories:
                        # Estimate mass of object based on volumetric data and default material density
                        objVoxFilename = os.path.join(self.suncgDatasetRoot,
                                                      'object_vox',
                                                      'object_vox_data',
                                                      modelId,
                                                      modelId + '.binvox')
                        voxelData = ObjectVoxelData.fromFile(objVoxFilename)
                        mass = Panda3dBulletPhysics.defaultDensity * voxelData.getFilledVolume(
                        )
                        node.setMass(mass)
                    else:
                        node.setMass(0.0)
                        node.setStatic(True)
                else:
                    node.setMass(0.0)
                    node.setStatic(True)

                if node.isStatic():
                    model.getParent().setTag('physics-mode', 'static')
                else:
                    model.getParent().setTag('physics-mode', 'dynamic')

                # Attach the physic-related node to the scene graph
                physicsNp = model.getParent().attachNewNode(node)
                physicsNp.setTransform(transform)

                # Reparent render and acoustics nodes (if any) below the new physic node
                #XXX: should be less error prone to just reparent all children (except the hidden model)
                renderNp = model.getParent().find('**/render')
                if not renderNp.isEmpty():
                    renderNp.reparentTo(physicsNp)
                acousticsNp = model.getParent().find('**/acoustics')
                if not acousticsNp.isEmpty():
                    acousticsNp.reparentTo(physicsNp)

                # NOTE: we need this to update the transform state of the internal bullet node
                node.setTransformDirty()

                # NOTE: we need to add the node to the bullet engine only after setting all attributes
                self.bulletWorld.attach(node)

                # Validation
                assert np.allclose(
                    mat4ToNumpyArray(physicsNp.getNetTransform().getMat()),
                    mat4ToNumpyArray(
                        model.getParent().getNetTransform().getMat()),
                    atol=1e-6)

            else:
                logger.debug('Object %s ignored from physics' % (modelId))

    def step(self, dt):
        self.bulletWorld.doPhysics(dt)

    def isCollision(self, root):
        isCollisionDetected = False
        if isinstance(root.node(), BulletRigidBodyNode):
            result = self.bulletWorld.contactTest(root.node())
            if result.getNumContacts() > 0:
                isCollisionDetected = True
        else:
            for nodePath in root.findAllMatches('**/+BulletBodyNode'):
                isCollisionDetected |= self.isCollision(nodePath)
        return isCollisionDetected

    def getCollisionInfo(self, root, dt):

        result = self.bulletWorld.contactTest(root.node())

        force = 0.0
        relativePosition = LVecBase3f(0.0, 0.0, 0.0)
        isCollisionDetected = False
        for _ in result.getContacts():

            # Iterate over all manifolds of the world
            # NOTE: it seems like the contact manifold doesn't hold the information
            #       to calculate contact force. We need the persistent manifolds for that.
            for manifold in self.bulletWorld.getManifolds():

                # Check if the root node is part of that manifold, by checking positions
                # TODO: there is surely a better way to compare the two nodes here
                #if (manifold.getNode0().getTransform().getPos() == root.getNetTransform().getPos()):
                if manifold.getNode0().getTag('model-id') == root.getTag(
                        'model-id'):

                    # Calculate the to
                    totalImpulse = 0.0
                    maxImpulse = 0.0
                    for pt in manifold.getManifoldPoints():
                        impulse = pt.getAppliedImpulse()
                        totalImpulse += impulse

                        if impulse > maxImpulse:
                            maxImpulse = impulse
                            relativePosition = pt.getLocalPointA()

                    force = totalImpulse / dt
                    isCollisionDetected = True

        return force, relativePosition, isCollisionDetected

    def calculate2dNavigationMap(self, agent, z=0.1, precision=0.1, yup=True):

        agentRbNp = agent.find('**/+BulletRigidBodyNode')

        # Calculate the bounding box of the scene
        bounds = []
        for nodePath in self.scene.scene.findAllMatches(
                '**/object*/+BulletRigidBodyNode'):
            node = nodePath.node()

            #NOTE: the bounding sphere doesn't seem to take into account the transform, so apply it manually (translation only)
            bsphere = node.getShapeBounds()
            center = nodePath.getNetTransform().getPos()
            bounds.extend(
                [center + bsphere.getMin(), center + bsphere.getMax()])

        minBounds, maxBounds = np.min(bounds, axis=0), np.max(bounds, axis=0)

        # Using the X and Y dimensions of the bounding box, discretize the 2D plan into a uniform grid with given precision
        X = np.arange(minBounds[0], maxBounds[0], step=precision)
        Y = np.arange(minBounds[1], maxBounds[1], step=precision)
        nbTotalCells = len(X) * len(Y)
        threshold10Perc = int(nbTotalCells / 10)

        # XXX: the simulation needs to be run a little before moving the agent, not sure why
        self.bulletWorld.doPhysics(0.1)

        # Sweep the position of the agent across the grid, checking if collision/contacts occurs with objects or walls in the scene.
        occupancyMap = np.zeros((len(X), len(Y)))
        occupancyMapCoord = np.zeros((len(X), len(Y), 2))
        n = 0
        for i, x in enumerate(X):
            for j, y in enumerate(Y):
                agentRbNp.setPos(LVecBase3f(x, y, z))

                if self.isCollision(agentRbNp):
                    occupancyMap[i, j] = 1.0

                occupancyMapCoord[i, j, 0] = x
                occupancyMapCoord[i, j, 1] = y

                n += 1
                if n % threshold10Perc == 0:
                    logger.debug('Collision test no.%d (out of %d total)' %
                                 (n, nbTotalCells))

        if yup:
            # Convert to image format (y,x)
            occupancyMap = np.flipud(occupancyMap.T)
            occupancyMapCoord = np.flipud(
                np.transpose(occupancyMapCoord, axes=(1, 0, 2)))

        return occupancyMap, occupancyMapCoord
Beispiel #9
0
class Panda3dSemanticsRenderer(World):

    def __init__(self, scene, suncgDatasetRoot, size=(512,512), mode='offscreen', zNear=0.1, zFar=1000.0, fov=40.0, cameraTransform=None):

        super(Panda3dSemanticsRenderer, self).__init__()
        
        self.__dict__.update(scene=scene, suncgDatasetRoot=suncgDatasetRoot, size=size, mode=mode, zNear=zNear, zFar=zFar, fov=fov, 
                             cameraTransform=cameraTransform)
        
        self.categoryMapping = ModelCategoryMapping(
            os.path.join(
                self.suncgDatasetRoot,
                'metadata',
                'ModelCategoryMapping.csv')
        )
        
        self.cameraMask = BitMask32.bit(4)
        self.graphicsEngine = GraphicsEngine.getGlobalPtr()
        self.loader = Loader.getGlobalPtr()
        self.graphicsEngine.setDefaultLoader(self.loader)
        
        self._initModels()
        
        selection = GraphicsPipeSelection.getGlobalPtr()
        self.pipe = selection.makeDefaultPipe()
        logger.debug('Using %s' % (self.pipe.getInterfaceName()))
        
        # Attach a camera to every agent in the scene
        self.cameras = []
        for agentNp in self.scene.scene.findAllMatches('**/agents/agent*'):
            camera = agentNp.attachNewNode(ModelNode('camera-semantics'))
            if self.cameraTransform is not None:
                camera.setTransform(cameraTransform)
            camera.node().setPreserveTransform(ModelNode.PTLocal)
            self.cameras.append(camera)
        
        self.rgbBuffers = dict()
        self.rgbTextures = dict()
        
        self._initRgbCapture()

        self.scene.worlds['render-semantics'] = self

    def _initCategoryColors(self):
        
        catNames = self.categoryMapping.getFineGrainedClassList()
        size = int(np.ceil(np.cbrt(len(catNames)) - 1e-6))
        
        # Uniform sampling of colors
        colors = np.zeros((size**3, 3))
        i = 0
        for r in np.linspace(0.0, 1.0, size):
            for g in np.linspace(0.0, 1.0, size):
                for b in np.linspace(0.0, 1.0, size):
                    colors[i] = [r,g,b]
                    i += 1
        
        # Shuffle
        indices = np.arange(len(colors))
        np.random.shuffle(indices)
        colors = colors[indices]
        
        self.catColors = dict()
        for i, name in enumerate(catNames):
            self.catColors[name] = colors[i]
            
            print '\'%s\': [%d, %d, %d],' % (name, 
                                            int(colors[i][0]*255),
                                            int(colors[i][1]*255),
                                            int(colors[i][2]*255))

    def _initModels(self):
        
        models = []
        for model in self.scene.scene.findAllMatches('**/objects/**/+ModelNode'):
            models.append(model)
        for model in self.scene.scene.findAllMatches('**/layouts/**/+ModelNode'):
            models.append(model)
        
        for model in models:
            
            objectNp = model.getParent()
            rendererNp = objectNp.attachNewNode('render-semantics')
            model = model.copyTo(rendererNp)
            
            # Set the model to be visible only to this camera
            model.node().adjustDrawMask(self.cameraMask, self.cameraMask, self.cameraMask)
            model.show()
            
            # Get semantic-related color of model
            modelId = model.getNetTag('model-id')
            if 'fr_' in modelId:
                if modelId.endswith('c'):
                    catName = 'ceiling'
                elif modelId.endswith('f'):
                    catName = 'floor'
                elif modelId.endswith('w'):
                    catName = 'wall'
            else:
                pass
                catName = self.categoryMapping.getFineGrainedCategoryForModelId(modelId)
            color = MODEL_CATEGORY_COLOR_MAPPING[catName]
            
            # Clear all GeomNode render attributes and set a specified flat color
            for nodePath in model.findAllMatches('**/+GeomNode'):
                geomNode = nodePath.node()
                for n in range(geomNode.getNumGeoms()):
                    geomNode.setGeomState(n, RenderState.make(ColorAttrib.makeFlat(LColor(color[0]/255.0, color[1]/255.0, color[2]/255.0, 1.0)), 1))
            
            # Disable lights for this model
            model.setLightOff(1)

            # Enable antialiasing
            model.setAntialias(AntialiasAttrib.MAuto)
            
            # Reparent render node below the existing physic node (if any)
            physicsNp = objectNp.find('**/physics')
            if not physicsNp.isEmpty():
                rendererNp.reparentTo(physicsNp)

    def _initRgbCapture(self):

        for camera in self.cameras:
            
            camNode = Camera('Semantic camera')
            camNode.setCameraMask(self.cameraMask)
            lens = PerspectiveLens()
            lens.setFov(self.fov)
            lens.setAspectRatio(float(self.size[0]) / float(self.size[1]))
            lens.setNear(self.zNear)
            lens.setFar(self.zFar)
            camNode.setLens(lens)
            camNode.setScene(self.scene.scene)
            cam = camera.attachNewNode(camNode)
            
            winprops = WindowProperties.size(self.size[0], self.size[1])
            fbprops = FrameBufferProperties.getDefault()
            fbprops = FrameBufferProperties(fbprops)
            fbprops.setRgbaBits(8, 8, 8, 8)
            
            flags = GraphicsPipe.BFFbPropsOptional
            if self.mode == 'onscreen':
                flags = flags | GraphicsPipe.BFRequireWindow
            elif self.mode == 'offscreen':
                flags = flags | GraphicsPipe.BFRefuseWindow
            else:
                raise Exception('Unsupported rendering mode: %s' % (self.mode))
            
            buf = self.graphicsEngine.makeOutput(self.pipe, 'RGB buffer', 0, fbprops,
                                                 winprops, flags)
            if buf is None:
                raise Exception('Unable to create RGB buffer')
            
            # Set to render at the end
            buf.setSort(10000)
            
            dr = buf.makeDisplayRegion()
            dr.setSort(0)
            dr.setCamera(cam)
            dr = camNode.getDisplayRegion(0)
            
            tex = Texture()
            tex.setFormat(Texture.FRgba8)
            tex.setComponentType(Texture.TUnsignedByte)
            buf.addRenderTexture(tex, GraphicsOutput.RTMCopyRam, GraphicsOutput.RTPColor)
            #XXX: should use tex.setMatchFramebufferFormat(True)?
        
            agent = camera.getParent()
            self.rgbBuffers[agent.getName()] = buf
            self.rgbTextures[agent.getName()] = tex
    
    def showRoomLayout(self, showCeilings=True, showWalls=True, showFloors=True):
        
        for np in self.scene.scene.findAllMatches('**/layouts/**/render-semantics/*c'):
            if showCeilings:
                np.show()
            else:
                np.hide()
    
        for np in self.scene.scene.findAllMatches('**/layouts/**/render-semantics/*w'):
            if showWalls:
                np.show()
            else:
                np.hide()
            
        for np in self.scene.scene.findAllMatches('**/layouts/**/render-semantics/*f'):
            if showFloors:
                np.show()
            else:
                np.hide()
        
    def destroy(self):
        self.graphicsEngine.removeAllWindows()
        del self.pipe

    def getRgbaImages(self, channelOrder="RGBA"):
        images = dict()
        for name, tex in self.rgbTextures.iteritems():
            data = tex.getRamImageAs(channelOrder)
            image = np.frombuffer(data.get_data(), np.uint8) # Must match Texture.TUnsignedByte
            image.shape = (tex.getYSize(), tex.getXSize(), 4)
            image = np.flipud(image)
            images[name] = image
            
        return images

    def step(self, dt):
        
        self.graphicsEngine.renderFrame()
        
        #NOTE: we need to call frame rendering twice in onscreen mode because of double-buffering
        if self.mode == 'onscreen':
            self.graphicsEngine.renderFrame()