This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/editors/mesh/editmesh_utils.c

1520 lines
39 KiB
C
Raw Normal View History

2011-10-24 23:32:24 +00:00
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* 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,
2012-02-11 04:16:17 +00:00
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2004 by Blender Foundation.
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): Joseph Eagar
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/editors/mesh/editmesh_utils.c
* \ingroup edmesh
*/
#include "MEM_guardedalloc.h"
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
2014-11-10 13:48:27 +01:00
#include "DNA_key_types.h"
#include "BLI_math.h"
#include "BLI_alloca.h"
#include "BLI_buffer.h"
#include "BLI_kdtree.h"
2014-11-10 13:48:27 +01:00
#include "BLI_listbase.h"
#include "BKE_DerivedMesh.h"
#include "BKE_context.h"
Threaded object update and EvaluationContext Summary: Made objects update happening from multiple threads. It is a task-based scheduling system which uses current dependency graph for spawning new tasks. This means threading happens on object level, but the system is flexible enough for higher granularity. Technical details: - Uses task scheduler which was recently committed to trunk (that one which Brecht ported from Cycles). - Added two utility functions to dependency graph: * DAG_threaded_update_begin, which is called to initialize threaded objects update. It will also schedule root DAG node to the queue, hence starting evaluation process. Initialization will calculate how much parents are to be evaluation before current DAG node can be scheduled. This value is used by task threads for faster detecting which nodes might be scheduled. * DAG_threaded_update_handle_node_updated which is called from task thread function when node was fully handled. This function decreases num_pending_parents of node children and schedules children with zero valency. As it might have become clear, task thread receives DAG nodes and decides which callback to call for it. Currently only BKE_object_handle_update is called for object nodes. In the future it'll call node->callback() from Ali's new DAG. - This required adding some workarounds to the render pipeline. Mainly to stop using get_object_dm() from modifiers' apply callback. Such a call was only a workaround for dependency graph glitch when rendering scene with, say, boolean modifiers before displaying this scene. Such change moves workaround from one place to another, so overall hackentropy remains the same. - Added paradigm of EvaluaitonContext. Currently it's more like just a more reliable replacement for G.is_rendering which fails in some circumstances. Future idea of this context is to also store all the local data needed for objects evaluation such as local time, Copy-on-Write data and so. There're two types of EvaluationContext: * Context used for viewport updated and owned by Main. In the future this context might be easily moved to Window or Screen to allo per-window/per-screen local time. * Context used by render engines to evaluate objects for render purposes. Render engine is an owner of this context. This context is passed to all object update routines. Reviewers: brecht, campbellbarton Reviewed By: brecht CC: lukastoenne Differential Revision: https://developer.blender.org/D94
2013-12-26 17:24:42 +06:00
#include "BKE_global.h"
#include "BKE_depsgraph.h"
#include "BKE_key.h"
Threaded object update and EvaluationContext Summary: Made objects update happening from multiple threads. It is a task-based scheduling system which uses current dependency graph for spawning new tasks. This means threading happens on object level, but the system is flexible enough for higher granularity. Technical details: - Uses task scheduler which was recently committed to trunk (that one which Brecht ported from Cycles). - Added two utility functions to dependency graph: * DAG_threaded_update_begin, which is called to initialize threaded objects update. It will also schedule root DAG node to the queue, hence starting evaluation process. Initialization will calculate how much parents are to be evaluation before current DAG node can be scheduled. This value is used by task threads for faster detecting which nodes might be scheduled. * DAG_threaded_update_handle_node_updated which is called from task thread function when node was fully handled. This function decreases num_pending_parents of node children and schedules children with zero valency. As it might have become clear, task thread receives DAG nodes and decides which callback to call for it. Currently only BKE_object_handle_update is called for object nodes. In the future it'll call node->callback() from Ali's new DAG. - This required adding some workarounds to the render pipeline. Mainly to stop using get_object_dm() from modifiers' apply callback. Such a call was only a workaround for dependency graph glitch when rendering scene with, say, boolean modifiers before displaying this scene. Such change moves workaround from one place to another, so overall hackentropy remains the same. - Added paradigm of EvaluaitonContext. Currently it's more like just a more reliable replacement for G.is_rendering which fails in some circumstances. Future idea of this context is to also store all the local data needed for objects evaluation such as local time, Copy-on-Write data and so. There're two types of EvaluationContext: * Context used for viewport updated and owned by Main. In the future this context might be easily moved to Window or Screen to allo per-window/per-screen local time. * Context used by render engines to evaluate objects for render purposes. Render engine is an owner of this context. This context is passed to all object update routines. Reviewers: brecht, campbellbarton Reviewed By: brecht CC: lukastoenne Differential Revision: https://developer.blender.org/D94
2013-12-26 17:24:42 +06:00
#include "BKE_main.h"
#include "BKE_mesh.h"
#include "BKE_mesh_mapping.h"
#include "BKE_report.h"
#include "BKE_editmesh.h"
2013-04-16 05:23:34 +00:00
#include "BKE_editmesh_bvh.h"
#include "BKE_object.h" /* XXX. only for EDBM_mesh_ensure_valid_dm_hack() which will be removed */
#include "WM_api.h"
#include "WM_types.h"
#include "ED_mesh.h"
#include "ED_screen.h"
#include "ED_util.h"
2013-04-16 05:23:34 +00:00
#include "ED_view3d.h"
#include "mesh_intern.h" /* own include */
/* mesh backup implementation. This would greatly benefit from some sort of binary diffing
* just as the undo stack would. So leaving this as an interface for further work */
BMBackup EDBM_redo_state_store(BMEditMesh *em)
{
BMBackup backup;
backup.bmcopy = BM_mesh_copy(em->bm);
return backup;
}
void EDBM_redo_state_restore(BMBackup backup, BMEditMesh *em, int recalctess)
{
BMesh *tmpbm;
if (!em || !backup.bmcopy)
return;
BM_mesh_data_free(em->bm);
tmpbm = BM_mesh_copy(backup.bmcopy);
*em->bm = *tmpbm;
MEM_freeN(tmpbm);
tmpbm = NULL;
if (recalctess)
BKE_editmesh_tessface_calc(em);
}
void EDBM_redo_state_free(BMBackup *backup, BMEditMesh *em, int recalctess)
{
if (em && backup->bmcopy) {
BM_mesh_data_free(em->bm);
*em->bm = *backup->bmcopy;
}
else if (backup->bmcopy) {
BM_mesh_data_free(backup->bmcopy);
}
if (backup->bmcopy)
MEM_freeN(backup->bmcopy);
backup->bmcopy = NULL;
if (recalctess && em)
BKE_editmesh_tessface_calc(em);
}
/* hack to workaround multiple operators being called within the same event loop without an update
* see: [#31811] */
void EDBM_mesh_ensure_valid_dm_hack(Scene *scene, BMEditMesh *em)
{
if ((((ID *)em->ob->data)->flag & LIB_ID_RECALC) ||
(em->ob->recalc & OB_RECALC_DATA))
{
Fix T38054: High CPU usage with many objects This is a regression since threaded dependency graph landed to master. Root of the issue goes to the loads of graph preparation being done even if there's nothing to be updated. The idea of this change is to use ID type recalc bits to determine whether there're objects to be updated. Generally speaking, we now check object and object data datablocks with DAG_id_type_tagged() and if there's no such IDs tagged we skip the whole task pool creation and so, The only difficult aspect was that in some circumstances it was possible that there are tagged objects but nothing in ID recalc bit fields. There were several different circumstances when it was possible: * When one assigns object->recalc flag directly DAG flush didn't set corresponding bits to ID recalc bits. Partially it is fixed by making it so flush will set bitfield, but also for object types there's no reason to assign recalc flag directly. Using generic DAG_id_type_tag works almost the same fast as direct assignment, ensures all the bitflags are set properly and for the long run it seems it's what we would actually want to. * DAG_on_visible_update() didn't set recalc bits at all. * Some areas were checking for object->recalc != 0, however it is was possible that object recalc flag contains PSYS_RECALC_CHILD which was never cleaned from there. No idea why would we need to assign such a flag when enabling scene simplification, this is to be investigated separately. * It is possible that scene_update_post and frame_update_post handlers will modify objects. The issue is that DAG_ids_clear_recalc is called just after callbacks, which leaves objects with recalc flags but no corresponding bit in ID recalc bitfield. This leads to some kind of regression when using ID type tag fields to check whether there objects to be updated internally comparing threaded DAG with legacy one. For now let's have a workaround which will preserve tag for ID_OB if there're objects with OB_RECALC_ALL bits. This keeps behavior unchanged comparing with 2.69 release.
2014-01-09 19:13:26 +06:00
/* since we may not have done selection flushing */
if ((em->ob->recalc & OB_RECALC_DATA) == 0) {
DAG_id_tag_update(&em->ob->id, OB_RECALC_DATA);
}
Threaded object update and EvaluationContext Summary: Made objects update happening from multiple threads. It is a task-based scheduling system which uses current dependency graph for spawning new tasks. This means threading happens on object level, but the system is flexible enough for higher granularity. Technical details: - Uses task scheduler which was recently committed to trunk (that one which Brecht ported from Cycles). - Added two utility functions to dependency graph: * DAG_threaded_update_begin, which is called to initialize threaded objects update. It will also schedule root DAG node to the queue, hence starting evaluation process. Initialization will calculate how much parents are to be evaluation before current DAG node can be scheduled. This value is used by task threads for faster detecting which nodes might be scheduled. * DAG_threaded_update_handle_node_updated which is called from task thread function when node was fully handled. This function decreases num_pending_parents of node children and schedules children with zero valency. As it might have become clear, task thread receives DAG nodes and decides which callback to call for it. Currently only BKE_object_handle_update is called for object nodes. In the future it'll call node->callback() from Ali's new DAG. - This required adding some workarounds to the render pipeline. Mainly to stop using get_object_dm() from modifiers' apply callback. Such a call was only a workaround for dependency graph glitch when rendering scene with, say, boolean modifiers before displaying this scene. Such change moves workaround from one place to another, so overall hackentropy remains the same. - Added paradigm of EvaluaitonContext. Currently it's more like just a more reliable replacement for G.is_rendering which fails in some circumstances. Future idea of this context is to also store all the local data needed for objects evaluation such as local time, Copy-on-Write data and so. There're two types of EvaluationContext: * Context used for viewport updated and owned by Main. In the future this context might be easily moved to Window or Screen to allo per-window/per-screen local time. * Context used by render engines to evaluate objects for render purposes. Render engine is an owner of this context. This context is passed to all object update routines. Reviewers: brecht, campbellbarton Reviewed By: brecht CC: lukastoenne Differential Revision: https://developer.blender.org/D94
2013-12-26 17:24:42 +06:00
BKE_object_handle_update(G.main->eval_ctx, scene, em->ob);
}
}
void EDBM_mesh_normals_update(BMEditMesh *em)
{
BM_mesh_normals_update(em->bm);
}
void EDBM_mesh_clear(BMEditMesh *em)
{
2012-02-10 18:09:19 +00:00
/* clear bmesh */
BM_mesh_clear(em->bm);
2012-02-10 18:09:19 +00:00
/* free derived meshes */
BKE_editmesh_free_derivedmesh(em);
/* free tessellation data */
em->tottri = 0;
if (em->looptris) {
MEM_freeN(em->looptris);
em->looptris = NULL;
}
}
void EDBM_stats_update(BMEditMesh *em)
{
const char iter_types[3] = {BM_VERTS_OF_MESH,
BM_EDGES_OF_MESH,
BM_FACES_OF_MESH};
BMIter iter;
BMElem *ele;
int *tots[3];
int i;
tots[0] = &em->bm->totvertsel;
tots[1] = &em->bm->totedgesel;
tots[2] = &em->bm->totfacesel;
em->bm->totvertsel = em->bm->totedgesel = em->bm->totfacesel = 0;
2012-02-10 18:09:19 +00:00
for (i = 0; i < 3; i++) {
ele = BM_iter_new(&iter, em->bm, iter_types[i], NULL);
for ( ; ele; ele = BM_iter_step(&iter)) {
if (BM_elem_flag_test(ele, BM_ELEM_SELECT)) {
(*tots[i])++;
}
}
}
}
DerivedMesh *EDBM_mesh_deform_dm_get(BMEditMesh *em)
{
return ((em->derivedFinal != NULL) &&
(em->derivedFinal->type == DM_TYPE_EDITBMESH) &&
(em->derivedFinal->deformedOnly != false)) ? em->derivedFinal : NULL;
}
bool EDBM_op_init(BMEditMesh *em, BMOperator *bmop, wmOperator *op, const char *fmt, ...)
{
BMesh *bm = em->bm;
va_list list;
va_start(list, fmt);
if (!BMO_op_vinitf(bm, bmop, BMO_FLAG_DEFAULTS, fmt, list)) {
BKE_reportf(op->reports, RPT_ERROR, "Parse error in %s", __func__);
va_end(list);
return false;
}
if (!em->emcopy)
em->emcopy = BKE_editmesh_copy(em);
em->emcopyusers++;
va_end(list);
return true;
}
2012-02-10 18:09:19 +00:00
/* returns 0 on error, 1 on success. executes and finishes a bmesh operator */
bool EDBM_op_finish(BMEditMesh *em, BMOperator *bmop, wmOperator *op, const bool do_report)
{
const char *errmsg;
BMO_op_finish(em->bm, bmop);
if (BMO_error_get(em->bm, &errmsg, NULL)) {
BMEditMesh *emcopy = em->emcopy;
if (do_report) {
BKE_report(op->reports, RPT_ERROR, errmsg);
}
EDBM_mesh_free(em);
*em = *emcopy;
2009-06-18 07:15:17 +00:00
MEM_freeN(emcopy);
em->emcopyusers = 0;
em->emcopy = NULL;
/* when copying, tessellation isn't to for faster copying,
* but means we need to re-tessellate here */
if (em->looptris == NULL) {
BKE_editmesh_tessface_calc(em);
}
return false;
2012-02-05 15:55:28 +00:00
}
else {
em->emcopyusers--;
if (em->emcopyusers < 0) {
2014-11-21 14:14:50 +01:00
printf("warning: em->emcopyusers was less than zero.\n");
}
if (em->emcopyusers <= 0) {
BKE_editmesh_free(em->emcopy);
MEM_freeN(em->emcopy);
em->emcopy = NULL;
}
return true;
}
}
bool EDBM_op_callf(BMEditMesh *em, wmOperator *op, const char *fmt, ...)
{
BMesh *bm = em->bm;
BMOperator bmop;
va_list list;
va_start(list, fmt);
if (!BMO_op_vinitf(bm, &bmop, BMO_FLAG_DEFAULTS, fmt, list)) {
BKE_reportf(op->reports, RPT_ERROR, "Parse error in %s", __func__);
va_end(list);
return false;
}
if (!em->emcopy)
em->emcopy = BKE_editmesh_copy(em);
em->emcopyusers++;
BMO_op_exec(bm, &bmop);
va_end(list);
return EDBM_op_finish(em, &bmop, op, true);
}
bool EDBM_op_call_and_selectf(BMEditMesh *em, wmOperator *op,
const char *select_slot_out, const bool select_extend,
const char *fmt, ...)
{
BMOpSlot *slot_select_out;
BMesh *bm = em->bm;
BMOperator bmop;
va_list list;
char hflag;
va_start(list, fmt);
if (!BMO_op_vinitf(bm, &bmop, BMO_FLAG_DEFAULTS, fmt, list)) {
BKE_reportf(op->reports, RPT_ERROR, "Parse error in %s", __func__);
va_end(list);
return false;
}
if (!em->emcopy)
em->emcopy = BKE_editmesh_copy(em);
em->emcopyusers++;
BMO_op_exec(bm, &bmop);
slot_select_out = BMO_slot_get(bmop.slots_out, select_slot_out);
hflag = slot_select_out->slot_subtype.elem & BM_ALL_NOLOOP;
BLI_assert(hflag != 0);
if (select_extend == false) {
BM_mesh_elem_hflag_disable_all(em->bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_SELECT, false);
}
BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, select_slot_out, hflag, BM_ELEM_SELECT, true);
va_end(list);
return EDBM_op_finish(em, &bmop, op, true);
}
bool EDBM_op_call_silentf(BMEditMesh *em, const char *fmt, ...)
{
BMesh *bm = em->bm;
BMOperator bmop;
va_list list;
va_start(list, fmt);
if (!BMO_op_vinitf(bm, &bmop, BMO_FLAG_DEFAULTS, fmt, list)) {
va_end(list);
return false;
}
if (!em->emcopy)
em->emcopy = BKE_editmesh_copy(em);
em->emcopyusers++;
BMO_op_exec(bm, &bmop);
va_end(list);
return EDBM_op_finish(em, &bmop, NULL, false);
}
void EDBM_selectmode_to_scene(bContext *C)
{
Scene *scene = CTX_data_scene(C);
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
if (!em)
return;
scene->toolsettings->selectmode = em->selectmode;
/* Request redraw of header buttons (to show new select mode) */
WM_event_add_notifier(C, NC_SCENE | ND_TOOLSETTINGS, scene);
}
void EDBM_mesh_make(ToolSettings *ts, Object *ob)
{
Mesh *me = ob->data;
BMesh *bm;
if (UNLIKELY(!me->mpoly && me->totface)) {
BKE_mesh_convert_mfaces_to_mpolys(me);
}
bm = BKE_mesh_to_bmesh(me, ob);
if (me->edit_btmesh) {
/* this happens when switching shape keys */
EDBM_mesh_free(me->edit_btmesh);
MEM_freeN(me->edit_btmesh);
}
/* currently executing operators re-tessellates, so we can avoid doing here
* but at some point it may need to be added back. */
#if 0
me->edit_btmesh = BKE_editmesh_create(bm, true);
#else
me->edit_btmesh = BKE_editmesh_create(bm, false);
#endif
2012-02-10 18:09:19 +00:00
me->edit_btmesh->selectmode = me->edit_btmesh->bm->selectmode = ts->selectmode;
me->edit_btmesh->mat_nr = (ob->actcol > 0) ? ob->actcol - 1 : 0;
me->edit_btmesh->ob = ob;
/* we need to flush selection because the mode may have changed from when last in editmode */
EDBM_selectmode_flush(me->edit_btmesh);
}
/**
* \warning This can invalidate the #DerivedMesh cache of other objects (for linked duplicates).
* Most callers should run #DAG_id_tag_update on \a ob->data, see: T46738, T46913
*/
void EDBM_mesh_load(Object *ob)
{
Mesh *me = ob->data;
BMesh *bm = me->edit_btmesh->bm;
/* Workaround for T42360, 'ob->shapenr' should be 1 in this case.
* however this isn't synchronized between objects at the moment. */
if (UNLIKELY((ob->shapenr == 0) && (me->key && !BLI_listbase_is_empty(&me->key->block)))) {
bm->shapenr = 1;
}
BM_mesh_bm_to_me(bm, me, false);
#ifdef USE_TESSFACE_DEFAULT
BKE_mesh_tessface_calc(me);
#endif
/* free derived mesh. usually this would happen through depsgraph but there
* are exceptions like file save that will not cause this, and we want to
* avoid ending up with an invalid derived mesh then */
BKE_object_free_derived_caches(ob);
}
/**
* Should only be called on the active editmesh, otherwise call #BKE_editmesh_free
*/
void EDBM_mesh_free(BMEditMesh *em)
{
/* These tables aren't used yet, so it's not strictly necessary
* to 'end' them (with 'e' param) but if someone tries to start
* using them, having these in place will save a lot of pain */
ED_mesh_mirror_spatial_table(NULL, NULL, NULL, 'e');
ED_mesh_mirror_topo_table(NULL, 'e');
BKE_editmesh_free(em);
}
2012-07-20 09:49:54 +00:00
void EDBM_selectmode_flush_ex(BMEditMesh *em, const short selectmode)
2009-08-05 02:34:54 +00:00
{
2012-07-20 09:49:54 +00:00
BM_mesh_select_mode_flush_ex(em->bm, selectmode);
2009-08-05 02:34:54 +00:00
}
void EDBM_selectmode_flush(BMEditMesh *em)
{
EDBM_selectmode_flush_ex(em, em->selectmode);
}
void EDBM_deselect_flush(BMEditMesh *em)
2009-09-16 17:43:09 +00:00
{
/* function below doesnt use. just do this to keep the values in sync */
em->bm->selectmode = em->selectmode;
BM_mesh_deselect_flush(em->bm);
2009-09-16 17:43:09 +00:00
}
void EDBM_select_flush(BMEditMesh *em)
{
/* function below doesnt use. just do this to keep the values in sync */
em->bm->selectmode = em->selectmode;
BM_mesh_select_flush(em->bm);
}
void EDBM_select_more(BMEditMesh *em, const bool use_face_step)
{
BMOperator bmop;
const bool use_faces = (em->selectmode == SCE_SELECT_FACE);
BMO_op_initf(em->bm, &bmop, BMO_FLAG_DEFAULTS,
"region_extend geom=%hvef use_contract=%b use_faces=%b use_face_step=%b",
BM_ELEM_SELECT, false, use_faces, use_face_step);
BMO_op_exec(em->bm, &bmop);
2012-03-18 07:38:51 +00:00
/* don't flush selection in edge/vertex mode */
BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, "geom.out", BM_ALL_NOLOOP, BM_ELEM_SELECT, use_faces ? true : false);
BMO_op_finish(em->bm, &bmop);
EDBM_selectmode_flush(em);
}
void EDBM_select_less(BMEditMesh *em, const bool use_face_step)
{
BMOperator bmop;
const bool use_faces = (em->selectmode == SCE_SELECT_FACE);
BMO_op_initf(em->bm, &bmop, BMO_FLAG_DEFAULTS,
"region_extend geom=%hvef use_contract=%b use_faces=%b use_face_step=%b",
BM_ELEM_SELECT, true, use_faces, use_face_step);
BMO_op_exec(em->bm, &bmop);
2012-03-18 07:38:51 +00:00
/* don't flush selection in edge/vertex mode */
BMO_slot_buffer_hflag_disable(em->bm, bmop.slots_out, "geom.out", BM_ALL_NOLOOP, BM_ELEM_SELECT, use_faces ? true : false);
BMO_op_finish(em->bm, &bmop);
EDBM_selectmode_flush(em);
}
void EDBM_flag_disable_all(BMEditMesh *em, const char hflag)
{
BM_mesh_elem_hflag_disable_all(em->bm, BM_VERT | BM_EDGE | BM_FACE, hflag, false);
}
void EDBM_flag_enable_all(BMEditMesh *em, const char hflag)
{
BM_mesh_elem_hflag_enable_all(em->bm, BM_VERT | BM_EDGE | BM_FACE, hflag, true);
}
/**************-------------- Undo ------------*****************/
/* for callbacks */
static void *getEditMesh(bContext *C)
{
2012-02-10 18:09:19 +00:00
Object *obedit = CTX_data_edit_object(C);
if (obedit && obedit->type == OB_MESH) {
Mesh *me = obedit->data;
return me->edit_btmesh;
}
return NULL;
}
typedef struct UndoMesh {
Mesh me;
int selectmode;
/** \note
* this isn't a prefect solution, if you edit keys and change shapes this works well (fixing [#32442]),
* but editing shape keys, going into object mode, removing or changing their order,
* then go back into editmode and undo will give issues - where the old index will be out of sync
* with the new object index.
*
* There are a few ways this could be made to work but for now its a known limitation with mixing
* object and editmode operations - Campbell */
int shapenr;
} UndoMesh;
2012-02-10 18:09:19 +00:00
/* undo simply makes copies of a bmesh */
2009-11-02 16:01:24 +00:00
static void *editbtMesh_to_undoMesh(void *emv, void *obdata)
{
BMEditMesh *em = emv;
2009-11-02 16:01:24 +00:00
Mesh *obme = obdata;
UndoMesh *um = MEM_callocN(sizeof(UndoMesh), "undo Mesh");
2012-02-10 18:09:19 +00:00
/* make sure shape keys work */
um->me.key = obme->key ? BKE_key_copy_nolib(obme->key) : NULL;
/* BM_mesh_validate(em->bm); */ /* for troubleshooting */
BM_mesh_bm_to_me(em->bm, &um->me, false);
um->selectmode = em->selectmode;
um->shapenr = em->bm->shapenr;
return um;
}
static void undoMesh_to_editbtMesh(void *umv, void *em_v, void *obdata)
{
BMEditMesh *em = em_v, *em_tmp;
Object *ob = em->ob;
UndoMesh *um = umv;
BMesh *bm;
Key *key = ((Mesh *) obdata)->key;
const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(&um->me);
em->bm->shapenr = um->shapenr;
2009-11-02 16:01:24 +00:00
EDBM_mesh_free(em);
bm = BM_mesh_create(&allocsize);
BM_mesh_bm_from_me(bm, &um->me, true, false, um->shapenr);
em_tmp = BKE_editmesh_create(bm, true);
*em = *em_tmp;
em->selectmode = um->selectmode;
bm->selectmode = um->selectmode;
em->ob = ob;
/* T35170: Restore the active key on the RealMesh. Otherwise 'fake' offset propagation happens
* if the active is a basis for any other. */
if (key && (key->type == KEY_RELATIVE)) {
/* Since we can't add, remove or reorder keyblocks in editmode, it's safe to assume
* shapenr from restored bmesh and keyblock indices are in sync. */
const int kb_act_idx = ob->shapenr - 1;
/* If it is, let's patch the current mesh key block to its restored value.
* Else, the offsets won't be computed and it won't matter. */
if (BKE_keyblock_is_basis(key, kb_act_idx)) {
KeyBlock *kb_act = BLI_findlink(&key->block, kb_act_idx);
if (kb_act->totelem != um->me.totvert) {
/* The current mesh has some extra/missing verts compared to the undo, adjust. */
MEM_SAFE_FREE(kb_act->data);
kb_act->data = MEM_mallocN((size_t)(key->elemsize * bm->totvert), __func__);
kb_act->totelem = um->me.totvert;
}
BKE_keyblock_update_from_mesh(&um->me, kb_act);
}
}
ob->shapenr = um->shapenr;
MEM_freeN(em_tmp);
}
static void free_undo(void *me_v)
{
Mesh *me = me_v;
if (me->key) {
BKE_key_free(me->key);
MEM_freeN(me->key);
2011-05-12 18:33:10 +00:00
}
BKE_mesh_free(me, false);
MEM_freeN(me);
}
/* and this is all the undo system needs to know */
2011-02-27 06:19:40 +00:00
void undo_push_mesh(bContext *C, const char *name)
{
/* em->ob gets out of date and crashes on mesh undo,
* this is an easy way to ensure its OK
* though we could investigate the matter further. */
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
em->ob = obedit;
undo_editmode_push(C, name, getEditMesh, free_undo, undoMesh_to_editbtMesh, editbtMesh_to_undoMesh, NULL);
}
/**
* Return a new UVVertMap from the editmesh
*/
UvVertMap *BM_uv_vert_map_create(
BMesh *bm,
const float limit[2], const bool use_select, const bool use_winding)
{
BMVert *ev;
BMFace *efa;
BMLoop *l;
BMIter iter, liter;
/* vars from original func */
UvVertMap *vmap;
UvMapVert *buf;
/* MTexPoly *tf; */ /* UNUSED */
MLoopUV *luv;
unsigned int a;
int totverts, i, totuv, totfaces;
const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV);
bool *winding = NULL;
BLI_buffer_declare_static(vec2f, tf_uv_buf, BLI_BUFFER_NOP, BM_DEFAULT_NGON_STACK_SIZE);
BM_mesh_elem_index_ensure(bm, BM_VERT | BM_FACE);
totfaces = bm->totface;
totverts = bm->totvert;
totuv = 0;
/* generate UvMapVert array */
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if ((use_select == false) || BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
totuv += efa->len;
}
}
2012-02-10 18:09:19 +00:00
if (totuv == 0) {
return NULL;
}
2012-02-10 18:09:19 +00:00
vmap = (UvVertMap *)MEM_callocN(sizeof(*vmap), "UvVertMap");
if (!vmap) {
return NULL;
}
2012-03-21 09:10:08 +00:00
vmap->vert = (UvMapVert **)MEM_callocN(sizeof(*vmap->vert) * totverts, "UvMapVert_pt");
buf = vmap->buf = (UvMapVert *)MEM_callocN(sizeof(*vmap->buf) * totuv, "UvMapVert");
if (use_winding) {
winding = MEM_callocN(sizeof(*winding) * totfaces, "winding");
}
if (!vmap->vert || !vmap->buf) {
2012-05-05 21:28:12 +00:00
BKE_mesh_uv_vert_map_free(vmap);
return NULL;
}
BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, a) {
if ((use_select == false) || BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
float (*tf_uv)[2];
if (use_winding) {
tf_uv = (float (*)[2])BLI_buffer_reinit_data(&tf_uv_buf, vec2f, efa->len);
}
BM_ITER_ELEM_INDEX(l, &liter, efa, BM_LOOPS_OF_FACE, i) {
2012-02-10 18:09:19 +00:00
buf->tfindex = i;
buf->f = a;
buf->separate = 0;
buf->next = vmap->vert[BM_elem_index_get(l->v)];
vmap->vert[BM_elem_index_get(l->v)] = buf;
buf++;
if (use_winding) {
luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
copy_v2_v2(tf_uv[i], luv->uv);
}
}
if (use_winding) {
winding[a] = cross_poly_v2((const float (*)[2])tf_uv, efa->len) > 0;
}
}
}
/* sort individual uvs for each vert */
BM_ITER_MESH_INDEX (ev, &iter, bm, BM_VERTS_OF_MESH, a) {
2012-02-10 18:09:19 +00:00
UvMapVert *newvlist = NULL, *vlist = vmap->vert[a];
UvMapVert *iterv, *v, *lastv, *next;
float *uv, *uv2, uvdiff[2];
2012-02-10 18:09:19 +00:00
while (vlist) {
v = vlist;
vlist = vlist->next;
v->next = newvlist;
newvlist = v;
efa = BM_face_at_index(bm, v->f);
/* tf = CustomData_bmesh_get(&bm->pdata, efa->head.data, CD_MTEXPOLY); */ /* UNUSED */
l = BM_iter_at_index(bm, BM_LOOPS_OF_FACE, efa, v->tfindex);
luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
2012-02-10 18:09:19 +00:00
uv = luv->uv;
2012-02-10 18:09:19 +00:00
lastv = NULL;
iterv = vlist;
2012-02-10 18:09:19 +00:00
while (iterv) {
next = iterv->next;
efa = BM_face_at_index(bm, iterv->f);
/* tf = CustomData_bmesh_get(&bm->pdata, efa->head.data, CD_MTEXPOLY); */ /* UNUSED */
l = BM_iter_at_index(bm, BM_LOOPS_OF_FACE, efa, iterv->tfindex);
luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
2012-02-10 18:09:19 +00:00
uv2 = luv->uv;
sub_v2_v2v2(uvdiff, uv2, uv);
if (fabsf(uvdiff[0]) < limit[0] && fabsf(uvdiff[1]) < limit[1] &&
(!use_winding || winding[iterv->f] == winding[v->f]))
{
2012-02-10 18:09:19 +00:00
if (lastv) lastv->next = next;
else vlist = next;
iterv->next = newvlist;
newvlist = iterv;
}
else {
2012-02-10 18:09:19 +00:00
lastv = iterv;
}
2012-02-10 18:09:19 +00:00
iterv = next;
}
newvlist->separate = 1;
}
2012-02-10 18:09:19 +00:00
vmap->vert[a] = newvlist;
}
if (use_winding) {
MEM_freeN(winding);
}
BLI_buffer_free(&tf_uv_buf);
return vmap;
}
UvMapVert *BM_uv_vert_map_at_index(UvVertMap *vmap, unsigned int v)
{
return vmap->vert[v];
}
/* A specialized vert map used by stitch operator */
UvElementMap *BM_uv_element_map_create(
BMesh *bm,
const bool selected, const bool use_winding, const bool do_islands)
{
BMVert *ev;
BMFace *efa;
BMLoop *l;
BMIter iter, liter;
/* vars from original func */
UvElementMap *element_map;
UvElement *buf;
bool *winding;
BLI_buffer_declare_static(vec2f, tf_uv_buf, BLI_BUFFER_NOP, BM_DEFAULT_NGON_STACK_SIZE);
MLoopUV *luv;
int totverts, totfaces, i, totuv, j;
const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV);
BM_mesh_elem_index_ensure(bm, BM_VERT | BM_FACE);
totfaces = bm->totface;
totverts = bm->totvert;
totuv = 0;
/* generate UvElement array */
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!selected || BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
totuv += efa->len;
}
}
2012-02-10 18:09:19 +00:00
if (totuv == 0) {
return NULL;
}
2012-02-10 18:09:19 +00:00
element_map = (UvElementMap *)MEM_callocN(sizeof(*element_map), "UvElementMap");
2012-02-06 21:00:50 +00:00
element_map->totalUVs = totuv;
2012-02-10 18:09:19 +00:00
element_map->vert = (UvElement **)MEM_callocN(sizeof(*element_map->vert) * totverts, "UvElementVerts");
buf = element_map->buf = (UvElement *)MEM_callocN(sizeof(*element_map->buf) * totuv, "UvElement");
if (use_winding) {
winding = MEM_mallocN(sizeof(*winding) * totfaces, "winding");
}
BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, j) {
if (use_winding) {
winding[j] = false;
}
if (!selected || BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
float (*tf_uv)[2];
if (use_winding) {
tf_uv = (float (*)[2])BLI_buffer_reinit_data(&tf_uv_buf, vec2f, efa->len);
}
BM_ITER_ELEM_INDEX (l, &liter, efa, BM_LOOPS_OF_FACE, i) {
buf->l = l;
buf->separate = 0;
buf->island = INVALID_ISLAND;
buf->tfindex = i;
buf->next = element_map->vert[BM_elem_index_get(l->v)];
element_map->vert[BM_elem_index_get(l->v)] = buf;
if (use_winding) {
luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
copy_v2_v2(tf_uv[i], luv->uv);
}
buf++;
}
if (use_winding) {
winding[j] = cross_poly_v2((const float (*)[2])tf_uv, efa->len) > 0;
}
}
}
/* sort individual uvs for each vert */
BM_ITER_MESH_INDEX (ev, &iter, bm, BM_VERTS_OF_MESH, i) {
2012-02-10 18:09:19 +00:00
UvElement *newvlist = NULL, *vlist = element_map->vert[i];
UvElement *iterv, *v, *lastv, *next;
float *uv, *uv2, uvdiff[2];
2012-02-10 18:09:19 +00:00
while (vlist) {
v = vlist;
vlist = vlist->next;
v->next = newvlist;
newvlist = v;
l = v->l;
luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
uv = luv->uv;
2012-02-10 18:09:19 +00:00
lastv = NULL;
iterv = vlist;
2012-02-10 18:09:19 +00:00
while (iterv) {
next = iterv->next;
l = iterv->l;
luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
uv2 = luv->uv;
sub_v2_v2v2(uvdiff, uv2, uv);
if (fabsf(uvdiff[0]) < STD_UV_CONNECT_LIMIT && fabsf(uvdiff[1]) < STD_UV_CONNECT_LIMIT &&
(!use_winding || winding[BM_elem_index_get(iterv->l->f)] == winding[BM_elem_index_get(v->l->f)]))
2015-04-13 22:08:51 +10:00
{
2012-02-10 18:09:19 +00:00
if (lastv) lastv->next = next;
else vlist = next;
iterv->next = newvlist;
newvlist = iterv;
}
else {
lastv = iterv;
}
2012-02-10 18:09:19 +00:00
iterv = next;
}
newvlist->separate = 1;
}
2012-02-10 18:09:19 +00:00
element_map->vert[i] = newvlist;
}
if (use_winding) {
MEM_freeN(winding);
}
2012-02-05 15:55:28 +00:00
if (do_islands) {
unsigned int *map;
BMFace **stack;
int stacksize = 0;
UvElement *islandbuf;
/* island number for faces */
int *island_number = NULL;
int nislands = 0, islandbufsize = 0;
2012-02-10 18:09:19 +00:00
/* map holds the map from current vmap->buf to the new, sorted map */
2012-03-21 09:10:08 +00:00
map = MEM_mallocN(sizeof(*map) * totuv, "uvelement_remap");
stack = MEM_mallocN(sizeof(*stack) * bm->totface, "uv_island_face_stack");
2012-03-21 09:10:08 +00:00
islandbuf = MEM_callocN(sizeof(*islandbuf) * totuv, "uvelement_island_buffer");
island_number = MEM_mallocN(sizeof(*island_number) * totfaces, "uv_island_number_face");
copy_vn_i(island_number, totfaces, INVALID_ISLAND);
/* at this point, every UvElement in vert points to a UvElement sharing the same vertex. Now we should sort uv's in islands. */
2012-02-10 18:09:19 +00:00
for (i = 0; i < totuv; i++) {
if (element_map->buf[i].island == INVALID_ISLAND) {
element_map->buf[i].island = nislands;
stack[0] = element_map->buf[i].l->f;
island_number[BM_elem_index_get(stack[0])] = nislands;
2012-02-10 18:09:19 +00:00
stacksize = 1;
2012-02-10 18:09:19 +00:00
while (stacksize > 0) {
efa = stack[--stacksize];
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
UvElement *element, *initelement = element_map->vert[BM_elem_index_get(l->v)];
2012-02-10 18:09:19 +00:00
for (element = initelement; element; element = element->next) {
2012-02-05 15:55:28 +00:00
if (element->separate)
initelement = element;
if (element->l->f == efa) {
/* found the uv corresponding to our face and vertex. Now fill it to the buffer */
element->island = nislands;
map[element - element_map->buf] = islandbufsize;
islandbuf[islandbufsize].l = element->l;
islandbuf[islandbufsize].separate = element->separate;
islandbuf[islandbufsize].tfindex = element->tfindex;
islandbuf[islandbufsize].island = nislands;
islandbufsize++;
2012-02-10 18:09:19 +00:00
for (element = initelement; element; element = element->next) {
2012-02-05 15:55:28 +00:00
if (element->separate && element != initelement)
break;
if (island_number[BM_elem_index_get(element->l->f)] == INVALID_ISLAND) {
stack[stacksize++] = element->l->f;
island_number[BM_elem_index_get(element->l->f)] = nislands;
}
}
break;
}
}
}
}
nislands++;
}
}
MEM_freeN(island_number);
/* remap */
for (i = 0; i < bm->totvert; i++) {
/* important since we may do selection only. Some of these may be NULL */
2012-02-10 18:09:19 +00:00
if (element_map->vert[i])
element_map->vert[i] = &islandbuf[map[element_map->vert[i] - element_map->buf]];
}
2012-03-21 09:10:08 +00:00
element_map->islandIndices = MEM_callocN(sizeof(*element_map->islandIndices) * nislands, "UvElementMap_island_indices");
j = 0;
2012-02-10 18:09:19 +00:00
for (i = 0; i < totuv; i++) {
UvElement *element = element_map->buf[i].next;
2012-02-05 15:55:28 +00:00
if (element == NULL)
islandbuf[map[i]].next = NULL;
else
islandbuf[map[i]].next = &islandbuf[map[element - element_map->buf]];
2012-02-05 15:55:28 +00:00
if (islandbuf[i].island != j) {
j++;
element_map->islandIndices[j] = i;
}
}
MEM_freeN(element_map->buf);
element_map->buf = islandbuf;
element_map->totalIslands = nislands;
MEM_freeN(stack);
MEM_freeN(map);
}
BLI_buffer_free(&tf_uv_buf);
return element_map;
}
void BM_uv_vert_map_free(UvVertMap *vmap)
{
if (vmap) {
if (vmap->vert) MEM_freeN(vmap->vert);
if (vmap->buf) MEM_freeN(vmap->buf);
MEM_freeN(vmap);
}
}
void BM_uv_element_map_free(UvElementMap *element_map)
{
if (element_map) {
if (element_map->vert) MEM_freeN(element_map->vert);
if (element_map->buf) MEM_freeN(element_map->buf);
if (element_map->islandIndices) MEM_freeN(element_map->islandIndices);
MEM_freeN(element_map);
}
}
UvElement *BM_uv_element_get(UvElementMap *map, BMFace *efa, BMLoop *l)
{
UvElement *element;
element = map->vert[BM_elem_index_get(l->v)];
for (; element; element = element->next)
if (element->l->f == efa)
return element;
return NULL;
}
/* last_sel, use em->act_face otherwise get the last selected face in the editselections
* at the moment, last_sel is mainly useful for making sure the space image dosnt flicker */
MTexPoly *EDBM_mtexpoly_active_get(BMEditMesh *em, BMFace **r_act_efa, const bool sloppy, const bool selected)
{
BMFace *efa = NULL;
if (!EDBM_mtexpoly_check(em))
return NULL;
efa = BM_mesh_active_face_get(em->bm, sloppy, selected);
if (efa) {
if (r_act_efa) *r_act_efa = efa;
return CustomData_bmesh_get(&em->bm->pdata, efa->head.data, CD_MTEXPOLY);
}
if (r_act_efa) *r_act_efa = NULL;
return NULL;
}
/* can we edit UV's for this mesh?*/
bool EDBM_mtexpoly_check(BMEditMesh *em)
{
/* some of these checks could be a touch overkill */
return em && em->bm->totface && CustomData_has_layer(&em->bm->pdata, CD_MTEXPOLY) &&
CustomData_has_layer(&em->bm->ldata, CD_MLOOPUV);
}
bool EDBM_vert_color_check(BMEditMesh *em)
{
/* some of these checks could be a touch overkill */
return em && em->bm->totface && CustomData_has_layer(&em->bm->ldata, CD_MLOOPCOL);
}
static BMVert *cache_mirr_intptr_as_bmvert(intptr_t *index_lookup, int index)
{
2012-02-10 18:09:19 +00:00
intptr_t eve_i = index_lookup[index];
return (eve_i == -1) ? NULL : (BMVert *)eve_i;
}
/**
2015-05-27 00:00:31 +10:00
* Mirror editing API, usage:
*
2015-05-27 00:00:31 +10:00
* \code{.c}
* EDBM_verts_mirror_cache_begin(em, ...);
*
2015-05-27 00:00:31 +10:00
* BM_ITER_MESH (v, &iter, em->bm, BM_VERTS_OF_MESH) {
* v_mirror = EDBM_verts_mirror_get(em, v);
* e_mirror = EDBM_verts_mirror_get_edge(em, e);
* f_mirror = EDBM_verts_mirror_get_face(em, f);
* }
*
2015-05-27 00:00:31 +10:00
* EDBM_verts_mirror_cache_end(em);
* \endcode
*/
/* BM_SEARCH_MAXDIST is too big, copied from 2.6x MOC_THRESH, should become a
* preference */
#define BM_SEARCH_MAXDIST_MIRR 0.00002f
#define BM_CD_LAYER_ID "__mirror_index"
/**
* \param em Editmesh.
* \param use_self Allow a vertex to point to its self (middle verts).
* \param use_select Restrict to selected verts.
* \param use_topology Use topology mirror.
* \param maxdist Distance for close point test.
* \param r_index Optional array to write into, as an alternative to a customdata layer (length of total verts).
*/
void EDBM_verts_mirror_cache_begin_ex(BMEditMesh *em, const int axis, const bool use_self, const bool use_select,
/* extra args */
const bool use_topology, float maxdist, int *r_index)
{
Mesh *me = (Mesh *)em->ob->data;
BMesh *bm = em->bm;
BMIter iter;
BMVert *v;
int cd_vmirr_offset;
int i;
const float maxdist_sq = SQUARE(maxdist);
/* one or the other is used depending if topo is enabled */
KDTree *tree = NULL;
2012-02-10 18:09:19 +00:00
MirrTopoStore_t mesh_topo_store = {NULL, -1, -1, -1};
BM_mesh_elem_table_ensure(bm, BM_VERT);
if (r_index == NULL) {
const char *layer_id = BM_CD_LAYER_ID;
em->mirror_cdlayer = CustomData_get_named_layer_index(&bm->vdata, CD_PROP_INT, layer_id);
if (em->mirror_cdlayer == -1) {
BM_data_layer_add_named(bm, &bm->vdata, CD_PROP_INT, layer_id);
em->mirror_cdlayer = CustomData_get_named_layer_index(&bm->vdata, CD_PROP_INT, layer_id);
}
cd_vmirr_offset = CustomData_get_n_offset(&bm->vdata, CD_PROP_INT,
em->mirror_cdlayer - CustomData_get_layer_index(&bm->vdata, CD_PROP_INT));
bm->vdata.layers[em->mirror_cdlayer].flag |= CD_FLAG_TEMPORARY;
}
BM_mesh_elem_index_ensure(bm, BM_VERT);
if (use_topology) {
ED_mesh_mirrtopo_init(me, -1, &mesh_topo_store, true);
}
else {
tree = BLI_kdtree_new(bm->totvert);
BM_ITER_MESH_INDEX (v, &iter, bm, BM_VERTS_OF_MESH, i) {
BLI_kdtree_insert(tree, i, v->co);
}
BLI_kdtree_balance(tree);
}
#define VERT_INTPTR(_v, _i) r_index ? &r_index[_i] : BM_ELEM_CD_GET_VOID_P(_v, cd_vmirr_offset);
BM_ITER_MESH_INDEX (v, &iter, bm, BM_VERTS_OF_MESH, i) {
BLI_assert(BM_elem_index_get(v) == i);
/* temporary for testing, check for selection */
if (use_select && !BM_elem_flag_test(v, BM_ELEM_SELECT)) {
/* do nothing */
}
else {
BMVert *v_mirr;
int *idx = VERT_INTPTR(v, i);
if (use_topology) {
v_mirr = cache_mirr_intptr_as_bmvert(mesh_topo_store.index_lookup, i);
}
else {
int i_mirr;
float co[3];
copy_v3_v3(co, v->co);
co[axis] *= -1.0f;
v_mirr = NULL;
i_mirr = BLI_kdtree_find_nearest(tree, co, NULL);
if (i_mirr != -1) {
BMVert *v_test = BM_vert_at_index(bm, i_mirr);
if (len_squared_v3v3(co, v_test->co) < maxdist_sq) {
v_mirr = v_test;
}
}
}
if (v_mirr && (use_self || (v_mirr != v))) {
const int i_mirr = BM_elem_index_get(v_mirr);
*idx = i_mirr;
idx = VERT_INTPTR(v_mirr, i_mirr);
*idx = i;
}
else {
*idx = -1;
}
}
}
#undef VERT_INTPTR
if (use_topology) {
ED_mesh_mirrtopo_free(&mesh_topo_store);
}
else {
BLI_kdtree_free(tree);
}
}
void EDBM_verts_mirror_cache_begin(BMEditMesh *em, const int axis,
const bool use_self, const bool use_select,
const bool use_topology)
{
EDBM_verts_mirror_cache_begin_ex(em, axis,
use_self, use_select,
/* extra args */
use_topology, BM_SEARCH_MAXDIST_MIRR, NULL);
}
BMVert *EDBM_verts_mirror_get(BMEditMesh *em, BMVert *v)
{
const int *mirr = CustomData_bmesh_get_layer_n(&em->bm->vdata, v->head.data, em->mirror_cdlayer);
BLI_assert(em->mirror_cdlayer != -1); /* invalid use */
2012-02-10 18:09:19 +00:00
if (mirr && *mirr >= 0 && *mirr < em->bm->totvert) {
if (!em->bm->vtable) {
printf("err: should only be called between "
"EDBM_verts_mirror_cache_begin and EDBM_verts_mirror_cache_end");
return NULL;
}
return em->bm->vtable[*mirr];
}
return NULL;
}
BMEdge *EDBM_verts_mirror_get_edge(BMEditMesh *em, BMEdge *e)
{
BMVert *v1_mirr = EDBM_verts_mirror_get(em, e->v1);
if (v1_mirr) {
BMVert *v2_mirr = EDBM_verts_mirror_get(em, e->v2);
if (v2_mirr) {
return BM_edge_exists(v1_mirr, v2_mirr);
}
}
return NULL;
}
BMFace *EDBM_verts_mirror_get_face(BMEditMesh *em, BMFace *f)
{
BMFace *f_mirr = NULL;
BMVert **v_mirr_arr = BLI_array_alloca(v_mirr_arr, f->len);
BMLoop *l_iter, *l_first;
unsigned int i = 0;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
if ((v_mirr_arr[i++] = EDBM_verts_mirror_get(em, l_iter->v)) == NULL) {
return NULL;
}
} while ((l_iter = l_iter->next) != l_first);
BM_face_exists(v_mirr_arr, f->len, &f_mirr);
return f_mirr;
}
void EDBM_verts_mirror_cache_clear(BMEditMesh *em, BMVert *v)
{
int *mirr = CustomData_bmesh_get_layer_n(&em->bm->vdata, v->head.data, em->mirror_cdlayer);
BLI_assert(em->mirror_cdlayer != -1); /* invalid use */
if (mirr) {
2012-02-10 18:09:19 +00:00
*mirr = -1;
}
}
void EDBM_verts_mirror_cache_end(BMEditMesh *em)
{
2012-02-10 18:09:19 +00:00
em->mirror_cdlayer = -1;
}
void EDBM_verts_mirror_apply(BMEditMesh *em, const int sel_from, const int sel_to)
{
BMIter iter;
BMVert *v;
BLI_assert((em->bm->vtable != NULL) && ((em->bm->elem_table_dirty & BM_VERT) == 0));
BM_ITER_MESH (v, &iter, em->bm, BM_VERTS_OF_MESH) {
if (BM_elem_flag_test(v, BM_ELEM_SELECT) == sel_from) {
BMVert *mirr = EDBM_verts_mirror_get(em, v);
if (mirr) {
if (BM_elem_flag_test(mirr, BM_ELEM_SELECT) == sel_to) {
copy_v3_v3(mirr->co, v->co);
mirr->co[0] *= -1.0f;
}
}
}
}
}
/* swap is 0 or 1, if 1 it hides not selected */
void EDBM_mesh_hide(BMEditMesh *em, bool swap)
{
BMIter iter;
BMElem *ele;
int itermode;
char hflag_swap = swap ? BM_ELEM_SELECT : 0;
if (em == NULL) return;
if (em->selectmode & SCE_SELECT_VERTEX)
itermode = BM_VERTS_OF_MESH;
else if (em->selectmode & SCE_SELECT_EDGE)
itermode = BM_EDGES_OF_MESH;
else
itermode = BM_FACES_OF_MESH;
BM_ITER_MESH (ele, &iter, em->bm, itermode) {
if (BM_elem_flag_test(ele, BM_ELEM_SELECT) ^ hflag_swap)
BM_elem_hide_set(em->bm, ele, true);
}
EDBM_selectmode_flush(em);
/* original hide flushing comment (OUTDATED):
* hide happens on least dominant select mode, and flushes up, not down! (helps preventing errors in subsurf) */
/* - vertex hidden, always means edge is hidden too
* - edge hidden, always means face is hidden too
* - face hidden, only set face hide
* - then only flush back down what's absolute hidden
*/
}
void EDBM_mesh_reveal(BMEditMesh *em)
{
const char iter_types[3] = {BM_VERTS_OF_MESH,
BM_EDGES_OF_MESH,
BM_FACES_OF_MESH};
2014-04-27 07:50:08 +10:00
const bool sels[3] = {
(em->selectmode & SCE_SELECT_VERTEX) != 0,
(em->selectmode & SCE_SELECT_EDGE) != 0,
(em->selectmode & SCE_SELECT_FACE) != 0,
};
int i;
/* Use tag flag to remember what was hidden before all is revealed.
* BM_ELEM_HIDDEN --> BM_ELEM_TAG */
#pragma omp parallel for schedule(static) if (em->bm->totvert + em->bm->totedge + em->bm->totface >= BM_OMP_LIMIT)
for (i = 0; i < 3; i++) {
BMIter iter;
BMElem *ele;
BM_ITER_MESH (ele, &iter, em->bm, iter_types[i]) {
BM_elem_flag_set(ele, BM_ELEM_TAG, BM_elem_flag_test(ele, BM_ELEM_HIDDEN));
}
}
/* Reveal everything */
EDBM_flag_disable_all(em, BM_ELEM_HIDDEN);
/* Select relevant just-revealed elements */
for (i = 0; i < 3; i++) {
BMIter iter;
BMElem *ele;
if (!sels[i]) {
continue;
}
BM_ITER_MESH (ele, &iter, em->bm, iter_types[i]) {
if (BM_elem_flag_test(ele, BM_ELEM_TAG)) {
BM_elem_select_set(em->bm, ele, true);
}
}
}
EDBM_selectmode_flush(em);
/* hidden faces can have invalid normals */
EDBM_mesh_normals_update(em);
}
/* so many tools call these that we better make it a generic function.
*/
2013-01-14 16:42:43 +00:00
void EDBM_update_generic(BMEditMesh *em, const bool do_tessface, const bool is_destructive)
{
Object *ob = em->ob;
/* order of calling isn't important */
DAG_id_tag_update(ob->data, OB_RECALC_DATA);
WM_main_add_notifier(NC_GEOM | ND_DATA, ob->data);
if (do_tessface) {
BKE_editmesh_tessface_calc(em);
}
if (is_destructive) {
/* TODO. we may be able to remove this now! - Campbell */
// BM_mesh_elem_table_free(em->bm, BM_ALL_NOLOOP);
}
else {
/* in debug mode double check we didn't need to recalculate */
BLI_assert(BM_mesh_elem_table_check(em->bm) == true);
}
/* don't keep stale derivedMesh data around, see: [#38872] */
BKE_editmesh_free_derivedmesh(em);
#ifdef DEBUG
{
BMEditSelection *ese;
for (ese = em->bm->selected.first; ese; ese = ese->next) {
BLI_assert(BM_elem_flag_test(ese->ele, BM_ELEM_SELECT));
}
}
#endif
}
/* poll call for mesh operators requiring a view3d context */
int EDBM_view3d_poll(bContext *C)
{
if (ED_operator_editmesh(C) && ED_operator_view3d_active(C))
return 1;
return 0;
}
2013-04-16 05:23:34 +00:00
2013-04-16 05:23:34 +00:00
/* -------------------------------------------------------------------- */
/* BMBVH functions */
// XXX
#if 0 //BMESH_TODO: not implemented yet
int BMBVH_VertVisible(BMBVHTree *tree, BMEdge *e, RegionView3D *r3d)
{
}
#endif
static BMFace *edge_ray_cast(struct BMBVHTree *tree, const float co[3], const float dir[3], float *r_hitout, BMEdge *e)
{
BMFace *f = BKE_bmbvh_ray_cast(tree, co, dir, 0.0f, NULL, r_hitout, NULL);
2013-04-16 05:23:34 +00:00
if (f && BM_edge_in_face(e, f))
2013-04-16 05:23:34 +00:00
return NULL;
return f;
}
static void scale_point(float c1[3], const float p[3], const float s)
{
sub_v3_v3(c1, p);
mul_v3_fl(c1, s);
add_v3_v3(c1, p);
}
bool BMBVH_EdgeVisible(struct BMBVHTree *tree, BMEdge *e, ARegion *ar, View3D *v3d, Object *obedit)
{
BMFace *f;
float co1[3], co2[3], co3[3], dir1[3], dir2[3], dir3[3];
float origin[3], invmat[4][4];
float epsilon = 0.01f;
float end[3];
const float mval_f[2] = {ar->winx / 2.0f,
ar->winy / 2.0f};
ED_view3d_win_to_segment(ar, v3d, mval_f, origin, end, false);
2013-04-16 05:23:34 +00:00
invert_m4_m4(invmat, obedit->obmat);
mul_m4_v3(invmat, origin);
copy_v3_v3(co1, e->v1->co);
mid_v3_v3v3(co2, e->v1->co, e->v2->co);
copy_v3_v3(co3, e->v2->co);
scale_point(co1, co2, 0.99);
scale_point(co3, co2, 0.99);
/* ok, idea is to generate rays going from the camera origin to the
* three points on the edge (v1, mid, v2)*/
sub_v3_v3v3(dir1, origin, co1);
sub_v3_v3v3(dir2, origin, co2);
sub_v3_v3v3(dir3, origin, co3);
normalize_v3(dir1);
normalize_v3(dir2);
normalize_v3(dir3);
mul_v3_fl(dir1, epsilon);
mul_v3_fl(dir2, epsilon);
mul_v3_fl(dir3, epsilon);
/* offset coordinates slightly along view vectors, to avoid
* hitting the faces that own the edge.*/
add_v3_v3v3(co1, co1, dir1);
add_v3_v3v3(co2, co2, dir2);
add_v3_v3v3(co3, co3, dir3);
normalize_v3(dir1);
normalize_v3(dir2);
normalize_v3(dir3);
/* do three samplings: left, middle, right */
f = edge_ray_cast(tree, co1, dir1, NULL, e);
if (f && !edge_ray_cast(tree, co2, dir2, NULL, e))
return true;
else if (f && !edge_ray_cast(tree, co3, dir3, NULL, e))
return true;
else if (!f)
return true;
return false;
}