Point Occlusion and Visibility

Is it possible to check if a point is occluded as seen from the camera?

Currently I’m thinking of extending lines from points towards the camera, and if they intersect with geometry then consider them occluded. This doesn’t seem like an efficient method though.

Cheers

@Alasdair, the visibility may also depend on the current display mode.

In case of a Point3d you can first look if it is visible in the current view, get the direction vector from the camera location and then shoot a ray to all objects visible in that view. Then measure the reflections and compare distances to find about occlusion.

There is a rs.ShootRay() method which works for untrimmed surfaces and polysurface only, so if you want this to work properly with all object types i guess could work with Intersection.MeshRay using rendermeshes of shadeable objects.
_
c.

Thanks @clement , I think I’ve got it working using Intersection.MeshRay and rendermeshes

import rhinoscriptsyntax as rs
import Rhino
import scriptcontext as sc

def visibleRenderMesh():
    all = rs.AllObjects()
    all = rs.ObjectsByType(8 | 16 | 32)

    objects = []
    for item in all:
        if rs.IsVisibleInView(item):
            objects.append(rs.coercerhinoobject(item))
            
    objRef = Rhino.DocObjects.RhinoObject.GetRenderMeshes(objects, False, True)
    
    meshes = []
    for item in objRef:
        mesh = item.Mesh()
        meshes.append(mesh)
    
    #sc.doc.Objects.AddMesh(mesh)    
    return meshes
 
def isVisible(point, object, renderMeshes):
   
    projection = rs.ViewProjection()
    if projection == 1: #Parallel
        viewCPlane = rs.ViewCPlane()
        bounds = rs.BoundingBox(object, viewCPlane)
        rs.AddLine(bounds[0], bounds[4])
        depth = rs.Distance(bounds[0], bounds[4])
        direction = rs.VectorScale(viewCPlane[3], depth*1.2)
        viewpoint = rs.PointAdd(point, direction)
        ray = Rhino.Geometry.Ray3d(point, direction)
        
    elif projection == 2: #Perspective
        viewpoint = rs.ViewCameraPlane()[0]
        direction = -rs.VectorCreate(viewpoint, point)
        ray = Rhino.Geometry.Ray3d(viewpoint, direction)
        
    else: return False
    
    occluded = False
    for mesh in renderMeshes:
        intersection = Rhino.Geometry.Intersect.Intersection.MeshRay(mesh, ray)
        if 0.99 >= intersection > 0.01:
            occluded = True
            print "Vertex is Occluded"
            break

    if occluded == False:
        return point    

def visibleEditPoints():
    object = rs.GetObject(preselect=True, select=True)
    if not object: return
    
    brep_obj = rs.coercebrep(object)
    vertices = brep_obj.Vertices
    
    rs.EnableRedraw(False)
    
    meshes = visibleRenderMesh()
    
    for i in range (0,vertices.Count):
        point = isVisible(vertices[i].Location, object, meshes)
        if point: rs.AddPoint(point)
    
    rs.EnableRedraw(True)

visibleEditPoints()

I’m trying to make it work in orthographic views too, which might need a different approach, since meshray returns points that are obscured by geometry in the same plane. I could try a regular intersection.

Hi @Alasdair, note that this returns a parameter along the ray:

To get the point where the ray hit something, you could use:

if intersection > 0.0: hit_pt = ray.PointAt(intersection)

If you know the distance (A) from the camera location to your brep vertex, compare this with the distance (B) from the hit_pt to the camera location. (for the perspective case). You might need to add a small tolerance to this comparison. If B < A, the vertex is likely occluded.

In a parallel view you might get the first distance (A) by measuring the vertex point to a closest point on a plane. The plane could be made from the camera direction (normal) the origin of this plane is the camera location. Then compare with a distance (B) from the hit_pt to that same plane…
_
c.

@clement Comparing the intersection distances works well. Here’s the updated function:

def isVisible(point, object, renderMeshes):
    projection = rs.ViewProjection()
    viewCPlane = rs.ViewCameraPlane()
    
    if projection == 1: #Parallel
        
        bounds = rs.BoundingBox(object, viewCPlane)
        direction = viewCPlane[3]
        
        viewpoint = rs.PlaneClosestPoint(viewCPlane, point)
        ray = Rhino.Geometry.Ray3d(viewpoint, -direction)
        distance = rs.Distance(viewpoint, point)

        occluded = False

        for mesh in renderMeshes:
            
            intersection = Rhino.Geometry.Intersect.Intersection.MeshRay(mesh, ray)
            
            if intersection != -1.0:
                if abs(intersection-distance) > 0.1:
                    occluded = True
                    break
    
    elif projection == 2: #Perspective
        viewpoint = rs.ViewCameraPlane()[0]
        direction = -rs.VectorCreate(viewpoint, point)
        ray = Rhino.Geometry.Ray3d(viewpoint, direction)
        
        distanceA = rs.Distance(viewpoint, point)
   
        occluded = False
        for mesh in renderMeshes:
            intersection = Rhino.Geometry.Intersect.Intersection.MeshRay(mesh, ray)

            hit_pt = ray.PointAt(intersection)
            distanceB = rs.Distance(hit_pt, viewpoint)
            
            if abs(distanceA - distanceB) > 0.1:
                if distanceB < distanceA:
                    occluded = True
                    break
        
    else: return False

    if occluded == False: return point

Hi @Alasdair, great. You might speed things up a litle by getting the projection type once, outside the function as this does not change during the script run. I’ve found that measuring distances in rhinoscript syntax like this can be very slow:

distance = rs.Distance(viewpoint, point)

because it makes some kind of conversion of the inputs every time. It can be done like this without:

distance = viewpoint.DistanceTo(point)

_
c.

1 Like

True, same goes for many other vector-based operations. If you are going for speed, you are better off doing the math in your own funcitons and not call rs. for this. So for example rs.vectorcreate could be instead calculated like this:
FastVectorCreate = array(arrEnd(0) - arrStart(0), arrEnd(1) - arrStart(1), arrEnd(2) - arrStart(2))

(this is my vbscipt example, but it should give you an idea for more speedup. Same goes to all other vector and point operations)

–jarek

1 Like

HI @Alasdaire, @clement,
I am trying to put together a Python script to select all existing points which are visible in my current view and see you have this more sophisticated version written but I’m getting a bit confused by your logic surrounding objects and renderMeshes. Any chance one of you could cut down your existing script to help me out?
Best wishes from Nantes,
Graham

Hi @Dancergraham, i guess you would then have for every point, iterate over all geometry and find out if it is obscuring the point by shooting rays. Depending on the amount of points and objects, this can take a while. I am wondering if there is a better (faster) way to do this, eg. using the ZBuffer…

_
c.

Hi,
I would usually be looking at several hundred points and up to a few thousand surfaces or mesh faces - I would want it to run within a few seconds.
… looks up zbuffer …
err :confused:

I ended up hacking together another method using tools I already know, adding small spheres around my points, checking their visibility with the Rhino command _SelVisible and deleting the spheres again. It seems to work well enough for me for now, but the default sphere diameter of 0.1 will probably need some tweaking.
I got a bit scared by the ray tracing method :open_mouth:

import rhinoscriptsyntax as rs

def annotate_visible():
    """Annotate all visible points with a text dot"""
    rs.EnableRedraw(False)
    previous_selection = rs.SelectedObjects()
    rs.UnselectAllObjects()
    
    spheres = {}
    for pt in rs.ObjectsByType(1):
        spheres[pt] = rs.AddSphere(rs.PointCoordinates(pt), 0.1)
    rs.Redraw()
    rs.Command("_SelVisible _Enter")
    visible = set(rs.SelectedObjects())
    for pt, sphere in spheres.iteritems():
        if sphere in visible:
            nom = rs.ObjectName(pt)
            col = rs.ObjectColor(pt)
            dot = rs.AddTextDot(nom, rs.PointCoordinates(pt))
            rs.ObjectColor(dot, col)
            rs.ObjectName(dot,nom)
        rs.DeleteObject(sphere)
    rs.UnselectAllObjects()
    rs.SelectObjects(previous_selection)
    rs.EnableRedraw(True)

if __name__ == "__main__":
    annotate_visible()