Previously, only predefined and limited set of intrinsics combinations could have been refined. This was caused by a bundle adjustment library used in the early days of the solver. Now it is possible to fully customize which intrinsics are to be refined during camera solving. Internally solver supports per-parameter settings but in the interface they are grouped as following: * Focal length * Optical center * Radial distortion coefficients (which includes k1, k2, k3, k4) * Tangential distortion coefficients (which includes p1, p2) Differential Revision: https://developer.blender.org/D9294
3202 lines
98 KiB
C
3202 lines
98 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.
|
|
*
|
|
* The Original Code is Copyright (C) 2011 Blender Foundation.
|
|
* All rights reserved.
|
|
*/
|
|
|
|
/** \file
|
|
* \ingroup bke
|
|
*/
|
|
|
|
#include <limits.h>
|
|
#include <math.h>
|
|
#include <memory.h>
|
|
#include <stddef.h>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "DNA_anim_types.h"
|
|
#include "DNA_camera_types.h"
|
|
#include "DNA_gpencil_types.h"
|
|
#include "DNA_movieclip_types.h"
|
|
#include "DNA_object_types.h" /* SELECT */
|
|
#include "DNA_scene_types.h"
|
|
|
|
#include "BLI_bitmap_draw_2d.h"
|
|
#include "BLI_ghash.h"
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_math.h"
|
|
#include "BLI_math_base.h"
|
|
#include "BLI_string.h"
|
|
#include "BLI_string_utils.h"
|
|
#include "BLI_threads.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "BLT_translation.h"
|
|
|
|
#include "BKE_fcurve.h"
|
|
#include "BKE_layer.h"
|
|
#include "BKE_lib_id.h"
|
|
#include "BKE_movieclip.h"
|
|
#include "BKE_object.h"
|
|
#include "BKE_scene.h"
|
|
#include "BKE_tracking.h"
|
|
|
|
#include "IMB_imbuf.h"
|
|
#include "IMB_imbuf_types.h"
|
|
|
|
#include "RNA_access.h"
|
|
|
|
#include "libmv-capi.h"
|
|
#include "tracking_private.h"
|
|
|
|
typedef struct MovieDistortion {
|
|
struct libmv_CameraIntrinsics *intrinsics;
|
|
/* Parameters needed for coordinates normalization. */
|
|
float principal[2];
|
|
float pixel_aspect;
|
|
float focal;
|
|
} MovieDistortion;
|
|
|
|
static struct {
|
|
ListBase tracks;
|
|
} tracking_clipboard;
|
|
|
|
/*********************** Common functions *************************/
|
|
|
|
/* Free the whole list of tracks, list's head and tail are set to NULL. */
|
|
static void tracking_tracks_free(ListBase *tracks)
|
|
{
|
|
LISTBASE_FOREACH (MovieTrackingTrack *, track, tracks) {
|
|
BKE_tracking_track_free(track);
|
|
}
|
|
|
|
BLI_freelistN(tracks);
|
|
}
|
|
|
|
/* Free the whole list of plane tracks, list's head and tail are set to NULL. */
|
|
static void tracking_plane_tracks_free(ListBase *plane_tracks)
|
|
{
|
|
LISTBASE_FOREACH (MovieTrackingPlaneTrack *, plane_track, plane_tracks) {
|
|
BKE_tracking_plane_track_free(plane_track);
|
|
}
|
|
|
|
BLI_freelistN(plane_tracks);
|
|
}
|
|
|
|
/* Free reconstruction structures, only frees contents of a structure,
|
|
* (if structure is allocated in heap, it shall be handled outside).
|
|
*
|
|
* All the pointers inside structure becomes invalid after this call.
|
|
*/
|
|
static void tracking_reconstruction_free(MovieTrackingReconstruction *reconstruction)
|
|
{
|
|
if (reconstruction->cameras) {
|
|
MEM_freeN(reconstruction->cameras);
|
|
}
|
|
}
|
|
|
|
/* Free memory used by tracking object, only frees contents of the structure,
|
|
* (if structure is allocated in heap, it shall be handled outside).
|
|
*
|
|
* All the pointers inside structure becomes invalid after this call.
|
|
*/
|
|
static void tracking_object_free(MovieTrackingObject *object)
|
|
{
|
|
tracking_tracks_free(&object->tracks);
|
|
tracking_plane_tracks_free(&object->plane_tracks);
|
|
tracking_reconstruction_free(&object->reconstruction);
|
|
}
|
|
|
|
/* Free list of tracking objects, list's head and tail is set to NULL. */
|
|
static void tracking_objects_free(ListBase *objects)
|
|
{
|
|
/* Free objects contents. */
|
|
LISTBASE_FOREACH (MovieTrackingObject *, object, objects) {
|
|
tracking_object_free(object);
|
|
}
|
|
|
|
/* Free objects themselves. */
|
|
BLI_freelistN(objects);
|
|
}
|
|
|
|
/* Free memory used by a dopesheet, only frees dopesheet contents.
|
|
* leaving dopesheet crystal clean for further usage.
|
|
*/
|
|
static void tracking_dopesheet_free(MovieTrackingDopesheet *dopesheet)
|
|
{
|
|
MovieTrackingDopesheetChannel *channel;
|
|
|
|
/* Free channel's segments. */
|
|
channel = dopesheet->channels.first;
|
|
while (channel) {
|
|
if (channel->segments) {
|
|
MEM_freeN(channel->segments);
|
|
}
|
|
|
|
channel = channel->next;
|
|
}
|
|
|
|
/* Free lists themselves. */
|
|
BLI_freelistN(&dopesheet->channels);
|
|
BLI_freelistN(&dopesheet->coverage_segments);
|
|
|
|
/* Ensure lists are clean. */
|
|
BLI_listbase_clear(&dopesheet->channels);
|
|
BLI_listbase_clear(&dopesheet->coverage_segments);
|
|
dopesheet->tot_channel = 0;
|
|
}
|
|
|
|
/* Free tracking structure, only frees structure contents
|
|
* (if structure is allocated in heap, it shall be handled outside).
|
|
*
|
|
* All the pointers inside structure becomes invalid after this call.
|
|
*/
|
|
void BKE_tracking_free(MovieTracking *tracking)
|
|
{
|
|
tracking_tracks_free(&tracking->tracks);
|
|
tracking_plane_tracks_free(&tracking->plane_tracks);
|
|
tracking_reconstruction_free(&tracking->reconstruction);
|
|
tracking_objects_free(&tracking->objects);
|
|
|
|
if (tracking->camera.intrinsics) {
|
|
BKE_tracking_distortion_free(tracking->camera.intrinsics);
|
|
}
|
|
|
|
tracking_dopesheet_free(&tracking->dopesheet);
|
|
}
|
|
|
|
/* Copy the whole list of tracks. */
|
|
static void tracking_tracks_copy(ListBase *tracks_dst,
|
|
const ListBase *tracks_src,
|
|
GHash *tracks_mapping,
|
|
const int flag)
|
|
{
|
|
BLI_listbase_clear(tracks_dst);
|
|
BLI_ghash_clear(tracks_mapping, NULL, NULL);
|
|
|
|
LISTBASE_FOREACH (MovieTrackingTrack *, track_src, tracks_src) {
|
|
MovieTrackingTrack *track_dst = MEM_dupallocN(track_src);
|
|
if (track_src->markers) {
|
|
track_dst->markers = MEM_dupallocN(track_src->markers);
|
|
}
|
|
if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0) {
|
|
id_us_plus(&track_dst->gpd->id);
|
|
}
|
|
BLI_addtail(tracks_dst, track_dst);
|
|
BLI_ghash_insert(tracks_mapping, track_src, track_dst);
|
|
}
|
|
}
|
|
|
|
/* Copy the whole list of plane tracks
|
|
* (need whole MovieTracking structures due to embedded pointers to tracks).
|
|
* WARNING: implies tracking_[dst/src] and their tracks have already been copied. */
|
|
static void tracking_plane_tracks_copy(ListBase *plane_tracks_list_dst,
|
|
const ListBase *plane_tracks_list_src,
|
|
GHash *tracks_mapping,
|
|
const int flag)
|
|
{
|
|
BLI_listbase_clear(plane_tracks_list_dst);
|
|
|
|
LISTBASE_FOREACH (MovieTrackingPlaneTrack *, plane_track_src, plane_tracks_list_src) {
|
|
MovieTrackingPlaneTrack *plane_track_dst = MEM_dupallocN(plane_track_src);
|
|
if (plane_track_src->markers) {
|
|
plane_track_dst->markers = MEM_dupallocN(plane_track_src->markers);
|
|
}
|
|
plane_track_dst->point_tracks = MEM_mallocN(
|
|
sizeof(*plane_track_dst->point_tracks) * plane_track_dst->point_tracksnr, __func__);
|
|
for (int i = 0; i < plane_track_dst->point_tracksnr; i++) {
|
|
plane_track_dst->point_tracks[i] = BLI_ghash_lookup(tracks_mapping,
|
|
plane_track_src->point_tracks[i]);
|
|
}
|
|
if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0) {
|
|
id_us_plus(&plane_track_dst->image->id);
|
|
}
|
|
BLI_addtail(plane_tracks_list_dst, plane_track_dst);
|
|
}
|
|
}
|
|
|
|
/* Copy reconstruction structure. */
|
|
static void tracking_reconstruction_copy(MovieTrackingReconstruction *reconstruction_dst,
|
|
const MovieTrackingReconstruction *reconstruction_src,
|
|
const int UNUSED(flag))
|
|
{
|
|
*reconstruction_dst = *reconstruction_src;
|
|
if (reconstruction_src->cameras) {
|
|
reconstruction_dst->cameras = MEM_dupallocN(reconstruction_src->cameras);
|
|
}
|
|
}
|
|
|
|
/* Copy stabilization structure. */
|
|
static void tracking_stabilization_copy(MovieTrackingStabilization *stabilization_dst,
|
|
const MovieTrackingStabilization *stabilization_src,
|
|
const int UNUSED(flag))
|
|
{
|
|
*stabilization_dst = *stabilization_src;
|
|
}
|
|
|
|
/* Copy tracking object. */
|
|
static void tracking_object_copy(MovieTrackingObject *object_dst,
|
|
const MovieTrackingObject *object_src,
|
|
GHash *tracks_mapping,
|
|
const int flag)
|
|
{
|
|
*object_dst = *object_src;
|
|
tracking_tracks_copy(&object_dst->tracks, &object_src->tracks, tracks_mapping, flag);
|
|
tracking_plane_tracks_copy(
|
|
&object_dst->plane_tracks, &object_src->plane_tracks, tracks_mapping, flag);
|
|
tracking_reconstruction_copy(&object_dst->reconstruction, &object_src->reconstruction, flag);
|
|
}
|
|
|
|
/* Copy list of tracking objects. */
|
|
static void tracking_objects_copy(ListBase *objects_dst,
|
|
const ListBase *objects_src,
|
|
GHash *tracks_mapping,
|
|
const int flag)
|
|
{
|
|
BLI_listbase_clear(objects_dst);
|
|
|
|
LISTBASE_FOREACH (MovieTrackingObject *, object_src, objects_src) {
|
|
MovieTrackingObject *object_dst = MEM_mallocN(sizeof(*object_dst), __func__);
|
|
tracking_object_copy(object_dst, object_src, tracks_mapping, flag);
|
|
BLI_addtail(objects_dst, object_dst);
|
|
}
|
|
}
|
|
|
|
/* Copy tracking structure content. */
|
|
void BKE_tracking_copy(MovieTracking *tracking_dst,
|
|
const MovieTracking *tracking_src,
|
|
const int flag)
|
|
{
|
|
GHash *tracks_mapping = BLI_ghash_ptr_new(__func__);
|
|
|
|
*tracking_dst = *tracking_src;
|
|
|
|
tracking_tracks_copy(&tracking_dst->tracks, &tracking_src->tracks, tracks_mapping, flag);
|
|
tracking_plane_tracks_copy(
|
|
&tracking_dst->plane_tracks, &tracking_src->plane_tracks, tracks_mapping, flag);
|
|
tracking_reconstruction_copy(&tracking_dst->reconstruction, &tracking_src->reconstruction, flag);
|
|
tracking_stabilization_copy(&tracking_dst->stabilization, &tracking_src->stabilization, flag);
|
|
if (tracking_src->act_track) {
|
|
tracking_dst->act_track = BLI_ghash_lookup(tracks_mapping, tracking_src->act_track);
|
|
}
|
|
if (tracking_src->act_plane_track) {
|
|
MovieTrackingPlaneTrack *plane_track_src, *plane_track_dst;
|
|
for (plane_track_src = tracking_src->plane_tracks.first,
|
|
plane_track_dst = tracking_dst->plane_tracks.first;
|
|
!ELEM(NULL, plane_track_src, plane_track_dst);
|
|
plane_track_src = plane_track_src->next, plane_track_dst = plane_track_dst->next) {
|
|
if (plane_track_src == tracking_src->act_plane_track) {
|
|
tracking_dst->act_plane_track = plane_track_dst;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Warning! Will override tracks_mapping. */
|
|
tracking_objects_copy(&tracking_dst->objects, &tracking_src->objects, tracks_mapping, flag);
|
|
|
|
/* Those remaining are runtime data, they will be reconstructed as needed,
|
|
* do not bother copying them. */
|
|
tracking_dst->dopesheet.ok = false;
|
|
BLI_listbase_clear(&tracking_dst->dopesheet.channels);
|
|
BLI_listbase_clear(&tracking_dst->dopesheet.coverage_segments);
|
|
|
|
tracking_dst->camera.intrinsics = NULL;
|
|
tracking_dst->stats = NULL;
|
|
|
|
BLI_ghash_free(tracks_mapping, NULL, NULL);
|
|
}
|
|
|
|
/* Initialize motion tracking settings to default values,
|
|
* used when new movie clip datablock is created.
|
|
*/
|
|
void BKE_tracking_settings_init(MovieTracking *tracking)
|
|
{
|
|
tracking->camera.sensor_width = 35.0f;
|
|
tracking->camera.pixel_aspect = 1.0f;
|
|
tracking->camera.units = CAMERA_UNITS_MM;
|
|
|
|
tracking->settings.default_motion_model = TRACK_MOTION_MODEL_TRANSLATION;
|
|
tracking->settings.default_minimum_correlation = 0.75;
|
|
tracking->settings.default_pattern_size = 21;
|
|
tracking->settings.default_search_size = 71;
|
|
tracking->settings.default_algorithm_flag |= TRACK_ALGORITHM_FLAG_USE_BRUTE;
|
|
tracking->settings.default_weight = 1.0f;
|
|
tracking->settings.dist = 1;
|
|
tracking->settings.object_distance = 1;
|
|
tracking->settings.refine_camera_intrinsics = REFINE_NO_INTRINSICS;
|
|
|
|
tracking->stabilization.scaleinf = 1.0f;
|
|
tracking->stabilization.anchor_frame = 1;
|
|
zero_v2(tracking->stabilization.target_pos);
|
|
tracking->stabilization.target_rot = 0.0f;
|
|
tracking->stabilization.scale = 1.0f;
|
|
|
|
tracking->stabilization.act_track = 0;
|
|
tracking->stabilization.act_rot_track = 0;
|
|
tracking->stabilization.tot_track = 0;
|
|
tracking->stabilization.tot_rot_track = 0;
|
|
|
|
tracking->stabilization.scaleinf = 1.0f;
|
|
tracking->stabilization.locinf = 1.0f;
|
|
tracking->stabilization.rotinf = 1.0f;
|
|
tracking->stabilization.maxscale = 2.0f;
|
|
tracking->stabilization.filter = TRACKING_FILTER_BILINEAR;
|
|
tracking->stabilization.flag |= TRACKING_SHOW_STAB_TRACKS;
|
|
|
|
BKE_tracking_object_add(tracking, "Camera");
|
|
}
|
|
|
|
/* Get list base of active object's tracks. */
|
|
ListBase *BKE_tracking_get_active_tracks(MovieTracking *tracking)
|
|
{
|
|
MovieTrackingObject *object = BKE_tracking_object_get_active(tracking);
|
|
|
|
if (object && (object->flag & TRACKING_OBJECT_CAMERA) == 0) {
|
|
return &object->tracks;
|
|
}
|
|
|
|
return &tracking->tracks;
|
|
}
|
|
|
|
/* Get list base of active object's plane tracks. */
|
|
ListBase *BKE_tracking_get_active_plane_tracks(MovieTracking *tracking)
|
|
{
|
|
MovieTrackingObject *object = BKE_tracking_object_get_active(tracking);
|
|
|
|
if (object && (object->flag & TRACKING_OBJECT_CAMERA) == 0) {
|
|
return &object->plane_tracks;
|
|
}
|
|
|
|
return &tracking->plane_tracks;
|
|
}
|
|
|
|
/* Get reconstruction data of active object. */
|
|
MovieTrackingReconstruction *BKE_tracking_get_active_reconstruction(MovieTracking *tracking)
|
|
{
|
|
MovieTrackingObject *object = BKE_tracking_object_get_active(tracking);
|
|
|
|
return BKE_tracking_object_get_reconstruction(tracking, object);
|
|
}
|
|
|
|
/* Get transformation matrix for a given object which is used
|
|
* for parenting motion tracker reconstruction to 3D world.
|
|
*/
|
|
void BKE_tracking_get_camera_object_matrix(Object *camera_object, float mat[4][4])
|
|
{
|
|
BLI_assert(camera_object != NULL);
|
|
/* NOTE: Construct matrix from scratch rather than using obmat because the camera object here
|
|
* will have camera solver constraint taken into account. But here we do not want or need it:
|
|
* object is solved in camera space (as in, camera is stationary and object is moving).
|
|
*
|
|
* This will include animation applied on the camera, but not possible camera rig. This isn't
|
|
* an issue in practice due to the way how VFX is constructed.
|
|
*
|
|
* If we ever need to support crazy setups like that one possible solution would be to use
|
|
* final camera matrix and multiple it by an inverse of solved camera matrix at the current
|
|
* frame. */
|
|
BKE_object_where_is_calc_mat4(camera_object, mat);
|
|
}
|
|
|
|
/* Get projection matrix for camera specified by given tracking object
|
|
* and frame number.
|
|
*
|
|
* NOTE: frame number should be in clip space, not scene space
|
|
*/
|
|
void BKE_tracking_get_projection_matrix(MovieTracking *tracking,
|
|
MovieTrackingObject *object,
|
|
int framenr,
|
|
int winx,
|
|
int winy,
|
|
float mat[4][4])
|
|
{
|
|
MovieReconstructedCamera *camera;
|
|
float lens = tracking->camera.focal * tracking->camera.sensor_width / (float)winx;
|
|
float viewfac, pixsize, left, right, bottom, top, clipsta, clipend;
|
|
float winmat[4][4];
|
|
float ycor = 1.0f / tracking->camera.pixel_aspect;
|
|
float shiftx, shifty, winside = (float)min_ii(winx, winy);
|
|
|
|
BKE_tracking_camera_shift_get(tracking, winx, winy, &shiftx, &shifty);
|
|
|
|
clipsta = 0.1f;
|
|
clipend = 1000.0f;
|
|
|
|
if (winx >= winy) {
|
|
viewfac = (lens * winx) / tracking->camera.sensor_width;
|
|
}
|
|
else {
|
|
viewfac = (ycor * lens * winy) / tracking->camera.sensor_width;
|
|
}
|
|
|
|
pixsize = clipsta / viewfac;
|
|
|
|
left = -0.5f * (float)winx + shiftx * winside;
|
|
bottom = -0.5f * (ycor) * (float)winy + shifty * winside;
|
|
right = 0.5f * (float)winx + shiftx * winside;
|
|
top = 0.5f * (ycor) * (float)winy + shifty * winside;
|
|
|
|
left *= pixsize;
|
|
right *= pixsize;
|
|
bottom *= pixsize;
|
|
top *= pixsize;
|
|
|
|
perspective_m4(winmat, left, right, bottom, top, clipsta, clipend);
|
|
|
|
camera = BKE_tracking_camera_get_reconstructed(tracking, object, framenr);
|
|
|
|
if (camera) {
|
|
float imat[4][4];
|
|
|
|
invert_m4_m4(imat, camera->mat);
|
|
mul_m4_m4m4(mat, winmat, imat);
|
|
}
|
|
else {
|
|
copy_m4_m4(mat, winmat);
|
|
}
|
|
}
|
|
|
|
/*********************** clipboard *************************/
|
|
|
|
/* Free clipboard by freeing memory used by all tracks in it. */
|
|
void BKE_tracking_clipboard_free(void)
|
|
{
|
|
MovieTrackingTrack *track = tracking_clipboard.tracks.first, *next_track;
|
|
|
|
while (track) {
|
|
next_track = track->next;
|
|
|
|
BKE_tracking_track_free(track);
|
|
MEM_freeN(track);
|
|
|
|
track = next_track;
|
|
}
|
|
|
|
BLI_listbase_clear(&tracking_clipboard.tracks);
|
|
}
|
|
|
|
/* Copy selected tracks from specified object to the clipboard. */
|
|
void BKE_tracking_clipboard_copy_tracks(MovieTracking *tracking, MovieTrackingObject *object)
|
|
{
|
|
ListBase *tracksbase = BKE_tracking_object_get_tracks(tracking, object);
|
|
MovieTrackingTrack *track = tracksbase->first;
|
|
|
|
/* First drop all tracks from current clipboard. */
|
|
BKE_tracking_clipboard_free();
|
|
|
|
/* Then copy all selected visible tracks to it. */
|
|
while (track) {
|
|
if (TRACK_SELECTED(track) && (track->flag & TRACK_HIDDEN) == 0) {
|
|
MovieTrackingTrack *new_track = BKE_tracking_track_duplicate(track);
|
|
|
|
BLI_addtail(&tracking_clipboard.tracks, new_track);
|
|
}
|
|
|
|
track = track->next;
|
|
}
|
|
}
|
|
|
|
/* Check whether there are any tracks in the clipboard. */
|
|
bool BKE_tracking_clipboard_has_tracks(void)
|
|
{
|
|
return (BLI_listbase_is_empty(&tracking_clipboard.tracks) == false);
|
|
}
|
|
|
|
/* Paste tracks from clipboard to specified object.
|
|
*
|
|
* Names of new tracks in object are guaranteed to
|
|
* be unique here.
|
|
*/
|
|
void BKE_tracking_clipboard_paste_tracks(MovieTracking *tracking, MovieTrackingObject *object)
|
|
{
|
|
ListBase *tracksbase = BKE_tracking_object_get_tracks(tracking, object);
|
|
MovieTrackingTrack *track = tracking_clipboard.tracks.first;
|
|
|
|
while (track) {
|
|
MovieTrackingTrack *new_track = BKE_tracking_track_duplicate(track);
|
|
if (track->prev == NULL) {
|
|
tracking->act_track = new_track;
|
|
}
|
|
|
|
BLI_addtail(tracksbase, new_track);
|
|
BKE_tracking_track_unique_name(tracksbase, new_track);
|
|
|
|
track = track->next;
|
|
}
|
|
}
|
|
|
|
/*********************** Tracks *************************/
|
|
|
|
/* Add new track to a specified tracks base.
|
|
*
|
|
* Coordinates are expected to be in normalized 0..1 space,
|
|
* frame number is expected to be in clip space.
|
|
*
|
|
* Width and height are clip's dimension used to scale track's
|
|
* pattern and search regions.
|
|
*/
|
|
MovieTrackingTrack *BKE_tracking_track_add(MovieTracking *tracking,
|
|
ListBase *tracksbase,
|
|
float x,
|
|
float y,
|
|
int framenr,
|
|
int width,
|
|
int height)
|
|
{
|
|
MovieTrackingTrack *track;
|
|
MovieTrackingMarker marker;
|
|
MovieTrackingSettings *settings = &tracking->settings;
|
|
|
|
float half_pattern = (float)settings->default_pattern_size / 2.0f;
|
|
float half_search = (float)settings->default_search_size / 2.0f;
|
|
float pat[2], search[2];
|
|
|
|
pat[0] = half_pattern / (float)width;
|
|
pat[1] = half_pattern / (float)height;
|
|
|
|
search[0] = half_search / (float)width;
|
|
search[1] = half_search / (float)height;
|
|
|
|
track = MEM_callocN(sizeof(MovieTrackingTrack), "add_marker_exec track");
|
|
strcpy(track->name, "Track");
|
|
|
|
/* fill track's settings from default settings */
|
|
track->motion_model = settings->default_motion_model;
|
|
track->minimum_correlation = settings->default_minimum_correlation;
|
|
track->margin = settings->default_margin;
|
|
track->pattern_match = settings->default_pattern_match;
|
|
track->frames_limit = settings->default_frames_limit;
|
|
track->flag = settings->default_flag;
|
|
track->algorithm_flag = settings->default_algorithm_flag;
|
|
track->weight = settings->default_weight;
|
|
track->weight_stab = settings->default_weight;
|
|
|
|
memset(&marker, 0, sizeof(marker));
|
|
marker.pos[0] = x;
|
|
marker.pos[1] = y;
|
|
marker.framenr = framenr;
|
|
|
|
marker.pattern_corners[0][0] = -pat[0];
|
|
marker.pattern_corners[0][1] = -pat[1];
|
|
|
|
marker.pattern_corners[1][0] = pat[0];
|
|
marker.pattern_corners[1][1] = -pat[1];
|
|
|
|
negate_v2_v2(marker.pattern_corners[2], marker.pattern_corners[0]);
|
|
negate_v2_v2(marker.pattern_corners[3], marker.pattern_corners[1]);
|
|
|
|
copy_v2_v2(marker.search_max, search);
|
|
negate_v2_v2(marker.search_min, search);
|
|
|
|
BKE_tracking_marker_insert(track, &marker);
|
|
|
|
BLI_addtail(tracksbase, track);
|
|
BKE_tracking_track_unique_name(tracksbase, track);
|
|
|
|
return track;
|
|
}
|
|
|
|
/* Duplicate the specified track, result will no belong to any list. */
|
|
MovieTrackingTrack *BKE_tracking_track_duplicate(MovieTrackingTrack *track)
|
|
{
|
|
MovieTrackingTrack *new_track;
|
|
|
|
new_track = MEM_callocN(sizeof(MovieTrackingTrack), "tracking_track_duplicate new_track");
|
|
|
|
*new_track = *track;
|
|
new_track->next = new_track->prev = NULL;
|
|
|
|
new_track->markers = MEM_dupallocN(new_track->markers);
|
|
|
|
/* Prevent duplicate from being used for 2D stabilization.
|
|
* If necessary, it shall be added explicitly.
|
|
*/
|
|
new_track->flag &= ~TRACK_USE_2D_STAB;
|
|
new_track->flag &= ~TRACK_USE_2D_STAB_ROT;
|
|
|
|
return new_track;
|
|
}
|
|
|
|
/* Ensure specified track has got unique name,
|
|
* if it's not name of specified track will be changed
|
|
* keeping names of all other tracks unchanged.
|
|
*/
|
|
void BKE_tracking_track_unique_name(ListBase *tracksbase, MovieTrackingTrack *track)
|
|
{
|
|
BLI_uniquename(tracksbase,
|
|
track,
|
|
CTX_DATA_(BLT_I18NCONTEXT_ID_MOVIECLIP, "Track"),
|
|
'.',
|
|
offsetof(MovieTrackingTrack, name),
|
|
sizeof(track->name));
|
|
}
|
|
|
|
/* Free specified track, only frees contents of a structure
|
|
* (if track is allocated in heap, it shall be handled outside).
|
|
*
|
|
* All the pointers inside track becomes invalid after this call.
|
|
*/
|
|
void BKE_tracking_track_free(MovieTrackingTrack *track)
|
|
{
|
|
if (track->markers) {
|
|
MEM_freeN(track->markers);
|
|
}
|
|
}
|
|
|
|
/* Set flag for all specified track's areas.
|
|
*
|
|
* area - which part of marker should be selected. see TRACK_AREA_* constants.
|
|
* flag - flag to be set for areas.
|
|
*/
|
|
void BKE_tracking_track_flag_set(MovieTrackingTrack *track, int area, int flag)
|
|
{
|
|
if (area == TRACK_AREA_NONE) {
|
|
return;
|
|
}
|
|
|
|
if (area & TRACK_AREA_POINT) {
|
|
track->flag |= flag;
|
|
}
|
|
if (area & TRACK_AREA_PAT) {
|
|
track->pat_flag |= flag;
|
|
}
|
|
if (area & TRACK_AREA_SEARCH) {
|
|
track->search_flag |= flag;
|
|
}
|
|
}
|
|
|
|
/* Clear flag from all specified track's areas.
|
|
*
|
|
* area - which part of marker should be selected. see TRACK_AREA_* constants.
|
|
* flag - flag to be cleared for areas.
|
|
*/
|
|
void BKE_tracking_track_flag_clear(MovieTrackingTrack *track, int area, int flag)
|
|
{
|
|
if (area == TRACK_AREA_NONE) {
|
|
return;
|
|
}
|
|
|
|
if (area & TRACK_AREA_POINT) {
|
|
track->flag &= ~flag;
|
|
}
|
|
if (area & TRACK_AREA_PAT) {
|
|
track->pat_flag &= ~flag;
|
|
}
|
|
if (area & TRACK_AREA_SEARCH) {
|
|
track->search_flag &= ~flag;
|
|
}
|
|
}
|
|
|
|
/* Check whether track has got marker at specified frame.
|
|
*
|
|
* NOTE: frame number should be in clip space, not scene space.
|
|
*/
|
|
bool BKE_tracking_track_has_marker_at_frame(MovieTrackingTrack *track, int framenr)
|
|
{
|
|
return BKE_tracking_marker_get_exact(track, framenr) != NULL;
|
|
}
|
|
|
|
/* Check whether track has got enabled marker at specified frame.
|
|
*
|
|
* NOTE: frame number should be in clip space, not scene space.
|
|
*/
|
|
bool BKE_tracking_track_has_enabled_marker_at_frame(MovieTrackingTrack *track, int framenr)
|
|
{
|
|
MovieTrackingMarker *marker = BKE_tracking_marker_get_exact(track, framenr);
|
|
|
|
return marker && (marker->flag & MARKER_DISABLED) == 0;
|
|
}
|
|
|
|
/* Clear track's path:
|
|
*
|
|
* - If action is TRACK_CLEAR_REMAINED path from ref_frame+1 up to
|
|
* end will be clear.
|
|
*
|
|
* - If action is TRACK_CLEAR_UPTO path from the beginning up to
|
|
* ref_frame-1 will be clear.
|
|
*
|
|
* - If action is TRACK_CLEAR_ALL only marker at frame ref_frame will remain.
|
|
*
|
|
* NOTE: frame number should be in clip space, not scene space
|
|
*/
|
|
void BKE_tracking_track_path_clear(MovieTrackingTrack *track, int ref_frame, int action)
|
|
{
|
|
int a;
|
|
|
|
if (action == TRACK_CLEAR_REMAINED) {
|
|
a = 1;
|
|
|
|
while (a < track->markersnr) {
|
|
if (track->markers[a].framenr > ref_frame) {
|
|
track->markersnr = a;
|
|
track->markers = MEM_reallocN(track->markers,
|
|
sizeof(MovieTrackingMarker) * track->markersnr);
|
|
|
|
break;
|
|
}
|
|
|
|
a++;
|
|
}
|
|
|
|
if (track->markersnr) {
|
|
tracking_marker_insert_disabled(track, &track->markers[track->markersnr - 1], false, true);
|
|
}
|
|
}
|
|
else if (action == TRACK_CLEAR_UPTO) {
|
|
a = track->markersnr - 1;
|
|
|
|
while (a >= 0) {
|
|
if (track->markers[a].framenr <= ref_frame) {
|
|
memmove(track->markers,
|
|
track->markers + a,
|
|
(track->markersnr - a) * sizeof(MovieTrackingMarker));
|
|
|
|
track->markersnr = track->markersnr - a;
|
|
track->markers = MEM_reallocN(track->markers,
|
|
sizeof(MovieTrackingMarker) * track->markersnr);
|
|
|
|
break;
|
|
}
|
|
|
|
a--;
|
|
}
|
|
|
|
if (track->markersnr) {
|
|
tracking_marker_insert_disabled(track, &track->markers[0], true, true);
|
|
}
|
|
}
|
|
else if (action == TRACK_CLEAR_ALL) {
|
|
MovieTrackingMarker *marker, marker_new;
|
|
|
|
marker = BKE_tracking_marker_get(track, ref_frame);
|
|
marker_new = *marker;
|
|
|
|
MEM_freeN(track->markers);
|
|
track->markers = NULL;
|
|
track->markersnr = 0;
|
|
|
|
BKE_tracking_marker_insert(track, &marker_new);
|
|
|
|
tracking_marker_insert_disabled(track, &marker_new, true, true);
|
|
tracking_marker_insert_disabled(track, &marker_new, false, true);
|
|
}
|
|
}
|
|
|
|
void BKE_tracking_tracks_join(MovieTracking *tracking,
|
|
MovieTrackingTrack *dst_track,
|
|
MovieTrackingTrack *src_track)
|
|
{
|
|
int i = 0, a = 0, b = 0, tot;
|
|
MovieTrackingMarker *markers;
|
|
|
|
tot = dst_track->markersnr + src_track->markersnr;
|
|
markers = MEM_callocN(tot * sizeof(MovieTrackingMarker), "tmp tracking joined tracks");
|
|
|
|
while (a < src_track->markersnr || b < dst_track->markersnr) {
|
|
if (b >= dst_track->markersnr) {
|
|
markers[i] = src_track->markers[a++];
|
|
}
|
|
else if (a >= src_track->markersnr) {
|
|
markers[i] = dst_track->markers[b++];
|
|
}
|
|
else if (src_track->markers[a].framenr < dst_track->markers[b].framenr) {
|
|
markers[i] = src_track->markers[a++];
|
|
}
|
|
else if (src_track->markers[a].framenr > dst_track->markers[b].framenr) {
|
|
markers[i] = dst_track->markers[b++];
|
|
}
|
|
else {
|
|
if ((src_track->markers[a].flag & MARKER_DISABLED) == 0) {
|
|
if ((dst_track->markers[b].flag & MARKER_DISABLED) == 0) {
|
|
/* both tracks are enabled on this frame, so find the whole segment
|
|
* on which tracks are intersecting and blend tracks using linear
|
|
* interpolation to prevent jumps
|
|
*/
|
|
|
|
MovieTrackingMarker *marker_a, *marker_b;
|
|
int start_a = a, start_b = b, len = 0, frame = src_track->markers[a].framenr;
|
|
int inverse = 0;
|
|
|
|
inverse = (b == 0) || (dst_track->markers[b - 1].flag & MARKER_DISABLED) ||
|
|
(dst_track->markers[b - 1].framenr != frame - 1);
|
|
|
|
/* find length of intersection */
|
|
while (a < src_track->markersnr && b < dst_track->markersnr) {
|
|
marker_a = &src_track->markers[a];
|
|
marker_b = &dst_track->markers[b];
|
|
|
|
if (marker_a->flag & MARKER_DISABLED || marker_b->flag & MARKER_DISABLED) {
|
|
break;
|
|
}
|
|
|
|
if (marker_a->framenr != frame || marker_b->framenr != frame) {
|
|
break;
|
|
}
|
|
|
|
frame++;
|
|
len++;
|
|
a++;
|
|
b++;
|
|
}
|
|
|
|
a = start_a;
|
|
b = start_b;
|
|
|
|
/* linear interpolation for intersecting frames */
|
|
for (int j = 0; j < len; j++) {
|
|
float fac = 0.5f;
|
|
|
|
if (len > 1) {
|
|
fac = 1.0f / (len - 1) * j;
|
|
}
|
|
|
|
if (inverse) {
|
|
fac = 1.0f - fac;
|
|
}
|
|
|
|
marker_a = &src_track->markers[a];
|
|
marker_b = &dst_track->markers[b];
|
|
|
|
markers[i] = dst_track->markers[b];
|
|
interp_v2_v2v2(markers[i].pos, marker_b->pos, marker_a->pos, fac);
|
|
a++;
|
|
b++;
|
|
i++;
|
|
}
|
|
|
|
/* this values will be incremented at the end of the loop cycle */
|
|
a--;
|
|
b--;
|
|
i--;
|
|
}
|
|
else {
|
|
markers[i] = src_track->markers[a];
|
|
}
|
|
}
|
|
else {
|
|
markers[i] = dst_track->markers[b];
|
|
}
|
|
|
|
a++;
|
|
b++;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
MEM_freeN(dst_track->markers);
|
|
|
|
dst_track->markers = MEM_mallocN(i * sizeof(MovieTrackingMarker), "tracking joined tracks");
|
|
memcpy(dst_track->markers, markers, i * sizeof(MovieTrackingMarker));
|
|
|
|
dst_track->markersnr = i;
|
|
|
|
MEM_freeN(markers);
|
|
|
|
BKE_tracking_dopesheet_tag_update(tracking);
|
|
}
|
|
|
|
MovieTrackingTrack *BKE_tracking_track_get_named(MovieTracking *tracking,
|
|
MovieTrackingObject *object,
|
|
const char *name)
|
|
{
|
|
ListBase *tracksbase = BKE_tracking_object_get_tracks(tracking, object);
|
|
MovieTrackingTrack *track = tracksbase->first;
|
|
|
|
while (track) {
|
|
if (STREQ(track->name, name)) {
|
|
return track;
|
|
}
|
|
|
|
track = track->next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
MovieTrackingTrack *BKE_tracking_track_get_indexed(MovieTracking *tracking,
|
|
int tracknr,
|
|
ListBase **r_tracksbase)
|
|
{
|
|
MovieTrackingObject *object;
|
|
int cur = 1;
|
|
|
|
object = tracking->objects.first;
|
|
while (object) {
|
|
ListBase *tracksbase = BKE_tracking_object_get_tracks(tracking, object);
|
|
MovieTrackingTrack *track = tracksbase->first;
|
|
|
|
while (track) {
|
|
if (track->flag & TRACK_HAS_BUNDLE) {
|
|
if (cur == tracknr) {
|
|
*r_tracksbase = tracksbase;
|
|
return track;
|
|
}
|
|
|
|
cur++;
|
|
}
|
|
|
|
track = track->next;
|
|
}
|
|
|
|
object = object->next;
|
|
}
|
|
|
|
*r_tracksbase = NULL;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
MovieTrackingTrack *BKE_tracking_track_get_active(MovieTracking *tracking)
|
|
{
|
|
ListBase *tracksbase;
|
|
|
|
if (!tracking->act_track) {
|
|
return NULL;
|
|
}
|
|
|
|
tracksbase = BKE_tracking_get_active_tracks(tracking);
|
|
|
|
/* check that active track is in current tracks list */
|
|
if (BLI_findindex(tracksbase, tracking->act_track) != -1) {
|
|
return tracking->act_track;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static bGPDlayer *track_mask_gpencil_layer_get(MovieTrackingTrack *track)
|
|
{
|
|
bGPDlayer *layer;
|
|
|
|
if (!track->gpd) {
|
|
return NULL;
|
|
}
|
|
|
|
layer = track->gpd->layers.first;
|
|
|
|
while (layer) {
|
|
if (layer->flag & GP_LAYER_ACTIVE) {
|
|
bGPDframe *frame = layer->frames.first;
|
|
bool ok = false;
|
|
|
|
while (frame) {
|
|
if (frame->strokes.first) {
|
|
ok = true;
|
|
break;
|
|
}
|
|
|
|
frame = frame->next;
|
|
}
|
|
|
|
if (ok) {
|
|
return layer;
|
|
}
|
|
}
|
|
|
|
layer = layer->next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
typedef struct TrackMaskSetPixelData {
|
|
float *mask;
|
|
int mask_width;
|
|
int mask_height;
|
|
} TrackMaskSetPixelData;
|
|
|
|
static void track_mask_set_pixel_cb(int x, int x_end, int y, void *user_data)
|
|
{
|
|
TrackMaskSetPixelData *data = (TrackMaskSetPixelData *)user_data;
|
|
size_t index = (size_t)y * data->mask_width + x;
|
|
size_t index_end = (size_t)y * data->mask_width + x_end;
|
|
do {
|
|
data->mask[index] = 1.0f;
|
|
} while (++index != index_end);
|
|
}
|
|
|
|
static void track_mask_gpencil_layer_rasterize(int frame_width,
|
|
int frame_height,
|
|
const float region_min[2],
|
|
bGPDlayer *layer,
|
|
float *mask,
|
|
int mask_width,
|
|
int mask_height)
|
|
{
|
|
bGPDframe *frame = layer->frames.first;
|
|
TrackMaskSetPixelData data;
|
|
|
|
data.mask = mask;
|
|
data.mask_width = mask_width;
|
|
data.mask_height = mask_height;
|
|
|
|
while (frame) {
|
|
bGPDstroke *stroke = frame->strokes.first;
|
|
|
|
while (stroke) {
|
|
bGPDspoint *stroke_points = stroke->points;
|
|
if (stroke->flag & GP_STROKE_2DSPACE) {
|
|
int *mask_points, *point;
|
|
point = mask_points = MEM_callocN(2 * stroke->totpoints * sizeof(int),
|
|
"track mask rasterization points");
|
|
for (int i = 0; i < stroke->totpoints; i++, point += 2) {
|
|
point[0] = stroke_points[i].x * frame_width - region_min[0];
|
|
point[1] = stroke_points[i].y * frame_height - region_min[1];
|
|
}
|
|
/* TODO: add an option to control whether AA is enabled or not */
|
|
BLI_bitmap_draw_2d_poly_v2i_n(0,
|
|
0,
|
|
mask_width,
|
|
mask_height,
|
|
(const int(*)[2])mask_points,
|
|
stroke->totpoints,
|
|
track_mask_set_pixel_cb,
|
|
&data);
|
|
MEM_freeN(mask_points);
|
|
}
|
|
stroke = stroke->next;
|
|
}
|
|
frame = frame->next;
|
|
}
|
|
}
|
|
|
|
/* Region is in pixel space, relative to marker's center. */
|
|
float *tracking_track_get_mask_for_region(int frame_width,
|
|
int frame_height,
|
|
const float region_min[2],
|
|
const float region_max[2],
|
|
MovieTrackingTrack *track)
|
|
{
|
|
float *mask = NULL;
|
|
bGPDlayer *layer = track_mask_gpencil_layer_get(track);
|
|
if (layer != NULL) {
|
|
const int mask_width = region_max[0] - region_min[0];
|
|
const int mask_height = region_max[1] - region_min[1];
|
|
mask = MEM_callocN(mask_width * mask_height * sizeof(float), "track mask");
|
|
track_mask_gpencil_layer_rasterize(
|
|
frame_width, frame_height, region_min, layer, mask, mask_width, mask_height);
|
|
}
|
|
return mask;
|
|
}
|
|
|
|
float *BKE_tracking_track_get_mask(int frame_width,
|
|
int frame_height,
|
|
MovieTrackingTrack *track,
|
|
MovieTrackingMarker *marker)
|
|
{
|
|
/* Convert normalized space marker's search area to pixel-space region. */
|
|
const float region_min[2] = {
|
|
marker->search_min[0] * frame_width,
|
|
marker->search_min[1] * frame_height,
|
|
};
|
|
const float region_max[2] = {
|
|
marker->search_max[0] * frame_width,
|
|
marker->search_max[1] * frame_height,
|
|
};
|
|
return tracking_track_get_mask_for_region(
|
|
frame_width, frame_height, region_min, region_max, track);
|
|
}
|
|
|
|
float BKE_tracking_track_get_weight_for_marker(MovieClip *clip,
|
|
MovieTrackingTrack *track,
|
|
MovieTrackingMarker *marker)
|
|
{
|
|
FCurve *weight_fcurve;
|
|
float weight = track->weight;
|
|
|
|
weight_fcurve = id_data_find_fcurve(
|
|
&clip->id, track, &RNA_MovieTrackingTrack, "weight", 0, NULL);
|
|
|
|
if (weight_fcurve) {
|
|
int scene_framenr = BKE_movieclip_remap_clip_to_scene_frame(clip, marker->framenr);
|
|
weight = evaluate_fcurve(weight_fcurve, scene_framenr);
|
|
}
|
|
|
|
return weight;
|
|
}
|
|
|
|
/* area - which part of marker should be selected. see TRACK_AREA_* constants */
|
|
void BKE_tracking_track_select(ListBase *tracksbase,
|
|
MovieTrackingTrack *track,
|
|
int area,
|
|
bool extend)
|
|
{
|
|
if (extend) {
|
|
BKE_tracking_track_flag_set(track, area, SELECT);
|
|
}
|
|
else {
|
|
MovieTrackingTrack *cur = tracksbase->first;
|
|
|
|
while (cur) {
|
|
if ((cur->flag & TRACK_HIDDEN) == 0) {
|
|
if (cur == track) {
|
|
BKE_tracking_track_flag_clear(cur, TRACK_AREA_ALL, SELECT);
|
|
BKE_tracking_track_flag_set(cur, area, SELECT);
|
|
}
|
|
else {
|
|
BKE_tracking_track_flag_clear(cur, TRACK_AREA_ALL, SELECT);
|
|
}
|
|
}
|
|
|
|
cur = cur->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
void BKE_tracking_track_deselect(MovieTrackingTrack *track, int area)
|
|
{
|
|
BKE_tracking_track_flag_clear(track, area, SELECT);
|
|
}
|
|
|
|
void BKE_tracking_tracks_deselect_all(ListBase *tracksbase)
|
|
{
|
|
LISTBASE_FOREACH (MovieTrackingTrack *, track, tracksbase) {
|
|
if ((track->flag & TRACK_HIDDEN) == 0) {
|
|
BKE_tracking_track_flag_clear(track, TRACK_AREA_ALL, SELECT);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************** Marker *************************/
|
|
|
|
MovieTrackingMarker *BKE_tracking_marker_insert(MovieTrackingTrack *track,
|
|
MovieTrackingMarker *marker)
|
|
{
|
|
MovieTrackingMarker *old_marker = NULL;
|
|
|
|
if (track->markersnr) {
|
|
old_marker = BKE_tracking_marker_get_exact(track, marker->framenr);
|
|
}
|
|
|
|
if (old_marker) {
|
|
/* simply replace settings for already allocated marker */
|
|
*old_marker = *marker;
|
|
|
|
return old_marker;
|
|
}
|
|
|
|
int a = track->markersnr;
|
|
|
|
/* find position in array where to add new marker */
|
|
while (a--) {
|
|
if (track->markers[a].framenr < marker->framenr) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
track->markersnr++;
|
|
|
|
if (track->markers) {
|
|
track->markers = MEM_reallocN(track->markers, sizeof(MovieTrackingMarker) * track->markersnr);
|
|
}
|
|
else {
|
|
track->markers = MEM_callocN(sizeof(MovieTrackingMarker), "MovieTracking markers");
|
|
}
|
|
|
|
/* shift array to "free" space for new marker */
|
|
memmove(track->markers + a + 2,
|
|
track->markers + a + 1,
|
|
(track->markersnr - a - 2) * sizeof(MovieTrackingMarker));
|
|
|
|
/* put new marker */
|
|
track->markers[a + 1] = *marker;
|
|
|
|
track->last_marker = a + 1;
|
|
|
|
return &track->markers[a + 1];
|
|
}
|
|
|
|
void BKE_tracking_marker_delete(MovieTrackingTrack *track, int framenr)
|
|
{
|
|
int a = 0;
|
|
|
|
while (a < track->markersnr) {
|
|
if (track->markers[a].framenr == framenr) {
|
|
if (track->markersnr > 1) {
|
|
memmove(track->markers + a,
|
|
track->markers + a + 1,
|
|
(track->markersnr - a - 1) * sizeof(MovieTrackingMarker));
|
|
track->markersnr--;
|
|
track->markers = MEM_reallocN(track->markers,
|
|
sizeof(MovieTrackingMarker) * track->markersnr);
|
|
}
|
|
else {
|
|
MEM_freeN(track->markers);
|
|
track->markers = NULL;
|
|
track->markersnr = 0;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
a++;
|
|
}
|
|
}
|
|
|
|
void BKE_tracking_marker_clamp(MovieTrackingMarker *marker, int event)
|
|
{
|
|
float pat_min[2], pat_max[2];
|
|
|
|
BKE_tracking_marker_pattern_minmax(marker, pat_min, pat_max);
|
|
|
|
if (event == CLAMP_PAT_DIM) {
|
|
for (int a = 0; a < 2; a++) {
|
|
/* search shouldn't be resized smaller than pattern */
|
|
marker->search_min[a] = min_ff(pat_min[a], marker->search_min[a]);
|
|
marker->search_max[a] = max_ff(pat_max[a], marker->search_max[a]);
|
|
}
|
|
}
|
|
else if (event == CLAMP_PAT_POS) {
|
|
float dim[2];
|
|
|
|
sub_v2_v2v2(dim, pat_max, pat_min);
|
|
|
|
for (int a = 0; a < 2; a++) {
|
|
/* pattern shouldn't be moved outside of search */
|
|
if (pat_min[a] < marker->search_min[a]) {
|
|
for (int b = 0; b < 4; b++) {
|
|
marker->pattern_corners[b][a] += marker->search_min[a] - pat_min[a];
|
|
}
|
|
}
|
|
if (pat_max[a] > marker->search_max[a]) {
|
|
for (int b = 0; b < 4; b++) {
|
|
marker->pattern_corners[b][a] -= pat_max[a] - marker->search_max[a];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (event == CLAMP_SEARCH_DIM) {
|
|
for (int a = 0; a < 2; a++) {
|
|
/* search shouldn't be resized smaller than pattern */
|
|
marker->search_min[a] = min_ff(pat_min[a], marker->search_min[a]);
|
|
marker->search_max[a] = max_ff(pat_max[a], marker->search_max[a]);
|
|
}
|
|
}
|
|
else if (event == CLAMP_SEARCH_POS) {
|
|
float dim[2];
|
|
|
|
sub_v2_v2v2(dim, marker->search_max, marker->search_min);
|
|
|
|
for (int a = 0; a < 2; a++) {
|
|
/* search shouldn't be moved inside pattern */
|
|
if (marker->search_min[a] > pat_min[a]) {
|
|
marker->search_min[a] = pat_min[a];
|
|
marker->search_max[a] = marker->search_min[a] + dim[a];
|
|
}
|
|
if (marker->search_max[a] < pat_max[a]) {
|
|
marker->search_max[a] = pat_max[a];
|
|
marker->search_min[a] = marker->search_max[a] - dim[a];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MovieTrackingMarker *BKE_tracking_marker_get(MovieTrackingTrack *track, int framenr)
|
|
{
|
|
int a = track->markersnr - 1;
|
|
|
|
if (!track->markersnr) {
|
|
return NULL;
|
|
}
|
|
|
|
/* approximate pre-first framenr marker with first marker */
|
|
if (framenr < track->markers[0].framenr) {
|
|
return &track->markers[0];
|
|
}
|
|
|
|
if (track->last_marker < track->markersnr) {
|
|
a = track->last_marker;
|
|
}
|
|
|
|
if (track->markers[a].framenr <= framenr) {
|
|
while (a < track->markersnr && track->markers[a].framenr <= framenr) {
|
|
if (track->markers[a].framenr == framenr) {
|
|
track->last_marker = a;
|
|
|
|
return &track->markers[a];
|
|
}
|
|
a++;
|
|
}
|
|
|
|
/* if there's no marker for exact position, use nearest marker from left side */
|
|
return &track->markers[a - 1];
|
|
}
|
|
|
|
while (a >= 0 && track->markers[a].framenr >= framenr) {
|
|
if (track->markers[a].framenr == framenr) {
|
|
track->last_marker = a;
|
|
|
|
return &track->markers[a];
|
|
}
|
|
|
|
a--;
|
|
}
|
|
|
|
/* if there's no marker for exact position, use nearest marker from left side */
|
|
return &track->markers[a];
|
|
|
|
return NULL;
|
|
}
|
|
|
|
MovieTrackingMarker *BKE_tracking_marker_get_exact(MovieTrackingTrack *track, int framenr)
|
|
{
|
|
MovieTrackingMarker *marker = BKE_tracking_marker_get(track, framenr);
|
|
|
|
if (marker->framenr != framenr) {
|
|
return NULL;
|
|
}
|
|
|
|
return marker;
|
|
}
|
|
|
|
MovieTrackingMarker *BKE_tracking_marker_ensure(MovieTrackingTrack *track, int framenr)
|
|
{
|
|
MovieTrackingMarker *marker = BKE_tracking_marker_get(track, framenr);
|
|
|
|
if (marker->framenr != framenr) {
|
|
MovieTrackingMarker marker_new;
|
|
|
|
marker_new = *marker;
|
|
marker_new.framenr = framenr;
|
|
|
|
BKE_tracking_marker_insert(track, &marker_new);
|
|
marker = BKE_tracking_marker_get(track, framenr);
|
|
}
|
|
|
|
return marker;
|
|
}
|
|
|
|
void BKE_tracking_marker_pattern_minmax(const MovieTrackingMarker *marker,
|
|
float min[2],
|
|
float max[2])
|
|
{
|
|
INIT_MINMAX2(min, max);
|
|
|
|
minmax_v2v2_v2(min, max, marker->pattern_corners[0]);
|
|
minmax_v2v2_v2(min, max, marker->pattern_corners[1]);
|
|
minmax_v2v2_v2(min, max, marker->pattern_corners[2]);
|
|
minmax_v2v2_v2(min, max, marker->pattern_corners[3]);
|
|
}
|
|
|
|
void BKE_tracking_marker_get_subframe_position(MovieTrackingTrack *track,
|
|
float framenr,
|
|
float pos[2])
|
|
{
|
|
MovieTrackingMarker *marker = BKE_tracking_marker_get(track, (int)framenr);
|
|
MovieTrackingMarker *marker_last = track->markers + (track->markersnr - 1);
|
|
|
|
if (marker != marker_last) {
|
|
MovieTrackingMarker *marker_next = marker + 1;
|
|
|
|
if (marker_next->framenr == marker->framenr + 1) {
|
|
/* currently only do subframing inside tracked ranges, do not extrapolate tracked segments
|
|
* could be changed when / if mask parent would be interpolating position in-between
|
|
* tracked segments
|
|
*/
|
|
|
|
float fac = (framenr - (int)framenr) / (marker_next->framenr - marker->framenr);
|
|
|
|
interp_v2_v2v2(pos, marker->pos, marker_next->pos, fac);
|
|
}
|
|
else {
|
|
copy_v2_v2(pos, marker->pos);
|
|
}
|
|
}
|
|
else {
|
|
copy_v2_v2(pos, marker->pos);
|
|
}
|
|
|
|
/* currently track offset is always wanted to be applied here, could be made an option later */
|
|
add_v2_v2(pos, track->offset);
|
|
}
|
|
|
|
/*********************** Plane Track *************************/
|
|
|
|
/* Creates new plane track out of selected point tracks */
|
|
MovieTrackingPlaneTrack *BKE_tracking_plane_track_add(MovieTracking *tracking,
|
|
ListBase *plane_tracks_base,
|
|
ListBase *tracks,
|
|
int framenr)
|
|
{
|
|
MovieTrackingPlaneTrack *plane_track;
|
|
MovieTrackingPlaneMarker plane_marker;
|
|
float tracks_min[2], tracks_max[2];
|
|
int num_selected_tracks = 0;
|
|
|
|
(void)tracking; /* Ignored. */
|
|
|
|
/* Use bounding box of selected markers as an initial size of plane. */
|
|
INIT_MINMAX2(tracks_min, tracks_max);
|
|
LISTBASE_FOREACH (MovieTrackingTrack *, track, tracks) {
|
|
if (TRACK_SELECTED(track)) {
|
|
MovieTrackingMarker *marker = BKE_tracking_marker_get(track, framenr);
|
|
float pattern_min[2], pattern_max[2];
|
|
BKE_tracking_marker_pattern_minmax(marker, pattern_min, pattern_max);
|
|
add_v2_v2(pattern_min, marker->pos);
|
|
add_v2_v2(pattern_max, marker->pos);
|
|
minmax_v2v2_v2(tracks_min, tracks_max, pattern_min);
|
|
minmax_v2v2_v2(tracks_min, tracks_max, pattern_max);
|
|
num_selected_tracks++;
|
|
}
|
|
}
|
|
|
|
if (num_selected_tracks < 4) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Allocate new plane track. */
|
|
plane_track = MEM_callocN(sizeof(MovieTrackingPlaneTrack), "new plane track");
|
|
|
|
/* Use some default name. */
|
|
strcpy(plane_track->name, "Plane Track");
|
|
|
|
plane_track->image_opacity = 1.0f;
|
|
|
|
/* Use selected tracks from given list as a plane. */
|
|
plane_track->point_tracks = MEM_mallocN(sizeof(MovieTrackingTrack *) * num_selected_tracks,
|
|
"new plane tracks array");
|
|
int track_index = 0;
|
|
LISTBASE_FOREACH (MovieTrackingTrack *, track, tracks) {
|
|
if (TRACK_SELECTED(track)) {
|
|
plane_track->point_tracks[track_index] = track;
|
|
track_index++;
|
|
}
|
|
}
|
|
plane_track->point_tracksnr = num_selected_tracks;
|
|
|
|
/* Setup new plane marker and add it to the track. */
|
|
plane_marker.framenr = framenr;
|
|
plane_marker.flag = 0;
|
|
|
|
copy_v2_v2(plane_marker.corners[0], tracks_min);
|
|
copy_v2_v2(plane_marker.corners[2], tracks_max);
|
|
|
|
plane_marker.corners[1][0] = tracks_max[0];
|
|
plane_marker.corners[1][1] = tracks_min[1];
|
|
plane_marker.corners[3][0] = tracks_min[0];
|
|
plane_marker.corners[3][1] = tracks_max[1];
|
|
|
|
BKE_tracking_plane_marker_insert(plane_track, &plane_marker);
|
|
|
|
/* Put new plane track to the list, ensure its name is unique. */
|
|
BLI_addtail(plane_tracks_base, plane_track);
|
|
BKE_tracking_plane_track_unique_name(plane_tracks_base, plane_track);
|
|
|
|
return plane_track;
|
|
}
|
|
|
|
void BKE_tracking_plane_track_unique_name(ListBase *plane_tracks_base,
|
|
MovieTrackingPlaneTrack *plane_track)
|
|
{
|
|
BLI_uniquename(plane_tracks_base,
|
|
plane_track,
|
|
CTX_DATA_(BLT_I18NCONTEXT_ID_MOVIECLIP, "Plane Track"),
|
|
'.',
|
|
offsetof(MovieTrackingPlaneTrack, name),
|
|
sizeof(plane_track->name));
|
|
}
|
|
|
|
/* Free specified plane track, only frees contents of a structure
|
|
* (if track is allocated in heap, it shall be handled outside).
|
|
*
|
|
* All the pointers inside track becomes invalid after this call.
|
|
*/
|
|
void BKE_tracking_plane_track_free(MovieTrackingPlaneTrack *plane_track)
|
|
{
|
|
if (plane_track->markers) {
|
|
MEM_freeN(plane_track->markers);
|
|
}
|
|
|
|
MEM_freeN(plane_track->point_tracks);
|
|
}
|
|
|
|
MovieTrackingPlaneTrack *BKE_tracking_plane_track_get_named(MovieTracking *tracking,
|
|
MovieTrackingObject *object,
|
|
const char *name)
|
|
{
|
|
ListBase *plane_tracks_base = BKE_tracking_object_get_plane_tracks(tracking, object);
|
|
|
|
LISTBASE_FOREACH (MovieTrackingPlaneTrack *, plane_track, plane_tracks_base) {
|
|
if (STREQ(plane_track->name, name)) {
|
|
return plane_track;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
MovieTrackingPlaneTrack *BKE_tracking_plane_track_get_active(struct MovieTracking *tracking)
|
|
{
|
|
if (tracking->act_plane_track == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
ListBase *plane_tracks_base = BKE_tracking_get_active_plane_tracks(tracking);
|
|
|
|
/* Check that active track is in current plane tracks list */
|
|
if (BLI_findindex(plane_tracks_base, tracking->act_plane_track) != -1) {
|
|
return tracking->act_plane_track;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void BKE_tracking_plane_tracks_deselect_all(ListBase *plane_tracks_base)
|
|
{
|
|
LISTBASE_FOREACH (MovieTrackingPlaneTrack *, plane_track, plane_tracks_base) {
|
|
plane_track->flag &= ~SELECT;
|
|
}
|
|
}
|
|
|
|
bool BKE_tracking_plane_track_has_point_track(MovieTrackingPlaneTrack *plane_track,
|
|
MovieTrackingTrack *track)
|
|
{
|
|
for (int i = 0; i < plane_track->point_tracksnr; i++) {
|
|
if (plane_track->point_tracks[i] == track) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool BKE_tracking_plane_track_remove_point_track(MovieTrackingPlaneTrack *plane_track,
|
|
MovieTrackingTrack *track)
|
|
{
|
|
if (plane_track->point_tracksnr <= 4) {
|
|
return false;
|
|
}
|
|
|
|
MovieTrackingTrack **new_point_tracks = MEM_mallocN(
|
|
sizeof(*new_point_tracks) * (plane_track->point_tracksnr - 1), "new point tracks array");
|
|
|
|
for (int i = 0, track_index = 0; i < plane_track->point_tracksnr; i++) {
|
|
if (plane_track->point_tracks[i] != track) {
|
|
new_point_tracks[track_index++] = plane_track->point_tracks[i];
|
|
}
|
|
}
|
|
|
|
MEM_freeN(plane_track->point_tracks);
|
|
plane_track->point_tracks = new_point_tracks;
|
|
plane_track->point_tracksnr--;
|
|
|
|
return true;
|
|
}
|
|
|
|
void BKE_tracking_plane_tracks_remove_point_track(MovieTracking *tracking,
|
|
MovieTrackingTrack *track)
|
|
{
|
|
ListBase *plane_tracks_base = BKE_tracking_get_active_plane_tracks(tracking);
|
|
LISTBASE_FOREACH_MUTABLE (MovieTrackingPlaneTrack *, plane_track, plane_tracks_base) {
|
|
if (BKE_tracking_plane_track_has_point_track(plane_track, track)) {
|
|
if (!BKE_tracking_plane_track_remove_point_track(plane_track, track)) {
|
|
/* Delete planes with less than 3 point tracks in it. */
|
|
BKE_tracking_plane_track_free(plane_track);
|
|
BLI_freelinkN(plane_tracks_base, plane_track);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BKE_tracking_plane_track_replace_point_track(MovieTrackingPlaneTrack *plane_track,
|
|
MovieTrackingTrack *old_track,
|
|
MovieTrackingTrack *new_track)
|
|
{
|
|
for (int i = 0; i < plane_track->point_tracksnr; i++) {
|
|
if (plane_track->point_tracks[i] == old_track) {
|
|
plane_track->point_tracks[i] = new_track;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void BKE_tracking_plane_tracks_replace_point_track(MovieTracking *tracking,
|
|
MovieTrackingTrack *old_track,
|
|
MovieTrackingTrack *new_track)
|
|
{
|
|
ListBase *plane_tracks_base = BKE_tracking_get_active_plane_tracks(tracking);
|
|
LISTBASE_FOREACH (MovieTrackingPlaneTrack *, plane_track, plane_tracks_base) {
|
|
if (BKE_tracking_plane_track_has_point_track(plane_track, old_track)) {
|
|
BKE_tracking_plane_track_replace_point_track(plane_track, old_track, new_track);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************** Plane Marker *************************/
|
|
|
|
MovieTrackingPlaneMarker *BKE_tracking_plane_marker_insert(MovieTrackingPlaneTrack *plane_track,
|
|
MovieTrackingPlaneMarker *plane_marker)
|
|
{
|
|
MovieTrackingPlaneMarker *old_plane_marker = NULL;
|
|
|
|
if (plane_track->markersnr) {
|
|
old_plane_marker = BKE_tracking_plane_marker_get_exact(plane_track, plane_marker->framenr);
|
|
}
|
|
|
|
if (old_plane_marker) {
|
|
/* Simply replace settings in existing marker. */
|
|
*old_plane_marker = *plane_marker;
|
|
|
|
return old_plane_marker;
|
|
}
|
|
|
|
int a = plane_track->markersnr;
|
|
|
|
/* Find position in array where to add new marker. */
|
|
/* TODO(sergey): we could use bisect to speed things up. */
|
|
while (a--) {
|
|
if (plane_track->markers[a].framenr < plane_marker->framenr) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
plane_track->markersnr++;
|
|
plane_track->markers = MEM_reallocN(plane_track->markers,
|
|
sizeof(MovieTrackingPlaneMarker) * plane_track->markersnr);
|
|
|
|
/* Shift array to "free" space for new marker. */
|
|
memmove(plane_track->markers + a + 2,
|
|
plane_track->markers + a + 1,
|
|
(plane_track->markersnr - a - 2) * sizeof(MovieTrackingPlaneMarker));
|
|
|
|
/* Put new marker to an array. */
|
|
plane_track->markers[a + 1] = *plane_marker;
|
|
plane_track->last_marker = a + 1;
|
|
|
|
return &plane_track->markers[a + 1];
|
|
}
|
|
|
|
void BKE_tracking_plane_marker_delete(MovieTrackingPlaneTrack *plane_track, int framenr)
|
|
{
|
|
int a = 0;
|
|
|
|
while (a < plane_track->markersnr) {
|
|
if (plane_track->markers[a].framenr == framenr) {
|
|
if (plane_track->markersnr > 1) {
|
|
memmove(plane_track->markers + a,
|
|
plane_track->markers + a + 1,
|
|
(plane_track->markersnr - a - 1) * sizeof(MovieTrackingPlaneMarker));
|
|
plane_track->markersnr--;
|
|
plane_track->markers = MEM_reallocN(plane_track->markers,
|
|
sizeof(MovieTrackingMarker) * plane_track->markersnr);
|
|
}
|
|
else {
|
|
MEM_freeN(plane_track->markers);
|
|
plane_track->markers = NULL;
|
|
plane_track->markersnr = 0;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
a++;
|
|
}
|
|
}
|
|
|
|
/* TODO(sergey): The next couple of functions are really quite the same as point marker version,
|
|
* would be nice to de-duplicate them somehow..
|
|
*/
|
|
|
|
/* Get a plane marker at given frame,
|
|
* If there's no such marker, closest one from the left side will be returned.
|
|
*/
|
|
MovieTrackingPlaneMarker *BKE_tracking_plane_marker_get(MovieTrackingPlaneTrack *plane_track,
|
|
int framenr)
|
|
{
|
|
int a = plane_track->markersnr - 1;
|
|
|
|
if (!plane_track->markersnr) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Approximate pre-first framenr marker with first marker. */
|
|
if (framenr < plane_track->markers[0].framenr) {
|
|
return &plane_track->markers[0];
|
|
}
|
|
|
|
if (plane_track->last_marker < plane_track->markersnr) {
|
|
a = plane_track->last_marker;
|
|
}
|
|
|
|
if (plane_track->markers[a].framenr <= framenr) {
|
|
while (a < plane_track->markersnr && plane_track->markers[a].framenr <= framenr) {
|
|
if (plane_track->markers[a].framenr == framenr) {
|
|
plane_track->last_marker = a;
|
|
|
|
return &plane_track->markers[a];
|
|
}
|
|
a++;
|
|
}
|
|
|
|
/* If there's no marker for exact position, use nearest marker from left side. */
|
|
return &plane_track->markers[a - 1];
|
|
}
|
|
|
|
while (a >= 0 && plane_track->markers[a].framenr >= framenr) {
|
|
if (plane_track->markers[a].framenr == framenr) {
|
|
plane_track->last_marker = a;
|
|
|
|
return &plane_track->markers[a];
|
|
}
|
|
|
|
a--;
|
|
}
|
|
|
|
/* If there's no marker for exact position, use nearest marker from left side. */
|
|
return &plane_track->markers[a];
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Get a plane marker at exact given frame, if there's no marker at the frame,
|
|
* NULL will be returned.
|
|
*/
|
|
MovieTrackingPlaneMarker *BKE_tracking_plane_marker_get_exact(MovieTrackingPlaneTrack *plane_track,
|
|
int framenr)
|
|
{
|
|
MovieTrackingPlaneMarker *plane_marker = BKE_tracking_plane_marker_get(plane_track, framenr);
|
|
|
|
if (plane_marker->framenr != framenr) {
|
|
return NULL;
|
|
}
|
|
|
|
return plane_marker;
|
|
}
|
|
|
|
/* Ensure there's a marker for the given frame. */
|
|
MovieTrackingPlaneMarker *BKE_tracking_plane_marker_ensure(MovieTrackingPlaneTrack *plane_track,
|
|
int framenr)
|
|
{
|
|
MovieTrackingPlaneMarker *plane_marker = BKE_tracking_plane_marker_get(plane_track, framenr);
|
|
|
|
if (plane_marker->framenr != framenr) {
|
|
MovieTrackingPlaneMarker plane_marker_new;
|
|
|
|
plane_marker_new = *plane_marker;
|
|
plane_marker_new.framenr = framenr;
|
|
|
|
plane_marker = BKE_tracking_plane_marker_insert(plane_track, &plane_marker_new);
|
|
}
|
|
|
|
return plane_marker;
|
|
}
|
|
|
|
void BKE_tracking_plane_marker_get_subframe_corners(MovieTrackingPlaneTrack *plane_track,
|
|
float framenr,
|
|
float corners[4][2])
|
|
{
|
|
MovieTrackingPlaneMarker *marker = BKE_tracking_plane_marker_get(plane_track, (int)framenr);
|
|
MovieTrackingPlaneMarker *marker_last = plane_track->markers + (plane_track->markersnr - 1);
|
|
if (marker != marker_last) {
|
|
MovieTrackingPlaneMarker *marker_next = marker + 1;
|
|
if (marker_next->framenr == marker->framenr + 1) {
|
|
float fac = (framenr - (int)framenr) / (marker_next->framenr - marker->framenr);
|
|
for (int i = 0; i < 4; i++) {
|
|
interp_v2_v2v2(corners[i], marker->corners[i], marker_next->corners[i], fac);
|
|
}
|
|
}
|
|
else {
|
|
for (int i = 0; i < 4; i++) {
|
|
copy_v2_v2(corners[i], marker->corners[i]);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for (int i = 0; i < 4; i++) {
|
|
copy_v2_v2(corners[i], marker->corners[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************** Object *************************/
|
|
|
|
MovieTrackingObject *BKE_tracking_object_add(MovieTracking *tracking, const char *name)
|
|
{
|
|
MovieTrackingObject *object = MEM_callocN(sizeof(MovieTrackingObject), "tracking object");
|
|
|
|
if (tracking->tot_object == 0) {
|
|
/* first object is always camera */
|
|
BLI_strncpy(object->name, "Camera", sizeof(object->name));
|
|
|
|
object->flag |= TRACKING_OBJECT_CAMERA;
|
|
}
|
|
else {
|
|
BLI_strncpy(object->name, name, sizeof(object->name));
|
|
}
|
|
|
|
BLI_addtail(&tracking->objects, object);
|
|
|
|
tracking->tot_object++;
|
|
tracking->objectnr = BLI_listbase_count(&tracking->objects) - 1;
|
|
|
|
object->scale = 1.0f;
|
|
object->keyframe1 = 1;
|
|
object->keyframe2 = 30;
|
|
|
|
BKE_tracking_object_unique_name(tracking, object);
|
|
BKE_tracking_dopesheet_tag_update(tracking);
|
|
|
|
return object;
|
|
}
|
|
|
|
bool BKE_tracking_object_delete(MovieTracking *tracking, MovieTrackingObject *object)
|
|
{
|
|
MovieTrackingTrack *track;
|
|
int index = BLI_findindex(&tracking->objects, object);
|
|
|
|
if (index == -1) {
|
|
return false;
|
|
}
|
|
|
|
if (object->flag & TRACKING_OBJECT_CAMERA) {
|
|
/* object used for camera solving can't be deleted */
|
|
return false;
|
|
}
|
|
|
|
track = object->tracks.first;
|
|
while (track) {
|
|
if (track == tracking->act_track) {
|
|
tracking->act_track = NULL;
|
|
}
|
|
|
|
track = track->next;
|
|
}
|
|
|
|
tracking_object_free(object);
|
|
BLI_freelinkN(&tracking->objects, object);
|
|
|
|
tracking->tot_object--;
|
|
|
|
if (index != 0) {
|
|
tracking->objectnr = index - 1;
|
|
}
|
|
else {
|
|
tracking->objectnr = 0;
|
|
}
|
|
|
|
BKE_tracking_dopesheet_tag_update(tracking);
|
|
|
|
return true;
|
|
}
|
|
|
|
void BKE_tracking_object_unique_name(MovieTracking *tracking, MovieTrackingObject *object)
|
|
{
|
|
BLI_uniquename(&tracking->objects,
|
|
object,
|
|
DATA_("Object"),
|
|
'.',
|
|
offsetof(MovieTrackingObject, name),
|
|
sizeof(object->name));
|
|
}
|
|
|
|
MovieTrackingObject *BKE_tracking_object_get_named(MovieTracking *tracking, const char *name)
|
|
{
|
|
MovieTrackingObject *object = tracking->objects.first;
|
|
|
|
while (object) {
|
|
if (STREQ(object->name, name)) {
|
|
return object;
|
|
}
|
|
|
|
object = object->next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
MovieTrackingObject *BKE_tracking_object_get_active(MovieTracking *tracking)
|
|
{
|
|
return BLI_findlink(&tracking->objects, tracking->objectnr);
|
|
}
|
|
|
|
MovieTrackingObject *BKE_tracking_object_get_camera(MovieTracking *tracking)
|
|
{
|
|
MovieTrackingObject *object = tracking->objects.first;
|
|
|
|
while (object) {
|
|
if (object->flag & TRACKING_OBJECT_CAMERA) {
|
|
return object;
|
|
}
|
|
|
|
object = object->next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
ListBase *BKE_tracking_object_get_tracks(MovieTracking *tracking, MovieTrackingObject *object)
|
|
{
|
|
if (object->flag & TRACKING_OBJECT_CAMERA) {
|
|
return &tracking->tracks;
|
|
}
|
|
|
|
return &object->tracks;
|
|
}
|
|
|
|
ListBase *BKE_tracking_object_get_plane_tracks(MovieTracking *tracking,
|
|
MovieTrackingObject *object)
|
|
{
|
|
if (object->flag & TRACKING_OBJECT_CAMERA) {
|
|
return &tracking->plane_tracks;
|
|
}
|
|
|
|
return &object->plane_tracks;
|
|
}
|
|
|
|
MovieTrackingReconstruction *BKE_tracking_object_get_reconstruction(MovieTracking *tracking,
|
|
MovieTrackingObject *object)
|
|
{
|
|
if (object->flag & TRACKING_OBJECT_CAMERA) {
|
|
return &tracking->reconstruction;
|
|
}
|
|
|
|
return &object->reconstruction;
|
|
}
|
|
|
|
/*********************** Camera *************************/
|
|
|
|
static int reconstructed_camera_index_get(MovieTrackingReconstruction *reconstruction,
|
|
int framenr,
|
|
bool nearest)
|
|
{
|
|
MovieReconstructedCamera *cameras = reconstruction->cameras;
|
|
int a = 0, d = 1;
|
|
|
|
if (!reconstruction->camnr) {
|
|
return -1;
|
|
}
|
|
|
|
if (framenr < cameras[0].framenr) {
|
|
if (nearest) {
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
if (framenr > cameras[reconstruction->camnr - 1].framenr) {
|
|
if (nearest) {
|
|
return reconstruction->camnr - 1;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
if (reconstruction->last_camera < reconstruction->camnr) {
|
|
a = reconstruction->last_camera;
|
|
}
|
|
|
|
if (cameras[a].framenr >= framenr) {
|
|
d = -1;
|
|
}
|
|
|
|
while (a >= 0 && a < reconstruction->camnr) {
|
|
int cfra = cameras[a].framenr;
|
|
|
|
/* check if needed framenr was "skipped" -- no data for requested frame */
|
|
|
|
if (d > 0 && cfra > framenr) {
|
|
/* interpolate with previous position */
|
|
if (nearest) {
|
|
return a - 1;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (d < 0 && cfra < framenr) {
|
|
/* interpolate with next position */
|
|
if (nearest) {
|
|
return a;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (cfra == framenr) {
|
|
reconstruction->last_camera = a;
|
|
|
|
return a;
|
|
}
|
|
|
|
a += d;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static void reconstructed_camera_scale_set(MovieTrackingObject *object, float mat[4][4])
|
|
{
|
|
if ((object->flag & TRACKING_OBJECT_CAMERA) == 0) {
|
|
float smat[4][4];
|
|
|
|
scale_m4_fl(smat, 1.0f / object->scale);
|
|
mul_m4_m4m4(mat, mat, smat);
|
|
}
|
|
}
|
|
|
|
/* converts principal offset from center to offset of blender's camera */
|
|
void BKE_tracking_camera_shift_get(
|
|
MovieTracking *tracking, int winx, int winy, float *shiftx, float *shifty)
|
|
{
|
|
/* Indeed in both of cases it should be winx -
|
|
* it's just how camera shift works for blender's camera. */
|
|
*shiftx = (0.5f * winx - tracking->camera.principal[0]) / winx;
|
|
*shifty = (0.5f * winy - tracking->camera.principal[1]) / winx;
|
|
}
|
|
|
|
void BKE_tracking_camera_to_blender(
|
|
MovieTracking *tracking, Scene *scene, Camera *camera, int width, int height)
|
|
{
|
|
float focal = tracking->camera.focal;
|
|
|
|
camera->sensor_x = tracking->camera.sensor_width;
|
|
camera->sensor_fit = CAMERA_SENSOR_FIT_AUTO;
|
|
camera->lens = focal * camera->sensor_x / width;
|
|
|
|
scene->r.xsch = width;
|
|
scene->r.ysch = height;
|
|
|
|
scene->r.xasp = tracking->camera.pixel_aspect;
|
|
scene->r.yasp = 1.0f;
|
|
|
|
BKE_tracking_camera_shift_get(tracking, width, height, &camera->shiftx, &camera->shifty);
|
|
}
|
|
|
|
MovieReconstructedCamera *BKE_tracking_camera_get_reconstructed(MovieTracking *tracking,
|
|
MovieTrackingObject *object,
|
|
int framenr)
|
|
{
|
|
MovieTrackingReconstruction *reconstruction;
|
|
int a;
|
|
|
|
reconstruction = BKE_tracking_object_get_reconstruction(tracking, object);
|
|
a = reconstructed_camera_index_get(reconstruction, framenr, false);
|
|
|
|
if (a == -1) {
|
|
return NULL;
|
|
}
|
|
|
|
return &reconstruction->cameras[a];
|
|
}
|
|
|
|
void BKE_tracking_camera_get_reconstructed_interpolate(MovieTracking *tracking,
|
|
MovieTrackingObject *object,
|
|
float framenr,
|
|
float mat[4][4])
|
|
{
|
|
MovieTrackingReconstruction *reconstruction;
|
|
MovieReconstructedCamera *cameras;
|
|
int a;
|
|
|
|
reconstruction = BKE_tracking_object_get_reconstruction(tracking, object);
|
|
cameras = reconstruction->cameras;
|
|
a = reconstructed_camera_index_get(reconstruction, (int)framenr, true);
|
|
|
|
if (a == -1) {
|
|
unit_m4(mat);
|
|
return;
|
|
}
|
|
|
|
if (cameras[a].framenr != framenr && a < reconstruction->camnr - 1) {
|
|
float t = ((float)framenr - cameras[a].framenr) /
|
|
(cameras[a + 1].framenr - cameras[a].framenr);
|
|
blend_m4_m4m4(mat, cameras[a].mat, cameras[a + 1].mat, t);
|
|
}
|
|
else {
|
|
copy_m4_m4(mat, cameras[a].mat);
|
|
}
|
|
|
|
reconstructed_camera_scale_set(object, mat);
|
|
}
|
|
|
|
/*********************** Distortion/Undistortion *************************/
|
|
|
|
MovieDistortion *BKE_tracking_distortion_new(MovieTracking *tracking,
|
|
int calibration_width,
|
|
int calibration_height)
|
|
{
|
|
MovieDistortion *distortion;
|
|
libmv_CameraIntrinsicsOptions camera_intrinsics_options;
|
|
|
|
tracking_cameraIntrinscisOptionsFromTracking(
|
|
tracking, calibration_width, calibration_height, &camera_intrinsics_options);
|
|
|
|
distortion = MEM_callocN(sizeof(MovieDistortion), "BKE_tracking_distortion_create");
|
|
distortion->intrinsics = libmv_cameraIntrinsicsNew(&camera_intrinsics_options);
|
|
|
|
const MovieTrackingCamera *camera = &tracking->camera;
|
|
copy_v2_v2(distortion->principal, camera->principal);
|
|
distortion->pixel_aspect = camera->pixel_aspect;
|
|
distortion->focal = camera->focal;
|
|
|
|
return distortion;
|
|
}
|
|
|
|
void BKE_tracking_distortion_update(MovieDistortion *distortion,
|
|
MovieTracking *tracking,
|
|
int calibration_width,
|
|
int calibration_height)
|
|
{
|
|
libmv_CameraIntrinsicsOptions camera_intrinsics_options;
|
|
|
|
tracking_cameraIntrinscisOptionsFromTracking(
|
|
tracking, calibration_width, calibration_height, &camera_intrinsics_options);
|
|
|
|
const MovieTrackingCamera *camera = &tracking->camera;
|
|
copy_v2_v2(distortion->principal, camera->principal);
|
|
distortion->pixel_aspect = camera->pixel_aspect;
|
|
distortion->focal = camera->focal;
|
|
|
|
libmv_cameraIntrinsicsUpdate(&camera_intrinsics_options, distortion->intrinsics);
|
|
}
|
|
|
|
void BKE_tracking_distortion_set_threads(MovieDistortion *distortion, int threads)
|
|
{
|
|
libmv_cameraIntrinsicsSetThreads(distortion->intrinsics, threads);
|
|
}
|
|
|
|
MovieDistortion *BKE_tracking_distortion_copy(MovieDistortion *distortion)
|
|
{
|
|
MovieDistortion *new_distortion;
|
|
|
|
new_distortion = MEM_callocN(sizeof(MovieDistortion), "BKE_tracking_distortion_create");
|
|
*new_distortion = *distortion;
|
|
new_distortion->intrinsics = libmv_cameraIntrinsicsCopy(distortion->intrinsics);
|
|
|
|
return new_distortion;
|
|
}
|
|
|
|
ImBuf *BKE_tracking_distortion_exec(MovieDistortion *distortion,
|
|
MovieTracking *tracking,
|
|
ImBuf *ibuf,
|
|
int calibration_width,
|
|
int calibration_height,
|
|
float overscan,
|
|
bool undistort)
|
|
{
|
|
ImBuf *resibuf;
|
|
|
|
BKE_tracking_distortion_update(distortion, tracking, calibration_width, calibration_height);
|
|
|
|
resibuf = IMB_dupImBuf(ibuf);
|
|
|
|
if (ibuf->rect_float) {
|
|
if (undistort) {
|
|
libmv_cameraIntrinsicsUndistortFloat(distortion->intrinsics,
|
|
ibuf->rect_float,
|
|
ibuf->x,
|
|
ibuf->y,
|
|
overscan,
|
|
ibuf->channels,
|
|
resibuf->rect_float);
|
|
}
|
|
else {
|
|
libmv_cameraIntrinsicsDistortFloat(distortion->intrinsics,
|
|
ibuf->rect_float,
|
|
ibuf->x,
|
|
ibuf->y,
|
|
overscan,
|
|
ibuf->channels,
|
|
resibuf->rect_float);
|
|
}
|
|
|
|
if (ibuf->rect) {
|
|
imb_freerectImBuf(ibuf);
|
|
}
|
|
}
|
|
else {
|
|
if (undistort) {
|
|
libmv_cameraIntrinsicsUndistortByte(distortion->intrinsics,
|
|
(unsigned char *)ibuf->rect,
|
|
ibuf->x,
|
|
ibuf->y,
|
|
overscan,
|
|
ibuf->channels,
|
|
(unsigned char *)resibuf->rect);
|
|
}
|
|
else {
|
|
libmv_cameraIntrinsicsDistortByte(distortion->intrinsics,
|
|
(unsigned char *)ibuf->rect,
|
|
ibuf->x,
|
|
ibuf->y,
|
|
overscan,
|
|
ibuf->channels,
|
|
(unsigned char *)resibuf->rect);
|
|
}
|
|
}
|
|
|
|
return resibuf;
|
|
}
|
|
|
|
void BKE_tracking_distortion_distort_v2(MovieDistortion *distortion,
|
|
const float co[2],
|
|
float r_co[2])
|
|
{
|
|
const float aspy = 1.0f / distortion->pixel_aspect;
|
|
|
|
/* Normalize coords. */
|
|
float inv_focal = 1.0f / distortion->focal;
|
|
double x = (co[0] - distortion->principal[0]) * inv_focal,
|
|
y = (co[1] - distortion->principal[1] * aspy) * inv_focal;
|
|
|
|
libmv_cameraIntrinsicsApply(distortion->intrinsics, x, y, &x, &y);
|
|
|
|
/* Result is in image coords already. */
|
|
r_co[0] = x;
|
|
r_co[1] = y;
|
|
}
|
|
|
|
void BKE_tracking_distortion_undistort_v2(MovieDistortion *distortion,
|
|
const float co[2],
|
|
float r_co[2])
|
|
{
|
|
double x = co[0], y = co[1];
|
|
libmv_cameraIntrinsicsInvert(distortion->intrinsics, x, y, &x, &y);
|
|
|
|
const float aspy = 1.0f / distortion->pixel_aspect;
|
|
r_co[0] = (float)x * distortion->focal + distortion->principal[0];
|
|
r_co[1] = (float)y * distortion->focal + distortion->principal[1] * aspy;
|
|
}
|
|
|
|
void BKE_tracking_distortion_free(MovieDistortion *distortion)
|
|
{
|
|
libmv_cameraIntrinsicsDestroy(distortion->intrinsics);
|
|
|
|
MEM_freeN(distortion);
|
|
}
|
|
|
|
void BKE_tracking_distort_v2(
|
|
MovieTracking *tracking, int image_width, int image_height, const float co[2], float r_co[2])
|
|
{
|
|
const MovieTrackingCamera *camera = &tracking->camera;
|
|
const float aspy = 1.0f / tracking->camera.pixel_aspect;
|
|
|
|
libmv_CameraIntrinsicsOptions camera_intrinsics_options;
|
|
tracking_cameraIntrinscisOptionsFromTracking(
|
|
tracking, image_width, image_height, &camera_intrinsics_options);
|
|
libmv_CameraIntrinsics *intrinsics = libmv_cameraIntrinsicsNew(&camera_intrinsics_options);
|
|
|
|
/* Normalize coordinates. */
|
|
double x = (co[0] - camera->principal[0]) / camera->focal,
|
|
y = (co[1] - camera->principal[1] * aspy) / camera->focal;
|
|
|
|
libmv_cameraIntrinsicsApply(intrinsics, x, y, &x, &y);
|
|
libmv_cameraIntrinsicsDestroy(intrinsics);
|
|
|
|
/* Result is in image coords already. */
|
|
r_co[0] = x;
|
|
r_co[1] = y;
|
|
}
|
|
|
|
void BKE_tracking_undistort_v2(
|
|
MovieTracking *tracking, int image_width, int image_height, const float co[2], float r_co[2])
|
|
{
|
|
const MovieTrackingCamera *camera = &tracking->camera;
|
|
const float aspy = 1.0f / tracking->camera.pixel_aspect;
|
|
|
|
libmv_CameraIntrinsicsOptions camera_intrinsics_options;
|
|
tracking_cameraIntrinscisOptionsFromTracking(
|
|
tracking, image_width, image_height, &camera_intrinsics_options);
|
|
libmv_CameraIntrinsics *intrinsics = libmv_cameraIntrinsicsNew(&camera_intrinsics_options);
|
|
|
|
double x = co[0], y = co[1];
|
|
libmv_cameraIntrinsicsInvert(intrinsics, x, y, &x, &y);
|
|
libmv_cameraIntrinsicsDestroy(intrinsics);
|
|
|
|
r_co[0] = (float)x * camera->focal + camera->principal[0];
|
|
r_co[1] = (float)y * camera->focal + camera->principal[1] * aspy;
|
|
}
|
|
|
|
ImBuf *BKE_tracking_undistort_frame(MovieTracking *tracking,
|
|
ImBuf *ibuf,
|
|
int calibration_width,
|
|
int calibration_height,
|
|
float overscan)
|
|
{
|
|
MovieTrackingCamera *camera = &tracking->camera;
|
|
|
|
if (camera->intrinsics == NULL) {
|
|
camera->intrinsics = BKE_tracking_distortion_new(
|
|
tracking, calibration_width, calibration_height);
|
|
}
|
|
|
|
return BKE_tracking_distortion_exec(
|
|
camera->intrinsics, tracking, ibuf, calibration_width, calibration_height, overscan, true);
|
|
}
|
|
|
|
ImBuf *BKE_tracking_distort_frame(MovieTracking *tracking,
|
|
ImBuf *ibuf,
|
|
int calibration_width,
|
|
int calibration_height,
|
|
float overscan)
|
|
{
|
|
MovieTrackingCamera *camera = &tracking->camera;
|
|
|
|
if (camera->intrinsics == NULL) {
|
|
camera->intrinsics = BKE_tracking_distortion_new(
|
|
tracking, calibration_width, calibration_height);
|
|
}
|
|
|
|
return BKE_tracking_distortion_exec(
|
|
camera->intrinsics, tracking, ibuf, calibration_width, calibration_height, overscan, false);
|
|
}
|
|
|
|
void BKE_tracking_max_distortion_delta_across_bound(MovieTracking *tracking,
|
|
int image_width,
|
|
int image_height,
|
|
rcti *rect,
|
|
bool undistort,
|
|
float delta[2])
|
|
{
|
|
float pos[2], warped_pos[2];
|
|
const int coord_delta = 5;
|
|
void (*apply_distortion)(MovieTracking * tracking,
|
|
int image_width,
|
|
int image_height,
|
|
const float pos[2],
|
|
float out[2]);
|
|
|
|
if (undistort) {
|
|
apply_distortion = BKE_tracking_undistort_v2;
|
|
}
|
|
else {
|
|
apply_distortion = BKE_tracking_distort_v2;
|
|
}
|
|
|
|
delta[0] = delta[1] = -FLT_MAX;
|
|
|
|
for (int a = rect->xmin; a <= rect->xmax + coord_delta; a += coord_delta) {
|
|
if (a > rect->xmax) {
|
|
a = rect->xmax;
|
|
}
|
|
|
|
/* bottom edge */
|
|
pos[0] = a;
|
|
pos[1] = rect->ymin;
|
|
|
|
apply_distortion(tracking, image_width, image_height, pos, warped_pos);
|
|
|
|
delta[0] = max_ff(delta[0], fabsf(pos[0] - warped_pos[0]));
|
|
delta[1] = max_ff(delta[1], fabsf(pos[1] - warped_pos[1]));
|
|
|
|
/* top edge */
|
|
pos[0] = a;
|
|
pos[1] = rect->ymax;
|
|
|
|
apply_distortion(tracking, image_width, image_height, pos, warped_pos);
|
|
|
|
delta[0] = max_ff(delta[0], fabsf(pos[0] - warped_pos[0]));
|
|
delta[1] = max_ff(delta[1], fabsf(pos[1] - warped_pos[1]));
|
|
|
|
if (a >= rect->xmax) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (int a = rect->ymin; a <= rect->ymax + coord_delta; a += coord_delta) {
|
|
if (a > rect->ymax) {
|
|
a = rect->ymax;
|
|
}
|
|
|
|
/* left edge */
|
|
pos[0] = rect->xmin;
|
|
pos[1] = a;
|
|
|
|
apply_distortion(tracking, image_width, image_height, pos, warped_pos);
|
|
|
|
delta[0] = max_ff(delta[0], fabsf(pos[0] - warped_pos[0]));
|
|
delta[1] = max_ff(delta[1], fabsf(pos[1] - warped_pos[1]));
|
|
|
|
/* right edge */
|
|
pos[0] = rect->xmax;
|
|
pos[1] = a;
|
|
|
|
apply_distortion(tracking, image_width, image_height, pos, warped_pos);
|
|
|
|
delta[0] = max_ff(delta[0], fabsf(pos[0] - warped_pos[0]));
|
|
delta[1] = max_ff(delta[1], fabsf(pos[1] - warped_pos[1]));
|
|
|
|
if (a >= rect->ymax) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************** Image sampling *************************/
|
|
|
|
static void disable_imbuf_channels(ImBuf *ibuf, MovieTrackingTrack *track, bool grayscale)
|
|
{
|
|
BKE_tracking_disable_channels(ibuf,
|
|
track->flag & TRACK_DISABLE_RED,
|
|
track->flag & TRACK_DISABLE_GREEN,
|
|
track->flag & TRACK_DISABLE_BLUE,
|
|
grayscale);
|
|
}
|
|
|
|
ImBuf *BKE_tracking_sample_pattern(int frame_width,
|
|
int frame_height,
|
|
ImBuf *search_ibuf,
|
|
MovieTrackingTrack *track,
|
|
MovieTrackingMarker *marker,
|
|
bool from_anchor,
|
|
bool use_mask,
|
|
int num_samples_x,
|
|
int num_samples_y,
|
|
float pos[2])
|
|
{
|
|
ImBuf *pattern_ibuf;
|
|
double src_pixel_x[5], src_pixel_y[5];
|
|
double warped_position_x, warped_position_y;
|
|
float *mask = NULL;
|
|
|
|
if (num_samples_x <= 0 || num_samples_y <= 0) {
|
|
return NULL;
|
|
}
|
|
|
|
pattern_ibuf = IMB_allocImBuf(
|
|
num_samples_x, num_samples_y, 32, search_ibuf->rect_float ? IB_rectfloat : IB_rect);
|
|
|
|
tracking_get_marker_coords_for_tracking(
|
|
frame_width, frame_height, marker, src_pixel_x, src_pixel_y);
|
|
|
|
/* from_anchor means search buffer was obtained for an anchored position,
|
|
* which means applying track offset rounded to pixel space (we could not
|
|
* store search buffer with sub-pixel precision)
|
|
*
|
|
* in this case we need to alter coordinates a bit, to compensate rounded
|
|
* fractional part of offset
|
|
*/
|
|
if (from_anchor) {
|
|
for (int a = 0; a < 5; a++) {
|
|
src_pixel_x[a] += (double)((track->offset[0] * frame_width) -
|
|
((int)(track->offset[0] * frame_width)));
|
|
src_pixel_y[a] += (double)((track->offset[1] * frame_height) -
|
|
((int)(track->offset[1] * frame_height)));
|
|
|
|
/* when offset is negative, rounding happens in opposite direction */
|
|
if (track->offset[0] < 0.0f) {
|
|
src_pixel_x[a] += 1.0;
|
|
}
|
|
if (track->offset[1] < 0.0f) {
|
|
src_pixel_y[a] += 1.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (use_mask) {
|
|
mask = BKE_tracking_track_get_mask(frame_width, frame_height, track, marker);
|
|
}
|
|
|
|
if (search_ibuf->rect_float) {
|
|
libmv_samplePlanarPatchFloat(search_ibuf->rect_float,
|
|
search_ibuf->x,
|
|
search_ibuf->y,
|
|
4,
|
|
src_pixel_x,
|
|
src_pixel_y,
|
|
num_samples_x,
|
|
num_samples_y,
|
|
mask,
|
|
pattern_ibuf->rect_float,
|
|
&warped_position_x,
|
|
&warped_position_y);
|
|
}
|
|
else {
|
|
libmv_samplePlanarPatchByte((unsigned char *)search_ibuf->rect,
|
|
search_ibuf->x,
|
|
search_ibuf->y,
|
|
4,
|
|
src_pixel_x,
|
|
src_pixel_y,
|
|
num_samples_x,
|
|
num_samples_y,
|
|
mask,
|
|
(unsigned char *)pattern_ibuf->rect,
|
|
&warped_position_x,
|
|
&warped_position_y);
|
|
}
|
|
|
|
if (pos) {
|
|
pos[0] = warped_position_x;
|
|
pos[1] = warped_position_y;
|
|
}
|
|
|
|
if (mask) {
|
|
MEM_freeN(mask);
|
|
}
|
|
|
|
return pattern_ibuf;
|
|
}
|
|
|
|
ImBuf *BKE_tracking_get_pattern_imbuf(ImBuf *ibuf,
|
|
MovieTrackingTrack *track,
|
|
MovieTrackingMarker *marker,
|
|
bool anchored,
|
|
bool disable_channels)
|
|
{
|
|
ImBuf *pattern_ibuf, *search_ibuf;
|
|
float pat_min[2], pat_max[2];
|
|
int num_samples_x, num_samples_y;
|
|
|
|
BKE_tracking_marker_pattern_minmax(marker, pat_min, pat_max);
|
|
|
|
num_samples_x = (pat_max[0] - pat_min[0]) * ibuf->x;
|
|
num_samples_y = (pat_max[1] - pat_min[1]) * ibuf->y;
|
|
|
|
search_ibuf = BKE_tracking_get_search_imbuf(ibuf, track, marker, anchored, disable_channels);
|
|
|
|
if (search_ibuf) {
|
|
pattern_ibuf = BKE_tracking_sample_pattern(ibuf->x,
|
|
ibuf->y,
|
|
search_ibuf,
|
|
track,
|
|
marker,
|
|
anchored,
|
|
false,
|
|
num_samples_x,
|
|
num_samples_y,
|
|
NULL);
|
|
|
|
IMB_freeImBuf(search_ibuf);
|
|
}
|
|
else {
|
|
pattern_ibuf = NULL;
|
|
}
|
|
|
|
return pattern_ibuf;
|
|
}
|
|
|
|
ImBuf *BKE_tracking_get_search_imbuf(ImBuf *ibuf,
|
|
MovieTrackingTrack *track,
|
|
MovieTrackingMarker *marker,
|
|
bool anchored,
|
|
bool disable_channels)
|
|
{
|
|
ImBuf *searchibuf;
|
|
int x, y, w, h;
|
|
float search_origin[2];
|
|
|
|
tracking_get_search_origin_frame_pixel(ibuf->x, ibuf->y, marker, search_origin);
|
|
|
|
x = search_origin[0];
|
|
y = search_origin[1];
|
|
|
|
if (anchored) {
|
|
x += track->offset[0] * ibuf->x;
|
|
y += track->offset[1] * ibuf->y;
|
|
}
|
|
|
|
w = (marker->search_max[0] - marker->search_min[0]) * ibuf->x;
|
|
h = (marker->search_max[1] - marker->search_min[1]) * ibuf->y;
|
|
|
|
if (w <= 0 || h <= 0) {
|
|
return NULL;
|
|
}
|
|
|
|
searchibuf = IMB_allocImBuf(w, h, 32, ibuf->rect_float ? IB_rectfloat : IB_rect);
|
|
|
|
IMB_rectcpy(searchibuf, ibuf, 0, 0, x, y, w, h);
|
|
|
|
if (disable_channels) {
|
|
if ((track->flag & TRACK_PREVIEW_GRAYSCALE) || (track->flag & TRACK_DISABLE_RED) ||
|
|
(track->flag & TRACK_DISABLE_GREEN) || (track->flag & TRACK_DISABLE_BLUE)) {
|
|
disable_imbuf_channels(searchibuf, track, true);
|
|
}
|
|
}
|
|
|
|
return searchibuf;
|
|
}
|
|
|
|
/* zap channels from the imbuf that are disabled by the user. this can lead to
|
|
* better tracks sometimes. however, instead of simply zeroing the channels
|
|
* out, do a partial grayscale conversion so the display is better.
|
|
*/
|
|
void BKE_tracking_disable_channels(
|
|
ImBuf *ibuf, bool disable_red, bool disable_green, bool disable_blue, bool grayscale)
|
|
{
|
|
if (!disable_red && !disable_green && !disable_blue && !grayscale) {
|
|
return;
|
|
}
|
|
|
|
/* if only some components are selected, it's important to rescale the result
|
|
* appropriately so that e.g. if only blue is selected, it's not zeroed out.
|
|
*/
|
|
float scale = (disable_red ? 0.0f : 0.2126f) + (disable_green ? 0.0f : 0.7152f) +
|
|
(disable_blue ? 0.0f : 0.0722f);
|
|
|
|
for (int y = 0; y < ibuf->y; y++) {
|
|
for (int x = 0; x < ibuf->x; x++) {
|
|
int pixel = ibuf->x * y + x;
|
|
|
|
if (ibuf->rect_float) {
|
|
float *rrgbf = ibuf->rect_float + pixel * 4;
|
|
float r = disable_red ? 0.0f : rrgbf[0];
|
|
float g = disable_green ? 0.0f : rrgbf[1];
|
|
float b = disable_blue ? 0.0f : rrgbf[2];
|
|
|
|
if (grayscale) {
|
|
float gray = (0.2126f * r + 0.7152f * g + 0.0722f * b) / scale;
|
|
|
|
rrgbf[0] = rrgbf[1] = rrgbf[2] = gray;
|
|
}
|
|
else {
|
|
rrgbf[0] = r;
|
|
rrgbf[1] = g;
|
|
rrgbf[2] = b;
|
|
}
|
|
}
|
|
else {
|
|
char *rrgb = (char *)ibuf->rect + pixel * 4;
|
|
char r = disable_red ? 0 : rrgb[0];
|
|
char g = disable_green ? 0 : rrgb[1];
|
|
char b = disable_blue ? 0 : rrgb[2];
|
|
|
|
if (grayscale) {
|
|
float gray = (0.2126f * r + 0.7152f * g + 0.0722f * b) / scale;
|
|
|
|
rrgb[0] = rrgb[1] = rrgb[2] = gray;
|
|
}
|
|
else {
|
|
rrgb[0] = r;
|
|
rrgb[1] = g;
|
|
rrgb[2] = b;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ibuf->rect_float) {
|
|
ibuf->userflags |= IB_RECT_INVALID;
|
|
}
|
|
}
|
|
|
|
/*********************** Dopesheet functions *************************/
|
|
|
|
/* ** Channels sort comparators ** */
|
|
|
|
static int channels_alpha_sort(const void *a, const void *b)
|
|
{
|
|
const MovieTrackingDopesheetChannel *channel_a = a;
|
|
const MovieTrackingDopesheetChannel *channel_b = b;
|
|
|
|
if (BLI_strcasecmp(channel_a->track->name, channel_b->track->name) > 0) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int channels_total_track_sort(const void *a, const void *b)
|
|
{
|
|
const MovieTrackingDopesheetChannel *channel_a = a;
|
|
const MovieTrackingDopesheetChannel *channel_b = b;
|
|
|
|
if (channel_a->total_frames > channel_b->total_frames) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int channels_longest_segment_sort(const void *a, const void *b)
|
|
{
|
|
const MovieTrackingDopesheetChannel *channel_a = a;
|
|
const MovieTrackingDopesheetChannel *channel_b = b;
|
|
|
|
if (channel_a->max_segment > channel_b->max_segment) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int channels_average_error_sort(const void *a, const void *b)
|
|
{
|
|
const MovieTrackingDopesheetChannel *channel_a = a;
|
|
const MovieTrackingDopesheetChannel *channel_b = b;
|
|
|
|
if (channel_a->track->error > channel_b->track->error) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int channels_alpha_inverse_sort(const void *a, const void *b)
|
|
{
|
|
if (channels_alpha_sort(a, b)) {
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int channels_total_track_inverse_sort(const void *a, const void *b)
|
|
{
|
|
if (channels_total_track_sort(a, b)) {
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int channels_longest_segment_inverse_sort(const void *a, const void *b)
|
|
{
|
|
if (channels_longest_segment_sort(a, b)) {
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int channels_average_error_inverse_sort(const void *a, const void *b)
|
|
{
|
|
const MovieTrackingDopesheetChannel *channel_a = a;
|
|
const MovieTrackingDopesheetChannel *channel_b = b;
|
|
|
|
if (channel_a->track->error < channel_b->track->error) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Calculate frames segments at which track is tracked continuously. */
|
|
static void tracking_dopesheet_channels_segments_calc(MovieTrackingDopesheetChannel *channel)
|
|
{
|
|
MovieTrackingTrack *track = channel->track;
|
|
int i, segment;
|
|
|
|
channel->tot_segment = 0;
|
|
channel->max_segment = 0;
|
|
channel->total_frames = 0;
|
|
|
|
/* TODO(sergey): looks a bit code-duplicated, need to look into
|
|
* logic de-duplication here.
|
|
*/
|
|
|
|
/* count */
|
|
i = 0;
|
|
while (i < track->markersnr) {
|
|
MovieTrackingMarker *marker = &track->markers[i];
|
|
|
|
if ((marker->flag & MARKER_DISABLED) == 0) {
|
|
int prev_fra = marker->framenr, len = 0;
|
|
|
|
i++;
|
|
while (i < track->markersnr) {
|
|
marker = &track->markers[i];
|
|
|
|
if (marker->framenr != prev_fra + 1) {
|
|
break;
|
|
}
|
|
if (marker->flag & MARKER_DISABLED) {
|
|
break;
|
|
}
|
|
|
|
prev_fra = marker->framenr;
|
|
len++;
|
|
i++;
|
|
}
|
|
|
|
channel->tot_segment++;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
if (!channel->tot_segment) {
|
|
return;
|
|
}
|
|
|
|
channel->segments = MEM_callocN(sizeof(int[2]) * channel->tot_segment,
|
|
"tracking channel segments");
|
|
|
|
/* create segments */
|
|
i = 0;
|
|
segment = 0;
|
|
while (i < track->markersnr) {
|
|
MovieTrackingMarker *marker = &track->markers[i];
|
|
|
|
if ((marker->flag & MARKER_DISABLED) == 0) {
|
|
MovieTrackingMarker *start_marker = marker;
|
|
int prev_fra = marker->framenr, len = 0;
|
|
|
|
i++;
|
|
while (i < track->markersnr) {
|
|
marker = &track->markers[i];
|
|
|
|
if (marker->framenr != prev_fra + 1) {
|
|
break;
|
|
}
|
|
if (marker->flag & MARKER_DISABLED) {
|
|
break;
|
|
}
|
|
|
|
prev_fra = marker->framenr;
|
|
channel->total_frames++;
|
|
len++;
|
|
i++;
|
|
}
|
|
|
|
channel->segments[2 * segment] = start_marker->framenr;
|
|
channel->segments[2 * segment + 1] = start_marker->framenr + len;
|
|
|
|
channel->max_segment = max_ii(channel->max_segment, len);
|
|
segment++;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
}
|
|
|
|
/* Create channels for tracks and calculate tracked segments for them. */
|
|
static void tracking_dopesheet_channels_calc(MovieTracking *tracking)
|
|
{
|
|
MovieTrackingObject *object = BKE_tracking_object_get_active(tracking);
|
|
MovieTrackingDopesheet *dopesheet = &tracking->dopesheet;
|
|
MovieTrackingReconstruction *reconstruction = BKE_tracking_object_get_reconstruction(tracking,
|
|
object);
|
|
ListBase *tracksbase = BKE_tracking_object_get_tracks(tracking, object);
|
|
|
|
bool sel_only = (dopesheet->flag & TRACKING_DOPE_SELECTED_ONLY) != 0;
|
|
bool show_hidden = (dopesheet->flag & TRACKING_DOPE_SHOW_HIDDEN) != 0;
|
|
|
|
LISTBASE_FOREACH (MovieTrackingTrack *, track, tracksbase) {
|
|
if (!show_hidden && (track->flag & TRACK_HIDDEN) != 0) {
|
|
continue;
|
|
}
|
|
|
|
if (sel_only && !TRACK_SELECTED(track)) {
|
|
continue;
|
|
}
|
|
|
|
MovieTrackingDopesheetChannel *channel = MEM_callocN(sizeof(MovieTrackingDopesheetChannel),
|
|
"tracking dopesheet channel");
|
|
channel->track = track;
|
|
|
|
if (reconstruction->flag & TRACKING_RECONSTRUCTED) {
|
|
BLI_snprintf(channel->name, sizeof(channel->name), "%s (%.4f)", track->name, track->error);
|
|
}
|
|
else {
|
|
BLI_strncpy(channel->name, track->name, sizeof(channel->name));
|
|
}
|
|
|
|
tracking_dopesheet_channels_segments_calc(channel);
|
|
|
|
BLI_addtail(&dopesheet->channels, channel);
|
|
dopesheet->tot_channel++;
|
|
}
|
|
}
|
|
|
|
/* Sot dopesheet channels using given method (name, average error, total coverage,
|
|
* longest tracked segment) and could also inverse the list if it's enabled.
|
|
*/
|
|
static void tracking_dopesheet_channels_sort(MovieTracking *tracking,
|
|
int sort_method,
|
|
bool inverse)
|
|
{
|
|
MovieTrackingDopesheet *dopesheet = &tracking->dopesheet;
|
|
|
|
if (inverse) {
|
|
if (sort_method == TRACKING_DOPE_SORT_NAME) {
|
|
BLI_listbase_sort(&dopesheet->channels, channels_alpha_inverse_sort);
|
|
}
|
|
else if (sort_method == TRACKING_DOPE_SORT_LONGEST) {
|
|
BLI_listbase_sort(&dopesheet->channels, channels_longest_segment_inverse_sort);
|
|
}
|
|
else if (sort_method == TRACKING_DOPE_SORT_TOTAL) {
|
|
BLI_listbase_sort(&dopesheet->channels, channels_total_track_inverse_sort);
|
|
}
|
|
else if (sort_method == TRACKING_DOPE_SORT_AVERAGE_ERROR) {
|
|
BLI_listbase_sort(&dopesheet->channels, channels_average_error_inverse_sort);
|
|
}
|
|
}
|
|
else {
|
|
if (sort_method == TRACKING_DOPE_SORT_NAME) {
|
|
BLI_listbase_sort(&dopesheet->channels, channels_alpha_sort);
|
|
}
|
|
else if (sort_method == TRACKING_DOPE_SORT_LONGEST) {
|
|
BLI_listbase_sort(&dopesheet->channels, channels_longest_segment_sort);
|
|
}
|
|
else if (sort_method == TRACKING_DOPE_SORT_TOTAL) {
|
|
BLI_listbase_sort(&dopesheet->channels, channels_total_track_sort);
|
|
}
|
|
else if (sort_method == TRACKING_DOPE_SORT_AVERAGE_ERROR) {
|
|
BLI_listbase_sort(&dopesheet->channels, channels_average_error_sort);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int coverage_from_count(int count)
|
|
{
|
|
/* Values are actually arbitrary here, probably need to be tweaked. */
|
|
if (count < 8) {
|
|
return TRACKING_COVERAGE_BAD;
|
|
}
|
|
if (count < 16) {
|
|
return TRACKING_COVERAGE_ACCEPTABLE;
|
|
}
|
|
return TRACKING_COVERAGE_OK;
|
|
}
|
|
|
|
/* Calculate coverage of frames with tracks, this information
|
|
* is used to highlight dopesheet background depending on how
|
|
* many tracks exists on the frame.
|
|
*/
|
|
static void tracking_dopesheet_calc_coverage(MovieTracking *tracking)
|
|
{
|
|
MovieTrackingDopesheet *dopesheet = &tracking->dopesheet;
|
|
MovieTrackingObject *object = BKE_tracking_object_get_active(tracking);
|
|
ListBase *tracksbase = BKE_tracking_object_get_tracks(tracking, object);
|
|
int frames, start_frame = INT_MAX, end_frame = -INT_MAX;
|
|
int *per_frame_counter;
|
|
int prev_coverage, last_segment_frame;
|
|
|
|
/* find frame boundaries */
|
|
LISTBASE_FOREACH (MovieTrackingTrack *, track, tracksbase) {
|
|
start_frame = min_ii(start_frame, track->markers[0].framenr);
|
|
end_frame = max_ii(end_frame, track->markers[track->markersnr - 1].framenr);
|
|
}
|
|
|
|
frames = end_frame - start_frame + 1;
|
|
|
|
/* this is a per-frame counter of markers (how many markers belongs to the same frame) */
|
|
per_frame_counter = MEM_callocN(sizeof(int) * frames, "per frame track counter");
|
|
|
|
/* find per-frame markers count */
|
|
LISTBASE_FOREACH (MovieTrackingTrack *, track, tracksbase) {
|
|
for (int i = 0; i < track->markersnr; i++) {
|
|
MovieTrackingMarker *marker = &track->markers[i];
|
|
|
|
/* TODO: perhaps we need to add check for non-single-frame track here */
|
|
if ((marker->flag & MARKER_DISABLED) == 0) {
|
|
per_frame_counter[marker->framenr - start_frame]++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* convert markers count to coverage and detect segments with the same coverage */
|
|
prev_coverage = coverage_from_count(per_frame_counter[0]);
|
|
last_segment_frame = start_frame;
|
|
|
|
/* means only disabled tracks in the beginning, could be ignored */
|
|
if (!per_frame_counter[0]) {
|
|
prev_coverage = TRACKING_COVERAGE_OK;
|
|
}
|
|
|
|
for (int i = 1; i < frames; i++) {
|
|
int coverage = coverage_from_count(per_frame_counter[i]);
|
|
|
|
/* means only disabled tracks in the end, could be ignored */
|
|
if (i == frames - 1 && !per_frame_counter[i]) {
|
|
coverage = TRACKING_COVERAGE_OK;
|
|
}
|
|
|
|
if (coverage != prev_coverage || i == frames - 1) {
|
|
MovieTrackingDopesheetCoverageSegment *coverage_segment;
|
|
int end_segment_frame = i - 1 + start_frame;
|
|
|
|
if (end_segment_frame == last_segment_frame) {
|
|
end_segment_frame++;
|
|
}
|
|
|
|
coverage_segment = MEM_callocN(sizeof(MovieTrackingDopesheetCoverageSegment),
|
|
"tracking coverage segment");
|
|
coverage_segment->coverage = prev_coverage;
|
|
coverage_segment->start_frame = last_segment_frame;
|
|
coverage_segment->end_frame = end_segment_frame;
|
|
|
|
BLI_addtail(&dopesheet->coverage_segments, coverage_segment);
|
|
|
|
last_segment_frame = end_segment_frame;
|
|
}
|
|
|
|
prev_coverage = coverage;
|
|
}
|
|
|
|
MEM_freeN(per_frame_counter);
|
|
}
|
|
|
|
/* Tag dopesheet for update, actual update will happen later
|
|
* when it'll be actually needed.
|
|
*/
|
|
void BKE_tracking_dopesheet_tag_update(MovieTracking *tracking)
|
|
{
|
|
MovieTrackingDopesheet *dopesheet = &tracking->dopesheet;
|
|
|
|
dopesheet->ok = false;
|
|
}
|
|
|
|
/* Do dopesheet update, if update is not needed nothing will happen. */
|
|
void BKE_tracking_dopesheet_update(MovieTracking *tracking)
|
|
{
|
|
MovieTrackingDopesheet *dopesheet = &tracking->dopesheet;
|
|
|
|
short sort_method = dopesheet->sort_method;
|
|
bool inverse = (dopesheet->flag & TRACKING_DOPE_SORT_INVERSE) != 0;
|
|
|
|
if (dopesheet->ok) {
|
|
return;
|
|
}
|
|
|
|
tracking_dopesheet_free(dopesheet);
|
|
|
|
/* channels */
|
|
tracking_dopesheet_channels_calc(tracking);
|
|
tracking_dopesheet_channels_sort(tracking, sort_method, inverse);
|
|
|
|
/* frame coverage */
|
|
tracking_dopesheet_calc_coverage(tracking);
|
|
|
|
dopesheet->ok = true;
|
|
}
|
|
|
|
/* NOTE: Returns NULL if the track comes from camera object, */
|
|
MovieTrackingObject *BKE_tracking_find_object_for_track(const MovieTracking *tracking,
|
|
const MovieTrackingTrack *track)
|
|
{
|
|
const ListBase *tracksbase = &tracking->tracks;
|
|
if (BLI_findindex(tracksbase, track) != -1) {
|
|
return NULL;
|
|
}
|
|
MovieTrackingObject *object = tracking->objects.first;
|
|
while (object != NULL) {
|
|
if (BLI_findindex(&object->tracks, track) != -1) {
|
|
return object;
|
|
}
|
|
object = object->next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
ListBase *BKE_tracking_find_tracks_list_for_track(MovieTracking *tracking,
|
|
const MovieTrackingTrack *track)
|
|
{
|
|
MovieTrackingObject *object = BKE_tracking_find_object_for_track(tracking, track);
|
|
if (object != NULL) {
|
|
return &object->tracks;
|
|
}
|
|
return &tracking->tracks;
|
|
}
|
|
|
|
/* NOTE: Returns NULL if the track comes from camera object, */
|
|
MovieTrackingObject *BKE_tracking_find_object_for_plane_track(
|
|
const MovieTracking *tracking, const MovieTrackingPlaneTrack *plane_track)
|
|
{
|
|
const ListBase *plane_tracks_base = &tracking->plane_tracks;
|
|
if (BLI_findindex(plane_tracks_base, plane_track) != -1) {
|
|
return NULL;
|
|
}
|
|
MovieTrackingObject *object = tracking->objects.first;
|
|
while (object != NULL) {
|
|
if (BLI_findindex(&object->plane_tracks, plane_track) != -1) {
|
|
return object;
|
|
}
|
|
object = object->next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
ListBase *BKE_tracking_find_tracks_list_for_plane_track(MovieTracking *tracking,
|
|
const MovieTrackingPlaneTrack *plane_track)
|
|
{
|
|
MovieTrackingObject *object = BKE_tracking_find_object_for_plane_track(tracking, plane_track);
|
|
if (object != NULL) {
|
|
return &object->plane_tracks;
|
|
}
|
|
return &tracking->plane_tracks;
|
|
}
|
|
|
|
void BKE_tracking_get_rna_path_for_track(const struct MovieTracking *tracking,
|
|
const struct MovieTrackingTrack *track,
|
|
char *rna_path,
|
|
size_t rna_path_len)
|
|
{
|
|
MovieTrackingObject *object = BKE_tracking_find_object_for_track(tracking, track);
|
|
char track_name_esc[MAX_NAME * 2];
|
|
BLI_strescape(track_name_esc, track->name, sizeof(track_name_esc));
|
|
if (object == NULL) {
|
|
BLI_snprintf(rna_path, rna_path_len, "tracking.tracks[\"%s\"]", track_name_esc);
|
|
}
|
|
else {
|
|
char object_name_esc[MAX_NAME * 2];
|
|
BLI_strescape(object_name_esc, object->name, sizeof(object_name_esc));
|
|
BLI_snprintf(rna_path,
|
|
rna_path_len,
|
|
"tracking.objects[\"%s\"].tracks[\"%s\"]",
|
|
object_name_esc,
|
|
track_name_esc);
|
|
}
|
|
}
|
|
|
|
void BKE_tracking_get_rna_path_prefix_for_track(const struct MovieTracking *tracking,
|
|
const struct MovieTrackingTrack *track,
|
|
char *rna_path,
|
|
size_t rna_path_len)
|
|
{
|
|
MovieTrackingObject *object = BKE_tracking_find_object_for_track(tracking, track);
|
|
if (object == NULL) {
|
|
BLI_strncpy(rna_path, "tracking.tracks", rna_path_len);
|
|
}
|
|
else {
|
|
char object_name_esc[MAX_NAME * 2];
|
|
BLI_strescape(object_name_esc, object->name, sizeof(object_name_esc));
|
|
BLI_snprintf(rna_path, rna_path_len, "tracking.objects[\"%s\"]", object_name_esc);
|
|
}
|
|
}
|
|
|
|
void BKE_tracking_get_rna_path_for_plane_track(const struct MovieTracking *tracking,
|
|
const struct MovieTrackingPlaneTrack *plane_track,
|
|
char *rna_path,
|
|
size_t rna_path_len)
|
|
{
|
|
MovieTrackingObject *object = BKE_tracking_find_object_for_plane_track(tracking, plane_track);
|
|
char track_name_esc[MAX_NAME * 2];
|
|
BLI_strescape(track_name_esc, plane_track->name, sizeof(track_name_esc));
|
|
if (object == NULL) {
|
|
BLI_snprintf(rna_path, rna_path_len, "tracking.plane_tracks[\"%s\"]", track_name_esc);
|
|
}
|
|
else {
|
|
char object_name_esc[MAX_NAME * 2];
|
|
BLI_strescape(object_name_esc, object->name, sizeof(object_name_esc));
|
|
BLI_snprintf(rna_path,
|
|
rna_path_len,
|
|
"tracking.objects[\"%s\"].plane_tracks[\"%s\"]",
|
|
object_name_esc,
|
|
track_name_esc);
|
|
}
|
|
}
|
|
|
|
void BKE_tracking_get_rna_path_prefix_for_plane_track(
|
|
const struct MovieTracking *tracking,
|
|
const struct MovieTrackingPlaneTrack *plane_track,
|
|
char *rna_path,
|
|
size_t rna_path_len)
|
|
{
|
|
MovieTrackingObject *object = BKE_tracking_find_object_for_plane_track(tracking, plane_track);
|
|
if (object == NULL) {
|
|
BLI_strncpy(rna_path, "tracking.plane_tracks", rna_path_len);
|
|
}
|
|
else {
|
|
char object_name_esc[MAX_NAME * 2];
|
|
BLI_strescape(object_name_esc, object->name, sizeof(object_name_esc));
|
|
BLI_snprintf(rna_path, rna_path_len, "tracking.objects[\"%s\"].plane_tracks", object_name_esc);
|
|
}
|
|
}
|