Refactor 'fit in camera view' code, and expose it to RNA.

This changes BKE's fitting code to use `BKE_camera_params_compute_viewplane` instead of
`BKE_camera_view_frame`. This allows that code to work with orthographic projection too.

Also, two funcs were added to rna's Object, to resp. get the projection matrix of that
object (mostly useful for cameras and lamps objects), and return position this object
should be to see all (to fit) a given set of points.

Reviewers: campbellbarton

Reviewed By: campbellbarton

Differential Revision: https://developer.blender.org/D961
This commit is contained in:
Bastien Montagne 2015-01-03 12:05:16 +01:00
parent 16ed20ff3c
commit 780bb88a7a
7 changed files with 270 additions and 75 deletions

View File

@ -118,7 +118,9 @@ void BKE_camera_view_frame_ex(struct Scene *scene, struct Camera *camera, float
void BKE_camera_view_frame(struct Scene *scene, struct Camera *camera, float r_vec[4][3]);
bool BKE_camera_view_frame_fit_to_scene(struct Scene *scene, struct View3D *v3d, struct Object *camera_ob,
float r_co[3]);
float r_co[3], float *r_scale);
bool BKE_camera_view_frame_fit_to_coords(struct Scene *scene, float (*cos)[3], int num_cos,
struct Object *camera_ob, float r_co[3], float *r_scale);
#ifdef __cplusplus
}

View File

@ -39,6 +39,7 @@
#include "BLI_math.h"
#include "BLI_utildefines.h"
#include "BLI_rect.h"
#include "BKE_animsys.h"
#include "BKE_camera.h"
@ -458,13 +459,21 @@ void BKE_camera_view_frame(Scene *scene, Camera *camera, float r_vec[4][3])
dummy_asp, dummy_shift, &dummy_drawsize, r_vec);
}
#define CAMERA_VIEWFRAME_NUM_PLANES 4
typedef struct CameraViewFrameData {
float plane_tx[4][4]; /* 4 planes (not 4x4 matrix)*/
float frame_tx[4][3];
float normal_tx[4][3];
float dist_vals_sq[4]; /* distance squared (signed) */
float plane_tx[CAMERA_VIEWFRAME_NUM_PLANES][4]; /* 4 planes */
float normal_tx[CAMERA_VIEWFRAME_NUM_PLANES][3];
float dist_vals_sq[CAMERA_VIEWFRAME_NUM_PLANES]; /* distance squared (signed) */
unsigned int tot;
/* Ortho camera only. */
bool is_ortho;
float camera_no[3];
float dist_to_cam;
/* Not used by callbacks... */
float camera_rotmat[3][3];
} CameraViewFrameData;
static void camera_to_frame_view_cb(const float co[3], void *user_data)
@ -472,67 +481,106 @@ static void camera_to_frame_view_cb(const float co[3], void *user_data)
CameraViewFrameData *data = (CameraViewFrameData *)user_data;
unsigned int i;
for (i = 0; i < 4; i++) {
float nd = dist_signed_squared_to_plane_v3(co, data->plane_tx[i]);
if (nd < data->dist_vals_sq[i]) {
data->dist_vals_sq[i] = nd;
}
for (i = 0; i < CAMERA_VIEWFRAME_NUM_PLANES; i++) {
const float nd = dist_signed_squared_to_plane_v3(co, data->plane_tx[i]);
CLAMP_MAX(data->dist_vals_sq[i], nd);
}
if (data->is_ortho) {
const float d = dot_v3v3(data->camera_no, co);
CLAMP_MAX(data->dist_to_cam, d);
}
data->tot++;
}
/* don't move the camera, just yield the fit location */
/* only valid for perspective cameras */
bool BKE_camera_view_frame_fit_to_scene(Scene *scene, struct View3D *v3d, Object *camera_ob, float r_co[3])
static void camera_frame_fit_data_init(Scene *scene, Object *ob, CameraParams *params, CameraViewFrameData *data)
{
float shift[2];
float plane_tx[4][3];
float rot_obmat[3][3];
const float zero[3] = {0, 0, 0};
CameraViewFrameData data_cb;
float camera_rotmat_transposed_inversed[4][4];
unsigned int i;
BKE_camera_view_frame(scene, camera_ob->data, data_cb.frame_tx);
/* setup parameters */
BKE_camera_params_init(params);
BKE_camera_params_from_object(params, ob);
copy_m3_m4(rot_obmat, camera_ob->obmat);
normalize_m3(rot_obmat);
for (i = 0; i < 4; i++) {
/* normalize so Z is always 1.0f*/
mul_v3_fl(data_cb.frame_tx[i], 1.0f / data_cb.frame_tx[i][2]);
/* compute matrix, viewplane, .. */
if (scene) {
BKE_camera_params_compute_viewplane(params, scene->r.xsch, scene->r.ysch, scene->r.xasp, scene->r.yasp);
}
/* get the shift back out of the frame */
shift[0] = (data_cb.frame_tx[0][0] +
data_cb.frame_tx[1][0] +
data_cb.frame_tx[2][0] +
data_cb.frame_tx[3][0]) / 4.0f;
shift[1] = (data_cb.frame_tx[0][1] +
data_cb.frame_tx[1][1] +
data_cb.frame_tx[2][1] +
data_cb.frame_tx[3][1]) / 4.0f;
for (i = 0; i < 4; i++) {
mul_m3_v3(rot_obmat, data_cb.frame_tx[i]);
}
for (i = 0; i < 4; i++) {
normal_tri_v3(data_cb.normal_tx[i], zero, data_cb.frame_tx[i], data_cb.frame_tx[(i + 1) % 4]);
plane_from_point_normal_v3(data_cb.plane_tx[i], data_cb.frame_tx[i], data_cb.normal_tx[i]);
else {
BKE_camera_params_compute_viewplane(params, 1, 1, 1.0f, 1.0f);
}
BKE_camera_params_compute_matrix(params);
/* initialize callback data */
copy_v4_fl(data_cb.dist_vals_sq, FLT_MAX);
data_cb.tot = 0;
/* run callback on all visible points */
BKE_scene_foreach_display_point(scene, v3d, BA_SELECT,
camera_to_frame_view_cb, &data_cb);
copy_m3_m4(data->camera_rotmat, ob->obmat);
normalize_m3(data->camera_rotmat);
/* To transform a plane which is in its homogeneous representation (4d vector),
* we need the inverse of the transpose of the transform matrix... */
copy_m4_m3(camera_rotmat_transposed_inversed, data->camera_rotmat);
transpose_m4(camera_rotmat_transposed_inversed);
invert_m4(camera_rotmat_transposed_inversed);
if (data_cb.tot <= 1) {
/* Extract frustum planes from projection matrix. */
planes_from_projmat(params->winmat,
/* left right top bottom near far */
data->plane_tx[2], data->plane_tx[0], data->plane_tx[3], data->plane_tx[1], NULL, NULL);
/* Rotate planes and get normals from them */
for (i = 0; i < CAMERA_VIEWFRAME_NUM_PLANES; i++) {
mul_m4_v4(camera_rotmat_transposed_inversed, data->plane_tx[i]);
normalize_v3_v3(data->normal_tx[i], data->plane_tx[i]);
}
copy_v4_fl(data->dist_vals_sq, FLT_MAX);
data->tot = 0;
data->is_ortho = params->is_ortho;
if (params->is_ortho) {
/* we want (0, 0, -1) transformed by camera_rotmat, this is a quicker shortcut. */
negate_v3_v3(data->camera_no, data->camera_rotmat[2]);
data->dist_to_cam = FLT_MAX;
}
}
static bool camera_frame_fit_calc_from_data(
CameraParams *params, CameraViewFrameData *data, float r_co[3], float *r_scale)
{
float plane_tx[CAMERA_VIEWFRAME_NUM_PLANES][3];
unsigned int i;
if (data->tot <= 1) {
return false;
}
if (params->is_ortho) {
const float *cam_axis_x = data->camera_rotmat[0];
const float *cam_axis_y = data->camera_rotmat[1];
const float *cam_axis_z = data->camera_rotmat[2];
float dists[CAMERA_VIEWFRAME_NUM_PLANES];
float scale_diff;
/* apply the dist-from-plane's to the transformed plane points */
for (i = 0; i < CAMERA_VIEWFRAME_NUM_PLANES; i++) {
dists[i] = sqrtf_signed(data->dist_vals_sq[i]);
}
if ((dists[0] + dists[2]) > (dists[1] + dists[3])) {
scale_diff = (dists[1] + dists[3]) *
(BLI_rctf_size_x(&params->viewplane) / BLI_rctf_size_y(&params->viewplane));
}
else {
scale_diff = (dists[0] + dists[2]) *
(BLI_rctf_size_y(&params->viewplane) / BLI_rctf_size_x(&params->viewplane));
}
*r_scale = params->ortho_scale - scale_diff;
zero_v3(r_co);
madd_v3_v3fl(r_co, cam_axis_x, (dists[2] - dists[0]) * 0.5f + params->shiftx * scale_diff);
madd_v3_v3fl(r_co, cam_axis_y, (dists[1] - dists[3]) * 0.5f + params->shifty * scale_diff);
madd_v3_v3fl(r_co, cam_axis_z, -(data->dist_to_cam - 1.0f - params->clipsta));
return true;
}
else {
float plane_isect_1[3], plane_isect_1_no[3], plane_isect_1_other[3];
float plane_isect_2[3], plane_isect_2_no[3], plane_isect_2_other[3];
@ -540,16 +588,16 @@ bool BKE_camera_view_frame_fit_to_scene(Scene *scene, struct View3D *v3d, Object
float plane_isect_pt_1[3], plane_isect_pt_2[3];
/* apply the dist-from-plane's to the transformed plane points */
for (i = 0; i < 4; i++) {
mul_v3_v3fl(plane_tx[i], data_cb.normal_tx[i], sqrtf_signed(data_cb.dist_vals_sq[i]));
for (i = 0; i < CAMERA_VIEWFRAME_NUM_PLANES; i++) {
mul_v3_v3fl(plane_tx[i], data->normal_tx[i], sqrtf_signed(data->dist_vals_sq[i]));
}
if ((!isect_plane_plane_v3(plane_isect_1, plane_isect_1_no,
plane_tx[0], data_cb.normal_tx[0],
plane_tx[2], data_cb.normal_tx[2])) ||
plane_tx[0], data->normal_tx[0],
plane_tx[2], data->normal_tx[2])) ||
(!isect_plane_plane_v3(plane_isect_2, plane_isect_2_no,
plane_tx[1], data_cb.normal_tx[1],
plane_tx[3], data_cb.normal_tx[3])))
plane_tx[1], data->normal_tx[1],
plane_tx[3], data->normal_tx[3])))
{
return false;
}
@ -559,16 +607,17 @@ bool BKE_camera_view_frame_fit_to_scene(Scene *scene, struct View3D *v3d, Object
if (isect_line_line_v3(plane_isect_1, plane_isect_1_other,
plane_isect_2, plane_isect_2_other,
plane_isect_pt_1, plane_isect_pt_2) == 0)
plane_isect_pt_1, plane_isect_pt_2) != 0)
{
return false;
}
else {
float cam_plane_no[3] = {0.0f, 0.0f, -1.0f};
float cam_plane_no[3];
float plane_isect_delta[3];
float plane_isect_delta_len;
mul_m3_v3(rot_obmat, cam_plane_no);
float shift_fac = BKE_camera_sensor_size(params->sensor_fit, params->sensor_x, params->sensor_y) /
params->lens;
/* we want (0, 0, -1) transformed by camera_rotmat, this is a quicker shortcut. */
negate_v3_v3(cam_plane_no, data->camera_rotmat[2]);
sub_v3_v3v3(plane_isect_delta, plane_isect_pt_2, plane_isect_pt_1);
plane_isect_delta_len = len_v3(plane_isect_delta);
@ -578,18 +627,58 @@ bool BKE_camera_view_frame_fit_to_scene(Scene *scene, struct View3D *v3d, Object
/* offset shift */
normalize_v3(plane_isect_1_no);
madd_v3_v3fl(r_co, plane_isect_1_no, shift[1] * -plane_isect_delta_len);
madd_v3_v3fl(r_co, plane_isect_1_no, params->shifty * plane_isect_delta_len * shift_fac);
}
else {
copy_v3_v3(r_co, plane_isect_pt_2);
/* offset shift */
normalize_v3(plane_isect_2_no);
madd_v3_v3fl(r_co, plane_isect_2_no, shift[0] * -plane_isect_delta_len);
madd_v3_v3fl(r_co, plane_isect_2_no, params->shiftx * plane_isect_delta_len * shift_fac);
}
return true;
}
}
return false;
}
/* don't move the camera, just yield the fit location */
/* r_scale only valid/useful for ortho cameras */
bool BKE_camera_view_frame_fit_to_scene(
Scene *scene, struct View3D *v3d, Object *camera_ob, float r_co[3], float *r_scale)
{
CameraParams params;
CameraViewFrameData data_cb;
/* just in case */
*r_scale = 1.0f;
camera_frame_fit_data_init(scene, camera_ob, &params, &data_cb);
/* run callback on all visible points */
BKE_scene_foreach_display_point(scene, v3d, BA_SELECT, camera_to_frame_view_cb, &data_cb);
return camera_frame_fit_calc_from_data(&params, &data_cb, r_co, r_scale);
}
bool BKE_camera_view_frame_fit_to_coords(
Scene *scene, float (*cos)[3], int num_cos, Object *camera_ob, float r_co[3], float *r_scale)
{
CameraParams params;
CameraViewFrameData data_cb;
/* just in case */
*r_scale = 1.0f;
camera_frame_fit_data_init(scene, camera_ob, &params, &data_cb);
/* run callback on all given coordinates */
while (num_cos--) {
camera_to_frame_view_cb(cos[num_cos], &data_cb);
}
return camera_frame_fit_calc_from_data(&params, &data_cb, r_co, r_scale);
}

View File

@ -265,6 +265,9 @@ void orthographic_m4(float mat[4][4], const float left, const float right,
void window_translate_m4(float winmat[4][4], float perspmat[4][4],
const float x, const float y);
void planes_from_projmat(float mat[4][4], float left[4], float right[4], float top[4], float bottom[4],
float front[4], float back[4]);
int box_clip_bounds_m4(float boundbox[2][3],
const float bounds[4], float winmat[4][4]);
void box_minmax_bounds_m4(float min[3], float max[3],

View File

@ -3104,6 +3104,59 @@ void window_translate_m4(float winmat[4][4], float perspmat[4][4], const float x
}
}
/**
* Frustum planes extraction from a projection matrix (homogeneous 4d vector representations of planes).
*
* plane parameters can be NULL if you do not need them.
*/
void planes_from_projmat(float mat[4][4], float left[4], float right[4], float top[4], float bottom[4],
float near[4], float far[4])
{
/* References:
*
* https://fgiesen.wordpress.com/2012/08/31/frustum-planes-from-the-projection-matrix/
* http://www8.cs.umu.se/kurser/5DV051/HT12/lab/plane_extraction.pdf
*/
int i;
if (left) {
for (i = 4; i--; ) {
left[i] = mat[i][3] + mat[i][0];
}
}
if (right) {
for (i = 4; i--; ) {
right[i] = mat[i][3] - mat[i][0];
}
}
if (bottom) {
for (i = 4; i--; ) {
bottom[i] = mat[i][3] + mat[i][1];
}
}
if (top) {
for (i = 4; i--; ) {
top[i] = mat[i][3] - mat[i][1];
}
}
if (near) {
for (i = 4; i--; ) {
near[i] = mat[i][3] + mat[i][2];
}
}
if (far) {
for (i = 4; i--; ) {
far[i] = mat[i][3] - mat[i][2];
}
}
}
static void i_multmatrix(float icand[4][4], float Vm[4][4])
{
int row, col;

View File

@ -496,26 +496,22 @@ static int view3d_camera_to_view_selected_exec(bContext *C, wmOperator *op)
Object *camera_ob = v3d ? v3d->camera : scene->camera;
float r_co[3]; /* the new location to apply */
float r_scale; /* only for ortho cameras */
if (camera_ob == NULL) {
BKE_report(op->reports, RPT_ERROR, "No active camera");
return OPERATOR_CANCELLED;
}
else if (camera_ob->type != OB_CAMERA) {
BKE_report(op->reports, RPT_ERROR, "Object not a camera");
return OPERATOR_CANCELLED;
}
else if (((Camera *)camera_ob->data)->type == R_ORTHO) {
BKE_report(op->reports, RPT_ERROR, "Orthographic cameras not supported");
return OPERATOR_CANCELLED;
}
/* this function does all the important stuff */
if (BKE_camera_view_frame_fit_to_scene(scene, v3d, camera_ob, r_co)) {
if (BKE_camera_view_frame_fit_to_scene(scene, v3d, camera_ob, r_co, &r_scale)) {
ObjectTfmProtectedChannels obtfm;
float obmat_new[4][4];
if ((camera_ob->type == OB_CAMERA) && (((Camera *)camera_ob->data)->type == CAM_ORTHO)) {
((Camera *)camera_ob->data)->ortho_scale = r_scale;
}
copy_m4_m4(obmat_new, camera_ob->obmat);
copy_v3_v3(obmat_new[3], r_co);

View File

@ -42,6 +42,7 @@
#include "BLI_utildefines.h"
#include "BLI_listbase.h"
#include "BKE_camera.h"
#include "BKE_paint.h"
#include "BKE_editmesh.h"
#include "BKE_group.h" /* needed for BKE_group_object_exists() */

View File

@ -112,6 +112,28 @@ static void rna_Scene_mat_convert_space(Object *ob, ReportList *reports, bPoseCh
BKE_constraint_mat_convertspace(ob, pchan, (float (*)[4])mat_ret, from, to);
}
static void rna_Object_calc_matrix_camera(
Object *ob, float mat_ret[16], int width, int height, float scalex, float scaley)
{
CameraParams params;
/* setup parameters */
BKE_camera_params_init(&params);
BKE_camera_params_from_object(&params, ob);
/* compute matrix, viewplane, .. */
BKE_camera_params_compute_viewplane(&params, width, height, scalex, scaley);
BKE_camera_params_compute_matrix(&params);
copy_m4_m4((float (*)[4])mat_ret, params.winmat);
}
static void rna_Object_camera_fit_coords(
Object *ob, Scene *scene, int num_cos, float *cos, float co_ret[3], float *scale_ret)
{
BKE_camera_view_frame_fit_to_coords(scene, (float (*)[3])cos, num_cos / 3, ob, co_ret, scale_ret);
}
/* copied from Mesh_getFromObject and adapted to RNA interface */
/* settings: 0 - preview, 1 - render */
static Mesh *rna_Object_to_mesh(
@ -468,6 +490,35 @@ void RNA_api_object(StructRNA *srna)
parm = RNA_def_enum(func, "to_space", space_items, CONSTRAINT_SPACE_WORLD, "",
"The space to which you want to transform 'matrix'");
/* Camera-related operations */
func = RNA_def_function(srna, "calc_matrix_camera", "rna_Object_calc_matrix_camera");
RNA_def_function_ui_description(func, "Generate the camera projection matrix of this object "
"(mostly useful for Camera and Lamp types)");
parm = RNA_def_property(func, "result", PROP_FLOAT, PROP_MATRIX);
RNA_def_property_multi_array(parm, 2, rna_matrix_dimsize_4x4);
RNA_def_property_ui_text(parm, "", "The camera projection matrix");
RNA_def_function_output(func, parm);
parm = RNA_def_int(func, "x", 1, 0, INT_MAX, "", "Width of the render area", 0, 10000);
parm = RNA_def_int(func, "y", 1, 0, INT_MAX, "", "Height of the render area", 0, 10000);
parm = RNA_def_float(func, "scale_x", 1.0f, 1.0e-6f, FLT_MAX, "", "Width scaling factor", 1.0e-2f, 100.0f);
parm = RNA_def_float(func, "scale_y", 1.0f, 1.0e-6f, FLT_MAX, "", "height scaling factor", 1.0e-2f, 100.0f);
func = RNA_def_function(srna, "camera_fit_coords", "rna_Object_camera_fit_coords");
RNA_def_function_ui_description(func, "Compute the coordinate (and scale for ortho cameras) "
"given object should be to 'see' all given coordinates");
parm = RNA_def_pointer(func, "scene", "Scene", "", "Scene to get render size information from, if available");
RNA_def_property_flag(parm, PROP_REQUIRED);
parm = RNA_def_float_array(func, "coordinates", 1, NULL, -FLT_MAX, FLT_MAX, "", "Coordinates to fit in",
-FLT_MAX, FLT_MAX);
RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_DYNAMIC);
parm = RNA_def_property(func, "co_return", PROP_FLOAT, PROP_XYZ);
RNA_def_property_array(parm, 3);
RNA_def_property_ui_text(parm, "", "The location to aim to be able to see all given points");
RNA_def_property_flag(parm, PROP_OUTPUT);
parm = RNA_def_property(func, "scale_return", PROP_FLOAT, PROP_NONE);
RNA_def_property_ui_text(parm, "", "The ortho scale to aim to be able to see all given points (if relevant)");
RNA_def_property_flag(parm, PROP_OUTPUT);
/* mesh */
func = RNA_def_function(srna, "to_mesh", "rna_Object_to_mesh");
RNA_def_function_ui_description(func, "Create a Mesh datablock with modifiers applied");