# How to find all objects in the Camera's view with Python?

I have a scene with a many objects and one camera.

I would like to select the objects that the camera sees, even partially.

Is there a way to do that in Python that accounts for the objects bounds?

Note, I know that KD trees and Octrees are used for the lookup. There is mathutils.kdtree but the example only suggests how to query objects close to a point. I'm not familiar with the math for combining camera frustrum and binary trees lookup.

• You might render the scene with shadeless materials and identify the visible objects by their color. In the next blender version there will be a new module which allows offscreen rendering. Jan 19 '16 at 14:05
• yes there may be a way and some decent workarounds, but it's not clear how you produce the mesh for splines: bevel / extrude? by another spline as taper? by mesh deformed by spline? - Here I see an issue, cause you may need to convert to mesh temporary anyway. After all you wanna see if the Resulting Mesh is in view and not the Spline line. So how are the splines/meshes or lod defined? (for mesh I have sort of a solution, but I'm not sure how to deal with splines)
– o.g.
Jan 20 '16 at 12:57
• I'm thinking about doing multiple kdtree searches in space along the vector that follows where the camera points to. First I could have a small hidden sphere (not an object, a math one) that follows the camera and everything colliding with it is meshed. That way nearby objects are meshed and then with several lookups in space a proper intervals, I find other objects in the camera field of view. Jan 20 '16 at 15:12
• You could use a kdtree if you have many many objects and so you filter a big part of them at first. Then perform the refined check only on a smaller list (as in example I gave). - For large amounts vs speed it makes sense
– o.g.
Jan 21 '16 at 10:56
• The only advantage of using a tree structure here would be if you have a static scene and want to move the camera around it. - In that case you could build a tree and re-use it for lookups. Jan 21 '16 at 21:43

There are a few different ways to handle this, you could...

• project into 2d space, then detect which objects are in the 2d frame.
• calculate the camera bounds in 3d and detect which objects are inside it.

Here is some sample code that demonstrates the second method. It uses a set of planes (4 or 5 for orthographic cameras) and finds all objects that have any part of their bounding boxes within the planes:

Note, this isn't all that elegant, we could for example have a single function that intersects 2 sets of planes - one for the camera - another for the bound-box. However this is at least working and can give you some starting point - others may like to improve or post a method that uses projection.

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

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

planes = []
from mathutils import Vector
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

## If we want the points
# p1_clip = p1.lerp(p2, p1_fac)
# p2_clip = p1.lerp(p2, p2_fac)
return True

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):
from mathutils import Vector

matrix = obj.matrix_world
box = [matrix @ Vector(v) for v in obj.bound_box]
for v in box:
if all(side_of_plane(p, v) > 0.0 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 objects_in_planes(objects, planes, origin):
"""
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():
from bpy import context
scene = context.scene
origin = scene.camera.matrix_world.to_translation()
planes = camera_as_planes(scene, scene.camera)
objects_in_view = objects_in_planes(scene.objects, planes, origin)

for obj in objects_in_view:
obj.select_set(True)

if __name__ == "__main__":
select_objects_in_camera()

• 2 questions: 1. isn't this what basically bpy_extras.object_utils.world_to_camera_view is giving as a factor (0-1 inside screen)? and 2. the bbox should be a bit larger (with a tolerance or so) so that it considers the result with bevel radius? or is that already there ?
– o.g.
Jan 21 '16 at 11:04
• 1) its just a different way to test - using planes instead of projection, wouldn't say its the same though. 2) As for padding the bound-box, curve bevel is taken into account, but perhaps render subdivision levels change the bound-box slightly - so you may want to do this, it depends on your exact use case - YMMV. Jan 21 '16 at 11:43
• ok, they are not the same and it may be better to clearly check by planes. As for padding, op stated that "he will not mesh the curves" unless in view, so we can assume the bbox goes only for the naked spline. - Thus the need for padding bbox or camera planes (probably simpler) to compensate for a "future" mesh, slightly larger than the initila bbox.
– o.g.
Jan 21 '16 at 12:06
• very cool answers both of you, I'll try all this between today and tomorrow, thank you ! Jan 21 '16 at 13:24
• There was an error in clip_segment_in_planes, now fixed. Jan 23 '16 at 1:27

First I will consider here:

• A list of objects, a scene, a camera, assuming you already defined these
• we only care about mesh or bezier curves (I include mesh for testing, you can skip it)
• we consider all splines inside a curve object (you can count only for first if the case)

• a tolerance: this helps with bevel. so we consider a bit before the spline point is in view

• a LOD, just for example (make your rules there)

Note that the tolerance here has to be related to a 0 to 1 factor and I just give a 0.03 out of the blue. this can be computed in relation to distance and a radius if u need more precision.

The steps are:

• for each object
• create a list of points, mesh points or by subdividing curves
• test if the point is inside rendered area, by screen factor 0 to 1
• create 0 if off screen or LOD (1, 2, 3 ...)
• produce a list of integers that store the state of each object

Key moments:

• for splines use mathutils.geometry.interpolate_bezier to subdivide it (we use points, so we need segments to be little here)
• for on screen use bpy_extras.object_utils.world_to_camera_view that gives a factor related to screen (0-1 for on screen, rest is off)

The code example would then be something like this:

# check if mesh or spline points are in view, (o.g. variant 1, 2016)

from mathutils.geometry import interpolate_bezier
from bpy_extras.object_utils import world_to_camera_view

def pointsOfMesh_or_subdivideSpline(object):
points = []

if getattr(object, 'type', '') == 'MESH':
points = [v.co for v in object.data.vertices]

elif getattr(object, 'type', '') ==  "CURVE":
splines = object.data.splines
for spline in splines:

if getattr(spline, 'type', '') == 'BEZIER':
resolution = spline.resolution_u + 1
last = 0 if spline.use_cyclic_u else 1

lenP = len(spline.bezier_points)
for i, p1 in enumerate(spline.bezier_points):
if i < lenP - last:
p2 = spline.bezier_points[(i + 1) % lenP]

knot1 = p1.co
handle1 = p1.handle_right
handle2 = p2.handle_left
knot2 = p2.co
_points = interpolate_bezier(knot1, handle1, handle2, knot2, resolution)
points.extend(_points)
return points

def testInView(coord, tolerance):
pointInView = False
z = -1

if scene is not None and camera is not None and coord is not None:
xFactor, yFactor, z = world_to_camera_view(scene, camera, coord)

# add this if you use ortho !!!:
#if camera.data.type != "PERSPECTIVE":
#    sx, sy = camera.data.shift_x, camera.data.shift_y
#    xFactor, yFactor = xFactor - 2 * sx, yFactor - 2 * sy

# !! tolerance can be computed with above z and radius or so
if -tolerance < xFactor < 1 + tolerance and  -tolerance < yFactor < 1 + tolerance and z > 0:
pointInView = True

return pointInView, z

objectsInViewList = []
LOD2limit = max(LOD2limit, LOD3limit)   #clamp lod 2 to lod 3

# mesh points or spline subdivision points with a tolerance
for object in ObjectList:
if object is None or getattr(object, 'type', '') not in ['MESH', 'CURVE']:
objectsInViewList.append(0)

else:
matrix = object.matrix_world
zlist = []

# see if any point is in view
points = pointsOfMesh_or_subdivideSpline(object)
for point in points:
pointInView, z = testInView(matrix * point, tolerance)
if pointInView: zlist.append(z)

# these LODs are just an example
if zlist == []: LOD = 0
elif min(zlist) < LOD3limit: LOD = 3
elif LOD3limit <= min(zlist) < LOD2limit: LOD = 2
else: LOD = 1

objectsInViewList.append(LOD)


so you get a list of 0 (hide) or LOD for each object in list

Again, note that tolerance is related to screen factor, so I just use a 0.03 here, but you can further calculate it related to z

Testing, just to see

All inputs there, I assume you have defined somehow. (I intentionally use None and lamp to see..)

Compare the result 0,1,2, list with the obvious state of objects Note that bezier 001 is slightly off screen, but I still get a 1, cause of the tolerance.

Note that I use Animation Nodes/ script node for convenience here, skips a lot of formalities, registering etc and I can use bogus obj list etc, plus it updates real time.

However, just to illustrate how the code works

• Checking if the points are in the view isn't totally reliable, since an edge may span the view but not have any vertices in it, or a plane may fill the camera completely (where no edges or vertices intersect). Jan 21 '16 at 10:56
• I know, that is why I have asked for clarification on how the splines mesh is done. Being done with bevel, I can use enough subdivisions and with a certain tolerance, that covers for bevel radius, it should work for the specific case. Also, the LOD is there so should cover for too close objects (I did not do that in the example) - Otherwise, I would have build the 4 planes for camera view sides and check the normals for i/o. That is basically what you did :), so no point in redoing
– o.g.
Jan 21 '16 at 11:59
• I would see the problem of no-vertice but edge intersection appear only at close-ups. To deal with this I could have the curves close to the camera meshed (as in my comment above). In this case, when in doubt, it's better to have more meshes than too little. Jan 21 '16 at 13:44
• @ideasman42 I tried to use your function but I caped getting error when multiplying the matrix_world and bound_box vector. first I tried to wrap it up with numpy array since the operator @ wasn't supported and after that I received a dimensionality error since the matrix holds shape of 4x4 and the vector shape of 3. I'm using blender 2.79b Jan 28 '20 at 6:30