Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHans Goudey <h.goudey@me.com>2022-03-28 22:59:42 +0300
committerHans Goudey <h.goudey@me.com>2022-03-28 22:59:42 +0300
commit037f7891115318e60a63ffcb1bf877b0bbc35d0f (patch)
tree560a21c91ce874f26482461fd089a0feadd12e89 /source/blender/editors/transform/transform_snap_object.cc
parent21e72496a6297a1cee4fcb77f3efa24b7ee3c418 (diff)
Cleanup: Move transform_snap_object.c to C++
This is meant to allow using C++ data structures in this file as a performance improvement. Particularly `Vector` instead of `ListBase` for `duplilist`. Differential Revision: https://developer.blender.org/D14475
Diffstat (limited to 'source/blender/editors/transform/transform_snap_object.cc')
-rw-r--r--source/blender/editors/transform/transform_snap_object.cc3323
1 files changed, 3323 insertions, 0 deletions
diff --git a/source/blender/editors/transform/transform_snap_object.cc b/source/blender/editors/transform/transform_snap_object.cc
new file mode 100644
index 00000000000..1c50b2fcebb
--- /dev/null
+++ b/source/blender/editors/transform/transform_snap_object.cc
@@ -0,0 +1,3323 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup edtransform
+ */
+
+#include <cstdlib>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_ghash.h"
+#include "BLI_kdopbvh.h"
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+#include "BLI_memarena.h"
+#include "BLI_utildefines.h"
+
+#include "DNA_armature_types.h"
+#include "DNA_curve_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_screen_types.h"
+#include "DNA_view3d_types.h"
+
+#include "BKE_armature.h"
+#include "BKE_bvhutils.h"
+#include "BKE_curve.h"
+#include "BKE_duplilist.h"
+#include "BKE_editmesh.h"
+#include "BKE_geometry_set.h"
+#include "BKE_global.h"
+#include "BKE_layer.h"
+#include "BKE_mesh.h"
+#include "BKE_mesh_runtime.h"
+#include "BKE_object.h"
+#include "BKE_tracking.h"
+
+#include "DEG_depsgraph_query.h"
+
+#include "ED_armature.h"
+#include "ED_transform_snap_object_context.h"
+#include "ED_view3d.h"
+
+/* -------------------------------------------------------------------- */
+/** \name Internal Data Types
+ * \{ */
+
+#define MAX_CLIPPLANE_LEN 3
+
+enum eViewProj {
+ VIEW_PROJ_NONE = -1,
+ VIEW_PROJ_ORTHO = 0,
+ VIEW_PROJ_PERSP = -1,
+};
+
+struct SnapObjectData {
+ enum class Type {
+ Mesh,
+ EditMesh,
+ };
+ Type type;
+
+ BVHTree *bvhtree[2]; /* MESH: loose edges, loose verts
+ * EDIT_MESH: verts, edges. */
+ bool cached[2];
+
+ union {
+ struct {
+ /* Type::Mesh */
+ BVHTreeFromMesh treedata_mesh;
+ const struct MPoly *poly;
+ uint has_looptris : 1;
+ uint has_loose_edge : 1;
+ uint has_loose_vert : 1;
+ };
+ struct {
+ /* Type::EditMesh */
+ BVHTreeFromEditMesh treedata_editmesh;
+ float min[3], max[3];
+ struct Mesh_Runtime *mesh_runtime;
+ };
+ };
+};
+
+struct SnapObjectContext {
+ Scene *scene;
+
+ int flag;
+
+ /* Object -> SnapObjectData map */
+ struct {
+ GHash *object_map;
+ /** Map object-data to objects so objects share edit mode data. */
+ GHash *data_to_object_map;
+ MemArena *mem_arena;
+ } cache;
+
+ /* Filter data, returns true to check this value */
+ struct {
+ struct {
+ bool (*test_vert_fn)(BMVert *, void *user_data);
+ bool (*test_edge_fn)(BMEdge *, void *user_data);
+ bool (*test_face_fn)(BMFace *, void *user_data);
+ void *user_data;
+ } edit_mesh;
+ } callbacks;
+
+ struct {
+ Depsgraph *depsgraph;
+ const ARegion *region;
+ const View3D *v3d;
+
+ float mval[2];
+ float pmat[4][4]; /* perspective matrix */
+ float win_size[2]; /* win x and y */
+ enum eViewProj view_proj;
+ float clip_plane[MAX_CLIPPLANE_LEN][4];
+ short clip_plane_len;
+ short snap_to_flag;
+ bool has_occlusion_plane; /* Ignore plane of occlusion in curves. */
+ } runtime;
+};
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Utilities
+ * \{ */
+
+/* Mesh used for snapping.
+ * If nullptr the BMesh should be used. */
+static Mesh *mesh_for_snap(Object *ob_eval, eSnapEditType edit_mode_type, bool *r_use_hide)
+{
+ Mesh *me_eval = BKE_object_get_evaluated_mesh(ob_eval);
+ bool use_hide = false;
+ if (BKE_object_is_in_editmode(ob_eval)) {
+ if (edit_mode_type == SNAP_GEOM_EDIT) {
+ return nullptr;
+ }
+
+ Mesh *editmesh_eval_final = BKE_object_get_editmesh_eval_final(ob_eval);
+ Mesh *editmesh_eval_cage = BKE_object_get_editmesh_eval_cage(ob_eval);
+
+ if ((edit_mode_type == SNAP_GEOM_FINAL) && editmesh_eval_final) {
+ if (editmesh_eval_final->runtime.wrapper_type == ME_WRAPPER_TYPE_BMESH) {
+ return nullptr;
+ }
+ me_eval = editmesh_eval_final;
+ use_hide = true;
+ }
+ else if ((edit_mode_type == SNAP_GEOM_CAGE) && editmesh_eval_cage) {
+ if (editmesh_eval_cage->runtime.wrapper_type == ME_WRAPPER_TYPE_BMESH) {
+ return nullptr;
+ }
+ me_eval = editmesh_eval_cage;
+ use_hide = true;
+ }
+ }
+ if (r_use_hide) {
+ *r_use_hide = use_hide;
+ }
+ return me_eval;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Snap Object Data
+ * \{ */
+
+/**
+ * Calculate the minimum and maximum coordinates of the box that encompasses this mesh.
+ */
+static void bm_mesh_minmax(BMesh *bm, float r_min[3], float r_max[3])
+{
+ INIT_MINMAX(r_min, r_max);
+ BMIter iter;
+ BMVert *v;
+
+ BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
+ minmax_v3v3_v3(r_min, r_max, v->co);
+ }
+}
+
+static void snap_object_data_mesh_clear(SnapObjectData *sod)
+{
+ BLI_assert(sod->type == SnapObjectData::Type::Mesh);
+ for (int i = 0; i < ARRAY_SIZE(sod->bvhtree); i++) {
+ if (!sod->cached[i]) {
+ BLI_bvhtree_free(sod->bvhtree[i]);
+ }
+ sod->bvhtree[i] = nullptr;
+ }
+ free_bvhtree_from_mesh(&sod->treedata_mesh);
+}
+
+static void snap_object_data_editmesh_clear(SnapObjectData *sod)
+{
+ BLI_assert(sod->type == SnapObjectData::Type::EditMesh);
+ for (int i = 0; i < ARRAY_SIZE(sod->bvhtree); i++) {
+ if (!sod->cached[i]) {
+ BLI_bvhtree_free(sod->bvhtree[i]);
+ }
+ sod->bvhtree[i] = nullptr;
+ }
+ free_bvhtree_from_editmesh(&sod->treedata_editmesh);
+}
+
+static void snap_object_data_clear(SnapObjectData *sod)
+{
+ switch (sod->type) {
+ case SnapObjectData::Type::Mesh: {
+ snap_object_data_mesh_clear(sod);
+ break;
+ }
+ case SnapObjectData::Type::EditMesh: {
+ snap_object_data_editmesh_clear(sod);
+ break;
+ }
+ }
+ memset(&sod->type, 0x0, sizeof(*sod) - offsetof(SnapObjectData, type));
+}
+
+static SnapObjectData *snap_object_data_lookup(SnapObjectContext *sctx, Object *ob_eval)
+{
+ SnapObjectData *sod = static_cast<SnapObjectData *>(
+ BLI_ghash_lookup(sctx->cache.object_map, ob_eval));
+ if (sod == nullptr) {
+ if (sctx->cache.data_to_object_map != nullptr) {
+ ob_eval = static_cast<Object *>(
+ BLI_ghash_lookup(sctx->cache.data_to_object_map, ob_eval->data));
+ /* Could be NULl when mixing edit-mode and non edit-mode objects. */
+ if (ob_eval != nullptr) {
+ sod = static_cast<SnapObjectData *>(BLI_ghash_lookup(sctx->cache.object_map, ob_eval));
+ }
+ }
+ }
+ return sod;
+}
+
+static SnapObjectData *snap_object_data_mesh_get(SnapObjectContext *sctx,
+ Object *ob_eval,
+ const Mesh *me_eval,
+ bool use_hide)
+{
+ SnapObjectData *sod;
+ void **sod_p;
+ bool init = false;
+
+ if (BLI_ghash_ensure_p(sctx->cache.object_map, ob_eval, &sod_p)) {
+ sod = static_cast<SnapObjectData *>(*sod_p);
+ bool is_dirty = false;
+ if (sod->type != SnapObjectData::Type::Mesh) {
+ is_dirty = true;
+ }
+ else if (sod->treedata_mesh.tree && sod->treedata_mesh.cached &&
+ !bvhcache_has_tree(me_eval->runtime.bvh_cache, sod->treedata_mesh.tree)) {
+ /* The tree is owned by the Mesh and may have been freed since we last used. */
+ is_dirty = true;
+ }
+ else if (sod->bvhtree[0] && sod->cached[0] &&
+ !bvhcache_has_tree(me_eval->runtime.bvh_cache, sod->bvhtree[0])) {
+ /* The tree is owned by the Mesh and may have been freed since we last used. */
+ is_dirty = true;
+ }
+ else if (sod->bvhtree[1] && sod->cached[1] &&
+ !bvhcache_has_tree(me_eval->runtime.bvh_cache, sod->bvhtree[1])) {
+ /* The tree is owned by the Mesh and may have been freed since we last used. */
+ is_dirty = true;
+ }
+ else if (!sod->treedata_mesh.looptri_allocated &&
+ sod->treedata_mesh.looptri != me_eval->runtime.looptris.array) {
+ is_dirty = true;
+ }
+ else if (!sod->treedata_mesh.vert_allocated && sod->treedata_mesh.vert != me_eval->mvert) {
+ is_dirty = true;
+ }
+ else if (!sod->treedata_mesh.loop_allocated && sod->treedata_mesh.loop != me_eval->mloop) {
+ is_dirty = true;
+ }
+ else if (!sod->treedata_mesh.edge_allocated && sod->treedata_mesh.edge != me_eval->medge) {
+ is_dirty = true;
+ }
+ else if (sod->poly != me_eval->mpoly) {
+ is_dirty = true;
+ }
+
+ if (is_dirty) {
+ snap_object_data_clear(sod);
+ init = true;
+ }
+ }
+ else {
+ *sod_p = BLI_memarena_calloc(sctx->cache.mem_arena, sizeof(*sod));
+ sod = static_cast<SnapObjectData *>(*sod_p);
+ init = true;
+ }
+
+ if (init) {
+ sod->type = SnapObjectData::Type::Mesh;
+
+ /* The BVHTree from looptris is always required. */
+ BLI_assert(sod->treedata_mesh.tree == nullptr);
+ BKE_bvhtree_from_mesh_get(&sod->treedata_mesh,
+ me_eval,
+ use_hide ? BVHTREE_FROM_LOOPTRI_NO_HIDDEN : BVHTREE_FROM_LOOPTRI,
+ 4);
+
+ if (sod->treedata_mesh.tree == nullptr) {
+ sod->treedata_mesh.vert = me_eval->mvert;
+ sod->treedata_mesh.vert_normals = BKE_mesh_vertex_normals_ensure(me_eval);
+ sod->treedata_mesh.loop = me_eval->mloop;
+ sod->treedata_mesh.looptri = BKE_mesh_runtime_looptri_ensure(me_eval);
+ BLI_assert(sod->has_looptris == false);
+ }
+ else {
+ BLI_assert(sod->treedata_mesh.vert != nullptr);
+ BLI_assert(sod->treedata_mesh.vert_normals != nullptr);
+ BLI_assert(sod->treedata_mesh.loop != nullptr);
+ BLI_assert(sod->treedata_mesh.looptri != nullptr);
+ sod->has_looptris = true;
+ }
+
+ /* Required for snapping with occlusion. */
+ sod->treedata_mesh.edge = me_eval->medge;
+ sod->poly = me_eval->mpoly;
+
+ /* Start assuming that it has each of these element types. */
+ sod->has_loose_edge = true;
+ sod->has_loose_vert = true;
+ }
+
+ return sod;
+}
+
+static struct Mesh_Runtime *snap_object_data_editmesh_runtime_get(Object *ob_eval)
+{
+ Mesh *editmesh_eval_final = BKE_object_get_editmesh_eval_final(ob_eval);
+ if (editmesh_eval_final) {
+ return &editmesh_eval_final->runtime;
+ }
+
+ Mesh *editmesh_eval_cage = BKE_object_get_editmesh_eval_cage(ob_eval);
+ if (editmesh_eval_cage) {
+ return &editmesh_eval_cage->runtime;
+ }
+
+ return &((Mesh *)ob_eval->data)->runtime;
+}
+
+static SnapObjectData *snap_object_data_editmesh_get(SnapObjectContext *sctx,
+ Object *ob_eval,
+ BMEditMesh *em)
+{
+ SnapObjectData *sod;
+ void **sod_p;
+ bool init = false;
+
+ {
+ /* Use object-data as the key in ghash since the editmesh
+ * is used to create bvhtree and is the same for each linked object. */
+ if (sctx->cache.data_to_object_map == nullptr) {
+ sctx->cache.data_to_object_map = BLI_ghash_ptr_new(__func__);
+ }
+ void **ob_p;
+ if (BLI_ghash_ensure_p(sctx->cache.data_to_object_map, ob_eval->data, &ob_p)) {
+ ob_eval = static_cast<Object *>(*ob_p);
+ }
+ else {
+ *ob_p = ob_eval;
+ }
+ }
+
+ if (BLI_ghash_ensure_p(sctx->cache.object_map, ob_eval, &sod_p)) {
+ sod = static_cast<SnapObjectData *>(*sod_p);
+ bool is_dirty = false;
+ /* Check if the geometry has changed. */
+ if (sod->type != SnapObjectData::Type::EditMesh) {
+ is_dirty = true;
+ }
+ else if (sod->treedata_editmesh.em != em) {
+ is_dirty = true;
+ }
+ else if (sod->mesh_runtime) {
+ if (sod->mesh_runtime != snap_object_data_editmesh_runtime_get(ob_eval)) {
+ if (G.moving) {
+ /* Hack to avoid updating while transforming. */
+ BLI_assert(!sod->treedata_editmesh.cached && !sod->cached[0] && !sod->cached[1]);
+ sod->mesh_runtime = snap_object_data_editmesh_runtime_get(ob_eval);
+ }
+ else {
+ is_dirty = true;
+ }
+ }
+ else if (sod->treedata_editmesh.tree && sod->treedata_editmesh.cached &&
+ !bvhcache_has_tree(sod->mesh_runtime->bvh_cache, sod->treedata_editmesh.tree)) {
+ /* The tree is owned by the EditMesh and may have been freed since we last used! */
+ is_dirty = true;
+ }
+ else if (sod->bvhtree[0] && sod->cached[0] &&
+ !bvhcache_has_tree(sod->mesh_runtime->bvh_cache, sod->bvhtree[0])) {
+ /* The tree is owned by the EditMesh and may have been freed since we last used! */
+ is_dirty = true;
+ }
+ else if (sod->bvhtree[1] && sod->cached[1] &&
+ !bvhcache_has_tree(sod->mesh_runtime->bvh_cache, sod->bvhtree[1])) {
+ /* The tree is owned by the EditMesh and may have been freed since we last used! */
+ is_dirty = true;
+ }
+ }
+
+ if (is_dirty) {
+ snap_object_data_clear(sod);
+ init = true;
+ }
+ }
+ else {
+ *sod_p = BLI_memarena_calloc(sctx->cache.mem_arena, sizeof(*sod));
+ sod = static_cast<SnapObjectData *>(*sod_p);
+ init = true;
+ }
+
+ if (init) {
+ sod->type = SnapObjectData::Type::EditMesh;
+ sod->treedata_editmesh.em = em;
+ sod->mesh_runtime = snap_object_data_editmesh_runtime_get(ob_eval);
+ bm_mesh_minmax(em->bm, sod->min, sod->max);
+ }
+
+ return sod;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Iterator
+ * \{ */
+
+using IterSnapObjsCallback = void (*)(SnapObjectContext *sctx,
+ const struct SnapObjectParams *params,
+ Object *ob_eval,
+ float obmat[4][4],
+ bool is_object_active,
+ void *data);
+
+static bool snap_object_is_snappable(const SnapObjectContext *sctx,
+ const eSnapSelect snap_select,
+ const Base *base_act,
+ const Base *base,
+ const bool is_in_object_mode)
+{
+ if (!BASE_VISIBLE(sctx->runtime.v3d, base)) {
+ return false;
+ }
+
+ if ((snap_select == SNAP_ALL) || (base->flag_legacy & BA_TRANSFORM_LOCKED_IN_PLACE)) {
+ return true;
+ }
+
+ if (base->flag_legacy & BA_SNAP_FIX_DEPS_FIASCO) {
+ return false;
+ }
+
+ if (snap_select == SNAP_NOT_ACTIVE) {
+ return base_act != base;
+ }
+
+ if (snap_select == SNAP_NOT_EDITED) {
+ return base->object->mode != OB_MODE_EDIT;
+ }
+
+ if (snap_select == SNAP_NOT_SELECTED) {
+ if (is_in_object_mode) {
+ return !((base->flag & BASE_SELECTED) || (base->flag_legacy & BA_WAS_SEL));
+ }
+
+ /* What is selectable or not is part of the object and depends on the mode. */
+ return true;
+ }
+
+ if (snap_select == SNAP_SELECTABLE) {
+ return (base->flag & BASE_SELECTABLE) != 0;
+ }
+
+ return true;
+}
+
+/**
+ * Walks through all objects in the scene to create the list of objects to snap.
+ */
+static void iter_snap_objects(SnapObjectContext *sctx,
+ const struct SnapObjectParams *params,
+ IterSnapObjsCallback sob_callback,
+ void *data)
+{
+ ViewLayer *view_layer = DEG_get_input_view_layer(sctx->runtime.depsgraph);
+ const eSnapSelect snap_select = params->snap_select;
+
+ Base *base_act = view_layer->basact;
+ const bool is_in_object_mode = !base_act || base_act->object->mode == OB_MODE_OBJECT;
+ LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) {
+ if (!snap_object_is_snappable(sctx, snap_select, base_act, base, is_in_object_mode)) {
+ continue;
+ }
+
+ const bool is_object_active = (base == base_act);
+ Object *obj_eval = DEG_get_evaluated_object(sctx->runtime.depsgraph, base->object);
+ if (obj_eval->transflag & OB_DUPLI || BKE_object_has_geometry_set_instances(obj_eval)) {
+ ListBase *lb = object_duplilist(sctx->runtime.depsgraph, sctx->scene, obj_eval);
+ LISTBASE_FOREACH (DupliObject *, dupli_ob, lb) {
+ BLI_assert(DEG_is_evaluated_object(dupli_ob->ob));
+ sob_callback(sctx, params, dupli_ob->ob, dupli_ob->mat, is_object_active, data);
+ }
+ free_object_duplilist(lb);
+ }
+
+ sob_callback(sctx, params, obj_eval, obj_eval->obmat, is_object_active, data);
+ }
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Ray Cast Funcs
+ * \{ */
+
+/* Store all ray-hits
+ * Support for storing all depths, not just the first (ray-cast 'all'). */
+
+struct RayCastAll_Data {
+ void *bvhdata;
+
+ /* internal vars for adding depths */
+ BVHTree_RayCastCallback raycast_callback;
+
+ const float (*obmat)[4];
+ const float (*timat)[3];
+
+ float len_diff;
+ float local_scale;
+
+ Object *ob_eval;
+ uint ob_uuid;
+
+ /* output data */
+ ListBase *hit_list;
+ bool retval;
+};
+
+static struct SnapObjectHitDepth *hit_depth_create(const float depth,
+ const float co[3],
+ const float no[3],
+ int index,
+ Object *ob_eval,
+ const float obmat[4][4],
+ uint ob_uuid)
+{
+ struct SnapObjectHitDepth *hit = MEM_new<SnapObjectHitDepth>(__func__);
+
+ hit->depth = depth;
+ copy_v3_v3(hit->co, co);
+ copy_v3_v3(hit->no, no);
+ hit->index = index;
+
+ hit->ob_eval = ob_eval;
+ copy_m4_m4(hit->obmat, (float(*)[4])obmat);
+ hit->ob_uuid = ob_uuid;
+
+ return hit;
+}
+
+static int hit_depth_cmp(const void *arg1, const void *arg2)
+{
+ const struct SnapObjectHitDepth *h1 = static_cast<const struct SnapObjectHitDepth *>(arg1);
+ const struct SnapObjectHitDepth *h2 = static_cast<const struct SnapObjectHitDepth *>(arg2);
+ int val = 0;
+
+ if (h1->depth < h2->depth) {
+ val = -1;
+ }
+ else if (h1->depth > h2->depth) {
+ val = 1;
+ }
+
+ return val;
+}
+
+static void raycast_all_cb(void *userdata, int index, const BVHTreeRay *ray, BVHTreeRayHit *hit)
+{
+ struct RayCastAll_Data *data = static_cast<struct RayCastAll_Data *>(userdata);
+ data->raycast_callback(data->bvhdata, index, ray, hit);
+ if (hit->index != -1) {
+ /* Get all values in world-space. */
+ float location[3], normal[3];
+ float depth;
+
+ /* World-space location. */
+ mul_v3_m4v3(location, (float(*)[4])data->obmat, hit->co);
+ depth = (hit->dist + data->len_diff) / data->local_scale;
+
+ /* World-space normal. */
+ copy_v3_v3(normal, hit->no);
+ mul_m3_v3((float(*)[3])data->timat, normal);
+ normalize_v3(normal);
+
+ struct SnapObjectHitDepth *hit_item = hit_depth_create(
+ depth, location, normal, hit->index, data->ob_eval, data->obmat, data->ob_uuid);
+ BLI_addtail(data->hit_list, hit_item);
+ }
+}
+
+static bool raycast_tri_backface_culling_test(
+ const float dir[3], const float v0[3], const float v1[3], const float v2[3], float no[3])
+{
+ cross_tri_v3(no, v0, v1, v2);
+ return dot_v3v3(no, dir) < 0.0f;
+}
+
+/* Callback to ray-cast with back-face culling (#Mesh). */
+static void mesh_looptri_raycast_backface_culling_cb(void *userdata,
+ int index,
+ const BVHTreeRay *ray,
+ BVHTreeRayHit *hit)
+{
+ const BVHTreeFromMesh *data = (BVHTreeFromMesh *)userdata;
+ const MVert *vert = data->vert;
+ const MLoopTri *lt = &data->looptri[index];
+ const float *vtri_co[3] = {
+ vert[data->loop[lt->tri[0]].v].co,
+ vert[data->loop[lt->tri[1]].v].co,
+ vert[data->loop[lt->tri[2]].v].co,
+ };
+ float dist = bvhtree_ray_tri_intersection(ray, hit->dist, UNPACK3(vtri_co));
+
+ if (dist >= 0 && dist < hit->dist) {
+ float no[3];
+ if (raycast_tri_backface_culling_test(ray->direction, UNPACK3(vtri_co), no)) {
+ hit->index = index;
+ hit->dist = dist;
+ madd_v3_v3v3fl(hit->co, ray->origin, ray->direction, dist);
+ normalize_v3_v3(hit->no, no);
+ }
+ }
+}
+
+/* Callback to ray-cast with back-face culling (#EditMesh). */
+static void editmesh_looptri_raycast_backface_culling_cb(void *userdata,
+ int index,
+ const BVHTreeRay *ray,
+ BVHTreeRayHit *hit)
+{
+ const BVHTreeFromEditMesh *data = (BVHTreeFromEditMesh *)userdata;
+ BMEditMesh *em = data->em;
+ const BMLoop **ltri = (const BMLoop **)em->looptris[index];
+
+ const float *t0, *t1, *t2;
+ t0 = ltri[0]->v->co;
+ t1 = ltri[1]->v->co;
+ t2 = ltri[2]->v->co;
+
+ {
+ float dist = bvhtree_ray_tri_intersection(ray, hit->dist, t0, t1, t2);
+
+ if (dist >= 0 && dist < hit->dist) {
+ float no[3];
+ if (raycast_tri_backface_culling_test(ray->direction, t0, t1, t2, no)) {
+ hit->index = index;
+ hit->dist = dist;
+ madd_v3_v3v3fl(hit->co, ray->origin, ray->direction, dist);
+ normalize_v3_v3(hit->no, no);
+ }
+ }
+ }
+}
+
+static bool raycastMesh(SnapObjectContext *sctx,
+ const struct SnapObjectParams *params,
+ const float ray_start[3],
+ const float ray_dir[3],
+ Object *ob_eval,
+ const Mesh *me_eval,
+ const float obmat[4][4],
+ const uint ob_index,
+ bool use_hide,
+ /* read/write args */
+ float *ray_depth,
+ /* return args */
+ float r_loc[3],
+ float r_no[3],
+ int *r_index,
+ ListBase *r_hit_list)
+{
+ bool retval = false;
+
+ if (me_eval->totpoly == 0) {
+ return retval;
+ }
+
+ float imat[4][4];
+ float ray_start_local[3], ray_normal_local[3];
+ float local_scale, local_depth, len_diff = 0.0f;
+
+ invert_m4_m4(imat, obmat);
+
+ copy_v3_v3(ray_start_local, ray_start);
+ copy_v3_v3(ray_normal_local, ray_dir);
+
+ mul_m4_v3(imat, ray_start_local);
+ mul_mat3_m4_v3(imat, ray_normal_local);
+
+ /* local scale in normal direction */
+ local_scale = normalize_v3(ray_normal_local);
+ local_depth = *ray_depth;
+ if (local_depth != BVH_RAYCAST_DIST_MAX) {
+ local_depth *= local_scale;
+ }
+
+ /* Test BoundBox */
+ BoundBox *bb = BKE_object_boundbox_get(ob_eval);
+ if (bb) {
+ /* was BKE_boundbox_ray_hit_check, see: cf6ca226fa58 */
+ if (!isect_ray_aabb_v3_simple(
+ ray_start_local, ray_normal_local, bb->vec[0], bb->vec[6], &len_diff, nullptr)) {
+ return retval;
+ }
+ }
+ /* We pass a temp ray_start, set from object's boundbox, to avoid precision issues with
+ * very far away ray_start values (as returned in case of ortho view3d), see T50486, T38358.
+ */
+ if (len_diff > 400.0f) {
+ len_diff -= local_scale; /* make temp start point a bit away from bbox hit point. */
+ madd_v3_v3fl(ray_start_local, ray_normal_local, len_diff);
+ local_depth -= len_diff;
+ }
+ else {
+ len_diff = 0.0f;
+ }
+
+ SnapObjectData *sod = snap_object_data_mesh_get(sctx, ob_eval, me_eval, use_hide);
+
+ BVHTreeFromMesh *treedata = &sod->treedata_mesh;
+
+ if (treedata->tree == nullptr) {
+ return retval;
+ }
+
+ float timat[3][3]; /* transpose inverse matrix for normals */
+ transpose_m3_m4(timat, imat);
+
+ BLI_assert(treedata->raycast_callback != nullptr);
+ if (r_hit_list) {
+ struct RayCastAll_Data data;
+
+ data.bvhdata = treedata;
+ data.raycast_callback = treedata->raycast_callback;
+ data.obmat = obmat;
+ data.timat = timat;
+ data.len_diff = len_diff;
+ data.local_scale = local_scale;
+ data.ob_eval = ob_eval;
+ data.ob_uuid = ob_index;
+ data.hit_list = r_hit_list;
+ data.retval = retval;
+
+ BLI_bvhtree_ray_cast_all(treedata->tree,
+ ray_start_local,
+ ray_normal_local,
+ 0.0f,
+ *ray_depth,
+ raycast_all_cb,
+ &data);
+
+ retval = data.retval;
+ }
+ else {
+ BVHTreeRayHit hit{};
+ hit.index = -1;
+ hit.dist = local_depth;
+
+ if (BLI_bvhtree_ray_cast(treedata->tree,
+ ray_start_local,
+ ray_normal_local,
+ 0.0f,
+ &hit,
+ params->use_backface_culling ?
+ mesh_looptri_raycast_backface_culling_cb :
+ treedata->raycast_callback,
+ treedata) != -1) {
+ hit.dist += len_diff;
+ hit.dist /= local_scale;
+ if (hit.dist <= *ray_depth) {
+ *ray_depth = hit.dist;
+ copy_v3_v3(r_loc, hit.co);
+
+ /* Back to world-space. */
+ mul_m4_v3(obmat, r_loc);
+
+ if (r_no) {
+ copy_v3_v3(r_no, hit.no);
+ mul_m3_v3(timat, r_no);
+ normalize_v3(r_no);
+ }
+
+ retval = true;
+
+ if (r_index) {
+ *r_index = treedata->looptri[hit.index].poly;
+ }
+ }
+ }
+ }
+
+ return retval;
+}
+
+static bool raycastEditMesh(SnapObjectContext *sctx,
+ const struct SnapObjectParams *params,
+ const float ray_start[3],
+ const float ray_dir[3],
+ Object *ob_eval,
+ BMEditMesh *em,
+ const float obmat[4][4],
+ const uint ob_index,
+ /* read/write args */
+ float *ray_depth,
+ /* return args */
+ float r_loc[3],
+ float r_no[3],
+ int *r_index,
+ ListBase *r_hit_list)
+{
+ bool retval = false;
+ if (em->bm->totface == 0) {
+ return retval;
+ }
+
+ float imat[4][4];
+ float ray_start_local[3], ray_normal_local[3];
+ float local_scale, local_depth, len_diff = 0.0f;
+
+ invert_m4_m4(imat, obmat);
+
+ copy_v3_v3(ray_start_local, ray_start);
+ copy_v3_v3(ray_normal_local, ray_dir);
+
+ mul_m4_v3(imat, ray_start_local);
+ mul_mat3_m4_v3(imat, ray_normal_local);
+
+ /* local scale in normal direction */
+ local_scale = normalize_v3(ray_normal_local);
+ local_depth = *ray_depth;
+ if (local_depth != BVH_RAYCAST_DIST_MAX) {
+ local_depth *= local_scale;
+ }
+
+ SnapObjectData *sod = snap_object_data_editmesh_get(sctx, ob_eval, em);
+
+ /* Test BoundBox */
+
+ /* was BKE_boundbox_ray_hit_check, see: cf6ca226fa58 */
+ if (!isect_ray_aabb_v3_simple(
+ ray_start_local, ray_normal_local, sod->min, sod->max, &len_diff, nullptr)) {
+ return retval;
+ }
+
+ /* We pass a temp ray_start, set from object's boundbox, to avoid precision issues with
+ * very far away ray_start values (as returned in case of ortho view3d), see T50486, T38358.
+ */
+ if (len_diff > 400.0f) {
+ len_diff -= local_scale; /* make temp start point a bit away from bbox hit point. */
+ madd_v3_v3fl(ray_start_local, ray_normal_local, len_diff);
+ local_depth -= len_diff;
+ }
+ else {
+ len_diff = 0.0f;
+ }
+
+ BVHTreeFromEditMesh *treedata = &sod->treedata_editmesh;
+
+ if (treedata->tree == nullptr) {
+ /* Operators only update the editmesh looptris of the original mesh. */
+ BLI_assert(sod->treedata_editmesh.em ==
+ BKE_editmesh_from_object(DEG_get_original_object(ob_eval)));
+ em = sod->treedata_editmesh.em;
+
+ if (sctx->callbacks.edit_mesh.test_face_fn) {
+ BMesh *bm = em->bm;
+ BLI_assert(poly_to_tri_count(bm->totface, bm->totloop) == em->tottri);
+
+ BLI_bitmap *elem_mask = BLI_BITMAP_NEW(em->tottri, __func__);
+ int looptri_num_active = BM_iter_mesh_bitmap_from_filter_tessface(
+ bm,
+ elem_mask,
+ sctx->callbacks.edit_mesh.test_face_fn,
+ sctx->callbacks.edit_mesh.user_data);
+
+ bvhtree_from_editmesh_looptri_ex(treedata,
+ em,
+ elem_mask,
+ looptri_num_active,
+ 0.0f,
+ 4,
+ 6,
+ BVHTREE_FROM_VERTS,
+ nullptr,
+ nullptr);
+
+ MEM_freeN(elem_mask);
+ }
+ else {
+ /* Only cache if bvhtree is created without a mask.
+ * This helps keep a standardized bvhtree in cache. */
+ BKE_bvhtree_from_editmesh_get(treedata,
+ em,
+ 4,
+ BVHTREE_FROM_EM_LOOPTRI,
+ &sod->mesh_runtime->bvh_cache,
+ static_cast<ThreadMutex *>(sod->mesh_runtime->eval_mutex));
+ }
+
+ if (treedata->tree == nullptr) {
+ return retval;
+ }
+ }
+
+ float timat[3][3]; /* transpose inverse matrix for normals */
+ transpose_m3_m4(timat, imat);
+
+ if (r_hit_list) {
+ struct RayCastAll_Data data;
+
+ data.bvhdata = treedata;
+ data.raycast_callback = treedata->raycast_callback;
+ data.obmat = obmat;
+ data.timat = timat;
+ data.len_diff = len_diff;
+ data.local_scale = local_scale;
+ data.ob_eval = ob_eval;
+ data.ob_uuid = ob_index;
+ data.hit_list = r_hit_list;
+ data.retval = retval;
+
+ BLI_bvhtree_ray_cast_all(treedata->tree,
+ ray_start_local,
+ ray_normal_local,
+ 0.0f,
+ *ray_depth,
+ raycast_all_cb,
+ &data);
+
+ retval = data.retval;
+ }
+ else {
+ BVHTreeRayHit hit{};
+ hit.index = -1;
+ hit.dist = local_depth;
+
+ if (BLI_bvhtree_ray_cast(treedata->tree,
+ ray_start_local,
+ ray_normal_local,
+ 0.0f,
+ &hit,
+ params->use_backface_culling ?
+ editmesh_looptri_raycast_backface_culling_cb :
+ treedata->raycast_callback,
+ treedata) != -1) {
+ hit.dist += len_diff;
+ hit.dist /= local_scale;
+ if (hit.dist <= *ray_depth) {
+ *ray_depth = hit.dist;
+ copy_v3_v3(r_loc, hit.co);
+
+ /* Back to world-space. */
+ mul_m4_v3(obmat, r_loc);
+
+ if (r_no) {
+ copy_v3_v3(r_no, hit.no);
+ mul_m3_v3(timat, r_no);
+ normalize_v3(r_no);
+ }
+
+ retval = true;
+
+ if (r_index) {
+ em = sod->treedata_editmesh.em;
+
+ *r_index = BM_elem_index_get(em->looptris[hit.index][0]->f);
+ }
+ }
+ }
+ }
+
+ return retval;
+}
+
+struct RaycastObjUserData {
+ const float *ray_start;
+ const float *ray_dir;
+ uint ob_index;
+ /* read/write args */
+ float *ray_depth;
+ /* return args */
+ float *r_loc;
+ float *r_no;
+ int *r_index;
+ Object **r_ob;
+ float (*r_obmat)[4];
+ ListBase *r_hit_list;
+ bool use_occlusion_test;
+ bool ret;
+};
+
+/**
+ * \note Duplicate args here are documented at #snapObjectsRay
+ */
+static void raycast_obj_fn(SnapObjectContext *sctx,
+ const struct SnapObjectParams *params,
+ Object *ob_eval,
+ float obmat[4][4],
+ bool is_object_active,
+ void *data)
+{
+ RaycastObjUserData *dt = static_cast<RaycastObjUserData *>(data);
+ const uint ob_index = dt->ob_index++;
+ bool use_occlusion_test = dt->use_occlusion_test;
+ /* read/write args */
+ float *ray_depth = dt->ray_depth;
+
+ bool retval = false;
+ if (use_occlusion_test) {
+ if (ELEM(ob_eval->dt, OB_BOUNDBOX, OB_WIRE)) {
+ /* Do not hit objects that are in wire or bounding box
+ * display mode. */
+ return;
+ }
+ }
+
+ switch (ob_eval->type) {
+ case OB_MESH: {
+ const eSnapEditType edit_mode_type = params->edit_mode_type;
+ bool use_hide = false;
+ Mesh *me_eval = mesh_for_snap(ob_eval, edit_mode_type, &use_hide);
+ if (me_eval == nullptr) {
+ /* Operators only update the editmesh looptris of the original mesh. */
+ BMEditMesh *em_orig = BKE_editmesh_from_object(DEG_get_original_object(ob_eval));
+ retval = raycastEditMesh(sctx,
+ params,
+ dt->ray_start,
+ dt->ray_dir,
+ ob_eval,
+ em_orig,
+ obmat,
+ ob_index,
+ ray_depth,
+ dt->r_loc,
+ dt->r_no,
+ dt->r_index,
+ dt->r_hit_list);
+ break;
+ }
+ retval = raycastMesh(sctx,
+ params,
+ dt->ray_start,
+ dt->ray_dir,
+ ob_eval,
+ me_eval,
+ obmat,
+ ob_index,
+ use_hide,
+ ray_depth,
+ dt->r_loc,
+ dt->r_no,
+ dt->r_index,
+ dt->r_hit_list);
+ break;
+ }
+ case OB_CURVES_LEGACY:
+ case OB_SURF:
+ case OB_FONT: {
+ if (!is_object_active) {
+ const Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob_eval);
+ if (mesh_eval) {
+ retval = raycastMesh(sctx,
+ params,
+ dt->ray_start,
+ dt->ray_dir,
+ ob_eval,
+ mesh_eval,
+ obmat,
+ ob_index,
+ false,
+ ray_depth,
+ dt->r_loc,
+ dt->r_no,
+ dt->r_index,
+ dt->r_hit_list);
+ }
+ }
+ break;
+ }
+ }
+
+ if (retval) {
+ if (dt->r_ob) {
+ *dt->r_ob = ob_eval;
+ }
+ if (dt->r_obmat) {
+ copy_m4_m4(dt->r_obmat, obmat);
+ }
+ dt->ret = true;
+ }
+}
+
+/**
+ * Main RayCast Function
+ * ======================
+ *
+ * Walks through all objects in the scene to find the `hit` on object surface.
+ *
+ * \param sctx: Snap context to store data.
+ *
+ * Read/Write Args
+ * ---------------
+ *
+ * \param ray_depth: maximum depth allowed for r_co,
+ * elements deeper than this value will be ignored.
+ *
+ * Output Args
+ * -----------
+ *
+ * \param r_loc: Hit location.
+ * \param r_no: Hit normal (optional).
+ * \param r_index: Hit index or -1 when no valid index is found.
+ * (currently only set to the polygon index when using `snap_to == SCE_SNAP_MODE_FACE`).
+ * \param r_ob: Hit object.
+ * \param r_obmat: Object matrix (may not be #Object.obmat with dupli-instances).
+ * \param r_hit_list: List of #SnapObjectHitDepth (caller must free).
+ */
+static bool raycastObjects(SnapObjectContext *sctx,
+ const struct SnapObjectParams *params,
+ const float ray_start[3],
+ const float ray_dir[3],
+ /* read/write args */
+ /* Parameters below cannot be const, because they are assigned to a
+ * non-const variable (readability-non-const-parameter). */
+ float *ray_depth /* NOLINT */,
+ /* return args */
+ float r_loc[3] /* NOLINT */,
+ float r_no[3] /* NOLINT */,
+ int *r_index /* NOLINT */,
+ Object **r_ob,
+ float r_obmat[4][4],
+ ListBase *r_hit_list)
+{
+ const View3D *v3d = sctx->runtime.v3d;
+ if (params->use_occlusion_test && v3d && XRAY_FLAG_ENABLED(v3d)) {
+ /* General testing of occlusion geometry is disabled if the snap is not intended for the edit
+ * cage. */
+ if (params->edit_mode_type == SNAP_GEOM_EDIT) {
+ return false;
+ }
+ }
+
+ RaycastObjUserData data = {};
+ data.ray_start = ray_start;
+ data.ray_dir = ray_dir;
+ data.ob_index = 0;
+ data.ray_depth = ray_depth;
+ data.r_loc = r_loc;
+ data.r_no = r_no;
+ data.r_index = r_index;
+ data.r_ob = r_ob;
+ data.r_obmat = r_obmat;
+ data.r_hit_list = r_hit_list;
+ data.use_occlusion_test = params->use_occlusion_test;
+ data.ret = false;
+
+ iter_snap_objects(sctx, params, raycast_obj_fn, &data);
+
+ return data.ret;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Snap Nearest utilities
+ * \{ */
+
+/* Test BoundBox */
+static bool snap_bound_box_check_dist(const float min[3],
+ const float max[3],
+ const float lpmat[4][4],
+ const float win_size[2],
+ const float mval[2],
+ float dist_px_sq)
+{
+ /* In vertex and edges you need to get the pixel distance from ray to BoundBox,
+ * see: T46099, T46816 */
+
+ struct DistProjectedAABBPrecalc data_precalc;
+ dist_squared_to_projected_aabb_precalc(&data_precalc, lpmat, win_size, mval);
+
+ bool dummy[3];
+ float bb_dist_px_sq = dist_squared_to_projected_aabb(&data_precalc, min, max, dummy);
+
+ if (bb_dist_px_sq > dist_px_sq) {
+ return false;
+ }
+ return true;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Callbacks
+ * \{ */
+
+struct Nearest2dUserData;
+
+using Nearest2DGetVertCoCallback = void (*)(const int index,
+ const struct Nearest2dUserData *data,
+ const float **r_co);
+using Nearest2DGetEdgeVertsCallback = void (*)(const int index,
+ const struct Nearest2dUserData *data,
+ int r_v_index[2]);
+using Nearest2DGetTriVertsCallback = void (*)(const int index,
+ const struct Nearest2dUserData *data,
+ int r_v_index[3]);
+/* Equal the previous one */
+using Nearest2DGetTriEdgesCallback = void (*)(const int index,
+ const struct Nearest2dUserData *data,
+ int r_e_index[3]);
+using Nearest2DCopyVertNoCallback = void (*)(const int index,
+ const struct Nearest2dUserData *data,
+ float r_no[3]);
+
+struct Nearest2dUserData {
+ Nearest2DGetVertCoCallback get_vert_co;
+ Nearest2DGetEdgeVertsCallback get_edge_verts_index;
+ Nearest2DGetTriVertsCallback get_tri_verts_index;
+ Nearest2DGetTriEdgesCallback get_tri_edges_index;
+ Nearest2DCopyVertNoCallback copy_vert_no;
+
+ union {
+ struct {
+ struct BMesh *bm;
+ };
+ struct {
+ const struct MVert *vert;
+ const float (*vert_normals)[3];
+ const struct MEdge *edge; /* only used for #BVHTreeFromMeshEdges */
+ const struct MLoop *loop;
+ const struct MLoopTri *looptri;
+ };
+ };
+
+ bool is_persp;
+ bool use_backface_culling;
+};
+
+static void cb_mvert_co_get(const int index, const Nearest2dUserData *data, const float **r_co)
+{
+ *r_co = data->vert[index].co;
+}
+
+static void cb_bvert_co_get(const int index, const Nearest2dUserData *data, const float **r_co)
+{
+ BMVert *eve = BM_vert_at_index(data->bm, index);
+ *r_co = eve->co;
+}
+
+static void cb_mvert_no_copy(const int index, const Nearest2dUserData *data, float r_no[3])
+{
+ copy_v3_v3(r_no, data->vert_normals[index]);
+}
+
+static void cb_bvert_no_copy(const int index, const Nearest2dUserData *data, float r_no[3])
+{
+ BMVert *eve = BM_vert_at_index(data->bm, index);
+
+ copy_v3_v3(r_no, eve->no);
+}
+
+static void cb_medge_verts_get(const int index, const Nearest2dUserData *data, int r_v_index[2])
+{
+ const MEdge *edge = &data->edge[index];
+
+ r_v_index[0] = edge->v1;
+ r_v_index[1] = edge->v2;
+}
+
+static void cb_bedge_verts_get(const int index, const Nearest2dUserData *data, int r_v_index[2])
+{
+ BMEdge *eed = BM_edge_at_index(data->bm, index);
+
+ r_v_index[0] = BM_elem_index_get(eed->v1);
+ r_v_index[1] = BM_elem_index_get(eed->v2);
+}
+
+static void cb_mlooptri_edges_get(const int index, const Nearest2dUserData *data, int r_v_index[3])
+{
+ const MEdge *medge = data->edge;
+ const MLoop *mloop = data->loop;
+ const MLoopTri *lt = &data->looptri[index];
+ for (int j = 2, j_next = 0; j_next < 3; j = j_next++) {
+ const MEdge *ed = &medge[mloop[lt->tri[j]].e];
+ const uint tri_edge[2] = {mloop[lt->tri[j]].v, mloop[lt->tri[j_next]].v};
+ if (ELEM(ed->v1, tri_edge[0], tri_edge[1]) && ELEM(ed->v2, tri_edge[0], tri_edge[1])) {
+ // printf("real edge found\n");
+ r_v_index[j] = mloop[lt->tri[j]].e;
+ }
+ else {
+ r_v_index[j] = -1;
+ }
+ }
+}
+
+static void cb_mlooptri_verts_get(const int index, const Nearest2dUserData *data, int r_v_index[3])
+{
+ const MLoop *loop = data->loop;
+ const MLoopTri *looptri = &data->looptri[index];
+
+ r_v_index[0] = loop[looptri->tri[0]].v;
+ r_v_index[1] = loop[looptri->tri[1]].v;
+ r_v_index[2] = loop[looptri->tri[2]].v;
+}
+
+static bool test_projected_vert_dist(const struct DistProjectedAABBPrecalc *precalc,
+ const float (*clip_plane)[4],
+ const int clip_plane_len,
+ const bool is_persp,
+ const float co[3],
+ float *dist_px_sq,
+ float r_co[3])
+{
+ if (!isect_point_planes_v3_negated(clip_plane, clip_plane_len, co)) {
+ return false;
+ }
+
+ float co2d[2] = {
+ (dot_m4_v3_row_x(precalc->pmat, co) + precalc->pmat[3][0]),
+ (dot_m4_v3_row_y(precalc->pmat, co) + precalc->pmat[3][1]),
+ };
+
+ if (is_persp) {
+ float w = mul_project_m4_v3_zfac(precalc->pmat, co);
+ mul_v2_fl(co2d, 1.0f / w);
+ }
+
+ const float dist_sq = len_squared_v2v2(precalc->mval, co2d);
+ if (dist_sq < *dist_px_sq) {
+ copy_v3_v3(r_co, co);
+ *dist_px_sq = dist_sq;
+ return true;
+ }
+ return false;
+}
+
+static bool test_projected_edge_dist(const struct DistProjectedAABBPrecalc *precalc,
+ const float (*clip_plane)[4],
+ const int clip_plane_len,
+ const bool is_persp,
+ const float va[3],
+ const float vb[3],
+ float *dist_px_sq,
+ float r_co[3])
+{
+ float near_co[3], lambda;
+ if (!isect_ray_line_v3(precalc->ray_origin, precalc->ray_direction, va, vb, &lambda)) {
+ copy_v3_v3(near_co, va);
+ }
+ else {
+ if (lambda <= 0.0f) {
+ copy_v3_v3(near_co, va);
+ }
+ else if (lambda >= 1.0f) {
+ copy_v3_v3(near_co, vb);
+ }
+ else {
+ interp_v3_v3v3(near_co, va, vb, lambda);
+ }
+ }
+
+ return test_projected_vert_dist(
+ precalc, clip_plane, clip_plane_len, is_persp, near_co, dist_px_sq, r_co);
+}
+
+static void cb_snap_vert(void *userdata,
+ int index,
+ const struct DistProjectedAABBPrecalc *precalc,
+ const float (*clip_plane)[4],
+ const int clip_plane_len,
+ BVHTreeNearest *nearest)
+{
+ Nearest2dUserData *data = static_cast<Nearest2dUserData *>(userdata);
+
+ const float *co;
+ data->get_vert_co(index, data, &co);
+
+ if (test_projected_vert_dist(precalc,
+ clip_plane,
+ clip_plane_len,
+ data->is_persp,
+ co,
+ &nearest->dist_sq,
+ nearest->co)) {
+ data->copy_vert_no(index, data, nearest->no);
+ nearest->index = index;
+ }
+}
+
+static void cb_snap_edge(void *userdata,
+ int index,
+ const struct DistProjectedAABBPrecalc *precalc,
+ const float (*clip_plane)[4],
+ const int clip_plane_len,
+ BVHTreeNearest *nearest)
+{
+ Nearest2dUserData *data = static_cast<Nearest2dUserData *>(userdata);
+
+ int vindex[2];
+ data->get_edge_verts_index(index, data, vindex);
+
+ const float *v_pair[2];
+ data->get_vert_co(vindex[0], data, &v_pair[0]);
+ data->get_vert_co(vindex[1], data, &v_pair[1]);
+
+ if (test_projected_edge_dist(precalc,
+ clip_plane,
+ clip_plane_len,
+ data->is_persp,
+ v_pair[0],
+ v_pair[1],
+ &nearest->dist_sq,
+ nearest->co)) {
+ sub_v3_v3v3(nearest->no, v_pair[0], v_pair[1]);
+ nearest->index = index;
+ }
+}
+
+static void cb_snap_edge_verts(void *userdata,
+ int index,
+ const struct DistProjectedAABBPrecalc *precalc,
+ const float (*clip_plane)[4],
+ const int clip_plane_len,
+ BVHTreeNearest *nearest)
+{
+ Nearest2dUserData *data = static_cast<Nearest2dUserData *>(userdata);
+
+ int vindex[2];
+ data->get_edge_verts_index(index, data, vindex);
+
+ for (int i = 2; i--;) {
+ if (vindex[i] == nearest->index) {
+ continue;
+ }
+ cb_snap_vert(userdata, vindex[i], precalc, clip_plane, clip_plane_len, nearest);
+ }
+}
+
+static void cb_snap_tri_edges(void *userdata,
+ int index,
+ const struct DistProjectedAABBPrecalc *precalc,
+ const float (*clip_plane)[4],
+ const int clip_plane_len,
+ BVHTreeNearest *nearest)
+{
+ Nearest2dUserData *data = static_cast<Nearest2dUserData *>(userdata);
+
+ if (data->use_backface_culling) {
+ int vindex[3];
+ data->get_tri_verts_index(index, data, vindex);
+
+ const float *t0, *t1, *t2;
+ data->get_vert_co(vindex[0], data, &t0);
+ data->get_vert_co(vindex[1], data, &t1);
+ data->get_vert_co(vindex[2], data, &t2);
+ float dummy[3];
+ if (raycast_tri_backface_culling_test(precalc->ray_direction, t0, t1, t2, dummy)) {
+ return;
+ }
+ }
+
+ int eindex[3];
+ data->get_tri_edges_index(index, data, eindex);
+ for (int i = 3; i--;) {
+ if (eindex[i] != -1) {
+ if (eindex[i] == nearest->index) {
+ continue;
+ }
+ cb_snap_edge(userdata, eindex[i], precalc, clip_plane, clip_plane_len, nearest);
+ }
+ }
+}
+
+static void cb_snap_tri_verts(void *userdata,
+ int index,
+ const struct DistProjectedAABBPrecalc *precalc,
+ const float (*clip_plane)[4],
+ const int clip_plane_len,
+ BVHTreeNearest *nearest)
+{
+ Nearest2dUserData *data = static_cast<Nearest2dUserData *>(userdata);
+
+ int vindex[3];
+ data->get_tri_verts_index(index, data, vindex);
+
+ if (data->use_backface_culling) {
+ const float *t0, *t1, *t2;
+ data->get_vert_co(vindex[0], data, &t0);
+ data->get_vert_co(vindex[1], data, &t1);
+ data->get_vert_co(vindex[2], data, &t2);
+ float dummy[3];
+ if (raycast_tri_backface_culling_test(precalc->ray_direction, t0, t1, t2, dummy)) {
+ return;
+ }
+ }
+
+ for (int i = 3; i--;) {
+ if (vindex[i] == nearest->index) {
+ continue;
+ }
+ cb_snap_vert(userdata, vindex[i], precalc, clip_plane, clip_plane_len, nearest);
+ }
+}
+
+static void nearest2d_data_init(SnapObjectData *sod,
+ bool is_persp,
+ bool use_backface_culling,
+ Nearest2dUserData *r_nearest2d)
+{
+ if (sod->type == SnapObjectData::Type::Mesh) {
+ r_nearest2d->get_vert_co = cb_mvert_co_get;
+ r_nearest2d->get_edge_verts_index = cb_medge_verts_get;
+ r_nearest2d->copy_vert_no = cb_mvert_no_copy;
+ r_nearest2d->get_tri_verts_index = cb_mlooptri_verts_get;
+ r_nearest2d->get_tri_edges_index = cb_mlooptri_edges_get;
+
+ r_nearest2d->vert = sod->treedata_mesh.vert;
+ r_nearest2d->vert_normals = sod->treedata_mesh.vert_normals;
+ r_nearest2d->edge = sod->treedata_mesh.edge;
+ r_nearest2d->loop = sod->treedata_mesh.loop;
+ r_nearest2d->looptri = sod->treedata_mesh.looptri;
+ }
+ else {
+ BLI_assert(sod->type == SnapObjectData::Type::EditMesh);
+ r_nearest2d->get_vert_co = cb_bvert_co_get;
+ r_nearest2d->get_edge_verts_index = cb_bedge_verts_get;
+ r_nearest2d->copy_vert_no = cb_bvert_no_copy;
+ r_nearest2d->get_tri_verts_index = nullptr;
+ r_nearest2d->get_tri_edges_index = nullptr;
+
+ r_nearest2d->bm = sod->treedata_editmesh.em->bm;
+ }
+
+ r_nearest2d->is_persp = is_persp;
+ r_nearest2d->use_backface_culling = use_backface_culling;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Internal Object Snapping API
+ * \{ */
+
+static short snap_mesh_polygon(SnapObjectContext *sctx,
+ const struct SnapObjectParams *params,
+ Object *ob_eval,
+ const float obmat[4][4],
+ /* read/write args */
+ float *dist_px,
+ /* return args */
+ float r_loc[3],
+ float r_no[3],
+ int *r_index)
+{
+ short elem = 0;
+
+ float lpmat[4][4];
+ mul_m4_m4m4(lpmat, sctx->runtime.pmat, obmat);
+
+ struct DistProjectedAABBPrecalc neasrest_precalc;
+ dist_squared_to_projected_aabb_precalc(
+ &neasrest_precalc, lpmat, sctx->runtime.win_size, sctx->runtime.mval);
+
+ float tobmat[4][4], clip_planes_local[MAX_CLIPPLANE_LEN][4];
+ transpose_m4_m4(tobmat, obmat);
+ for (int i = sctx->runtime.clip_plane_len; i--;) {
+ mul_v4_m4v4(clip_planes_local[i], tobmat, sctx->runtime.clip_plane[i]);
+ }
+
+ BVHTreeNearest nearest{};
+ nearest.index = -1;
+ nearest.dist_sq = square_f(*dist_px);
+
+ SnapObjectData *sod = snap_object_data_lookup(sctx, ob_eval);
+ BLI_assert(sod != nullptr);
+
+ Nearest2dUserData nearest2d;
+ nearest2d_data_init(
+ sod, sctx->runtime.view_proj == VIEW_PROJ_PERSP, params->use_backface_culling, &nearest2d);
+
+ if (sod->type == SnapObjectData::Type::Mesh) {
+ BVHTreeFromMesh *treedata = &sod->treedata_mesh;
+
+ const MPoly *mp = &sod->poly[*r_index];
+ const MLoop *ml = &treedata->loop[mp->loopstart];
+ if (sctx->runtime.snap_to_flag & SCE_SNAP_MODE_EDGE) {
+ elem = SCE_SNAP_MODE_EDGE;
+ BLI_assert(treedata->edge != nullptr);
+ for (int i = mp->totloop; i--; ml++) {
+ cb_snap_edge(&nearest2d,
+ ml->e,
+ &neasrest_precalc,
+ clip_planes_local,
+ sctx->runtime.clip_plane_len,
+ &nearest);
+ }
+ }
+ else {
+ elem = SCE_SNAP_MODE_VERTEX;
+ for (int i = mp->totloop; i--; ml++) {
+ cb_snap_vert(&nearest2d,
+ ml->v,
+ &neasrest_precalc,
+ clip_planes_local,
+ sctx->runtime.clip_plane_len,
+ &nearest);
+ }
+ }
+ }
+ else {
+ BLI_assert(sod->type == SnapObjectData::Type::EditMesh);
+ BMEditMesh *em = sod->treedata_editmesh.em;
+
+ BM_mesh_elem_table_ensure(em->bm, BM_FACE);
+ BMFace *f = BM_face_at_index(em->bm, *r_index);
+ BMLoop *l_iter, *l_first;
+ l_iter = l_first = BM_FACE_FIRST_LOOP(f);
+ if (sctx->runtime.snap_to_flag & SCE_SNAP_MODE_EDGE) {
+ elem = SCE_SNAP_MODE_EDGE;
+ BM_mesh_elem_index_ensure(em->bm, BM_VERT | BM_EDGE);
+ BM_mesh_elem_table_ensure(em->bm, BM_VERT | BM_EDGE);
+ do {
+ cb_snap_edge(&nearest2d,
+ BM_elem_index_get(l_iter->e),
+ &neasrest_precalc,
+ clip_planes_local,
+ sctx->runtime.clip_plane_len,
+ &nearest);
+ } while ((l_iter = l_iter->next) != l_first);
+ }
+ else {
+ elem = SCE_SNAP_MODE_VERTEX;
+ BM_mesh_elem_index_ensure(em->bm, BM_VERT);
+ BM_mesh_elem_table_ensure(em->bm, BM_VERT);
+ do {
+ cb_snap_vert(&nearest2d,
+ BM_elem_index_get(l_iter->v),
+ &neasrest_precalc,
+ clip_planes_local,
+ sctx->runtime.clip_plane_len,
+ &nearest);
+ } while ((l_iter = l_iter->next) != l_first);
+ }
+ }
+
+ if (nearest.index != -1) {
+ *dist_px = sqrtf(nearest.dist_sq);
+
+ copy_v3_v3(r_loc, nearest.co);
+ mul_m4_v3(obmat, r_loc);
+
+ if (r_no) {
+ float imat[4][4];
+ invert_m4_m4(imat, obmat);
+
+ copy_v3_v3(r_no, nearest.no);
+ mul_transposed_mat3_m4_v3(imat, r_no);
+ normalize_v3(r_no);
+ }
+
+ *r_index = nearest.index;
+ return elem;
+ }
+
+ return 0;
+}
+
+static short snap_mesh_edge_verts_mixed(SnapObjectContext *sctx,
+ const struct SnapObjectParams *params,
+ Object *ob_eval,
+ const float obmat[4][4],
+ float original_dist_px,
+ const float prev_co[3],
+ /* read/write args */
+ float *dist_px,
+ /* return args */
+ float r_loc[3],
+ float r_no[3],
+ int *r_index)
+{
+ short elem = SCE_SNAP_MODE_EDGE;
+
+ if (ob_eval->type != OB_MESH) {
+ return elem;
+ }
+
+ SnapObjectData *sod = snap_object_data_lookup(sctx, ob_eval);
+ BLI_assert(sod != nullptr);
+
+ Nearest2dUserData nearest2d;
+ nearest2d_data_init(
+ sod, sctx->runtime.view_proj == VIEW_PROJ_PERSP, params->use_backface_culling, &nearest2d);
+
+ int vindex[2];
+ nearest2d.get_edge_verts_index(*r_index, &nearest2d, vindex);
+
+ const float *v_pair[2];
+ nearest2d.get_vert_co(vindex[0], &nearest2d, &v_pair[0]);
+ nearest2d.get_vert_co(vindex[1], &nearest2d, &v_pair[1]);
+
+ struct DistProjectedAABBPrecalc neasrest_precalc;
+ {
+ float lpmat[4][4];
+ mul_m4_m4m4(lpmat, sctx->runtime.pmat, obmat);
+
+ dist_squared_to_projected_aabb_precalc(
+ &neasrest_precalc, lpmat, sctx->runtime.win_size, sctx->runtime.mval);
+ }
+
+ BVHTreeNearest nearest{};
+ nearest.index = -1;
+ nearest.dist_sq = square_f(original_dist_px);
+
+ float lambda;
+ if (!isect_ray_line_v3(neasrest_precalc.ray_origin,
+ neasrest_precalc.ray_direction,
+ v_pair[0],
+ v_pair[1],
+ &lambda)) {
+ /* do nothing */
+ }
+ else {
+ short snap_to_flag = sctx->runtime.snap_to_flag;
+ int e_mode_len = ((snap_to_flag & SCE_SNAP_MODE_EDGE) != 0) +
+ ((snap_to_flag & SCE_SNAP_MODE_VERTEX) != 0) +
+ ((snap_to_flag & SCE_SNAP_MODE_EDGE_MIDPOINT) != 0);
+
+ float range = 1.0f / (2 * e_mode_len - 1);
+ if (snap_to_flag & SCE_SNAP_MODE_VERTEX) {
+ if (lambda < (range) || (1.0f - range) < lambda) {
+ int v_id = lambda < 0.5f ? 0 : 1;
+
+ if (test_projected_vert_dist(&neasrest_precalc,
+ nullptr,
+ 0,
+ nearest2d.is_persp,
+ v_pair[v_id],
+ &nearest.dist_sq,
+ nearest.co)) {
+ nearest.index = vindex[v_id];
+ elem = SCE_SNAP_MODE_VERTEX;
+ if (r_no) {
+ float imat[4][4];
+ invert_m4_m4(imat, obmat);
+ nearest2d.copy_vert_no(vindex[v_id], &nearest2d, r_no);
+ mul_transposed_mat3_m4_v3(imat, r_no);
+ normalize_v3(r_no);
+ }
+ }
+ }
+ }
+
+ if (snap_to_flag & SCE_SNAP_MODE_EDGE_MIDPOINT) {
+ range *= e_mode_len - 1;
+ if ((range) < lambda && lambda < (1.0f - range)) {
+ float vmid[3];
+ mid_v3_v3v3(vmid, v_pair[0], v_pair[1]);
+
+ if (test_projected_vert_dist(&neasrest_precalc,
+ nullptr,
+ 0,
+ nearest2d.is_persp,
+ vmid,
+ &nearest.dist_sq,
+ nearest.co)) {
+ nearest.index = *r_index;
+ elem = SCE_SNAP_MODE_EDGE_MIDPOINT;
+ }
+ }
+ }
+
+ if (prev_co && (sctx->runtime.snap_to_flag & SCE_SNAP_MODE_EDGE_PERPENDICULAR)) {
+ float v_near[3], va_g[3], vb_g[3];
+
+ mul_v3_m4v3(va_g, obmat, v_pair[0]);
+ mul_v3_m4v3(vb_g, obmat, v_pair[1]);
+ lambda = line_point_factor_v3(prev_co, va_g, vb_g);
+
+ if (IN_RANGE(lambda, 0.0f, 1.0f)) {
+ interp_v3_v3v3(v_near, va_g, vb_g, lambda);
+
+ if (len_squared_v3v3(prev_co, v_near) > FLT_EPSILON) {
+ dist_squared_to_projected_aabb_precalc(
+ &neasrest_precalc, sctx->runtime.pmat, sctx->runtime.win_size, sctx->runtime.mval);
+
+ if (test_projected_vert_dist(&neasrest_precalc,
+ nullptr,
+ 0,
+ nearest2d.is_persp,
+ v_near,
+ &nearest.dist_sq,
+ nearest.co)) {
+ nearest.index = *r_index;
+ elem = SCE_SNAP_MODE_EDGE_PERPENDICULAR;
+ }
+ }
+ }
+ }
+ }
+
+ if (nearest.index != -1) {
+ *dist_px = sqrtf(nearest.dist_sq);
+
+ copy_v3_v3(r_loc, nearest.co);
+ if (elem != SCE_SNAP_MODE_EDGE_PERPENDICULAR) {
+ mul_m4_v3(obmat, r_loc);
+ }
+
+ *r_index = nearest.index;
+ }
+
+ return elem;
+}
+
+static short snapArmature(SnapObjectContext *sctx,
+ const struct SnapObjectParams *params,
+ Object *ob_eval,
+ const float obmat[4][4],
+ bool is_object_active,
+ /* read/write args */
+ float *dist_px,
+ /* return args */
+ float r_loc[3],
+ float *UNUSED(r_no),
+ int *r_index)
+{
+ short retval = 0;
+
+ if (sctx->runtime.snap_to_flag == SCE_SNAP_MODE_FACE) { /* Currently only edge and vert */
+ return retval;
+ }
+
+ float lpmat[4][4], dist_px_sq = square_f(*dist_px);
+ mul_m4_m4m4(lpmat, sctx->runtime.pmat, obmat);
+
+ struct DistProjectedAABBPrecalc neasrest_precalc;
+ dist_squared_to_projected_aabb_precalc(
+ &neasrest_precalc, lpmat, sctx->runtime.win_size, sctx->runtime.mval);
+
+ bArmature *arm = static_cast<bArmature *>(ob_eval->data);
+ const bool is_editmode = arm->edbo != nullptr;
+
+ if (is_editmode == false) {
+ /* Test BoundBox */
+ BoundBox *bb = BKE_armature_boundbox_get(ob_eval);
+ if (bb && !snap_bound_box_check_dist(bb->vec[0],
+ bb->vec[6],
+ lpmat,
+ sctx->runtime.win_size,
+ sctx->runtime.mval,
+ dist_px_sq)) {
+ return retval;
+ }
+ }
+
+ float tobmat[4][4], clip_planes_local[MAX_CLIPPLANE_LEN][4];
+ transpose_m4_m4(tobmat, obmat);
+ for (int i = sctx->runtime.clip_plane_len; i--;) {
+ mul_v4_m4v4(clip_planes_local[i], tobmat, sctx->runtime.clip_plane[i]);
+ }
+
+ const bool is_posemode = is_object_active && (ob_eval->mode & OB_MODE_POSE);
+ const bool skip_selected = (is_editmode || is_posemode) &&
+ (params->snap_select == SNAP_NOT_SELECTED);
+ const bool is_persp = sctx->runtime.view_proj == VIEW_PROJ_PERSP;
+
+ if (arm->edbo) {
+ LISTBASE_FOREACH (EditBone *, eBone, arm->edbo) {
+ if (eBone->layer & arm->layer) {
+ if (eBone->flag & BONE_HIDDEN_A) {
+ /* Skip hidden bones. */
+ continue;
+ }
+
+ const bool is_selected = (eBone->flag & (BONE_ROOTSEL | BONE_TIPSEL)) != 0;
+ if (is_selected && skip_selected) {
+ continue;
+ }
+ bool has_vert_snap = false;
+
+ if (sctx->runtime.snap_to_flag & SCE_SNAP_MODE_VERTEX) {
+ has_vert_snap = test_projected_vert_dist(&neasrest_precalc,
+ clip_planes_local,
+ sctx->runtime.clip_plane_len,
+ is_persp,
+ eBone->head,
+ &dist_px_sq,
+ r_loc);
+ has_vert_snap |= test_projected_vert_dist(&neasrest_precalc,
+ clip_planes_local,
+ sctx->runtime.clip_plane_len,
+ is_persp,
+ eBone->tail,
+
+ &dist_px_sq,
+ r_loc);
+
+ if (has_vert_snap) {
+ retval = SCE_SNAP_MODE_VERTEX;
+ }
+ }
+ if (!has_vert_snap && sctx->runtime.snap_to_flag & SCE_SNAP_MODE_EDGE) {
+ if (test_projected_edge_dist(&neasrest_precalc,
+ clip_planes_local,
+ sctx->runtime.clip_plane_len,
+ is_persp,
+ eBone->head,
+ eBone->tail,
+ &dist_px_sq,
+ r_loc)) {
+ retval = SCE_SNAP_MODE_EDGE;
+ }
+ }
+ }
+ }
+ }
+ else if (ob_eval->pose && ob_eval->pose->chanbase.first) {
+ LISTBASE_FOREACH (bPoseChannel *, pchan, &ob_eval->pose->chanbase) {
+ Bone *bone = pchan->bone;
+ if (!bone || (bone->flag & (BONE_HIDDEN_P | BONE_HIDDEN_PG))) {
+ /* Skip hidden bones. */
+ continue;
+ }
+
+ const bool is_selected = (bone->flag & (BONE_SELECTED | BONE_ROOTSEL | BONE_TIPSEL)) != 0;
+ if (is_selected && skip_selected) {
+ continue;
+ }
+
+ bool has_vert_snap = false;
+ const float *head_vec = pchan->pose_head;
+ const float *tail_vec = pchan->pose_tail;
+
+ if (sctx->runtime.snap_to_flag & SCE_SNAP_MODE_VERTEX) {
+ has_vert_snap = test_projected_vert_dist(&neasrest_precalc,
+ clip_planes_local,
+ sctx->runtime.clip_plane_len,
+ is_persp,
+ head_vec,
+ &dist_px_sq,
+ r_loc);
+ has_vert_snap |= test_projected_vert_dist(&neasrest_precalc,
+ clip_planes_local,
+ sctx->runtime.clip_plane_len,
+ is_persp,
+ tail_vec,
+ &dist_px_sq,
+ r_loc);
+
+ if (has_vert_snap) {
+ retval = SCE_SNAP_MODE_VERTEX;
+ }
+ }
+ if (!has_vert_snap && sctx->runtime.snap_to_flag & SCE_SNAP_MODE_EDGE) {
+ if (test_projected_edge_dist(&neasrest_precalc,
+ clip_planes_local,
+ sctx->runtime.clip_plane_len,
+ is_persp,
+ head_vec,
+ tail_vec,
+ &dist_px_sq,
+ r_loc)) {
+ retval = SCE_SNAP_MODE_EDGE;
+ }
+ }
+ }
+ }
+
+ if (retval) {
+ *dist_px = sqrtf(dist_px_sq);
+ mul_m4_v3(obmat, r_loc);
+ if (r_index) {
+ /* Does not support index. */
+ *r_index = -1;
+ }
+ return retval;
+ }
+
+ return 0;
+}
+
+static short snapCurve(SnapObjectContext *sctx,
+ const struct SnapObjectParams *params,
+ Object *ob_eval,
+ const float obmat[4][4],
+ /* read/write args */
+ float *dist_px,
+ /* return args */
+ float r_loc[3],
+ float *UNUSED(r_no),
+ int *r_index)
+{
+ bool has_snap = false;
+
+ /* only vertex snapping mode (eg control points and handles) supported for now) */
+ if ((sctx->runtime.snap_to_flag & SCE_SNAP_MODE_VERTEX) == 0) {
+ return 0;
+ }
+
+ Curve *cu = static_cast<Curve *>(ob_eval->data);
+ float dist_px_sq = square_f(*dist_px);
+
+ float lpmat[4][4];
+ mul_m4_m4m4(lpmat, sctx->runtime.pmat, obmat);
+
+ struct DistProjectedAABBPrecalc neasrest_precalc;
+ dist_squared_to_projected_aabb_precalc(
+ &neasrest_precalc, lpmat, sctx->runtime.win_size, sctx->runtime.mval);
+
+ const bool use_obedit = BKE_object_is_in_editmode(ob_eval);
+
+ if (use_obedit == false) {
+ /* Test BoundBox */
+ BoundBox *bb = BKE_curve_boundbox_get(ob_eval);
+ if (bb && !snap_bound_box_check_dist(bb->vec[0],
+ bb->vec[6],
+ lpmat,
+ sctx->runtime.win_size,
+ sctx->runtime.mval,
+ dist_px_sq)) {
+ return 0;
+ }
+ }
+
+ float tobmat[4][4];
+ transpose_m4_m4(tobmat, obmat);
+
+ float(*clip_planes)[4] = sctx->runtime.clip_plane;
+ int clip_plane_len = sctx->runtime.clip_plane_len;
+
+ if (sctx->runtime.has_occlusion_plane) {
+ /* We snap to vertices even if occluded. */
+ clip_planes++;
+ clip_plane_len--;
+ }
+
+ float clip_planes_local[MAX_CLIPPLANE_LEN][4];
+ for (int i = clip_plane_len; i--;) {
+ mul_v4_m4v4(clip_planes_local[i], tobmat, clip_planes[i]);
+ }
+
+ bool is_persp = sctx->runtime.view_proj == VIEW_PROJ_PERSP;
+ bool skip_selected = params->snap_select == SNAP_NOT_SELECTED;
+
+ LISTBASE_FOREACH (Nurb *, nu, (use_obedit ? &cu->editnurb->nurbs : &cu->nurb)) {
+ for (int u = 0; u < nu->pntsu; u++) {
+ if (sctx->runtime.snap_to_flag & SCE_SNAP_MODE_VERTEX) {
+ if (use_obedit) {
+ if (nu->bezt) {
+ if (nu->bezt[u].hide) {
+ /* Skip hidden. */
+ continue;
+ }
+
+ bool is_selected = (nu->bezt[u].f2 & SELECT) != 0;
+ if (is_selected && skip_selected) {
+ continue;
+ }
+ has_snap |= test_projected_vert_dist(&neasrest_precalc,
+ clip_planes_local,
+ clip_plane_len,
+ is_persp,
+ nu->bezt[u].vec[1],
+ &dist_px_sq,
+ r_loc);
+ /* Don't snap if handle is selected (moving),
+ * or if it is aligning to a moving handle. */
+ is_selected = (!(nu->bezt[u].f1 & SELECT) &&
+ !(nu->bezt[u].h1 & HD_ALIGN && nu->bezt[u].f3 & SELECT)) != 0;
+ if (!(is_selected && skip_selected)) {
+ has_snap |= test_projected_vert_dist(&neasrest_precalc,
+ clip_planes_local,
+ clip_plane_len,
+ is_persp,
+ nu->bezt[u].vec[0],
+ &dist_px_sq,
+ r_loc);
+ }
+
+ is_selected = (!(nu->bezt[u].f3 & SELECT) &&
+ !(nu->bezt[u].h2 & HD_ALIGN && nu->bezt[u].f1 & SELECT)) != 0;
+ if (!(is_selected && skip_selected)) {
+ has_snap |= test_projected_vert_dist(&neasrest_precalc,
+ clip_planes_local,
+ clip_plane_len,
+ is_persp,
+ nu->bezt[u].vec[2],
+ &dist_px_sq,
+ r_loc);
+ }
+ }
+ else {
+ if (nu->bp[u].hide) {
+ /* Skip hidden. */
+ continue;
+ }
+
+ bool is_selected = (nu->bp[u].f1 & SELECT) != 0;
+ if (is_selected && skip_selected) {
+ continue;
+ }
+
+ has_snap |= test_projected_vert_dist(&neasrest_precalc,
+ clip_planes_local,
+ clip_plane_len,
+ is_persp,
+ nu->bp[u].vec,
+ &dist_px_sq,
+ r_loc);
+ }
+ }
+ else {
+ /* curve is not visible outside editmode if nurb length less than two */
+ if (nu->pntsu > 1) {
+ if (nu->bezt) {
+ has_snap |= test_projected_vert_dist(&neasrest_precalc,
+ clip_planes_local,
+ clip_plane_len,
+ is_persp,
+ nu->bezt[u].vec[1],
+ &dist_px_sq,
+ r_loc);
+ }
+ else {
+ has_snap |= test_projected_vert_dist(&neasrest_precalc,
+ clip_planes_local,
+ clip_plane_len,
+ is_persp,
+ nu->bp[u].vec,
+ &dist_px_sq,
+ r_loc);
+ }
+ }
+ }
+ }
+ }
+ }
+ if (has_snap) {
+ *dist_px = sqrtf(dist_px_sq);
+ mul_m4_v3(obmat, r_loc);
+ if (r_index) {
+ /* Does not support index yet. */
+ *r_index = -1;
+ }
+ return SCE_SNAP_MODE_VERTEX;
+ }
+
+ return 0;
+}
+
+/* may extend later (for now just snaps to empty center) */
+static short snap_object_center(const SnapObjectContext *sctx,
+ Object *ob_eval,
+ const float obmat[4][4],
+ /* read/write args */
+ float *dist_px,
+ /* return args */
+ float r_loc[3],
+ float *UNUSED(r_no),
+ int *r_index)
+{
+ short retval = 0;
+
+ if (ob_eval->transflag & OB_DUPLI) {
+ return retval;
+ }
+
+ /* for now only vertex supported */
+ if (sctx->runtime.snap_to_flag & SCE_SNAP_MODE_VERTEX) {
+ struct DistProjectedAABBPrecalc neasrest_precalc;
+ dist_squared_to_projected_aabb_precalc(
+ &neasrest_precalc, sctx->runtime.pmat, sctx->runtime.win_size, sctx->runtime.mval);
+
+ float tobmat[4][4], clip_planes_local[MAX_CLIPPLANE_LEN][4];
+ transpose_m4_m4(tobmat, obmat);
+ for (int i = sctx->runtime.clip_plane_len; i--;) {
+ mul_v4_m4v4(clip_planes_local[i], tobmat, sctx->runtime.clip_plane[i]);
+ }
+
+ bool is_persp = sctx->runtime.view_proj == VIEW_PROJ_PERSP;
+ float dist_px_sq = square_f(*dist_px);
+ float co[3];
+ copy_v3_v3(co, obmat[3]);
+ if (test_projected_vert_dist(&neasrest_precalc,
+ clip_planes_local,
+ sctx->runtime.clip_plane_len,
+ is_persp,
+ co,
+ &dist_px_sq,
+ r_loc)) {
+ *dist_px = sqrtf(dist_px_sq);
+ retval = SCE_SNAP_MODE_VERTEX;
+ }
+ }
+
+ if (retval) {
+ if (r_index) {
+ /* Does not support index. */
+ *r_index = -1;
+ }
+ return retval;
+ }
+
+ return 0;
+}
+
+static short snapCamera(const SnapObjectContext *sctx,
+ Object *object,
+ float obmat[4][4],
+ /* read/write args */
+ float *dist_px,
+ /* return args */
+ float r_loc[3],
+ float *r_no,
+ int *r_index)
+{
+ short retval = 0;
+
+ Scene *scene = sctx->scene;
+
+ bool is_persp = sctx->runtime.view_proj == VIEW_PROJ_PERSP;
+ float dist_px_sq = square_f(*dist_px);
+
+ float orig_camera_mat[4][4], orig_camera_imat[4][4], imat[4][4];
+ MovieClip *clip = BKE_object_movieclip_get(scene, object, false);
+ MovieTracking *tracking;
+
+ if (clip == nullptr) {
+ return snap_object_center(sctx, object, obmat, dist_px, r_loc, r_no, r_index);
+ }
+ if (object->transflag & OB_DUPLI) {
+ return retval;
+ }
+
+ tracking = &clip->tracking;
+
+ BKE_tracking_get_camera_object_matrix(object, orig_camera_mat);
+
+ invert_m4_m4(orig_camera_imat, orig_camera_mat);
+ invert_m4_m4(imat, obmat);
+
+ if (sctx->runtime.snap_to_flag & SCE_SNAP_MODE_VERTEX) {
+ struct DistProjectedAABBPrecalc neasrest_precalc;
+ dist_squared_to_projected_aabb_precalc(
+ &neasrest_precalc, sctx->runtime.pmat, sctx->runtime.win_size, sctx->runtime.mval);
+
+ LISTBASE_FOREACH (MovieTrackingObject *, tracking_object, &tracking->objects) {
+ ListBase *tracksbase = BKE_tracking_object_get_tracks(tracking, tracking_object);
+ float reconstructed_camera_mat[4][4], reconstructed_camera_imat[4][4];
+ float(*vertex_obmat)[4];
+
+ if ((tracking_object->flag & TRACKING_OBJECT_CAMERA) == 0) {
+ BKE_tracking_camera_get_reconstructed_interpolate(
+ tracking, tracking_object, CFRA, reconstructed_camera_mat);
+
+ invert_m4_m4(reconstructed_camera_imat, reconstructed_camera_mat);
+ }
+
+ LISTBASE_FOREACH (MovieTrackingTrack *, track, tracksbase) {
+ float bundle_pos[3];
+
+ if ((track->flag & TRACK_HAS_BUNDLE) == 0) {
+ continue;
+ }
+
+ copy_v3_v3(bundle_pos, track->bundle_pos);
+ if (tracking_object->flag & TRACKING_OBJECT_CAMERA) {
+ vertex_obmat = orig_camera_mat;
+ }
+ else {
+ mul_m4_v3(reconstructed_camera_imat, bundle_pos);
+ vertex_obmat = obmat;
+ }
+
+ mul_m4_v3(vertex_obmat, bundle_pos);
+ if (test_projected_vert_dist(&neasrest_precalc,
+ sctx->runtime.clip_plane,
+ sctx->runtime.clip_plane_len,
+ is_persp,
+ bundle_pos,
+ &dist_px_sq,
+ r_loc)) {
+ retval = SCE_SNAP_MODE_VERTEX;
+ }
+ }
+ }
+ }
+
+ if (retval) {
+ *dist_px = sqrtf(dist_px_sq);
+ if (r_index) {
+ /* Does not support index. */
+ *r_index = -1;
+ }
+ return retval;
+ }
+
+ return 0;
+}
+
+static short snapMesh(SnapObjectContext *sctx,
+ const struct SnapObjectParams *params,
+ Object *ob_eval,
+ Mesh *me_eval,
+ const float obmat[4][4],
+ bool use_hide,
+ /* read/write args */
+ float *dist_px,
+ /* return args */
+ float r_loc[3],
+ float r_no[3],
+ int *r_index)
+{
+ BLI_assert(sctx->runtime.snap_to_flag != SCE_SNAP_MODE_FACE);
+ if (me_eval->totvert == 0) {
+ return 0;
+ }
+ if (me_eval->totedge == 0 && !(sctx->runtime.snap_to_flag & SCE_SNAP_MODE_VERTEX)) {
+ return 0;
+ }
+
+ float lpmat[4][4];
+ mul_m4_m4m4(lpmat, sctx->runtime.pmat, obmat);
+
+ float dist_px_sq = square_f(*dist_px);
+
+ /* Test BoundBox */
+ BoundBox *bb = BKE_object_boundbox_get(ob_eval);
+ if (bb &&
+ !snap_bound_box_check_dist(
+ bb->vec[0], bb->vec[6], lpmat, sctx->runtime.win_size, sctx->runtime.mval, dist_px_sq)) {
+ return 0;
+ }
+
+ SnapObjectData *sod = snap_object_data_mesh_get(sctx, ob_eval, me_eval, use_hide);
+
+ BVHTreeFromMesh *treedata, treedata_tmp;
+ treedata = &sod->treedata_mesh;
+
+ if (sod->has_loose_edge && sod->bvhtree[0] == nullptr) {
+ sod->bvhtree[0] = BKE_bvhtree_from_mesh_get(
+ &treedata_tmp, me_eval, BVHTREE_FROM_LOOSEEDGES, 2);
+ if (sod->bvhtree[0] == nullptr) {
+ sod->has_loose_edge = false;
+ }
+ sod->cached[0] = treedata_tmp.cached;
+ BLI_assert(!ELEM(true,
+ treedata_tmp.vert_allocated,
+ treedata_tmp.edge_allocated,
+ treedata_tmp.face_allocated,
+ treedata_tmp.loop_allocated,
+ treedata_tmp.looptri_allocated));
+ }
+
+ if (sctx->runtime.snap_to_flag & SCE_SNAP_MODE_VERTEX) {
+ if (sod->has_loose_vert && sod->bvhtree[1] == nullptr) {
+ sod->bvhtree[1] = BKE_bvhtree_from_mesh_get(
+ &treedata_tmp, me_eval, BVHTREE_FROM_LOOSEVERTS, 2);
+ if (sod->bvhtree[1] == nullptr) {
+ sod->has_loose_vert = false;
+ }
+ sod->cached[1] = treedata_tmp.cached;
+ BLI_assert(!ELEM(true,
+ treedata_tmp.vert_allocated,
+ treedata_tmp.edge_allocated,
+ treedata_tmp.face_allocated,
+ treedata_tmp.loop_allocated,
+ treedata_tmp.looptri_allocated));
+ }
+ }
+ else {
+ /* Not necessary, just to keep the data more consistent. */
+ sod->has_loose_vert = false;
+ }
+
+ Nearest2dUserData nearest2d;
+ nearest2d_data_init(
+ sod, sctx->runtime.view_proj == VIEW_PROJ_PERSP, params->use_backface_culling, &nearest2d);
+
+ BVHTreeNearest nearest{};
+ nearest.index = -1;
+ nearest.dist_sq = dist_px_sq;
+
+ int last_index = nearest.index;
+ short elem = SCE_SNAP_MODE_VERTEX;
+
+ float tobmat[4][4], clip_planes_local[MAX_CLIPPLANE_LEN][4];
+ transpose_m4_m4(tobmat, obmat);
+ for (int i = sctx->runtime.clip_plane_len; i--;) {
+ mul_v4_m4v4(clip_planes_local[i], tobmat, sctx->runtime.clip_plane[i]);
+ }
+
+ if (sod->bvhtree[1] && (sctx->runtime.snap_to_flag & SCE_SNAP_MODE_VERTEX)) {
+ /* snap to loose verts */
+ BLI_bvhtree_find_nearest_projected(sod->bvhtree[1],
+ lpmat,
+ sctx->runtime.win_size,
+ sctx->runtime.mval,
+ clip_planes_local,
+ sctx->runtime.clip_plane_len,
+ &nearest,
+ cb_snap_vert,
+ &nearest2d);
+
+ last_index = nearest.index;
+ }
+
+ if (sctx->runtime.snap_to_flag & SCE_SNAP_MODE_EDGE) {
+ if (sod->bvhtree[0]) {
+ /* snap to loose edges */
+ BLI_bvhtree_find_nearest_projected(sod->bvhtree[0],
+ lpmat,
+ sctx->runtime.win_size,
+ sctx->runtime.mval,
+ clip_planes_local,
+ sctx->runtime.clip_plane_len,
+ &nearest,
+ cb_snap_edge,
+ &nearest2d);
+ }
+
+ if (treedata->tree) {
+ /* snap to looptris */
+ BLI_bvhtree_find_nearest_projected(treedata->tree,
+ lpmat,
+ sctx->runtime.win_size,
+ sctx->runtime.mval,
+ clip_planes_local,
+ sctx->runtime.clip_plane_len,
+ &nearest,
+ cb_snap_tri_edges,
+ &nearest2d);
+ }
+
+ if (last_index != nearest.index) {
+ elem = SCE_SNAP_MODE_EDGE;
+ }
+ }
+ else {
+ BLI_assert(sctx->runtime.snap_to_flag & SCE_SNAP_MODE_VERTEX);
+ if (sod->bvhtree[0]) {
+ /* snap to loose edge verts */
+ BLI_bvhtree_find_nearest_projected(sod->bvhtree[0],
+ lpmat,
+ sctx->runtime.win_size,
+ sctx->runtime.mval,
+ clip_planes_local,
+ sctx->runtime.clip_plane_len,
+ &nearest,
+ cb_snap_edge_verts,
+ &nearest2d);
+ }
+
+ if (treedata->tree) {
+ /* snap to looptri verts */
+ BLI_bvhtree_find_nearest_projected(treedata->tree,
+ lpmat,
+ sctx->runtime.win_size,
+ sctx->runtime.mval,
+ clip_planes_local,
+ sctx->runtime.clip_plane_len,
+ &nearest,
+ cb_snap_tri_verts,
+ &nearest2d);
+ }
+ }
+
+ if (nearest.index != -1) {
+ *dist_px = sqrtf(nearest.dist_sq);
+
+ copy_v3_v3(r_loc, nearest.co);
+ mul_m4_v3(obmat, r_loc);
+
+ if (r_no) {
+ float imat[4][4];
+ invert_m4_m4(imat, obmat);
+
+ copy_v3_v3(r_no, nearest.no);
+ mul_transposed_mat3_m4_v3(imat, r_no);
+ normalize_v3(r_no);
+ }
+ if (r_index) {
+ *r_index = nearest.index;
+ }
+
+ return elem;
+ }
+
+ return 0;
+}
+
+static short snapEditMesh(SnapObjectContext *sctx,
+ const struct SnapObjectParams *params,
+ Object *ob_eval,
+ BMEditMesh *em,
+ const float obmat[4][4],
+ /* read/write args */
+ float *dist_px,
+ /* return args */
+ float r_loc[3],
+ float r_no[3],
+ int *r_index)
+{
+ BLI_assert(sctx->runtime.snap_to_flag != SCE_SNAP_MODE_FACE);
+
+ if ((sctx->runtime.snap_to_flag & ~SCE_SNAP_MODE_FACE) == SCE_SNAP_MODE_VERTEX) {
+ if (em->bm->totvert == 0) {
+ return 0;
+ }
+ }
+ else {
+ if (em->bm->totedge == 0) {
+ return 0;
+ }
+ }
+
+ float lpmat[4][4];
+ mul_m4_m4m4(lpmat, sctx->runtime.pmat, obmat);
+
+ float dist_px_sq = square_f(*dist_px);
+
+ SnapObjectData *sod = snap_object_data_editmesh_get(sctx, ob_eval, em);
+
+ /* Test BoundBox */
+
+ /* was BKE_boundbox_ray_hit_check, see: cf6ca226fa58 */
+ if (!snap_bound_box_check_dist(
+ sod->min, sod->max, lpmat, sctx->runtime.win_size, sctx->runtime.mval, dist_px_sq)) {
+ return 0;
+ }
+
+ if (sctx->runtime.snap_to_flag & SCE_SNAP_MODE_VERTEX) {
+ BVHTreeFromEditMesh treedata{};
+ treedata.tree = sod->bvhtree[0];
+
+ if (treedata.tree == nullptr) {
+ BLI_bitmap *verts_mask = nullptr;
+ int verts_num_active = -1;
+ if (sctx->callbacks.edit_mesh.test_vert_fn) {
+ verts_mask = BLI_BITMAP_NEW(em->bm->totvert, __func__);
+ verts_num_active = BM_iter_mesh_bitmap_from_filter(
+ BM_VERTS_OF_MESH,
+ em->bm,
+ verts_mask,
+ (bool (*)(BMElem *, void *))sctx->callbacks.edit_mesh.test_vert_fn,
+ sctx->callbacks.edit_mesh.user_data);
+
+ bvhtree_from_editmesh_verts_ex(&treedata,
+ em,
+ verts_mask,
+ verts_num_active,
+ 0.0f,
+ 2,
+ 6,
+ BVHTREE_FROM_VERTS,
+ nullptr,
+ nullptr);
+ MEM_freeN(verts_mask);
+ }
+ else {
+ BKE_bvhtree_from_editmesh_get(&treedata,
+ em,
+ 2,
+ BVHTREE_FROM_EM_VERTS,
+ &sod->mesh_runtime->bvh_cache,
+ (ThreadMutex *)sod->mesh_runtime->eval_mutex);
+ }
+ sod->bvhtree[0] = treedata.tree;
+ sod->cached[0] = treedata.cached;
+ }
+ }
+
+ if (sctx->runtime.snap_to_flag & SCE_SNAP_MODE_EDGE) {
+ BVHTreeFromEditMesh treedata{};
+ treedata.tree = sod->bvhtree[1];
+
+ if (treedata.tree == nullptr) {
+ BLI_bitmap *edges_mask = nullptr;
+ int edges_num_active = -1;
+ if (sctx->callbacks.edit_mesh.test_edge_fn) {
+ edges_mask = BLI_BITMAP_NEW(em->bm->totedge, __func__);
+ edges_num_active = BM_iter_mesh_bitmap_from_filter(
+ BM_EDGES_OF_MESH,
+ em->bm,
+ edges_mask,
+ (bool (*)(BMElem *, void *))sctx->callbacks.edit_mesh.test_edge_fn,
+ sctx->callbacks.edit_mesh.user_data);
+
+ bvhtree_from_editmesh_edges_ex(&treedata,
+ em,
+ edges_mask,
+ edges_num_active,
+ 0.0f,
+ 2,
+ 6,
+ BVHTREE_FROM_VERTS,
+ nullptr,
+ nullptr);
+ MEM_freeN(edges_mask);
+ }
+ else {
+ BKE_bvhtree_from_editmesh_get(&treedata,
+ em,
+ 2,
+ BVHTREE_FROM_EM_EDGES,
+ &sod->mesh_runtime->bvh_cache,
+ static_cast<ThreadMutex *>(sod->mesh_runtime->eval_mutex));
+ }
+ sod->bvhtree[1] = treedata.tree;
+ sod->cached[1] = treedata.cached;
+ }
+ }
+
+ Nearest2dUserData nearest2d;
+ nearest2d_data_init(
+ sod, sctx->runtime.view_proj == VIEW_PROJ_PERSP, params->use_backface_culling, &nearest2d);
+
+ BVHTreeNearest nearest{};
+ nearest.index = -1;
+ nearest.dist_sq = dist_px_sq;
+
+ short elem = SCE_SNAP_MODE_VERTEX;
+
+ float tobmat[4][4], clip_planes_local[MAX_CLIPPLANE_LEN][4];
+ transpose_m4_m4(tobmat, obmat);
+
+ for (int i = sctx->runtime.clip_plane_len; i--;) {
+ mul_v4_m4v4(clip_planes_local[i], tobmat, sctx->runtime.clip_plane[i]);
+ }
+
+ if (sod->bvhtree[0] && (sctx->runtime.snap_to_flag & SCE_SNAP_MODE_VERTEX)) {
+ BM_mesh_elem_table_ensure(em->bm, BM_VERT);
+ BM_mesh_elem_index_ensure(em->bm, BM_VERT);
+ BLI_bvhtree_find_nearest_projected(sod->bvhtree[0],
+ lpmat,
+ sctx->runtime.win_size,
+ sctx->runtime.mval,
+ clip_planes_local,
+ sctx->runtime.clip_plane_len,
+ &nearest,
+ cb_snap_vert,
+ &nearest2d);
+ }
+
+ if (sod->bvhtree[1] && (sctx->runtime.snap_to_flag & SCE_SNAP_MODE_EDGE)) {
+ int last_index = nearest.index;
+ nearest.index = -1;
+ BM_mesh_elem_table_ensure(em->bm, BM_EDGE | BM_VERT);
+ BM_mesh_elem_index_ensure(em->bm, BM_EDGE | BM_VERT);
+ BLI_bvhtree_find_nearest_projected(sod->bvhtree[1],
+ lpmat,
+ sctx->runtime.win_size,
+ sctx->runtime.mval,
+ clip_planes_local,
+ sctx->runtime.clip_plane_len,
+ &nearest,
+ cb_snap_edge,
+ &nearest2d);
+
+ if (nearest.index != -1) {
+ elem = SCE_SNAP_MODE_EDGE;
+ }
+ else {
+ nearest.index = last_index;
+ }
+ }
+
+ if (nearest.index != -1) {
+ *dist_px = sqrtf(nearest.dist_sq);
+
+ copy_v3_v3(r_loc, nearest.co);
+ mul_m4_v3(obmat, r_loc);
+ if (r_no) {
+ float imat[4][4];
+ invert_m4_m4(imat, obmat);
+
+ copy_v3_v3(r_no, nearest.no);
+ mul_transposed_mat3_m4_v3(imat, r_no);
+ normalize_v3(r_no);
+ }
+ if (r_index) {
+ *r_index = nearest.index;
+ }
+
+ return elem;
+ }
+
+ return 0;
+}
+
+struct SnapObjUserData {
+ /* read/write args */
+ float *dist_px;
+ /* return args */
+ float *r_loc;
+ float *r_no;
+ int *r_index;
+ Object **r_ob;
+ float (*r_obmat)[4];
+ short ret;
+};
+
+/**
+ * \note Duplicate args here are documented at #snapObjectsRay
+ */
+static void snap_obj_fn(SnapObjectContext *sctx,
+ const struct SnapObjectParams *params,
+ Object *ob_eval,
+ float obmat[4][4],
+ bool is_object_active,
+ void *data)
+{
+ SnapObjUserData *dt = static_cast<SnapObjUserData *>(data);
+ short retval = 0;
+
+ switch (ob_eval->type) {
+ case OB_MESH: {
+ const eSnapEditType edit_mode_type = params->edit_mode_type;
+ bool use_hide;
+ Mesh *me_eval = mesh_for_snap(ob_eval, edit_mode_type, &use_hide);
+ if (me_eval == nullptr) {
+ /* Operators only update the editmesh looptris of the original mesh. */
+ BMEditMesh *em_orig = BKE_editmesh_from_object(DEG_get_original_object(ob_eval));
+ retval = snapEditMesh(
+ sctx, params, ob_eval, em_orig, obmat, dt->dist_px, dt->r_loc, dt->r_no, dt->r_index);
+ break;
+ }
+ if (ob_eval->dt == OB_BOUNDBOX) {
+ /* Do not snap to objects that are in bounding box display mode */
+ return;
+ }
+
+ retval = snapMesh(sctx,
+ params,
+ ob_eval,
+ me_eval,
+ obmat,
+ use_hide,
+ dt->dist_px,
+ dt->r_loc,
+ dt->r_no,
+ dt->r_index);
+ break;
+ }
+ case OB_ARMATURE:
+ retval = snapArmature(sctx,
+ params,
+ ob_eval,
+ obmat,
+ is_object_active,
+ dt->dist_px,
+ dt->r_loc,
+ dt->r_no,
+ dt->r_index);
+ break;
+ case OB_CURVES_LEGACY:
+ retval = snapCurve(
+ sctx, params, ob_eval, obmat, dt->dist_px, dt->r_loc, dt->r_no, dt->r_index);
+ break; /* Use ATTR_FALLTHROUGH if we want to snap to the generated mesh. */
+ case OB_SURF:
+ case OB_FONT: {
+ Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob_eval);
+ if (mesh_eval) {
+ retval |= snapMesh(sctx,
+ params,
+ ob_eval,
+ mesh_eval,
+ obmat,
+ false,
+ dt->dist_px,
+ dt->r_loc,
+ dt->r_no,
+ dt->r_index);
+ }
+ break;
+ }
+ case OB_EMPTY:
+ case OB_GPENCIL:
+ case OB_LAMP:
+ retval = snap_object_center(
+ sctx, ob_eval, obmat, dt->dist_px, dt->r_loc, dt->r_no, dt->r_index);
+ break;
+ case OB_CAMERA:
+ retval = snapCamera(sctx, ob_eval, obmat, dt->dist_px, dt->r_loc, dt->r_no, dt->r_index);
+ break;
+ }
+
+ if (retval) {
+ if (dt->r_ob) {
+ *dt->r_ob = ob_eval;
+ }
+ if (dt->r_obmat) {
+ copy_m4_m4(dt->r_obmat, obmat);
+ }
+ dt->ret = retval;
+ }
+}
+
+/**
+ * Main Snapping Function
+ * ======================
+ *
+ * Walks through all objects in the scene to find the closest snap element ray.
+ *
+ * \param sctx: Snap context to store data.
+ *
+ * Read/Write Args
+ * ---------------
+ *
+ * \param dist_px: Maximum threshold distance (in pixels).
+ *
+ * Output Args
+ * -----------
+ *
+ * \param r_loc: Hit location.
+ * \param r_no: Hit normal (optional).
+ * \param r_index: Hit index or -1 when no valid index is found.
+ * (currently only set to the polygon index when using `snap_to == SCE_SNAP_MODE_FACE`).
+ * \param r_ob: Hit object.
+ * \param r_obmat: Object matrix (may not be #Object.obmat with dupli-instances).
+ */
+static short snapObjectsRay(SnapObjectContext *sctx,
+ const struct SnapObjectParams *params,
+ /* read/write args */
+ /* Parameters below cannot be const, because they are assigned to a
+ * non-const variable (readability-non-const-parameter). */
+ float *dist_px /* NOLINT */,
+ /* return args */
+ float r_loc[3] /* NOLINT */,
+ float r_no[3] /* NOLINT */,
+ int *r_index /* NOLINT */,
+ Object **r_ob,
+ float r_obmat[4][4])
+{
+ SnapObjUserData data = {};
+ data.dist_px = dist_px;
+ data.r_loc = r_loc;
+ data.r_no = r_no;
+ data.r_ob = r_ob;
+ data.r_index = r_index;
+ data.r_obmat = r_obmat;
+ data.ret = 0;
+
+ iter_snap_objects(sctx, params, snap_obj_fn, &data);
+
+ return data.ret;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Public Object Snapping API
+ * \{ */
+
+SnapObjectContext *ED_transform_snap_object_context_create(Scene *scene, int flag)
+{
+ SnapObjectContext *sctx = MEM_cnew<SnapObjectContext>(__func__);
+
+ sctx->flag = flag;
+
+ sctx->scene = scene;
+
+ sctx->cache.object_map = BLI_ghash_ptr_new(__func__);
+ /* Initialize as needed (edit-mode only). */
+ sctx->cache.data_to_object_map = nullptr;
+ sctx->cache.mem_arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
+
+ return sctx;
+}
+
+static void snap_object_data_free(void *sod_v)
+{
+ SnapObjectData *sod = static_cast<SnapObjectData *>(sod_v);
+ snap_object_data_clear(sod);
+}
+
+void ED_transform_snap_object_context_destroy(SnapObjectContext *sctx)
+{
+ BLI_ghash_free(sctx->cache.object_map, nullptr, snap_object_data_free);
+ if (sctx->cache.data_to_object_map != nullptr) {
+ BLI_ghash_free(sctx->cache.data_to_object_map, nullptr, nullptr);
+ }
+ BLI_memarena_free(sctx->cache.mem_arena);
+
+ MEM_freeN(sctx);
+}
+
+void ED_transform_snap_object_context_set_editmesh_callbacks(
+ SnapObjectContext *sctx,
+ bool (*test_vert_fn)(BMVert *, void *user_data),
+ bool (*test_edge_fn)(BMEdge *, void *user_data),
+ bool (*test_face_fn)(BMFace *, void *user_data),
+ void *user_data)
+{
+ sctx->callbacks.edit_mesh.test_vert_fn = test_vert_fn;
+ sctx->callbacks.edit_mesh.test_edge_fn = test_edge_fn;
+ sctx->callbacks.edit_mesh.test_face_fn = test_face_fn;
+
+ sctx->callbacks.edit_mesh.user_data = user_data;
+}
+
+bool ED_transform_snap_object_project_ray_ex(SnapObjectContext *sctx,
+ Depsgraph *depsgraph,
+ const View3D *v3d,
+ const struct SnapObjectParams *params,
+ const float ray_start[3],
+ const float ray_normal[3],
+ float *ray_depth,
+ float r_loc[3],
+ float r_no[3],
+ int *r_index,
+ Object **r_ob,
+ float r_obmat[4][4])
+{
+ sctx->runtime.depsgraph = depsgraph;
+ sctx->runtime.v3d = v3d;
+
+ return raycastObjects(sctx,
+ params,
+ ray_start,
+ ray_normal,
+ ray_depth,
+ r_loc,
+ r_no,
+ r_index,
+ r_ob,
+ r_obmat,
+ nullptr);
+}
+
+bool ED_transform_snap_object_project_ray_all(SnapObjectContext *sctx,
+ Depsgraph *depsgraph,
+ const View3D *v3d,
+ const struct SnapObjectParams *params,
+ const float ray_start[3],
+ const float ray_normal[3],
+ float ray_depth,
+ bool sort,
+ ListBase *r_hit_list)
+{
+ sctx->runtime.depsgraph = depsgraph;
+ sctx->runtime.v3d = v3d;
+
+ if (ray_depth == -1.0f) {
+ ray_depth = BVH_RAYCAST_DIST_MAX;
+ }
+
+#ifdef DEBUG
+ float ray_depth_prev = ray_depth;
+#endif
+
+ bool retval = raycastObjects(sctx,
+ params,
+ ray_start,
+ ray_normal,
+ &ray_depth,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ r_hit_list);
+
+ /* meant to be readonly for 'all' hits, ensure it is */
+#ifdef DEBUG
+ BLI_assert(ray_depth_prev == ray_depth);
+#endif
+
+ if (sort) {
+ BLI_listbase_sort(r_hit_list, hit_depth_cmp);
+ }
+
+ return retval;
+}
+
+/**
+ * Convenience function for snap ray-casting.
+ *
+ * Given a ray, cast it into the scene (snapping to faces).
+ *
+ * \return Snap success
+ */
+static bool transform_snap_context_project_ray_impl(SnapObjectContext *sctx,
+ Depsgraph *depsgraph,
+ const View3D *v3d,
+ const struct SnapObjectParams *params,
+ const float ray_start[3],
+ const float ray_normal[3],
+ float *ray_depth,
+ float r_co[3],
+ float r_no[3])
+{
+ bool ret;
+
+ /* try snap edge, then face if it fails */
+ ret = ED_transform_snap_object_project_ray_ex(sctx,
+ depsgraph,
+ v3d,
+ params,
+ ray_start,
+ ray_normal,
+ ray_depth,
+ r_co,
+ r_no,
+ nullptr,
+ nullptr,
+ nullptr);
+
+ return ret;
+}
+
+bool ED_transform_snap_object_project_ray(SnapObjectContext *sctx,
+ Depsgraph *depsgraph,
+ const View3D *v3d,
+ const struct SnapObjectParams *params,
+ const float ray_origin[3],
+ const float ray_direction[3],
+ float *ray_depth,
+ float r_co[3],
+ float r_no[3])
+{
+ float ray_depth_fallback;
+ if (ray_depth == nullptr) {
+ ray_depth_fallback = BVH_RAYCAST_DIST_MAX;
+ ray_depth = &ray_depth_fallback;
+ }
+
+ return transform_snap_context_project_ray_impl(
+ sctx, depsgraph, v3d, params, ray_origin, ray_direction, ray_depth, r_co, r_no);
+}
+
+static short transform_snap_context_project_view3d_mixed_impl(
+ SnapObjectContext *sctx,
+ Depsgraph *depsgraph,
+ const ARegion *region,
+ const View3D *v3d,
+ const ushort snap_to_flag,
+ const struct SnapObjectParams *params,
+ const float mval[2],
+ const float prev_co[3],
+ float *dist_px,
+ float r_loc[3],
+ float r_no[3],
+ int *r_index,
+ Object **r_ob,
+ float r_obmat[4][4],
+ float r_face_nor[3])
+{
+ sctx->runtime.depsgraph = depsgraph;
+ sctx->runtime.region = region;
+ sctx->runtime.v3d = v3d;
+
+ BLI_assert((snap_to_flag & (SCE_SNAP_MODE_VERTEX | SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_FACE |
+ SCE_SNAP_MODE_EDGE_MIDPOINT | SCE_SNAP_MODE_EDGE_PERPENDICULAR)) !=
+ 0);
+
+ short retval = 0;
+
+ bool has_hit = false;
+ Object *ob_eval = nullptr;
+ float loc[3];
+ /* Not all snapping callbacks set the normal,
+ * initialize this since any hit copies both the `loc` and `no`. */
+ float no[3] = {0.0f, 0.0f, 0.0f};
+ float obmat[4][4];
+ int index = -1;
+
+ const RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
+
+ bool use_occlusion_test = params->use_occlusion_test && !XRAY_ENABLED(v3d);
+
+ if (snap_to_flag & SCE_SNAP_MODE_FACE || use_occlusion_test) {
+ float ray_start[3], ray_normal[3];
+ if (!ED_view3d_win_to_ray_clipped_ex(
+ depsgraph, region, v3d, mval, nullptr, ray_normal, ray_start, true)) {
+ return 0;
+ }
+
+ float dummy_ray_depth = BVH_RAYCAST_DIST_MAX;
+
+ has_hit = raycastObjects(sctx,
+ params,
+ ray_start,
+ ray_normal,
+ &dummy_ray_depth,
+ loc,
+ no,
+ &index,
+ &ob_eval,
+ obmat,
+ nullptr);
+
+ if (has_hit) {
+ if (r_face_nor) {
+ copy_v3_v3(r_face_nor, no);
+ }
+
+ if ((snap_to_flag & SCE_SNAP_MODE_FACE)) {
+ retval = SCE_SNAP_MODE_FACE;
+
+ copy_v3_v3(r_loc, loc);
+ if (r_no) {
+ copy_v3_v3(r_no, no);
+ }
+ if (r_ob) {
+ *r_ob = ob_eval;
+ }
+ if (r_obmat) {
+ copy_m4_m4(r_obmat, obmat);
+ }
+ if (r_index) {
+ *r_index = index;
+ }
+ }
+ }
+ }
+
+ if (snap_to_flag & (SCE_SNAP_MODE_VERTEX | SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_EDGE_MIDPOINT |
+ SCE_SNAP_MODE_EDGE_PERPENDICULAR)) {
+ short elem_test, elem = 0;
+ float dist_px_tmp = *dist_px;
+
+ copy_m4_m4(sctx->runtime.pmat, rv3d->persmat);
+ sctx->runtime.win_size[0] = region->winx;
+ sctx->runtime.win_size[1] = region->winy;
+ copy_v2_v2(sctx->runtime.mval, mval);
+ sctx->runtime.view_proj = rv3d->is_persp ? VIEW_PROJ_PERSP : VIEW_PROJ_ORTHO;
+
+ /* First snap to edge instead of middle or perpendicular. */
+ sctx->runtime.snap_to_flag = snap_to_flag & (SCE_SNAP_MODE_VERTEX | SCE_SNAP_MODE_EDGE);
+ if (snap_to_flag & (SCE_SNAP_MODE_EDGE_MIDPOINT | SCE_SNAP_MODE_EDGE_PERPENDICULAR)) {
+ sctx->runtime.snap_to_flag |= SCE_SNAP_MODE_EDGE;
+ }
+
+ planes_from_projmat(sctx->runtime.pmat,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ sctx->runtime.clip_plane[0],
+ sctx->runtime.clip_plane[1]);
+
+ sctx->runtime.clip_plane_len = 2;
+ sctx->runtime.has_occlusion_plane = false;
+
+ /* By convention we only snap to the original elements of a curve. */
+ if (has_hit && ob_eval->type != OB_CURVES_LEGACY) {
+ /* Compute the new clip_pane but do not add it yet. */
+ float new_clipplane[4];
+ BLI_ASSERT_UNIT_V3(no);
+ plane_from_point_normal_v3(new_clipplane, loc, no);
+ if (dot_v3v3(sctx->runtime.clip_plane[0], new_clipplane) > 0.0f) {
+ /* The plane is facing the wrong direction. */
+ negate_v4(new_clipplane);
+ }
+
+ /* Small offset to simulate a kind of volume for edges and vertices. */
+ new_clipplane[3] += 0.01f;
+
+ /* Try to snap only to the polygon. */
+ elem_test = snap_mesh_polygon(sctx, params, ob_eval, obmat, &dist_px_tmp, loc, no, &index);
+ if (elem_test) {
+ elem = elem_test;
+ }
+
+ /* Add the new clip plane to the beginning of the list. */
+ for (int i = sctx->runtime.clip_plane_len; i != 0; i--) {
+ copy_v4_v4(sctx->runtime.clip_plane[i], sctx->runtime.clip_plane[i - 1]);
+ }
+ copy_v4_v4(sctx->runtime.clip_plane[0], new_clipplane);
+ sctx->runtime.clip_plane_len++;
+ sctx->runtime.has_occlusion_plane = true;
+ }
+
+ elem_test = snapObjectsRay(sctx, params, &dist_px_tmp, loc, no, &index, &ob_eval, obmat);
+ if (elem_test) {
+ elem = elem_test;
+ }
+
+ if ((elem == SCE_SNAP_MODE_EDGE) &&
+ (snap_to_flag & (SCE_SNAP_MODE_VERTEX | SCE_SNAP_MODE_EDGE_MIDPOINT |
+ SCE_SNAP_MODE_EDGE_PERPENDICULAR))) {
+ sctx->runtime.snap_to_flag = snap_to_flag;
+ elem = snap_mesh_edge_verts_mixed(
+ sctx, params, ob_eval, obmat, *dist_px, prev_co, &dist_px_tmp, loc, no, &index);
+ }
+
+ if (elem & snap_to_flag) {
+ retval = elem;
+
+ copy_v3_v3(r_loc, loc);
+ if (r_no) {
+ copy_v3_v3(r_no, no);
+ }
+ if (r_ob) {
+ *r_ob = ob_eval;
+ }
+ if (r_obmat) {
+ copy_m4_m4(r_obmat, obmat);
+ }
+ if (r_index) {
+ *r_index = index;
+ }
+ if (r_face_nor && !has_hit) {
+ /* Fallback. */
+ copy_v3_v3(r_face_nor, no);
+ }
+
+ *dist_px = dist_px_tmp;
+ }
+ }
+
+ return retval;
+}
+
+short ED_transform_snap_object_project_view3d_ex(SnapObjectContext *sctx,
+ Depsgraph *depsgraph,
+ const ARegion *region,
+ const View3D *v3d,
+ const ushort snap_to,
+ const struct SnapObjectParams *params,
+ const float mval[2],
+ const float prev_co[3],
+ float *dist_px,
+ float r_loc[3],
+ float r_no[3],
+ int *r_index,
+ Object **r_ob,
+ float r_obmat[4][4],
+ float r_face_nor[3])
+{
+ return transform_snap_context_project_view3d_mixed_impl(sctx,
+ depsgraph,
+ region,
+ v3d,
+ snap_to,
+ params,
+ mval,
+ prev_co,
+ dist_px,
+ r_loc,
+ r_no,
+ r_index,
+ r_ob,
+ r_obmat,
+ r_face_nor);
+}
+
+short ED_transform_snap_object_project_view3d(SnapObjectContext *sctx,
+ Depsgraph *depsgraph,
+ const ARegion *region,
+ const View3D *v3d,
+ const ushort snap_to,
+ const struct SnapObjectParams *params,
+ const float mval[2],
+ const float prev_co[3],
+ float *dist_px,
+ float r_loc[3],
+ float r_no[3])
+{
+ return ED_transform_snap_object_project_view3d_ex(sctx,
+ depsgraph,
+ region,
+ v3d,
+ snap_to,
+ params,
+ mval,
+ prev_co,
+ dist_px,
+ r_loc,
+ r_no,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr);
+}
+
+bool ED_transform_snap_object_project_all_view3d_ex(SnapObjectContext *sctx,
+ Depsgraph *depsgraph,
+ const ARegion *region,
+ const View3D *v3d,
+ const struct SnapObjectParams *params,
+ const float mval[2],
+ float ray_depth,
+ bool sort,
+ ListBase *r_hit_list)
+{
+ float ray_start[3], ray_normal[3];
+
+ if (!ED_view3d_win_to_ray_clipped_ex(
+ depsgraph, region, v3d, mval, nullptr, ray_normal, ray_start, true)) {
+ return false;
+ }
+
+ return ED_transform_snap_object_project_ray_all(
+ sctx, depsgraph, v3d, params, ray_start, ray_normal, ray_depth, sort, r_hit_list);
+}
+
+/** \} */