This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/editors/space_view3d/view3d_utils.c
Sergey Sharybin f17fbf8065 Refactor: Rename Object->obmat to Object->object_to_world
Motivation is to disambiguate on the naming level what the matrix
actually means. It is very easy to understand the meaning backwards,
especially since in Python the name goes the opposite way (it is
called `world_matrix` in the Python API).

It is important to disambiguate the naming without making developers
to look into the comment in the header file (which is also not super
clear either). Additionally, more clear naming facilitates the unit
verification (or, in this case, space validation) when reading an
expression.

This patch calls the matrix `object_to_world` which makes it clear
from the local code what is it exactly going on. This is only done
on DNA level, and a lot of local variables still follow the old
naming.

A DNA rename is setup in a way that there is no change on the file
level, so there should be no regressions at all.

The possibility is to add `_matrix` or `_mat` suffix to the name
to make it explicit that it is a matrix. Although, not sure if it
really helps the readability, or is it something redundant.

Differential Revision: https://developer.blender.org/D16328
2022-11-01 10:48:18 +01:00

1749 lines
49 KiB
C

/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2008 Blender Foundation. All rights reserved. */
/** \file
* \ingroup spview3d
*
* 3D View checks and manipulation (no operators).
*/
#include <float.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include "DNA_camera_types.h"
#include "DNA_curve_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_world_types.h"
#include "MEM_guardedalloc.h"
#include "BLI_array_utils.h"
#include "BLI_bitmap_draw_2d.h"
#include "BLI_blenlib.h"
#include "BLI_math.h"
#include "BLI_utildefines.h"
#include "BKE_camera.h"
#include "BKE_context.h"
#include "BKE_object.h"
#include "BKE_scene.h"
#include "BKE_screen.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
#include "BIF_glutil.h"
#include "GPU_matrix.h"
#include "WM_api.h"
#include "WM_types.h"
#include "ED_keyframing.h"
#include "ED_screen.h"
#include "ED_undo.h"
#include "ED_view3d.h"
#include "UI_resources.h"
#include "view3d_intern.h" /* own include */
/* -------------------------------------------------------------------- */
/** \name View Data Access Utilities
* \{ */
void ED_view3d_background_color_get(const Scene *scene, const View3D *v3d, float r_color[3])
{
if (v3d->shading.background_type == V3D_SHADING_BACKGROUND_WORLD) {
if (scene->world) {
copy_v3_v3(r_color, &scene->world->horr);
return;
}
}
else if (v3d->shading.background_type == V3D_SHADING_BACKGROUND_VIEWPORT) {
copy_v3_v3(r_color, v3d->shading.background_color);
return;
}
UI_GetThemeColor3fv(TH_BACK, r_color);
}
bool ED_view3d_has_workbench_in_texture_color(const Scene *scene,
const Object *ob,
const View3D *v3d)
{
if (v3d->shading.type == OB_SOLID) {
if (v3d->shading.color_type == V3D_SHADING_TEXTURE_COLOR) {
return true;
}
if (ob && ob->mode == OB_MODE_TEXTURE_PAINT) {
return true;
}
}
else if (v3d->shading.type == OB_RENDER) {
if (STREQ(scene->r.engine, RE_engine_id_BLENDER_WORKBENCH)) {
return scene->display.shading.color_type == V3D_SHADING_TEXTURE_COLOR;
}
}
return false;
}
Camera *ED_view3d_camera_data_get(View3D *v3d, RegionView3D *rv3d)
{
/* establish the camera object,
* so we can default to view mapping if anything is wrong with it */
if ((rv3d->persp == RV3D_CAMOB) && v3d->camera && (v3d->camera->type == OB_CAMERA)) {
return v3d->camera->data;
}
return NULL;
}
void ED_view3d_dist_range_get(const View3D *v3d, float r_dist_range[2])
{
r_dist_range[0] = v3d->grid * 0.001f;
r_dist_range[1] = v3d->clip_end * 10.0f;
}
bool ED_view3d_clip_range_get(const Depsgraph *depsgraph,
const View3D *v3d,
const RegionView3D *rv3d,
float *r_clipsta,
float *r_clipend,
const bool use_ortho_factor)
{
CameraParams params;
BKE_camera_params_init(&params);
BKE_camera_params_from_view3d(&params, depsgraph, v3d, rv3d);
if (use_ortho_factor && params.is_ortho) {
const float fac = 2.0f / (params.clip_end - params.clip_start);
params.clip_start *= fac;
params.clip_end *= fac;
}
if (r_clipsta) {
*r_clipsta = params.clip_start;
}
if (r_clipend) {
*r_clipend = params.clip_end;
}
return params.is_ortho;
}
bool ED_view3d_viewplane_get(Depsgraph *depsgraph,
const View3D *v3d,
const RegionView3D *rv3d,
int winx,
int winy,
rctf *r_viewplane,
float *r_clip_start,
float *r_clip_end,
float *r_pixsize)
{
CameraParams params;
BKE_camera_params_init(&params);
BKE_camera_params_from_view3d(&params, depsgraph, v3d, rv3d);
BKE_camera_params_compute_viewplane(&params, winx, winy, 1.0f, 1.0f);
if (r_viewplane) {
*r_viewplane = params.viewplane;
}
if (r_clip_start) {
*r_clip_start = params.clip_start;
}
if (r_clip_end) {
*r_clip_end = params.clip_end;
}
if (r_pixsize) {
*r_pixsize = params.viewdx;
}
return params.is_ortho;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name View State/Context Utilities
* \{ */
void view3d_operator_needs_opengl(const bContext *C)
{
wmWindow *win = CTX_wm_window(C);
ARegion *region = CTX_wm_region(C);
view3d_region_operator_needs_opengl(win, region);
}
void view3d_region_operator_needs_opengl(wmWindow *UNUSED(win), ARegion *region)
{
/* for debugging purpose, context should always be OK */
if ((region == NULL) || (region->regiontype != RGN_TYPE_WINDOW)) {
printf("view3d_region_operator_needs_opengl error, wrong region\n");
}
else {
RegionView3D *rv3d = region->regiondata;
wmViewport(&region->winrct); /* TODO: bad */
GPU_matrix_projection_set(rv3d->winmat);
GPU_matrix_set(rv3d->viewmat);
}
}
void ED_view3d_polygon_offset(const RegionView3D *rv3d, const float dist)
{
if (rv3d->rflag & RV3D_ZOFFSET_DISABLED) {
return;
}
float viewdist = rv3d->dist;
/* Special exception for orthographic camera (`viewdist` isn't used for perspective cameras). */
if (dist != 0.0f) {
if (rv3d->persp == RV3D_CAMOB) {
if (rv3d->is_persp == false) {
viewdist = 1.0f / max_ff(fabsf(rv3d->winmat[0][0]), fabsf(rv3d->winmat[1][1]));
}
}
}
GPU_polygon_offset(viewdist, dist);
}
bool ED_view3d_context_activate(bContext *C)
{
bScreen *screen = CTX_wm_screen(C);
ScrArea *area = CTX_wm_area(C);
/* area can be NULL when called from python */
if (area == NULL || area->spacetype != SPACE_VIEW3D) {
area = BKE_screen_find_big_area(screen, SPACE_VIEW3D, 0);
}
if (area == NULL) {
return false;
}
ARegion *region = BKE_area_find_region_active_win(area);
if (region == NULL) {
return false;
}
/* Bad context switch. */
CTX_wm_area_set(C, area);
CTX_wm_region_set(C, region);
return true;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name View Clipping Utilities
* \{ */
void ED_view3d_clipping_calc_from_boundbox(float clip[4][4],
const BoundBox *bb,
const bool is_flip)
{
for (int val = 0; val < 4; val++) {
normal_tri_v3(clip[val], bb->vec[val], bb->vec[val == 3 ? 0 : val + 1], bb->vec[val + 4]);
if (UNLIKELY(is_flip)) {
negate_v3(clip[val]);
}
clip[val][3] = -dot_v3v3(clip[val], bb->vec[val]);
}
}
void ED_view3d_clipping_calc(
BoundBox *bb, float planes[4][4], const ARegion *region, const Object *ob, const rcti *rect)
{
/* init in case unproject fails */
memset(bb->vec, 0, sizeof(bb->vec));
/* four clipping planes and bounding volume */
/* first do the bounding volume */
for (int val = 0; val < 4; val++) {
float xs = ELEM(val, 0, 3) ? rect->xmin : rect->xmax;
float ys = ELEM(val, 0, 1) ? rect->ymin : rect->ymax;
ED_view3d_unproject_v3(region, xs, ys, 0.0, bb->vec[val]);
ED_view3d_unproject_v3(region, xs, ys, 1.0, bb->vec[4 + val]);
}
/* optionally transform to object space */
if (ob) {
float imat[4][4];
invert_m4_m4(imat, ob->object_to_world);
for (int val = 0; val < 8; val++) {
mul_m4_v3(imat, bb->vec[val]);
}
}
/* verify if we have negative scale. doing the transform before cross
* product flips the sign of the vector compared to doing cross product
* before transform then, so we correct for that. */
int flip_sign = (ob) ? is_negative_m4(ob->object_to_world) : false;
ED_view3d_clipping_calc_from_boundbox(planes, bb, flip_sign);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name View Clipping Clamp Min/Max
* \{ */
struct PointsInPlanesMinMax_UserData {
float min[3];
float max[3];
};
static void points_in_planes_minmax_fn(
const float co[3], int UNUSED(i), int UNUSED(j), int UNUSED(k), void *user_data_p)
{
struct PointsInPlanesMinMax_UserData *user_data = user_data_p;
minmax_v3v3_v3(user_data->min, user_data->max, co);
}
bool ED_view3d_clipping_clamp_minmax(const RegionView3D *rv3d, float min[3], float max[3])
{
/* 6 planes for the cube, 4..6 for the current view clipping planes. */
float planes[6 + 6][4];
/* Convert the min/max to 6 planes. */
for (int i = 0; i < 3; i++) {
float *plane_min = planes[(i * 2) + 0];
float *plane_max = planes[(i * 2) + 1];
zero_v3(plane_min);
zero_v3(plane_max);
plane_min[i] = -1.0f;
plane_min[3] = +min[i];
plane_max[i] = +1.0f;
plane_max[3] = -max[i];
}
/* Copy planes from the viewport & flip. */
int planes_len = 6;
int clip_len = (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXCLIP) ? 4 : 6;
for (int i = 0; i < clip_len; i++) {
negate_v4_v4(planes[planes_len], rv3d->clip[i]);
planes_len += 1;
}
/* Calculate points intersecting all planes (effectively intersecting two bounding boxes). */
struct PointsInPlanesMinMax_UserData user_data;
INIT_MINMAX(user_data.min, user_data.max);
const float eps_coplanar = 1e-4f;
const float eps_isect = 1e-6f;
if (isect_planes_v3_fn(
planes, planes_len, eps_coplanar, eps_isect, points_in_planes_minmax_fn, &user_data)) {
copy_v3_v3(min, user_data.min);
copy_v3_v3(max, user_data.max);
return true;
}
return false;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name View Bound-Box Utilities
* \{ */
static bool view3d_boundbox_clip_m4(const BoundBox *bb, const float persmatob[4][4])
{
int a, flag = -1, fl;
for (a = 0; a < 8; a++) {
float vec[4], min, max;
copy_v3_v3(vec, bb->vec[a]);
vec[3] = 1.0;
mul_m4_v4(persmatob, vec);
max = vec[3];
min = -vec[3];
fl = 0;
if (vec[0] < min) {
fl += 1;
}
if (vec[0] > max) {
fl += 2;
}
if (vec[1] < min) {
fl += 4;
}
if (vec[1] > max) {
fl += 8;
}
if (vec[2] < min) {
fl += 16;
}
if (vec[2] > max) {
fl += 32;
}
flag &= fl;
if (flag == 0) {
return true;
}
}
return false;
}
bool ED_view3d_boundbox_clip_ex(const RegionView3D *rv3d, const BoundBox *bb, float obmat[4][4])
{
/* return 1: draw */
float persmatob[4][4];
if (bb == NULL) {
return true;
}
mul_m4_m4m4(persmatob, (float(*)[4])rv3d->persmat, obmat);
return view3d_boundbox_clip_m4(bb, persmatob);
}
bool ED_view3d_boundbox_clip(RegionView3D *rv3d, const BoundBox *bb)
{
if (bb == NULL) {
return true;
}
return view3d_boundbox_clip_m4(bb, rv3d->persmatob);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name View Perspective & Mode Switching
*
* Misc view utility functions.
* \{ */
bool ED_view3d_offset_lock_check(const View3D *v3d, const RegionView3D *rv3d)
{
return (rv3d->persp != RV3D_CAMOB) && (v3d->ob_center_cursor || v3d->ob_center);
}
void ED_view3d_lastview_store(RegionView3D *rv3d)
{
copy_qt_qt(rv3d->lviewquat, rv3d->viewquat);
rv3d->lview = rv3d->view;
rv3d->lview_axis_roll = rv3d->view_axis_roll;
if (rv3d->persp != RV3D_CAMOB) {
rv3d->lpersp = rv3d->persp;
}
}
void ED_view3d_lock_clear(View3D *v3d)
{
v3d->ob_center = NULL;
v3d->ob_center_bone[0] = '\0';
v3d->ob_center_cursor = false;
v3d->flag2 &= ~V3D_LOCK_CAMERA;
}
void ED_view3d_persp_switch_from_camera(const Depsgraph *depsgraph,
View3D *v3d,
RegionView3D *rv3d,
const char persp)
{
BLI_assert(rv3d->persp == RV3D_CAMOB);
BLI_assert(persp != RV3D_CAMOB);
if (v3d->camera) {
Object *ob_camera_eval = DEG_get_evaluated_object(depsgraph, v3d->camera);
rv3d->dist = ED_view3d_offset_distance(
ob_camera_eval->object_to_world, rv3d->ofs, VIEW3D_DIST_FALLBACK);
ED_view3d_from_object(ob_camera_eval, rv3d->ofs, rv3d->viewquat, &rv3d->dist, NULL);
}
if (!ED_view3d_camera_lock_check(v3d, rv3d)) {
rv3d->persp = persp;
}
}
bool ED_view3d_persp_ensure(const Depsgraph *depsgraph, View3D *v3d, ARegion *region)
{
RegionView3D *rv3d = region->regiondata;
const bool autopersp = (U.uiflag & USER_AUTOPERSP) != 0;
BLI_assert((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ANY_TRANSFORM) == 0);
if (ED_view3d_camera_lock_check(v3d, rv3d)) {
return false;
}
if (rv3d->persp != RV3D_PERSP) {
if (rv3d->persp == RV3D_CAMOB) {
/* If autopersp and previous view was an axis one,
* switch back to PERSP mode, else reuse previous mode. */
char persp = (autopersp && RV3D_VIEW_IS_AXIS(rv3d->lview)) ? RV3D_PERSP : rv3d->lpersp;
ED_view3d_persp_switch_from_camera(depsgraph, v3d, rv3d, persp);
}
else if (autopersp && RV3D_VIEW_IS_AXIS(rv3d->view)) {
rv3d->persp = RV3D_PERSP;
}
return true;
}
return false;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Camera View Utilities
*
* Utilities for manipulating the camera-view.
* \{ */
bool ED_view3d_camera_view_zoom_scale(RegionView3D *rv3d, const float scale)
{
const float camzoom_init = rv3d->camzoom;
float zoomfac = BKE_screen_view3d_zoom_to_fac(rv3d->camzoom);
/* Clamp both before and after conversion to prevent NAN on negative values. */
zoomfac = zoomfac * scale;
CLAMP(zoomfac, RV3D_CAMZOOM_MIN_FACTOR, RV3D_CAMZOOM_MAX_FACTOR);
rv3d->camzoom = BKE_screen_view3d_zoom_from_fac(zoomfac);
CLAMP(rv3d->camzoom, RV3D_CAMZOOM_MIN, RV3D_CAMZOOM_MAX);
return (rv3d->camzoom != camzoom_init);
}
bool ED_view3d_camera_view_pan(ARegion *region, const float event_ofs[2])
{
RegionView3D *rv3d = region->regiondata;
const float camdxy_init[2] = {rv3d->camdx, rv3d->camdy};
const float zoomfac = BKE_screen_view3d_zoom_to_fac(rv3d->camzoom) * 2.0f;
rv3d->camdx += event_ofs[0] / (region->winx * zoomfac);
rv3d->camdy += event_ofs[1] / (region->winy * zoomfac);
CLAMP(rv3d->camdx, -1.0f, 1.0f);
CLAMP(rv3d->camdy, -1.0f, 1.0f);
return (camdxy_init[0] != rv3d->camdx) || (camdxy_init[1] != rv3d->camdy);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Camera Lock API
*
* Lock the camera to the 3D Viewport, allowing view manipulation to transform the camera.
* \{ */
bool ED_view3d_camera_lock_check(const View3D *v3d, const RegionView3D *rv3d)
{
return ((v3d->camera) && !ID_IS_LINKED(v3d->camera) && (v3d->flag2 & V3D_LOCK_CAMERA) &&
(rv3d->persp == RV3D_CAMOB));
}
void ED_view3d_camera_lock_init_ex(const Depsgraph *depsgraph,
View3D *v3d,
RegionView3D *rv3d,
const bool calc_dist)
{
if (ED_view3d_camera_lock_check(v3d, rv3d)) {
Object *ob_camera_eval = DEG_get_evaluated_object(depsgraph, v3d->camera);
if (calc_dist) {
/* using a fallback dist is OK here since ED_view3d_from_object() compensates for it */
rv3d->dist = ED_view3d_offset_distance(
ob_camera_eval->object_to_world, rv3d->ofs, VIEW3D_DIST_FALLBACK);
}
ED_view3d_from_object(ob_camera_eval, rv3d->ofs, rv3d->viewquat, &rv3d->dist, NULL);
}
}
void ED_view3d_camera_lock_init(const Depsgraph *depsgraph, View3D *v3d, RegionView3D *rv3d)
{
ED_view3d_camera_lock_init_ex(depsgraph, v3d, rv3d, true);
}
bool ED_view3d_camera_lock_sync(const Depsgraph *depsgraph, View3D *v3d, RegionView3D *rv3d)
{
if (ED_view3d_camera_lock_check(v3d, rv3d)) {
ObjectTfmProtectedChannels obtfm;
Object *root_parent;
if (v3d->camera->transflag & OB_TRANSFORM_ADJUST_ROOT_PARENT_FOR_VIEW_LOCK &&
(root_parent = v3d->camera->parent)) {
Object *ob_update;
float tmat[4][4];
float imat[4][4];
float view_mat[4][4];
float diff_mat[4][4];
float parent_mat[4][4];
while (root_parent->parent) {
root_parent = root_parent->parent;
}
Object *ob_camera_eval = DEG_get_evaluated_object(depsgraph, v3d->camera);
Object *root_parent_eval = DEG_get_evaluated_object(depsgraph, root_parent);
ED_view3d_to_m4(view_mat, rv3d->ofs, rv3d->viewquat, rv3d->dist);
normalize_m4_m4(tmat, ob_camera_eval->object_to_world);
invert_m4_m4(imat, tmat);
mul_m4_m4m4(diff_mat, view_mat, imat);
mul_m4_m4m4(parent_mat, diff_mat, root_parent_eval->object_to_world);
BKE_object_tfm_protected_backup(root_parent, &obtfm);
BKE_object_apply_mat4(root_parent, parent_mat, true, false);
BKE_object_tfm_protected_restore(root_parent, &obtfm, root_parent->protectflag);
ob_update = v3d->camera;
while (ob_update) {
DEG_id_tag_update(&ob_update->id, ID_RECALC_TRANSFORM);
WM_main_add_notifier(NC_OBJECT | ND_TRANSFORM, ob_update);
ob_update = ob_update->parent;
}
}
else {
/* always maintain the same scale */
const short protect_scale_all = (OB_LOCK_SCALEX | OB_LOCK_SCALEY | OB_LOCK_SCALEZ);
BKE_object_tfm_protected_backup(v3d->camera, &obtfm);
ED_view3d_to_object(depsgraph, v3d->camera, rv3d->ofs, rv3d->viewquat, rv3d->dist);
BKE_object_tfm_protected_restore(
v3d->camera, &obtfm, v3d->camera->protectflag | protect_scale_all);
DEG_id_tag_update(&v3d->camera->id, ID_RECALC_TRANSFORM);
WM_main_add_notifier(NC_OBJECT | ND_TRANSFORM, v3d->camera);
}
return true;
}
return false;
}
bool ED_view3d_camera_autokey(const Scene *scene,
ID *id_key,
struct bContext *C,
const bool do_rotate,
const bool do_translate)
{
if (autokeyframe_cfra_can_key(scene, id_key)) {
const float cfra = (float)scene->r.cfra;
ListBase dsources = {NULL, NULL};
/* add data-source override for the camera object */
ANIM_relative_keyingset_add_source(&dsources, id_key, NULL, NULL);
/* insert keyframes
* 1) on the first frame
* 2) on each subsequent frame
* TODO: need to check in future that frame changed before doing this
*/
if (do_rotate) {
struct KeyingSet *ks = ANIM_get_keyingset_for_autokeying(scene, ANIM_KS_ROTATION_ID);
ANIM_apply_keyingset(C, &dsources, NULL, ks, MODIFYKEY_MODE_INSERT, cfra);
}
if (do_translate) {
struct KeyingSet *ks = ANIM_get_keyingset_for_autokeying(scene, ANIM_KS_LOCATION_ID);
ANIM_apply_keyingset(C, &dsources, NULL, ks, MODIFYKEY_MODE_INSERT, cfra);
}
/* free temp data */
BLI_freelistN(&dsources);
return true;
}
return false;
}
bool ED_view3d_camera_lock_autokey(View3D *v3d,
RegionView3D *rv3d,
struct bContext *C,
const bool do_rotate,
const bool do_translate)
{
/* similar to ED_view3d_cameracontrol_update */
if (ED_view3d_camera_lock_check(v3d, rv3d)) {
Scene *scene = CTX_data_scene(C);
ID *id_key;
Object *root_parent;
if (v3d->camera->transflag & OB_TRANSFORM_ADJUST_ROOT_PARENT_FOR_VIEW_LOCK &&
(root_parent = v3d->camera->parent)) {
while (root_parent->parent) {
root_parent = root_parent->parent;
}
id_key = &root_parent->id;
}
else {
id_key = &v3d->camera->id;
}
return ED_view3d_camera_autokey(scene, id_key, C, do_rotate, do_translate);
}
return false;
}
bool ED_view3d_camera_lock_undo_test(const View3D *v3d,
const RegionView3D *rv3d,
struct bContext *C)
{
if (ED_view3d_camera_lock_check(v3d, rv3d)) {
if (ED_undo_is_memfile_compatible(C)) {
return true;
}
}
return false;
}
/**
* Create a MEMFILE undo-step for locked camera movement when transforming the view.
* Edit and texture paint mode don't use MEMFILE undo so undo push is skipped for them.
* NDOF and track-pad navigation would create an undo step on every gesture and we may end up with
* unnecessary undo steps so undo push for them is not supported for now.
* Operators that use smooth view for navigation are supported via an optional parameter field,
* see: #V3D_SmoothParams.undo_str.
*/
static bool view3d_camera_lock_undo_ex(const char *str,
const View3D *v3d,
const RegionView3D *rv3d,
struct bContext *C,
const bool undo_group)
{
if (ED_view3d_camera_lock_undo_test(v3d, rv3d, C)) {
if (undo_group) {
ED_undo_grouped_push(C, str);
}
else {
ED_undo_push(C, str);
}
return true;
}
return false;
}
bool ED_view3d_camera_lock_undo_push(const char *str,
const View3D *v3d,
const RegionView3D *rv3d,
bContext *C)
{
return view3d_camera_lock_undo_ex(str, v3d, rv3d, C, false);
}
bool ED_view3d_camera_lock_undo_grouped_push(const char *str,
const View3D *v3d,
const RegionView3D *rv3d,
bContext *C)
{
return view3d_camera_lock_undo_ex(str, v3d, rv3d, C, true);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Box View Support
*
* Use with quad-split so each view is clipped by the bounds of each view axis.
* \{ */
static void view3d_boxview_clip(ScrArea *area)
{
BoundBox *bb = MEM_callocN(sizeof(BoundBox), "clipbb");
float clip[6][4];
float x1 = 0.0f, y1 = 0.0f, z1 = 0.0f, ofs[3] = {0.0f, 0.0f, 0.0f};
/* create bounding box */
LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
if (region->regiontype == RGN_TYPE_WINDOW) {
RegionView3D *rv3d = region->regiondata;
if (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXCLIP) {
if (ELEM(rv3d->view, RV3D_VIEW_TOP, RV3D_VIEW_BOTTOM)) {
if (region->winx > region->winy) {
x1 = rv3d->dist;
}
else {
x1 = region->winx * rv3d->dist / region->winy;
}
if (region->winx > region->winy) {
y1 = region->winy * rv3d->dist / region->winx;
}
else {
y1 = rv3d->dist;
}
copy_v2_v2(ofs, rv3d->ofs);
}
else if (ELEM(rv3d->view, RV3D_VIEW_FRONT, RV3D_VIEW_BACK)) {
ofs[2] = rv3d->ofs[2];
if (region->winx > region->winy) {
z1 = region->winy * rv3d->dist / region->winx;
}
else {
z1 = rv3d->dist;
}
}
}
}
}
for (int val = 0; val < 8; val++) {
if (ELEM(val, 0, 3, 4, 7)) {
bb->vec[val][0] = -x1 - ofs[0];
}
else {
bb->vec[val][0] = x1 - ofs[0];
}
if (ELEM(val, 0, 1, 4, 5)) {
bb->vec[val][1] = -y1 - ofs[1];
}
else {
bb->vec[val][1] = y1 - ofs[1];
}
if (val > 3) {
bb->vec[val][2] = -z1 - ofs[2];
}
else {
bb->vec[val][2] = z1 - ofs[2];
}
}
/* normals for plane equations */
normal_tri_v3(clip[0], bb->vec[0], bb->vec[1], bb->vec[4]);
normal_tri_v3(clip[1], bb->vec[1], bb->vec[2], bb->vec[5]);
normal_tri_v3(clip[2], bb->vec[2], bb->vec[3], bb->vec[6]);
normal_tri_v3(clip[3], bb->vec[3], bb->vec[0], bb->vec[7]);
normal_tri_v3(clip[4], bb->vec[4], bb->vec[5], bb->vec[6]);
normal_tri_v3(clip[5], bb->vec[0], bb->vec[2], bb->vec[1]);
/* then plane equations */
for (int val = 0; val < 6; val++) {
clip[val][3] = -dot_v3v3(clip[val], bb->vec[val % 5]);
}
/* create bounding box */
LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
if (region->regiontype == RGN_TYPE_WINDOW) {
RegionView3D *rv3d = region->regiondata;
if (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXCLIP) {
rv3d->rflag |= RV3D_CLIPPING;
memcpy(rv3d->clip, clip, sizeof(clip));
if (rv3d->clipbb) {
MEM_freeN(rv3d->clipbb);
}
rv3d->clipbb = MEM_dupallocN(bb);
}
}
}
MEM_freeN(bb);
}
/**
* Find which axis values are shared between both views and copy to \a rv3d_dst
* taking axis flipping into account.
*/
static void view3d_boxview_sync_axis(RegionView3D *rv3d_dst, RegionView3D *rv3d_src)
{
/* absolute axis values above this are considered to be set (will be ~1.0f) */
const float axis_eps = 0.5f;
float viewinv[4];
/* use the view rotation to identify which axis to sync on */
float view_axis_all[4][3] = {
{1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}};
float *view_src_x = &view_axis_all[0][0];
float *view_src_y = &view_axis_all[1][0];
float *view_dst_x = &view_axis_all[2][0];
float *view_dst_y = &view_axis_all[3][0];
int i;
/* we could use rv3d->viewinv, but better not depend on view matrix being updated */
if (UNLIKELY(ED_view3d_quat_from_axis_view(rv3d_src->view, rv3d_src->view_axis_roll, viewinv) ==
false)) {
return;
}
invert_qt_normalized(viewinv);
mul_qt_v3(viewinv, view_src_x);
mul_qt_v3(viewinv, view_src_y);
if (UNLIKELY(ED_view3d_quat_from_axis_view(rv3d_dst->view, rv3d_dst->view_axis_roll, viewinv) ==
false)) {
return;
}
invert_qt_normalized(viewinv);
mul_qt_v3(viewinv, view_dst_x);
mul_qt_v3(viewinv, view_dst_y);
/* check source and dest have a matching axis */
for (i = 0; i < 3; i++) {
if (((fabsf(view_src_x[i]) > axis_eps) || (fabsf(view_src_y[i]) > axis_eps)) &&
((fabsf(view_dst_x[i]) > axis_eps) || (fabsf(view_dst_y[i]) > axis_eps))) {
rv3d_dst->ofs[i] = rv3d_src->ofs[i];
}
}
}
void view3d_boxview_sync(ScrArea *area, ARegion *region)
{
RegionView3D *rv3d = region->regiondata;
short clip = 0;
LISTBASE_FOREACH (ARegion *, region_test, &area->regionbase) {
if (region_test != region && region_test->regiontype == RGN_TYPE_WINDOW) {
RegionView3D *rv3dtest = region_test->regiondata;
if (RV3D_LOCK_FLAGS(rv3dtest) & RV3D_LOCK_ROTATION) {
rv3dtest->dist = rv3d->dist;
view3d_boxview_sync_axis(rv3dtest, rv3d);
clip |= RV3D_LOCK_FLAGS(rv3dtest) & RV3D_BOXCLIP;
ED_region_tag_redraw(region_test);
}
}
}
if (clip) {
view3d_boxview_clip(area);
}
}
void view3d_boxview_copy(ScrArea *area, ARegion *region)
{
RegionView3D *rv3d = region->regiondata;
bool clip = false;
LISTBASE_FOREACH (ARegion *, region_test, &area->regionbase) {
if (region_test != region && region_test->regiontype == RGN_TYPE_WINDOW) {
RegionView3D *rv3dtest = region_test->regiondata;
if (RV3D_LOCK_FLAGS(rv3dtest)) {
rv3dtest->dist = rv3d->dist;
copy_v3_v3(rv3dtest->ofs, rv3d->ofs);
ED_region_tag_redraw(region_test);
clip |= ((RV3D_LOCK_FLAGS(rv3dtest) & RV3D_BOXCLIP) != 0);
}
}
}
if (clip) {
view3d_boxview_clip(area);
}
}
void ED_view3d_quadview_update(ScrArea *area, ARegion *region, bool do_clip)
{
ARegion *region_sync = NULL;
RegionView3D *rv3d = region->regiondata;
/* this function copies flags from the first of the 3 other quadview
* regions to the 2 other, so it assumes this is the region whose
* properties are always being edited, weak */
short viewlock = rv3d->viewlock;
if ((viewlock & RV3D_LOCK_ROTATION) == 0) {
do_clip = (viewlock & RV3D_BOXCLIP) != 0;
viewlock = 0;
}
else if ((viewlock & RV3D_BOXVIEW) == 0 && (viewlock & RV3D_BOXCLIP) != 0) {
do_clip = true;
viewlock &= ~RV3D_BOXCLIP;
}
for (; region; region = region->prev) {
if (region->alignment == RGN_ALIGN_QSPLIT) {
rv3d = region->regiondata;
rv3d->viewlock = viewlock;
if (do_clip && (viewlock & RV3D_BOXCLIP) == 0) {
rv3d->rflag &= ~RV3D_BOXCLIP;
}
/* use region_sync so we sync with one of the aligned views below
* else the view jumps on changing view settings like 'clip'
* since it copies from the perspective view */
region_sync = region;
}
}
if (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW) {
view3d_boxview_sync(area, region_sync ? region_sync : area->regionbase.last);
}
/* ensure locked regions have an axis, locked user views don't make much sense */
if (viewlock & RV3D_LOCK_ROTATION) {
int index_qsplit = 0;
for (region = area->regionbase.first; region; region = region->next) {
if (region->alignment == RGN_ALIGN_QSPLIT) {
rv3d = region->regiondata;
if (rv3d->viewlock) {
if (!RV3D_VIEW_IS_AXIS(rv3d->view) || (rv3d->view_axis_roll != RV3D_VIEW_AXIS_ROLL_0)) {
rv3d->view = ED_view3d_lock_view_from_index(index_qsplit);
rv3d->view_axis_roll = RV3D_VIEW_AXIS_ROLL_0;
rv3d->persp = RV3D_ORTHO;
ED_view3d_lock(rv3d);
}
}
index_qsplit++;
}
}
}
ED_area_tag_redraw(area);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name View Auto-Depth Utilities
* \{ */
static float view_autodist_depth_margin(ARegion *region, const int mval[2], int margin)
{
rcti rect;
if (margin == 0) {
/* Get Z Depths, needed for perspective, nice for ortho */
rect.xmin = mval[0];
rect.ymin = mval[1];
rect.xmax = mval[0] + 1;
rect.ymax = mval[1] + 1;
}
else {
BLI_rcti_init_pt_radius(&rect, mval, margin);
}
ViewDepths depth_temp = {0};
view3d_depths_rect_create(region, &rect, &depth_temp);
float depth_close = view3d_depth_near(&depth_temp);
MEM_SAFE_FREE(depth_temp.depths);
return depth_close;
}
bool ED_view3d_autodist(Depsgraph *depsgraph,
ARegion *region,
View3D *v3d,
const int mval[2],
float mouse_worldloc[3],
const bool UNUSED(alphaoverride),
const float fallback_depth_pt[3])
{
float depth_close;
int margin_arr[] = {0, 2, 4};
bool depth_ok = false;
/* Get Z Depths, needed for perspective, nice for ortho */
ED_view3d_depth_override(depsgraph, region, v3d, NULL, V3D_DEPTH_NO_GPENCIL, NULL);
/* Attempt with low margin's first */
int i = 0;
do {
depth_close = view_autodist_depth_margin(region, mval, margin_arr[i++] * U.pixelsize);
depth_ok = (depth_close != FLT_MAX);
} while ((depth_ok == false) && (i < ARRAY_SIZE(margin_arr)));
if (depth_ok) {
float centx = (float)mval[0] + 0.5f;
float centy = (float)mval[1] + 0.5f;
if (ED_view3d_unproject_v3(region, centx, centy, depth_close, mouse_worldloc)) {
return true;
}
}
if (fallback_depth_pt) {
ED_view3d_win_to_3d_int(v3d, region, fallback_depth_pt, mval, mouse_worldloc);
return true;
}
return false;
}
bool ED_view3d_autodist_simple(ARegion *region,
const int mval[2],
float mouse_worldloc[3],
int margin,
const float *force_depth)
{
/* Get Z Depths, needed for perspective, nice for ortho */
float depth;
if (force_depth) {
depth = *force_depth;
}
else {
depth = view_autodist_depth_margin(region, mval, margin);
}
if (depth == FLT_MAX) {
return false;
}
float centx = (float)mval[0] + 0.5f;
float centy = (float)mval[1] + 0.5f;
return ED_view3d_unproject_v3(region, centx, centy, depth, mouse_worldloc);
}
static bool depth_segment_cb(int x, int y, void *userData)
{
struct {
const ViewDepths *vd;
int margin;
float depth;
} *data = userData;
int mval[2];
float depth;
mval[0] = x;
mval[1] = y;
if (ED_view3d_depth_read_cached(data->vd, mval, data->margin, &depth)) {
data->depth = depth;
return false;
}
return true;
}
bool ED_view3d_depth_read_cached_seg(
const ViewDepths *vd, const int mval_sta[2], const int mval_end[2], int margin, float *depth)
{
struct {
const ViewDepths *vd;
int margin;
float depth;
} data = {NULL};
int p1[2];
int p2[2];
data.vd = vd;
data.margin = margin;
data.depth = 1.0f;
copy_v2_v2_int(p1, mval_sta);
copy_v2_v2_int(p2, mval_end);
BLI_bitmap_draw_2d_line_v2v2i(p1, p2, depth_segment_cb, &data);
*depth = data.depth;
return (*depth != 1.0f);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name View Radius/Distance Utilities
*
* Use to calculate a distance to a point based on its radius.
* \{ */
float ED_view3d_radius_to_dist_persp(const float angle, const float radius)
{
return radius * (1.0f / tanf(angle / 2.0f));
}
float ED_view3d_radius_to_dist_ortho(const float lens, const float radius)
{
return radius / (DEFAULT_SENSOR_WIDTH / lens);
}
float ED_view3d_radius_to_dist(const View3D *v3d,
const ARegion *region,
const struct Depsgraph *depsgraph,
const char persp,
const bool use_aspect,
const float radius)
{
float dist;
BLI_assert(ELEM(persp, RV3D_ORTHO, RV3D_PERSP, RV3D_CAMOB));
BLI_assert((persp != RV3D_CAMOB) || v3d->camera);
if (persp == RV3D_ORTHO) {
dist = ED_view3d_radius_to_dist_ortho(v3d->lens, radius);
}
else {
float lens, sensor_size, zoom;
if (persp == RV3D_CAMOB) {
CameraParams params;
BKE_camera_params_init(&params);
params.clip_start = v3d->clip_start;
params.clip_end = v3d->clip_end;
Object *camera_eval = DEG_get_evaluated_object(depsgraph, v3d->camera);
BKE_camera_params_from_object(&params, camera_eval);
lens = params.lens;
sensor_size = BKE_camera_sensor_size(params.sensor_fit, params.sensor_x, params.sensor_y);
/* ignore 'rv3d->camzoom' because we want to fit to the cameras frame */
zoom = CAMERA_PARAM_ZOOM_INIT_CAMOB;
}
else {
lens = v3d->lens;
sensor_size = DEFAULT_SENSOR_WIDTH;
zoom = CAMERA_PARAM_ZOOM_INIT_PERSP;
}
float angle = focallength_to_fov(lens, sensor_size);
/* zoom influences lens, correct this by scaling the angle as a distance
* (by the zoom-level) */
angle = atanf(tanf(angle / 2.0f) * zoom) * 2.0f;
dist = ED_view3d_radius_to_dist_persp(angle, radius);
}
if (use_aspect) {
const RegionView3D *rv3d = region->regiondata;
float winx, winy;
if (persp == RV3D_CAMOB) {
/* camera frame x/y in pixels */
winx = region->winx / rv3d->viewcamtexcofac[0];
winy = region->winy / rv3d->viewcamtexcofac[1];
}
else {
winx = region->winx;
winy = region->winy;
}
if (winx && winy) {
float aspect = winx / winy;
if (aspect < 1.0f) {
aspect = 1.0f / aspect;
}
dist *= aspect;
}
}
return dist;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name View Distance Utilities
* \{ */
float ED_view3d_offset_distance(const float mat[4][4],
const float ofs[3],
const float fallback_dist)
{
float pos[4] = {0.0f, 0.0f, 0.0f, 1.0f};
float dir[4] = {0.0f, 0.0f, 1.0f, 0.0f};
mul_m4_v4(mat, pos);
add_v3_v3(pos, ofs);
mul_m4_v4(mat, dir);
normalize_v3(dir);
float dist = dot_v3v3(pos, dir);
if ((dist < FLT_EPSILON) && (fallback_dist != 0.0f)) {
dist = fallback_dist;
}
return dist;
}
void ED_view3d_distance_set(RegionView3D *rv3d, const float dist)
{
float viewinv[4];
float tvec[3];
BLI_assert(dist >= 0.0f);
copy_v3_fl3(tvec, 0.0f, 0.0f, rv3d->dist - dist);
/* rv3d->viewinv isn't always valid */
#if 0
mul_mat3_m4_v3(rv3d->viewinv, tvec);
#else
invert_qt_qt_normalized(viewinv, rv3d->viewquat);
mul_qt_v3(viewinv, tvec);
#endif
sub_v3_v3(rv3d->ofs, tvec);
rv3d->dist = dist;
}
bool ED_view3d_distance_set_from_location(RegionView3D *rv3d,
const float dist_co[3],
const float dist_min)
{
float viewinv[4];
invert_qt_qt_normalized(viewinv, rv3d->viewquat);
float tvec[3] = {0.0f, 0.0f, -1.0f};
mul_qt_v3(viewinv, tvec);
float dist_co_local[3];
negate_v3_v3(dist_co_local, rv3d->ofs);
sub_v3_v3v3(dist_co_local, dist_co, dist_co_local);
const float delta = dot_v3v3(tvec, dist_co_local);
const float dist_new = rv3d->dist + delta;
if (dist_new >= dist_min) {
madd_v3_v3fl(rv3d->ofs, tvec, -delta);
rv3d->dist = dist_new;
return true;
}
return false;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name View Axis Utilities
* \{ */
/**
* Lookup by axis-view, axis-roll.
*/
static float view3d_quat_axis[6][4][4] = {
/* RV3D_VIEW_FRONT */
{
{M_SQRT1_2, -M_SQRT1_2, 0.0f, 0.0f},
{0.5f, -0.5f, -0.5f, 0.5f},
{0, 0, -M_SQRT1_2, M_SQRT1_2},
{-0.5f, 0.5f, -0.5f, 0.5f},
},
/* RV3D_VIEW_BACK */
{
{0.0f, 0.0f, -M_SQRT1_2, -M_SQRT1_2},
{0.5f, 0.5f, -0.5f, -0.5f},
{M_SQRT1_2, M_SQRT1_2, 0, 0},
{0.5f, 0.5f, 0.5f, 0.5f},
},
/* RV3D_VIEW_LEFT */
{
{0.5f, -0.5f, 0.5f, 0.5f},
{0, -M_SQRT1_2, 0.0f, M_SQRT1_2},
{-0.5f, -0.5f, -0.5f, 0.5f},
{-M_SQRT1_2, 0, -M_SQRT1_2, 0},
},
/* RV3D_VIEW_RIGHT */
{
{0.5f, -0.5f, -0.5f, -0.5f},
{M_SQRT1_2, 0, -M_SQRT1_2, 0},
{0.5f, 0.5f, -0.5f, 0.5f},
{0, M_SQRT1_2, 0, M_SQRT1_2},
},
/* RV3D_VIEW_TOP */
{
{1.0f, 0.0f, 0.0f, 0.0f},
{M_SQRT1_2, 0, 0, M_SQRT1_2},
{0, 0, 0, 1},
{-M_SQRT1_2, 0, 0, M_SQRT1_2},
},
/* RV3D_VIEW_BOTTOM */
{
{0.0f, -1.0f, 0.0f, 0.0f},
{0, -M_SQRT1_2, -M_SQRT1_2, 0},
{0, 0, -1, 0},
{0, M_SQRT1_2, -M_SQRT1_2, 0},
},
};
bool ED_view3d_quat_from_axis_view(const char view, const char view_axis_roll, float r_quat[4])
{
BLI_assert(view_axis_roll <= RV3D_VIEW_AXIS_ROLL_270);
if (RV3D_VIEW_IS_AXIS(view)) {
copy_qt_qt(r_quat, view3d_quat_axis[view - RV3D_VIEW_FRONT][view_axis_roll]);
return true;
}
return false;
}
bool ED_view3d_quat_to_axis_view(const float quat[4],
const float epsilon,
char *r_view,
char *r_view_axis_roll)
{
*r_view = RV3D_VIEW_USER;
*r_view_axis_roll = RV3D_VIEW_AXIS_ROLL_0;
/* Quaternion values are all unit length. */
if (epsilon < M_PI_4) {
/* Under 45 degrees, just pick the closest value. */
for (int view = RV3D_VIEW_FRONT; view <= RV3D_VIEW_BOTTOM; view++) {
for (int view_axis_roll = RV3D_VIEW_AXIS_ROLL_0; view_axis_roll <= RV3D_VIEW_AXIS_ROLL_270;
view_axis_roll++) {
if (fabsf(angle_signed_qtqt(
quat, view3d_quat_axis[view - RV3D_VIEW_FRONT][view_axis_roll])) < epsilon) {
*r_view = view;
*r_view_axis_roll = view_axis_roll;
return true;
}
}
}
}
else {
/* Epsilon over 45 degrees, check all & find use the closest. */
float delta_best = FLT_MAX;
for (int view = RV3D_VIEW_FRONT; view <= RV3D_VIEW_BOTTOM; view++) {
for (int view_axis_roll = RV3D_VIEW_AXIS_ROLL_0; view_axis_roll <= RV3D_VIEW_AXIS_ROLL_270;
view_axis_roll++) {
const float delta_test = fabsf(
angle_signed_qtqt(quat, view3d_quat_axis[view - RV3D_VIEW_FRONT][view_axis_roll]));
if (delta_best > delta_test) {
delta_best = delta_test;
*r_view = view;
*r_view_axis_roll = view_axis_roll;
}
}
}
if (*r_view != RV3D_VIEW_USER) {
return true;
}
}
return false;
}
bool ED_view3d_quat_to_axis_view_and_reset_quat(float quat[4],
const float epsilon,
char *r_view,
char *r_view_axis_roll)
{
const bool is_axis_view = ED_view3d_quat_to_axis_view(quat, epsilon, r_view, r_view_axis_roll);
if (is_axis_view) {
/* Reset `quat` to it's view axis, so axis-aligned views are always *exactly* aligned. */
BLI_assert(*r_view != RV3D_VIEW_USER);
ED_view3d_quat_from_axis_view(*r_view, *r_view_axis_roll, quat);
}
return is_axis_view;
}
char ED_view3d_lock_view_from_index(int index)
{
switch (index) {
case 0:
return RV3D_VIEW_FRONT;
case 1:
return RV3D_VIEW_TOP;
case 2:
return RV3D_VIEW_RIGHT;
default:
return RV3D_VIEW_USER;
}
}
char ED_view3d_axis_view_opposite(char view)
{
switch (view) {
case RV3D_VIEW_FRONT:
return RV3D_VIEW_BACK;
case RV3D_VIEW_BACK:
return RV3D_VIEW_FRONT;
case RV3D_VIEW_LEFT:
return RV3D_VIEW_RIGHT;
case RV3D_VIEW_RIGHT:
return RV3D_VIEW_LEFT;
case RV3D_VIEW_TOP:
return RV3D_VIEW_BOTTOM;
case RV3D_VIEW_BOTTOM:
return RV3D_VIEW_TOP;
}
return RV3D_VIEW_USER;
}
bool ED_view3d_lock(RegionView3D *rv3d)
{
return ED_view3d_quat_from_axis_view(rv3d->view, rv3d->view_axis_roll, rv3d->viewquat);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name View Transform Utilities
* \{ */
void ED_view3d_from_m4(const float mat[4][4], float ofs[3], float quat[4], const float *dist)
{
float nmat[3][3];
/* dist depends on offset */
BLI_assert(dist == NULL || ofs != NULL);
copy_m3_m4(nmat, mat);
normalize_m3(nmat);
/* Offset */
if (ofs) {
negate_v3_v3(ofs, mat[3]);
}
/* Quat */
if (quat) {
mat3_normalized_to_quat(quat, nmat);
invert_qt_normalized(quat);
}
if (ofs && dist) {
madd_v3_v3fl(ofs, nmat[2], *dist);
}
}
void ED_view3d_to_m4(float mat[4][4], const float ofs[3], const float quat[4], const float dist)
{
const float iviewquat[4] = {-quat[0], quat[1], quat[2], quat[3]};
float dvec[3] = {0.0f, 0.0f, dist};
quat_to_mat4(mat, iviewquat);
mul_mat3_m4_v3(mat, dvec);
sub_v3_v3v3(mat[3], dvec, ofs);
}
void ED_view3d_from_object(const Object *ob, float ofs[3], float quat[4], float *dist, float *lens)
{
ED_view3d_from_m4(ob->object_to_world, ofs, quat, dist);
if (lens) {
CameraParams params;
BKE_camera_params_init(&params);
BKE_camera_params_from_object(&params, ob);
*lens = params.lens;
}
}
void ED_view3d_to_object(const Depsgraph *depsgraph,
Object *ob,
const float ofs[3],
const float quat[4],
const float dist)
{
float mat[4][4];
ED_view3d_to_m4(mat, ofs, quat, dist);
Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob);
BKE_object_apply_mat4_ex(ob, mat, ob_eval->parent, ob_eval->parentinv, true);
}
static bool view3d_camera_to_view_selected_impl(struct Main *bmain,
Depsgraph *depsgraph,
const Scene *scene,
Object *camera_ob,
float *r_clip_start,
float *r_clip_end)
{
Object *camera_ob_eval = DEG_get_evaluated_object(depsgraph, camera_ob);
float co[3]; /* the new location to apply */
float scale; /* only for ortho cameras */
if (BKE_camera_view_frame_fit_to_scene(
depsgraph, scene, camera_ob_eval, co, &scale, r_clip_start, r_clip_end)) {
ObjectTfmProtectedChannels obtfm;
float obmat_new[4][4];
bool is_ortho_camera = false;
if ((camera_ob_eval->type == OB_CAMERA) &&
(((Camera *)camera_ob_eval->data)->type == CAM_ORTHO)) {
((Camera *)camera_ob->data)->ortho_scale = scale;
is_ortho_camera = true;
}
copy_m4_m4(obmat_new, camera_ob_eval->object_to_world);
copy_v3_v3(obmat_new[3], co);
/* only touch location */
BKE_object_tfm_protected_backup(camera_ob, &obtfm);
BKE_object_apply_mat4(camera_ob, obmat_new, true, true);
BKE_object_tfm_protected_restore(camera_ob, &obtfm, OB_LOCK_SCALE | OB_LOCK_ROT4D);
/* notifiers */
DEG_id_tag_update_ex(bmain, &camera_ob->id, ID_RECALC_TRANSFORM);
if (is_ortho_camera) {
DEG_id_tag_update_ex(bmain, camera_ob->data, ID_RECALC_PARAMETERS);
}
return true;
}
return false;
}
bool ED_view3d_camera_to_view_selected(struct Main *bmain,
Depsgraph *depsgraph,
const Scene *scene,
Object *camera_ob)
{
return view3d_camera_to_view_selected_impl(bmain, depsgraph, scene, camera_ob, NULL, NULL);
}
bool ED_view3d_camera_to_view_selected_with_set_clipping(struct Main *bmain,
Depsgraph *depsgraph,
const Scene *scene,
Object *camera_ob)
{
float clip_start;
float clip_end;
if (view3d_camera_to_view_selected_impl(
bmain, depsgraph, scene, camera_ob, &clip_start, &clip_end)) {
((Camera *)camera_ob->data)->clip_start = clip_start;
((Camera *)camera_ob->data)->clip_end = clip_end;
/* TODO: Support update via #ID_RECALC_PARAMETERS. */
Object *camera_ob_eval = DEG_get_evaluated_object(depsgraph, camera_ob);
((Camera *)camera_ob_eval->data)->clip_start = clip_start;
((Camera *)camera_ob_eval->data)->clip_end = clip_end;
return true;
}
return false;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Depth Buffer Utilities
* \{ */
struct ReadData {
int count;
int count_max;
float r_depth;
};
static bool depth_read_test_fn(const void *value, void *userdata)
{
struct ReadData *data = userdata;
float depth = *(float *)value;
if (depth < data->r_depth) {
data->r_depth = depth;
}
if ((++data->count) >= data->count_max) {
/* Outside the margin. */
return true;
}
return false;
}
bool ED_view3d_depth_read_cached(const ViewDepths *vd,
const int mval[2],
int margin,
float *r_depth)
{
BLI_assert(1.0 <= vd->depth_range[1]);
*r_depth = 1.0f;
if (!vd || !vd->depths) {
return false;
}
int x = mval[0];
int y = mval[1];
if (x < 0 || y < 0 || x >= vd->w || y >= vd->h) {
return false;
}
float depth = 1.0f;
if (margin) {
int shape[2] = {vd->w, vd->h};
int pixel_count = (min_ii(x + margin + 1, shape[1]) - max_ii(x - margin, 0)) *
(min_ii(y + margin + 1, shape[0]) - max_ii(y - margin, 0));
struct ReadData data;
data.count = 0;
data.count_max = pixel_count;
data.r_depth = 1.0f;
/* TODO: No need to go spiral. */
BLI_array_iter_spiral_square(vd->depths, shape, mval, depth_read_test_fn, &data);
depth = data.r_depth;
}
else {
depth = vd->depths[y * vd->w + x];
}
if (depth != 1.0f) {
*r_depth = depth;
return true;
}
return false;
}
bool ED_view3d_depth_read_cached_normal(const ARegion *region,
const ViewDepths *depths,
const int mval[2],
float r_normal[3])
{
/* NOTE: we could support passing in a radius.
* For now just read 9 pixels. */
/* pixels surrounding */
bool depths_valid[9] = {false};
float coords[9][3] = {{0}};
for (int x = 0, i = 0; x < 2; x++) {
for (int y = 0; y < 2; y++) {
const int mval_ofs[2] = {mval[0] + (x - 1), mval[1] + (y - 1)};
float depth_fl = 1.0f;
ED_view3d_depth_read_cached(depths, mval_ofs, 0, &depth_fl);
const double depth = (double)depth_fl;
if ((depth > depths->depth_range[0]) && (depth < depths->depth_range[1])) {
if (ED_view3d_depth_unproject_v3(region, mval_ofs, depth, coords[i])) {
depths_valid[i] = true;
}
}
i++;
}
}
const int edges[2][6][2] = {
/* x edges */
{{0, 1}, {1, 2}, {3, 4}, {4, 5}, {6, 7}, {7, 8}},
/* y edges */
{{0, 3}, {3, 6}, {1, 4}, {4, 7}, {2, 5}, {5, 8}},
};
float cross[2][3] = {{0.0f}};
for (int i = 0; i < 6; i++) {
for (int axis = 0; axis < 2; axis++) {
if (depths_valid[edges[axis][i][0]] && depths_valid[edges[axis][i][1]]) {
float delta[3];
sub_v3_v3v3(delta, coords[edges[axis][i][0]], coords[edges[axis][i][1]]);
add_v3_v3(cross[axis], delta);
}
}
}
cross_v3_v3v3(r_normal, cross[0], cross[1]);
if (normalize_v3(r_normal) != 0.0f) {
return true;
}
return false;
}
bool ED_view3d_depth_unproject_v3(const ARegion *region,
const int mval[2],
const double depth,
float r_location_world[3])
{
float centx = (float)mval[0] + 0.5f;
float centy = (float)mval[1] + 0.5f;
return ED_view3d_unproject_v3(region, centx, centy, depth, r_location_world);
}
/** \} */