ClipPlane doesn't work for all shapes ?

Hello,

I try to clip a shape in several pieces by using ClipPlane. In order to do this, I display several times the shape, each one clipped with different ClipPlane. When I do this on shapes like Circle or Square (in fact, a Rectangle with the same width and height), it works well :

But when the Rectangle is really rectangular, it doesn’t always works. If the shape is near the “center’s” ClipPlane, the clipping is effectively done :

And if I try to move away the shape from the “center’s” ClipPlane, it does very weird things like this :

I try several shapes (Circle, Line, Polygon) and the clipping always works. The only case in which it doesn’t work is rectangular rectangle.

Here is the code, if somebody wants to try.

#panda
from pandac.PandaModules import *
# need to be before the Direct Start Import
loadPrcFileData("", "framebuffer-multisample 1")
loadPrcFileData("", "want-directtools 1")
loadPrcFileData("", "want-tk 1")
import direct.directbase.DirectStart

# standlib
import os, sys

mesh_dico = {}

class Primitive (object):
    z = 0
    def __init__(self, node, pos, scale, heading = None, color = None):
        self.node = node
        self.setPos(pos)
        self.setScale(scale)
        if color is not None : self.node.setColor(*color)
        if heading is not None : self.node.setHpr(heading, 0, 0)

    def setPos(self, pos):
        pos  = self.add_z(pos)
        self.node.setPos(pos)

    def add_z(self, point):
        assert len(point) == 2
        return Vec3(point[0], point[1], self.__class__.z)

    def delete(self):
        self.node.detachNode()

class Rect(Primitive):
    def __init__(self, pos, size, color=None, heading=None, parent = None):
        node = create_placeholder(mesh_dico, 'square', parent)
        Primitive.__init__(self, node, pos, size, heading, color)

    def setScale(self, scale):
        self.node.setScale(scale[0], scale[1], 1)
        
class Circle (Primitive):
    def __init__(self, center, radius, color, parent = None):
        node = create_placeholder(mesh_dico, 'circle', parent)
        Primitive.__init__(self, node, center, radius, color = color)

    def setScale(self, scale):
        self.node.setScale(scale, scale, 1)

def GEN_counter(i=-1):
    while True:
        i += 1
        yield i

def unique_name(prefix='id', suffix='', g=GEN_counter()):
    name = prefix+str(g.next())
    return name

def create_placeholder(dico, mesh_name, parent = None):
    if parent is None : parent = render.attachNewNode('shader')
    placeholder = parent.attachNewNode(unique_name())
    dico[mesh_name].instanceTo(placeholder)
    return placeholder

def load_meshes():
    mesh_dico['square'] = loader.loadModel("square.egg")
    mesh_dico['square'].setScale(0.3048,0.3048,0.3048)
    mesh_dico['square'].setHpr(0,90,0)
    mesh_dico['circle'] = loader.loadModel("circle.egg")
    mesh_dico['circle'].setScale(0.3048,0.3048,0.3048)

def TEST_clipping():

    #== First part of the Shape
    
    #Uncomment for a square Rectangle
    #p = Rect((12,20), (20,20), heading = 0, color = (1,0.3,1,1))
    
    #Uncomment for a circle
    #p = Circle((11,15),10, (1,0.3,1,1))
    
    #uncomment for a rectangular rectangle
    p = Rect((11,15),(40,30), color = (1,0.3,1,1))
    
    shaderNode = render.find('shader')
    p.node.wrtReparentTo(shaderNode)
    
    point_init = Point3(12, 15, 0.0)
    vect = Vec3(1, -0.5, 0.0)
    plane = Plane(vect, point_init)
    planeNode = PlaneNode( 'shapePlane' )
    planeNode.setPlane( plane )
    planeNode.setVizScale(10)
    node = render.attachNewNode( planeNode )
    planeNP = NodePath(node)
    planeNP.wrtReparentTo(p.node)
    planeNP.show()
    p.node.setClipPlane(planeNP)


    #== Second part of the Shape

    #Uncomment for a square Rectangle
    #p1 = Rect((12,20), (20,20), heading = 0, color = (0,0,1,1))
    
    #Uncomment for a circle
    #p1 = Circle((11,15),10, (0,0,1,1))
    
    #uncomment for a rectangular rectangle
    p1 = Rect((11,15),(40,30), color = (0,0,1,1))
    
    shaderNode = render.find('shader')
    p1.node.wrtReparentTo(shaderNode)
    
    point_init = Point3(12, 17, 0.0)
    vect = Vec3(-1, 0.5, 0.0)
    plane = Plane(vect, point_init)
    planeNode = PlaneNode( 'shapePlane' )
    planeNode.setPlane( plane )
    planeNode.setVizScale(10)
    node = render.attachNewNode( planeNode )
    planeNP = NodePath(node)
    planeNP.wrtReparentTo(p1.node)
    planeNP.show()
    p1.node.setClipPlane(planeNP)


if __name__ == '__main__':
    load_meshes()
    TEST_clipping()
    run()

The meshes loaded can be found on panda’s website in download/3Dmodel/shapes.
After the launch, just push the ‘O’ key in order to see the shape. The camera’s control are these one : http://panda3d.org/manual/index.php/Enhanced_Mouse_Navigation

Thanks in advance for your answers.

too bad, I couldn’t see what’s going on.
I parented the clipped node (both instances) to the camera, so it’s easy to see the result only by moving the camera around. And the clipping is not reliable for any shape.

from pandac.PandaModules import *
# need to be before the Direct Start Import
# loadPrcFileData("", "framebuffer-multisample 1")
# loadPrcFileData("", "want-directtools 1")
# loadPrcFileData("", "want-tk 1")
import direct.directbase.DirectStart

# standlib 
import os, sys 

mesh_dico = {} 

class Primitive (object): 
    z = 0 
    def __init__(self, node, pos, scale, heading = None, color = None): 
        self.node = node 
        self.setPos(pos) 
        self.setScale(scale) 
        if color is not None : self.node.setColor(*color) 
        if heading is not None : self.node.setHpr(heading, 0, 0) 

    def setPos(self, pos): 
        pos  = self.add_z(pos) 
        self.node.setPos(pos)

    def add_z(self, point):
        assert len(point) == 2
        return Vec3(point[0], point[1], self.__class__.z)

    def delete(self):
        self.node.detachNode()

class Rect(Primitive):
    def __init__(self, pos, size, color=None, heading=None, parent = None):
        node = create_placeholder(mesh_dico, 'square', parent)
        Primitive.__init__(self, node, pos, size, heading, color)

    def setScale(self, scale):
        self.node.setScale(scale[0], scale[1], 1)

class Circle (Primitive):
    def __init__(self, center, radius, color, parent = None):
        node = create_placeholder(mesh_dico, 'circle', parent)
        Primitive.__init__(self, node, center, radius, color = color)

    def setScale(self, scale):
        self.node.setScale(scale, scale, 1)

def GEN_counter(i=-1):
    while True:
        i += 1
        yield i

def unique_name(prefix='id', suffix='', g=GEN_counter()):
    name = prefix+str(g.next())
    return name

def create_placeholder(dico, mesh_name, parent = None):
    if parent is None : parent = render.attachNewNode('shader')
    placeholder = parent.attachNewNode(unique_name())
    dico[mesh_name].instanceTo(placeholder)
    return placeholder

def load_meshes():
    mesh_dico['square'] = loader.loadModel("misc/rgbCube")
    mesh_dico['square'].setScale(0.3048,0.3048,0.3048)
    mesh_dico['square'].setHpr(0,90,0)
    mesh_dico['circle'] = loader.loadModel("smiley.egg")
    mesh_dico['circle'].setScale(0.3048,0.3048,0.3048)

def TEST_clipping():

    #== First part of the Shape

    #Uncomment for a square Rectangle
    #p = Rect((12,20), (20,20), heading = 0, color = (1,0.3,1,1))

    #Uncomment for a circle
    #p = Circle((11,15),10, (1,0.3,1,1))

    #uncomment for a rectangular rectangle
    p = Rect((11,15),(40,30), color = (1,0.3,1,1))

    clippedNode=render.attachNewNode('')

    shaderNode = render.find('shader')
    p.node.wrtReparentTo(clippedNode)


    point_init = Point3(12, 15, 0.0)
    vect = Vec3(1, -0.5, 0.0)
    plane = Plane(vect, point_init)
    planeNode = PlaneNode( 'shapePlane' )
    planeNode.setPlane( plane )
    planeNode.setVizScale(10)
    node = render.attachNewNode( planeNode )
    planeNP = NodePath(node)
    planeNP.wrtReparentTo(render)
    planeNP.show()
    p.node.setClipPlane(planeNP)


    #== Second part of the Shape

    #Uncomment for a square Rectangle
    #p1 = Rect((12,20), (20,20), heading = 0, color = (0,0,1,1))

    #Uncomment for a circle
    #p1 = Circle((11,15),10, (0,0,1,1))

    #uncomment for a rectangular rectangle
    p1 = Rect((11,15),(40,30), color = (0,0,1,1))

    shaderNode = render.find('shader')
    p1.node.wrtReparentTo(clippedNode)

    point_init = Point3(12, 17, 0.0)
    vect = Vec3(-1, 0.5, 0.0)
    plane = Plane(vect, point_init)
    planeNode = PlaneNode( 'shapePlane' )
    planeNode.setPlane( plane )
    planeNode.setVizScale(10)
    node = render.attachNewNode( planeNode )
    planeNP = NodePath(node)
    planeNP.wrtReparentTo(render)
    planeNP.show()
    p1.node.setClipPlane(planeNP)

    base.cam.setPos(-2.5,-15,45)
    base.cam.lookAt(Point3(shaderNode.getPos()+Point3(10,10,0)))
    clippedNode.wrtReparentTo(camera)


if __name__ == '__main__':
    load_meshes()
    TEST_clipping()
    run()

Hmm, this surprises me. I haven’t had any problems with clipping planes before.

One thing that you should understand: there are two parts to clipping. First, Panda will cull out (completely remove) anything whose bounding voume appears to be entirely behind the clipping plane. It will also implicitly disable the clipping plane for anything whose bounding volume appears to be entirely in front of the clipping plane.

Second, Panda will render the objects, and any object whose bounding volume intersects the clipping plane itself–that is, the object is neither entirely behind or entirely in front of the plane–will be rendered with commands to the graphics pipe to apply the clipping plane. This produces the visible effect of a slice through the object. (And this is why clipping planes work only on a per-object level when you have a shader in effect, unless you specifically program your shader to observe clipping planes.)

So, from your screenshots, I think it appears that it is the first step that is failing: Panda is incorrectly determining the bounding volume of these objects, or it is incorrectly deciding that their bounding volumes are entirely on one side of the plane or the other.

It may be the unusual scales you’re applying. Non-uniform scales can have surprising effects. Try flattening out the scales onto the vertices, with something like shape.flattenLight(). If that solves the problem, it’s a bug, but it’s a solvable bug.

You can also reveal the bounding volumes with shape.showBounds().

David

Thanks for the answer David, it works very well with flattenLight.
The only thing that bother me is that now, I have to make a copy of the mesh stored in the dico (in create_placeholder). If I only make an instance, it cumulates the scales of the objects who references the same mesh.

Is there any reason not to make a copy? The only reason I can think of resisting making a copy is if you are very low on memory and the object in question uses many hundreds of thousands of vertices.

David

Hi,
I’d like to know if this ‘bug’ is solved in the new version ? Cause now I can’t make a copy of all objects I have to display cause it’s taking a lot of memory.
Thanks.

I believe the bug is fixed; have you tried it?

But, wow, it would take a lot of vertices before you started using up many megabytes of memory. Are you really running out?

David

I just tried and unfortunately, it doesn’t work :cry:

In fact I want to display a city with more than 1000 buildings (around 300/350 triangles) and have a lot of deco on the roads (like cars, tree,…) which also have a lot of triangle.

So at the beginning the copy is not a problem, but each time I want a new tree or car on my road (and there are more than one tree/car per road), the use of memory increase. And it also increase at each new building (which basically are the same (around 5 differents model) but only with a different size).

I also want to stay constant in memory (no matter the size of my city), so that’s why I really want to do an instance instead of a copy.

You’re absolutely right, it doesn’t work. My apologies. :frowning:

Here’s another workaround. In your Config.prc, put the line:
clip-plane-cull 0
This should disable Panda’s attempt to cull to the clip planes, and let the graphics driver handle all of the clipping/culling. It solves the problem for now. In the meantime, I’ll fix it again within Panda.

David

FYI, this problem is now truly fixed on the trunk. The fix will be part of a future Panda release. It turned out that the real problem was the extreme non-uniform scales applied to the geometry in the example code, which was triggering a bug in the plane transform code (which wasn’t properly accounting for non-uniform scales).

In any case, the above workaround should be a perfectly acceptable solution until the real fix is available. My apologies once more.

David

Thank you very much.