This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/editors/space_view3d/view3d_camera_control.c
Hans Goudey cd49afc596 Fix T82596: Fly/walk navigation crash
Add a NULL check for the View3D's camera, because these
modes can be entered even without an active camera.
2020-11-12 17:06:57 -05:00

379 lines
12 KiB
C

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup spview3d
*
* The purpose of View3DCameraControl is to allow editing \a rv3d manipulation
* (mainly \a ofs and \a viewquat) for the purpose of view navigation
* without having to worry about positioning the camera, its parent...
* or other details.
* Typical view-control usage:
*
* - Acquire a view-control (#ED_view3d_cameracontrol_acquire).
* - Modify ``rv3d->ofs``, ``rv3d->viewquat``.
* - Update the view data (#ED_view3d_cameracontrol_acquire) -
* within a loop which draws the viewport.
* - Finish and release the view-control (#ED_view3d_cameracontrol_release),
* either keeping the current view or restoring the initial view.
*
* Notes:
*
* - when acquiring ``rv3d->dist`` is set to zero
* (so ``rv3d->ofs`` is always the view-point)
* - updating can optionally keyframe the camera object.
*/
#include "DNA_camera_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "MEM_guardedalloc.h"
#include "BLI_math.h"
#include "BLI_utildefines.h"
#include "BKE_object.h"
#include "DEG_depsgraph.h"
#include "ED_screen.h"
#include "view3d_intern.h" /* own include */
#include "BLI_strict_flags.h"
typedef struct View3DCameraControl {
/* -------------------------------------------------------------------- */
/* Context (assign these to vars before use) */
Scene *ctx_scene;
View3D *ctx_v3d;
RegionView3D *ctx_rv3d;
/* -------------------------------------------------------------------- */
/* internal vars */
/* for parenting calculation */
float view_mat_prev[4][4];
/* -------------------------------------------------------------------- */
/* optional capabilities */
bool use_parent_root;
/* -------------------------------------------------------------------- */
/* initial values */
/* root most parent */
Object *root_parent;
/* backup values */
float dist_backup;
/* backup the views distance since we use a zero dist for fly mode */
float ofs_backup[3];
/* backup the views offset in case the user cancels flying in non camera mode */
/* backup the views quat in case the user cancels flying in non camera mode. */
float rot_backup[4];
/* remember if we're ortho or not, only used for restoring the view if it was a ortho view */
char persp_backup;
/* are we flying an ortho camera in perspective view,
* which was originally in ortho view?
* could probably figure it out but better be explicit */
bool is_ortho_cam;
/* backup the objects transform */
void *obtfm;
} View3DCameraControl;
BLI_INLINE Object *view3d_cameracontrol_object(const View3DCameraControl *vctrl)
{
return vctrl->root_parent ? vctrl->root_parent : vctrl->ctx_v3d->camera;
}
/**
* Returns the object which is being manipulated or NULL.
*/
Object *ED_view3d_cameracontrol_object_get(View3DCameraControl *vctrl)
{
RegionView3D *rv3d = vctrl->ctx_rv3d;
if (rv3d->persp == RV3D_CAMOB) {
return view3d_cameracontrol_object(vctrl);
}
return NULL;
}
/**
* Creates a #View3DCameraControl handle and sets up
* the view for first-person style navigation.
*/
struct View3DCameraControl *ED_view3d_cameracontrol_acquire(Depsgraph *depsgraph,
Scene *scene,
View3D *v3d,
RegionView3D *rv3d)
{
View3DCameraControl *vctrl;
vctrl = MEM_callocN(sizeof(View3DCameraControl), __func__);
/* Store context */
vctrl->ctx_scene = scene;
vctrl->ctx_v3d = v3d;
vctrl->ctx_rv3d = rv3d;
vctrl->use_parent_root = v3d->camera != NULL &&
v3d->camera->transflag & OB_TRANSFORM_ADJUST_ROOT_PARENT_FOR_VIEW_LOCK;
vctrl->persp_backup = rv3d->persp;
vctrl->dist_backup = rv3d->dist;
/* check for flying ortho camera - which we cant support well
* we _could_ also check for an ortho camera but this is easier */
if ((rv3d->persp == RV3D_CAMOB) && (rv3d->is_persp == false)) {
((Camera *)v3d->camera->data)->type = CAM_PERSP;
vctrl->is_ortho_cam = true;
}
if (rv3d->persp == RV3D_CAMOB) {
Object *ob_back;
if (vctrl->use_parent_root && (vctrl->root_parent = v3d->camera->parent)) {
while (vctrl->root_parent->parent) {
vctrl->root_parent = vctrl->root_parent->parent;
}
ob_back = vctrl->root_parent;
}
else {
ob_back = v3d->camera;
}
/* store the original camera loc and rot */
vctrl->obtfm = BKE_object_tfm_backup(ob_back);
BKE_object_where_is_calc(depsgraph, scene, v3d->camera);
negate_v3_v3(rv3d->ofs, v3d->camera->obmat[3]);
rv3d->dist = 0.0;
}
else {
/* perspective or ortho */
if (rv3d->persp == RV3D_ORTHO) {
/* if ortho projection, make perspective */
rv3d->persp = RV3D_PERSP;
}
copy_qt_qt(vctrl->rot_backup, rv3d->viewquat);
copy_v3_v3(vctrl->ofs_backup, rv3d->ofs);
/* The dist defines a vector that is in front of the offset
* to rotate the view about.
* this is no good for fly mode because we
* want to rotate about the viewers center.
* but to correct the dist removal we must
* alter offset so the view doesn't jump. */
ED_view3d_distance_set(rv3d, 0.0f);
/* Done with correcting for the dist */
}
ED_view3d_to_m4(vctrl->view_mat_prev, rv3d->ofs, rv3d->viewquat, rv3d->dist);
return vctrl;
}
/**
* A version of #BKE_object_apply_mat4 that respects #Object.protectflag,
* applying the locking back to the view to avoid the view.
* This is needed so the view doesn't get out of sync with the object,
* causing visible jittering when in fly/walk mode for e.g.
*
* \note This could be exposed as an API option, as we might not want the view
* to be constrained by the thing it's controlling.
*/
static bool object_apply_mat4_with_protect(Object *ob,
const float obmat[4][4],
const bool use_parent,
/* Only use when applying lock. */
RegionView3D *rv3d,
const float view_mat[4][4])
{
const bool use_protect = (ob->protectflag != 0);
bool view_changed = false;
ObjectTfmProtectedChannels obtfm;
if (use_protect) {
BKE_object_tfm_protected_backup(ob, &obtfm);
}
BKE_object_apply_mat4(ob, obmat, true, use_parent);
if (use_protect) {
float obmat_noprotect[4][4], obmat_protect[4][4];
BKE_object_to_mat4(ob, obmat_noprotect);
BKE_object_tfm_protected_restore(ob, &obtfm, ob->protectflag);
BKE_object_to_mat4(ob, obmat_protect);
if (!equals_m4m4(obmat_noprotect, obmat_protect)) {
/* Apply the lock protection back to the view, without this the view
* keeps moving, ignoring the object locking, causing jittering in some cases. */
float diff_mat[4][4];
float view_mat_protect[4][4];
float obmat_noprotect_inv[4][4];
invert_m4_m4(obmat_noprotect_inv, obmat_noprotect);
mul_m4_m4m4(diff_mat, obmat_protect, obmat_noprotect_inv);
mul_m4_m4m4(view_mat_protect, diff_mat, view_mat);
ED_view3d_from_m4(view_mat_protect, rv3d->ofs, rv3d->viewquat, &rv3d->dist);
view_changed = true;
}
}
return view_changed;
}
/**
* Updates cameras from the ``rv3d`` values, optionally auto-keyframing.
*/
void ED_view3d_cameracontrol_update(View3DCameraControl *vctrl,
/* args for keyframing */
const bool use_autokey,
struct bContext *C,
const bool do_rotate,
const bool do_translate)
{
/* We are in camera view so apply the view offset and rotation to the view matrix
* and set the camera to the view. */
Scene *scene = vctrl->ctx_scene;
View3D *v3d = vctrl->ctx_v3d;
RegionView3D *rv3d = vctrl->ctx_rv3d;
ID *id_key;
float view_mat[4][4];
ED_view3d_to_m4(view_mat, rv3d->ofs, rv3d->viewquat, rv3d->dist);
/* transform the parent or the camera? */
if (vctrl->root_parent) {
Object *ob_update;
float prev_view_imat[4][4];
float diff_mat[4][4];
float parent_mat[4][4];
invert_m4_m4(prev_view_imat, vctrl->view_mat_prev);
mul_m4_m4m4(diff_mat, view_mat, prev_view_imat);
mul_m4_m4m4(parent_mat, diff_mat, vctrl->root_parent->obmat);
if (object_apply_mat4_with_protect(vctrl->root_parent, parent_mat, false, rv3d, view_mat)) {
/* Calculate again since the view locking changes the matrix. */
ED_view3d_to_m4(view_mat, rv3d->ofs, rv3d->viewquat, rv3d->dist);
}
ob_update = v3d->camera->parent;
while (ob_update) {
DEG_id_tag_update(&ob_update->id, ID_RECALC_TRANSFORM);
ob_update = ob_update->parent;
}
copy_m4_m4(vctrl->view_mat_prev, view_mat);
id_key = &vctrl->root_parent->id;
}
else {
float scale_mat[4][4];
float scale_back[3];
/* even though we handle the scale matrix, this still changes over time */
copy_v3_v3(scale_back, v3d->camera->scale);
size_to_mat4(scale_mat, v3d->camera->scale);
mul_m4_m4m4(view_mat, view_mat, scale_mat);
object_apply_mat4_with_protect(v3d->camera, view_mat, true, rv3d, view_mat);
DEG_id_tag_update(&v3d->camera->id, ID_RECALC_TRANSFORM);
copy_v3_v3(v3d->camera->scale, scale_back);
id_key = &v3d->camera->id;
}
/* record the motion */
if (use_autokey) {
ED_view3d_camera_autokey(scene, id_key, C, do_rotate, do_translate);
}
}
/**
* Release view control.
*
* \param restore: Sets the view state to the values that were set
* before #ED_view3d_control_acquire was called.
*/
void ED_view3d_cameracontrol_release(View3DCameraControl *vctrl, const bool restore)
{
View3D *v3d = vctrl->ctx_v3d;
RegionView3D *rv3d = vctrl->ctx_rv3d;
if (restore) {
/* Revert to original view? */
if (vctrl->persp_backup == RV3D_CAMOB) { /* a camera view */
Object *ob_back = view3d_cameracontrol_object(vctrl);
/* store the original camera loc and rot */
BKE_object_tfm_restore(ob_back, vctrl->obtfm);
DEG_id_tag_update(&ob_back->id, ID_RECALC_TRANSFORM);
}
else {
/* Non Camera we need to reset the view back
* to the original location because the user canceled. */
copy_qt_qt(rv3d->viewquat, vctrl->rot_backup);
rv3d->persp = vctrl->persp_backup;
}
/* always, is set to zero otherwise */
copy_v3_v3(rv3d->ofs, vctrl->ofs_backup);
rv3d->dist = vctrl->dist_backup;
}
else if (vctrl->persp_backup == RV3D_CAMOB) { /* camera */
DEG_id_tag_update((ID *)view3d_cameracontrol_object(vctrl), ID_RECALC_TRANSFORM);
/* always, is set to zero otherwise */
copy_v3_v3(rv3d->ofs, vctrl->ofs_backup);
rv3d->dist = vctrl->dist_backup;
}
else { /* not camera */
/* Apply the fly mode view */
/* restore the dist */
ED_view3d_distance_set(rv3d, vctrl->dist_backup);
/* Done with correcting for the dist */
}
if (vctrl->is_ortho_cam) {
((Camera *)v3d->camera->data)->type = CAM_ORTHO;
}
if (vctrl->obtfm) {
MEM_freeN(vctrl->obtfm);
}
MEM_freeN(vctrl);
}