0
$\begingroup$

With python, I search to select objects in the field of view of the main camera. I found this great script by ideasman42.

It's half perfect. I don't understand why the script also selects objects behind the main camera. I need help please: How to not select objects behind main camera?

$\endgroup$

2 Answers 2

2
$\begingroup$

Add a plane on perspective camera looking forward.

As a quick work around is to add a plane looking forward on a perspective camera, by adding this else clause circa line 32 of original code.

if not is_persp:
    # add a 5th plane to ignore objects behind the view
    n = normal(frame[0], frame[1], frame[2])
    d = -n.dot(origin)
    planes.append((n, d))
else:
    n = (matrix * Vector((0, 0, -1)) - origin).normalized()
    d = 0
    planes.append((n, d))

A cameras line of view is its negative local Z axis. All above does is adds a plane that has a global normal looking in the same direction.

enter image description here Image shows plane with normal, the Z axis pointing in direction of camera's -Z axis.

$\endgroup$
2
  • $\begingroup$ Thanks for feedback and clear captures ! I added this else condition but the result is not change. Why objects behind main camera are select ? $\endgroup$
    – LukeNukem
    Commented Mar 28, 2018 at 9:25
  • $\begingroup$ I think the issue has to do with the is_segment_in_planes() function - when I disable that check, the objects behind don't get selected. $\endgroup$ Commented Nov 3, 2021 at 21:56
0
$\begingroup$

Despite being late, here goes. I too tried the script and although it worked in some cases, it didn't for others. The issue was not due to the lack of a back plane from the other answer (although it is a more accurate), since some objects in my tests would be selected behind or even to the side of the camera. It's due to the planes extending beyond the frustrum, potentially intersecting objects behind or even to the side of the camera.

I noticed a snippet of commented code to get the clipped points from is_segment_in_planes. So I used those points and double checked they were reasonably within the camera frustrum via a negative epsilon:

p1_clip = p1.lerp(p2, p1_fac)
p2_clip = p1.lerp(p2, p2_fac)
epsilon = -0.5
return all(side_of_plane(p, p1_clip) > epsilon and side_of_plane(p, p2_clip) > epsilon for p in planes)

And it threw out objects that did not intersect the planes within the frustrum.

tl:dr, here's my solution:

from mathutils.geometry import normal
from mathutils import Vector
from bpy import context

def camera_as_planes(scene, obj):
    """
    Return planes in world-space which represent the camera view bounds.
    """

    camera = obj.data
    # normalize to ignore camera scale
    matrix = obj.matrix_world
    frame = [matrix @ v for v in camera.view_frame(scene=scene)]
    origin = matrix.to_translation()

    planes = []
    is_persp = (camera.type != 'ORTHO')
    for i in range(4):
        # find the 3rd point to define the planes direction
        if is_persp:
            frame_other = origin
        else:
            frame_other = frame[i] + matrix.col[2].xyz

        n = normal((frame_other, frame[i - 1], frame[i]))
        d = -n.dot(frame_other)
        planes.append((n, d))

    if not is_persp:
        # add a 5th plane to ignore objects behind the view
        n = normal((frame[0], frame[1], frame[2]))
        d = -n.dot(origin)
        planes.append((n, d))

    return planes


def side_of_plane(p, v):
    return p[0].dot(v) + p[1]


def is_segment_in_planes(p1, p2, planes):
    dp = p2 - p1

    p1_fac = 0.0
    p2_fac = 1.0

    for p in planes:
        div = dp.dot(p[0])
        if div != 0.0:
            t = -side_of_plane(p, p1)
            if div > 0.0:
                # clip p1 lower bounds
                if t >= div:
                    return False
                if t > 0.0:
                    fac = (t / div)
                    p1_fac = max(fac, p1_fac)
                    if p1_fac > p2_fac:
                        return False
            elif div < 0.0:
                # clip p2 upper bounds
                if t > 0.0:
                    return False
                if t > div:
                    fac = (t / div)
                    p2_fac = min(fac, p2_fac)
                    if p1_fac > p2_fac:
                        return False
    p1_clip = p1.lerp(p2, p1_fac)
    p2_clip = p1.lerp(p2, p2_fac)
    epsilon = -0.5
    return all(side_of_plane(p, p1_clip) > epsilon and side_of_plane(p, p2_clip) > epsilon for p in planes)


def point_in_object(obj, pt):
    xs = [v[0] for v in obj.bound_box]
    ys = [v[1] for v in obj.bound_box]
    zs = [v[2] for v in obj.bound_box]
    pt = obj.matrix_world.inverted() @ pt
    return (min(xs) <= pt.x <= max(xs) and
            min(ys) <= pt.y <= max(ys) and
            min(zs) <= pt.z <= max(zs))


def object_in_planes(obj, planes):
    matrix = obj.matrix_world
    box = [matrix @ Vector(v) for v in obj.bound_box]
    epsilon = -0.00001
    for v in box:
        if all(side_of_plane(p, v) > epsilon for p in planes):
            # one point was in all planes
            return True

    # possible one of our edges intersects
    edges = ((0, 1), (0, 3), (0, 4), (1, 2),
             (1, 5), (2, 3), (2, 6), (3, 7),
             (4, 5), (4, 7), (5, 6), (6, 7))
    if any(is_segment_in_planes(box[e[0]], box[e[1]], planes)
           for e in edges):
        return True

    return False


def visible_objects(objects, planes, origin, camera):
    """
    Return all objects which are inside (even partially) all planes.
    """
    return [obj for obj in objects
            if point_in_object(obj, origin) or object_in_planes(obj, planes)]


def select_objects_in_camera():
    scene = context.scene
    camera = context.scene.objects['Camera.002']
    origin = camera.matrix_world.to_translation()
    planes = camera_as_planes(scene, camera)
    objects_in_view = visible_objects(scene.objects, planes, origin, camera)

    for obj in objects_in_view:
        obj.select_set(True)


if __name__ == "__main__":
    select_objects_in_camera()
```
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .