545 lines
17 KiB
C
545 lines
17 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup spview3d
|
|
*/
|
|
|
|
#include "DNA_camera_types.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_math.h"
|
|
|
|
#include "BKE_context.h"
|
|
|
|
#include "DEG_depsgraph_query.h"
|
|
|
|
#include "WM_api.h"
|
|
|
|
#include "ED_screen.h"
|
|
|
|
#include "view3d_intern.h"
|
|
#include "view3d_navigate.h" /* own include */
|
|
|
|
static void view3d_smoothview_apply_with_interp(
|
|
bContext *C, View3D *v3d, RegionView3D *rv3d, const bool use_autokey, const float factor);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Smooth View Undo Handling
|
|
*
|
|
* When the camera is locked to the viewport smooth-view operations
|
|
* may need to perform an undo push.
|
|
*
|
|
* In this case the smooth-view camera transformation is temporarily completed,
|
|
* undo is pushed then the change is rewound, and smooth-view completes from it's timer.
|
|
* In the case smooth-view executed the change immediately - an undo push is called.
|
|
*
|
|
* NOTE(@campbellbarton): While this is not ideal it's necessary as making the undo-push
|
|
* once smooth-view is complete because smooth-view is non-blocking and it's possible other
|
|
* operations are executed once smooth-view has started.
|
|
* \{ */
|
|
|
|
void ED_view3d_smooth_view_undo_begin(bContext *C, const ScrArea *area)
|
|
{
|
|
const View3D *v3d = area->spacedata.first;
|
|
Object *camera = v3d->camera;
|
|
if (!camera) {
|
|
return;
|
|
}
|
|
|
|
/* Tag the camera object so it's known smooth-view is applied to the view-ports camera
|
|
* (needed to detect when a locked camera is being manipulated).
|
|
* NOTE: It doesn't matter if the actual object being manipulated is the camera or not. */
|
|
camera->id.tag &= ~LIB_TAG_DOIT;
|
|
|
|
LISTBASE_FOREACH (const ARegion *, region, &area->regionbase) {
|
|
if (region->regiontype != RGN_TYPE_WINDOW) {
|
|
continue;
|
|
}
|
|
const RegionView3D *rv3d = region->regiondata;
|
|
if (ED_view3d_camera_lock_undo_test(v3d, rv3d, C)) {
|
|
camera->id.tag |= LIB_TAG_DOIT;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ED_view3d_smooth_view_undo_end(bContext *C,
|
|
const ScrArea *area,
|
|
const char *undo_str,
|
|
const bool undo_grouped)
|
|
{
|
|
View3D *v3d = area->spacedata.first;
|
|
Object *camera = v3d->camera;
|
|
if (!camera) {
|
|
return;
|
|
}
|
|
if (camera->id.tag & LIB_TAG_DOIT) {
|
|
/* Smooth view didn't touch the camera. */
|
|
camera->id.tag &= ~LIB_TAG_DOIT;
|
|
return;
|
|
}
|
|
|
|
if ((U.uiflag & USER_GLOBALUNDO) == 0) {
|
|
return;
|
|
}
|
|
|
|
/* NOTE(@campbellbarton): It is not possible that a single viewport references different cameras
|
|
* so even in the case there is a quad-view with multiple camera views set, these will all
|
|
* reference the same camera. In this case it doesn't matter which region is used.
|
|
* If in the future multiple cameras are supported, this logic can be extended. */
|
|
const ARegion *region_camera = NULL;
|
|
|
|
/* An undo push should be performed. */
|
|
bool is_interactive = false;
|
|
LISTBASE_FOREACH (const ARegion *, region, &area->regionbase) {
|
|
if (region->regiontype != RGN_TYPE_WINDOW) {
|
|
continue;
|
|
}
|
|
const RegionView3D *rv3d = region->regiondata;
|
|
if (ED_view3d_camera_lock_undo_test(v3d, rv3d, C)) {
|
|
region_camera = region;
|
|
if (rv3d->sms) {
|
|
is_interactive = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (region_camera == NULL) {
|
|
return;
|
|
}
|
|
|
|
RegionView3D *rv3d = region_camera->regiondata;
|
|
|
|
/* Fast forward, undo push, then rewind. */
|
|
if (is_interactive) {
|
|
view3d_smoothview_apply_with_interp(C, v3d, rv3d, false, 1.0f);
|
|
}
|
|
|
|
if (undo_grouped) {
|
|
ED_view3d_camera_lock_undo_grouped_push(undo_str, v3d, rv3d, C);
|
|
}
|
|
else {
|
|
ED_view3d_camera_lock_undo_push(undo_str, v3d, rv3d, C);
|
|
}
|
|
|
|
if (is_interactive) {
|
|
view3d_smoothview_apply_with_interp(C, v3d, rv3d, false, 0.0f);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Smooth View Operator & Utilities
|
|
*
|
|
* Use for view transitions to have smooth (animated) transitions.
|
|
* \{ */
|
|
|
|
/* This operator is one of the 'timer refresh' ones like animation playback */
|
|
|
|
struct SmoothView3DState {
|
|
float dist;
|
|
float lens;
|
|
float quat[4];
|
|
float ofs[3];
|
|
};
|
|
|
|
struct SmoothView3DStore {
|
|
/* Source. */
|
|
struct SmoothView3DState src; /* source */
|
|
struct SmoothView3DState dst; /* destination */
|
|
struct SmoothView3DState org; /* original */
|
|
|
|
bool to_camera;
|
|
|
|
bool use_dyn_ofs;
|
|
float dyn_ofs[3];
|
|
|
|
/* When smooth-view is enabled, store the 'rv3d->view' here,
|
|
* assign back when the view motion is completed. */
|
|
char org_view;
|
|
|
|
double time_allowed;
|
|
};
|
|
|
|
static void view3d_smooth_view_state_backup(struct SmoothView3DState *sms_state,
|
|
const View3D *v3d,
|
|
const RegionView3D *rv3d)
|
|
{
|
|
copy_v3_v3(sms_state->ofs, rv3d->ofs);
|
|
copy_qt_qt(sms_state->quat, rv3d->viewquat);
|
|
sms_state->dist = rv3d->dist;
|
|
sms_state->lens = v3d->lens;
|
|
}
|
|
|
|
static void view3d_smooth_view_state_restore(const struct SmoothView3DState *sms_state,
|
|
View3D *v3d,
|
|
RegionView3D *rv3d)
|
|
{
|
|
copy_v3_v3(rv3d->ofs, sms_state->ofs);
|
|
copy_qt_qt(rv3d->viewquat, sms_state->quat);
|
|
rv3d->dist = sms_state->dist;
|
|
v3d->lens = sms_state->lens;
|
|
}
|
|
|
|
/* will start timer if appropriate */
|
|
void ED_view3d_smooth_view_ex(
|
|
/* avoid passing in the context */
|
|
const Depsgraph *depsgraph,
|
|
wmWindowManager *wm,
|
|
wmWindow *win,
|
|
ScrArea *area,
|
|
View3D *v3d,
|
|
ARegion *region,
|
|
const int smooth_viewtx,
|
|
const V3D_SmoothParams *sview)
|
|
{
|
|
/* In this case use #ED_view3d_smooth_view_undo_begin & end functions
|
|
* instead of passing in undo. */
|
|
BLI_assert_msg(sview->undo_str == NULL,
|
|
"Only the 'ED_view3d_smooth_view' version of this function handles undo!");
|
|
|
|
RegionView3D *rv3d = region->regiondata;
|
|
struct SmoothView3DStore sms = {{0}};
|
|
|
|
/* initialize sms */
|
|
view3d_smooth_view_state_backup(&sms.dst, v3d, rv3d);
|
|
view3d_smooth_view_state_backup(&sms.src, v3d, rv3d);
|
|
/* If smooth-view runs multiple times. */
|
|
if (rv3d->sms == NULL) {
|
|
view3d_smooth_view_state_backup(&sms.org, v3d, rv3d);
|
|
}
|
|
else {
|
|
sms.org = rv3d->sms->org;
|
|
}
|
|
sms.org_view = rv3d->view;
|
|
|
|
/* sms.to_camera = false; */ /* initialized to zero anyway */
|
|
|
|
/* note on camera locking, this is a little confusing but works ok.
|
|
* we may be changing the view 'as if' there is no active camera, but in fact
|
|
* there is an active camera which is locked to the view.
|
|
*
|
|
* In the case where smooth view is moving _to_ a camera we don't want that
|
|
* camera to be moved or changed, so only when the camera is not being set should
|
|
* we allow camera option locking to initialize the view settings from the camera.
|
|
*/
|
|
if (sview->camera == NULL && sview->camera_old == NULL) {
|
|
ED_view3d_camera_lock_init(depsgraph, v3d, rv3d);
|
|
}
|
|
|
|
/* store the options we want to end with */
|
|
if (sview->ofs) {
|
|
copy_v3_v3(sms.dst.ofs, sview->ofs);
|
|
}
|
|
if (sview->quat) {
|
|
copy_qt_qt(sms.dst.quat, sview->quat);
|
|
}
|
|
if (sview->dist) {
|
|
sms.dst.dist = *sview->dist;
|
|
}
|
|
if (sview->lens) {
|
|
sms.dst.lens = *sview->lens;
|
|
}
|
|
|
|
if (sview->dyn_ofs) {
|
|
BLI_assert(sview->ofs == NULL);
|
|
BLI_assert(sview->quat != NULL);
|
|
|
|
copy_v3_v3(sms.dyn_ofs, sview->dyn_ofs);
|
|
sms.use_dyn_ofs = true;
|
|
|
|
/* calculate the final destination offset */
|
|
view3d_orbit_apply_dyn_ofs(sms.dst.ofs, sms.src.ofs, sms.src.quat, sms.dst.quat, sms.dyn_ofs);
|
|
}
|
|
|
|
if (sview->camera) {
|
|
Object *ob_camera_eval = DEG_get_evaluated_object(depsgraph, sview->camera);
|
|
if (sview->ofs != NULL) {
|
|
sms.dst.dist = ED_view3d_offset_distance(
|
|
ob_camera_eval->obmat, sview->ofs, VIEW3D_DIST_FALLBACK);
|
|
}
|
|
ED_view3d_from_object(ob_camera_eval, sms.dst.ofs, sms.dst.quat, &sms.dst.dist, &sms.dst.lens);
|
|
sms.to_camera = true; /* restore view3d values in end */
|
|
}
|
|
|
|
if ((sview->camera_old == sview->camera) && /* Camera. */
|
|
(sms.dst.dist == rv3d->dist) && /* Distance. */
|
|
(sms.dst.lens == v3d->lens) && /* Lens. */
|
|
equals_v3v3(sms.dst.ofs, rv3d->ofs) && /* Offset. */
|
|
equals_v4v4(sms.dst.quat, rv3d->viewquat) /* Rotation. */
|
|
) {
|
|
/* Early return if nothing changed. */
|
|
return;
|
|
}
|
|
|
|
/* Skip smooth viewing for external render engine draw. */
|
|
if (smooth_viewtx && !(v3d->shading.type == OB_RENDER && rv3d->render_engine)) {
|
|
|
|
/* original values */
|
|
if (sview->camera_old) {
|
|
Object *ob_camera_old_eval = DEG_get_evaluated_object(depsgraph, sview->camera_old);
|
|
if (sview->ofs != NULL) {
|
|
sms.src.dist = ED_view3d_offset_distance(ob_camera_old_eval->obmat, sview->ofs, 0.0f);
|
|
}
|
|
ED_view3d_from_object(
|
|
ob_camera_old_eval, sms.src.ofs, sms.src.quat, &sms.src.dist, &sms.src.lens);
|
|
}
|
|
/* grid draw as floor */
|
|
if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) == 0) {
|
|
/* use existing if exists, means multiple calls to smooth view
|
|
* won't lose the original 'view' setting */
|
|
rv3d->view = RV3D_VIEW_USER;
|
|
}
|
|
|
|
sms.time_allowed = (double)smooth_viewtx / 1000.0;
|
|
|
|
/* If this is view rotation only we can decrease the time allowed by the angle between quats
|
|
* this means small rotations won't lag. */
|
|
if (sview->quat && !sview->ofs && !sview->dist) {
|
|
/* scale the time allowed by the rotation */
|
|
/* 180deg == 1.0 */
|
|
sms.time_allowed *= (double)fabsf(angle_signed_normalized_qtqt(sms.dst.quat, sms.src.quat)) /
|
|
M_PI;
|
|
}
|
|
|
|
/* ensure it shows correct */
|
|
if (sms.to_camera) {
|
|
/* use ortho if we move from an ortho view to an ortho camera */
|
|
Object *ob_camera_eval = DEG_get_evaluated_object(depsgraph, sview->camera);
|
|
rv3d->persp = (((rv3d->is_persp == false) && (ob_camera_eval->type == OB_CAMERA) &&
|
|
(((Camera *)ob_camera_eval->data)->type == CAM_ORTHO)) ?
|
|
RV3D_ORTHO :
|
|
RV3D_PERSP);
|
|
}
|
|
|
|
rv3d->rflag |= RV3D_NAVIGATING;
|
|
|
|
/* not essential but in some cases the caller will tag the area for redraw, and in that
|
|
* case we can get a flicker of the 'org' user view but we want to see 'src' */
|
|
view3d_smooth_view_state_restore(&sms.src, v3d, rv3d);
|
|
|
|
/* keep track of running timer! */
|
|
if (rv3d->sms == NULL) {
|
|
rv3d->sms = MEM_mallocN(sizeof(struct SmoothView3DStore), "smoothview v3d");
|
|
}
|
|
*rv3d->sms = sms;
|
|
if (rv3d->smooth_timer) {
|
|
WM_event_remove_timer(wm, win, rv3d->smooth_timer);
|
|
}
|
|
/* #TIMER1 is hard-coded in key-map. */
|
|
rv3d->smooth_timer = WM_event_add_timer(wm, win, TIMER1, 1.0 / 100.0);
|
|
}
|
|
else {
|
|
/* Animation is disabled, apply immediately. */
|
|
if (sms.to_camera == false) {
|
|
copy_v3_v3(rv3d->ofs, sms.dst.ofs);
|
|
copy_qt_qt(rv3d->viewquat, sms.dst.quat);
|
|
rv3d->dist = sms.dst.dist;
|
|
v3d->lens = sms.dst.lens;
|
|
|
|
ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
|
|
}
|
|
|
|
if (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW) {
|
|
view3d_boxview_copy(area, region);
|
|
}
|
|
|
|
ED_region_tag_redraw(region);
|
|
|
|
WM_event_add_mousemove(win);
|
|
}
|
|
|
|
if (sms.to_camera == false) {
|
|
/* See comments in #ED_view3d_smooth_view_undo_begin for why this is needed. */
|
|
if (v3d->camera) {
|
|
v3d->camera->id.tag &= ~LIB_TAG_DOIT;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ED_view3d_smooth_view(bContext *C,
|
|
View3D *v3d,
|
|
ARegion *region,
|
|
const int smooth_viewtx,
|
|
const struct V3D_SmoothParams *sview)
|
|
{
|
|
const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
wmWindow *win = CTX_wm_window(C);
|
|
ScrArea *area = CTX_wm_area(C);
|
|
|
|
/* #ED_view3d_smooth_view_ex asserts this is not set as it doesn't support undo. */
|
|
struct V3D_SmoothParams sview_no_undo = *sview;
|
|
sview_no_undo.undo_str = NULL;
|
|
sview_no_undo.undo_grouped = false;
|
|
|
|
const bool do_undo = (sview->undo_str != NULL);
|
|
if (do_undo) {
|
|
ED_view3d_smooth_view_undo_begin(C, area);
|
|
}
|
|
|
|
ED_view3d_smooth_view_ex(depsgraph, wm, win, area, v3d, region, smooth_viewtx, &sview_no_undo);
|
|
|
|
if (do_undo) {
|
|
ED_view3d_smooth_view_undo_end(C, area, sview->undo_str, sview->undo_grouped);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Apply with interpolation, on completion run #view3d_smoothview_apply_and_finish.
|
|
*/
|
|
static void view3d_smoothview_apply_with_interp(
|
|
bContext *C, View3D *v3d, RegionView3D *rv3d, const bool use_autokey, const float factor)
|
|
{
|
|
struct SmoothView3DStore *sms = rv3d->sms;
|
|
|
|
interp_qt_qtqt(rv3d->viewquat, sms->src.quat, sms->dst.quat, factor);
|
|
|
|
if (sms->use_dyn_ofs) {
|
|
view3d_orbit_apply_dyn_ofs(
|
|
rv3d->ofs, sms->src.ofs, sms->src.quat, rv3d->viewquat, sms->dyn_ofs);
|
|
}
|
|
else {
|
|
interp_v3_v3v3(rv3d->ofs, sms->src.ofs, sms->dst.ofs, factor);
|
|
}
|
|
|
|
rv3d->dist = interpf(sms->dst.dist, sms->src.dist, factor);
|
|
v3d->lens = interpf(sms->dst.lens, sms->src.lens, factor);
|
|
|
|
const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
|
|
if (ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d)) {
|
|
if (use_autokey) {
|
|
ED_view3d_camera_lock_autokey(v3d, rv3d, C, true, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Apply the view-port transformation & free smooth-view related data.
|
|
*/
|
|
static void view3d_smoothview_apply_and_finish(bContext *C, View3D *v3d, RegionView3D *rv3d)
|
|
{
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
struct SmoothView3DStore *sms = rv3d->sms;
|
|
|
|
wmWindow *win = CTX_wm_window(C);
|
|
|
|
/* if we went to camera, store the original */
|
|
if (sms->to_camera) {
|
|
rv3d->persp = RV3D_CAMOB;
|
|
view3d_smooth_view_state_restore(&sms->org, v3d, rv3d);
|
|
}
|
|
else {
|
|
const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
|
|
|
|
view3d_smooth_view_state_restore(&sms->dst, v3d, rv3d);
|
|
|
|
if (ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d)) {
|
|
ED_view3d_camera_lock_autokey(v3d, rv3d, C, true, true);
|
|
}
|
|
}
|
|
|
|
if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) == 0) {
|
|
rv3d->view = sms->org_view;
|
|
}
|
|
|
|
MEM_freeN(rv3d->sms);
|
|
rv3d->sms = NULL;
|
|
|
|
WM_event_remove_timer(wm, win, rv3d->smooth_timer);
|
|
rv3d->smooth_timer = NULL;
|
|
rv3d->rflag &= ~RV3D_NAVIGATING;
|
|
|
|
/* Event handling won't know if a UI item has been moved under the pointer. */
|
|
WM_event_add_mousemove(win);
|
|
|
|
/* NOTE: this doesn't work right because the v3d->lens is now used in ortho mode r51636,
|
|
* when switching camera in quad-view the other ortho views would zoom & reset.
|
|
*
|
|
* For now only redraw all regions when smooth-view finishes.
|
|
*/
|
|
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d);
|
|
}
|
|
|
|
/* only meant for timer usage */
|
|
|
|
static void view3d_smoothview_apply_from_timer(bContext *C, View3D *v3d, ARegion *region)
|
|
{
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
RegionView3D *rv3d = region->regiondata;
|
|
struct SmoothView3DStore *sms = rv3d->sms;
|
|
float factor;
|
|
|
|
if (sms->time_allowed != 0.0) {
|
|
factor = (float)((rv3d->smooth_timer->duration) / sms->time_allowed);
|
|
}
|
|
else {
|
|
factor = 1.0f;
|
|
}
|
|
if (factor >= 1.0f) {
|
|
view3d_smoothview_apply_and_finish(C, v3d, rv3d);
|
|
}
|
|
else {
|
|
/* Ease in/out smoothing. */
|
|
factor = (3.0f * factor * factor - 2.0f * factor * factor * factor);
|
|
const bool use_autokey = ED_screen_animation_playing(wm);
|
|
view3d_smoothview_apply_with_interp(C, v3d, rv3d, use_autokey, factor);
|
|
}
|
|
|
|
if (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW) {
|
|
view3d_boxview_copy(CTX_wm_area(C), region);
|
|
}
|
|
|
|
ED_region_tag_redraw(region);
|
|
}
|
|
|
|
static int view3d_smoothview_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
|
|
{
|
|
View3D *v3d = CTX_wm_view3d(C);
|
|
ARegion *region = CTX_wm_region(C);
|
|
RegionView3D *rv3d = region->regiondata;
|
|
|
|
/* escape if not our timer */
|
|
if (rv3d->smooth_timer == NULL || rv3d->smooth_timer != event->customdata) {
|
|
return OPERATOR_PASS_THROUGH;
|
|
}
|
|
|
|
view3d_smoothview_apply_from_timer(C, v3d, region);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void ED_view3d_smooth_view_force_finish(bContext *C, View3D *v3d, ARegion *region)
|
|
{
|
|
RegionView3D *rv3d = region->regiondata;
|
|
if (rv3d && rv3d->sms) {
|
|
view3d_smoothview_apply_and_finish(C, v3d, rv3d);
|
|
|
|
/* Force update of view matrix so tools that run immediately after
|
|
* can use them without redrawing first */
|
|
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
|
|
Scene *scene = CTX_data_scene(C);
|
|
ED_view3d_update_viewmat(depsgraph, scene, v3d, region, NULL, NULL, NULL, false);
|
|
}
|
|
}
|
|
|
|
void VIEW3D_OT_smoothview(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Smooth View";
|
|
ot->idname = "VIEW3D_OT_smoothview";
|
|
|
|
/* api callbacks */
|
|
ot->invoke = view3d_smoothview_invoke;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_INTERNAL;
|
|
|
|
ot->poll = ED_operator_view3d_active;
|
|
}
|
|
|
|
/** \} */
|