3
$\begingroup$

I am writing a toy operator which select the visible vertices of a mesh from active camera.

My approach is to do a perspective projection on vertices of that mesh to NDC(Normalized Device Coordinates. It is a glossary from rasterization graphic API like OpenGL) and check that whether the NDC is in range of [-1, 1]^3.

I encounter a problem when I construct the projection matrix using camera.view_frame() function: It seems that view_frame() returns double of the real size of camera's sensor.

I am confused with this behavior of view_frame().


Supplementary background: I write a Blog post for details about this toy operator on http://linearconstraints.net/?p=53

And the full source code is on https://gist.github.com/thebusytypist/8900746

Note that I divide the left/right/bottom/top by 2 in line 75-76.

$\endgroup$
1
  • $\begingroup$ Comparing Camera.clip_start with the z-values of Camera.view_frame() it seems it is being doubled. There is Camera.clip_end, Camera.fov and you might get the aspect ratio from Scene.render.resolution_x/y. An explanation how exactly blender does the calculation would indeed be helpful. $\endgroup$ Commented Feb 9, 2014 at 18:22

1 Answer 1

4
$\begingroup$

There's a utility function world_to_camera_view(), which makes it really easy to select the view frustum:

import bpy
import bmesh
from bpy_extras.object_utils import world_to_camera_view

def main(self, context):
    sce = context.scene
    cam = sce.camera
    ob = context.object
    me = ob.data
    mat = ob.matrix_world

    if not me.is_editmode:
        bpy.ops.object.mode_set(mode='EDIT')

    bm = bmesh.from_edit_mesh(me)

    if not self.extend:
        bpy.ops.mesh.select_all(action='DESELECT')

    for v in bm.verts:
        x, y, z = world_to_camera_view(sce, cam, mat * v.co)
        if (0.0 <= x <= 1.0 and 
            0.0 <= y <= 1.0 and
            (not self.clip or cam.data.clip_start < z < cam.data.clip_end)):
            v.select = True

    bm.select_flush(True)


class MESH_OT_select_view_frustum(bpy.types.Operator):
    """Select geometry in view frustum (uses active object and scene camera)"""
    bl_idname = "mesh.select_view_frustum"
    bl_label = "Select View Frustum"
    bl_options = {'REGISTER', 'UNDO'}

    clip = bpy.props.BoolProperty(
        name="Clip",
        description="Take camera near and far clipping distances into account",
        default=True
    )

    extend = bpy.props.BoolProperty(
        name="Extend",
        description="Extend the current selection",
        default=False
    )

    @classmethod
    def poll(cls, context):
        return (context.object is not None and
                context.object.type == 'MESH' and
                context.scene.camera is not None)

    def execute(self, context):
        main(self, context)
        return {'FINISHED'}


def register():
    bpy.utils.register_class(MESH_OT_select_view_frustum)


def unregister():
    bpy.utils.unregister_class(MESH_OT_select_view_frustum)


if __name__ == "__main__":
    register()
$\endgroup$

You must log in to answer this question.

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