Use "vert" instead of "vertex" when referring to mesh normals. This was
discussed as part of 1af62cb3bf but never completely
implemented.
748 lines
26 KiB
C++
748 lines
26 KiB
C++
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup modifiers
|
|
*/
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_bitmap.h"
|
|
#include "BLI_linklist.h"
|
|
#include "BLI_math_vector.h"
|
|
|
|
#include "BLT_translation.h"
|
|
|
|
#include "DNA_defaults.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 "BKE_attribute.hh"
|
|
#include "BKE_context.h"
|
|
#include "BKE_deform.h"
|
|
#include "BKE_lib_id.h"
|
|
#include "BKE_mesh.h"
|
|
#include "BKE_mesh_mapping.h"
|
|
#include "BKE_screen.h"
|
|
|
|
#include "UI_interface.h"
|
|
#include "UI_resources.h"
|
|
|
|
#include "RNA_access.h"
|
|
#include "RNA_prototypes.h"
|
|
|
|
#include "MOD_modifiertypes.h"
|
|
#include "MOD_ui_common.h"
|
|
#include "MOD_util.h"
|
|
|
|
#include "bmesh.h"
|
|
|
|
#define CLNORS_VALID_VEC_LEN (1e-6f)
|
|
|
|
struct ModePair {
|
|
float val; /* Contains mode based value (face area / corner angle). */
|
|
int index; /* Index value per poly or per loop. */
|
|
};
|
|
|
|
/* Sorting function used in modifier, sorts in decreasing order. */
|
|
static int modepair_cmp_by_val_inverse(const void *p1, const void *p2)
|
|
{
|
|
ModePair *r1 = (ModePair *)p1;
|
|
ModePair *r2 = (ModePair *)p2;
|
|
|
|
return (r1->val < r2->val) ? 1 : ((r1->val > r2->val) ? -1 : 0);
|
|
}
|
|
|
|
/* There will be one of those per vertex
|
|
* (simple case, computing one normal per vertex), or per smooth fan. */
|
|
struct WeightedNormalDataAggregateItem {
|
|
float normal[3];
|
|
|
|
int loops_num; /* Count number of loops using this item so far. */
|
|
float curr_val; /* Current max val for this item. */
|
|
int curr_strength; /* Current max strength encountered for this item. */
|
|
};
|
|
|
|
#define NUM_CACHED_INVERSE_POWERS_OF_WEIGHT 128
|
|
|
|
struct WeightedNormalData {
|
|
int verts_num;
|
|
|
|
const float (*vert_positions)[3];
|
|
const float (*vert_normals)[3];
|
|
blender::Span<MEdge> edges;
|
|
bool *sharp_edges;
|
|
|
|
blender::Span<MLoop> loops;
|
|
blender::Span<int> loop_to_poly;
|
|
short (*clnors)[2];
|
|
bool has_clnors; /* True if clnors already existed, false if we had to create them. */
|
|
float split_angle;
|
|
|
|
blender::Span<MPoly> polys;
|
|
const float (*poly_normals)[3];
|
|
const int *poly_strength;
|
|
|
|
const MDeformVert *dvert;
|
|
int defgrp_index;
|
|
bool use_invert_vgroup;
|
|
|
|
float weight;
|
|
short mode;
|
|
|
|
/* Lower-level, internal processing data. */
|
|
float cached_inverse_powers_of_weight[NUM_CACHED_INVERSE_POWERS_OF_WEIGHT];
|
|
|
|
WeightedNormalDataAggregateItem *items_data;
|
|
|
|
ModePair *mode_pair;
|
|
};
|
|
|
|
/**
|
|
* Check strength of given poly compared to those found so far for that given item
|
|
* (vertex or smooth fan), and reset matching item_data in case we get a stronger new strength.
|
|
*/
|
|
static bool check_item_poly_strength(WeightedNormalData *wn_data,
|
|
WeightedNormalDataAggregateItem *item_data,
|
|
const int mp_index)
|
|
{
|
|
BLI_assert(wn_data->poly_strength != nullptr);
|
|
|
|
const int mp_strength = wn_data->poly_strength[mp_index];
|
|
|
|
if (mp_strength > item_data->curr_strength) {
|
|
item_data->curr_strength = mp_strength;
|
|
item_data->curr_val = 0.0f;
|
|
item_data->loops_num = 0;
|
|
zero_v3(item_data->normal);
|
|
}
|
|
|
|
return mp_strength == item_data->curr_strength;
|
|
}
|
|
|
|
static void aggregate_item_normal(WeightedNormalModifierData *wnmd,
|
|
WeightedNormalData *wn_data,
|
|
WeightedNormalDataAggregateItem *item_data,
|
|
const int mv_index,
|
|
const int mp_index,
|
|
const float curr_val,
|
|
const bool use_face_influence)
|
|
{
|
|
const float(*poly_normals)[3] = wn_data->poly_normals;
|
|
|
|
const MDeformVert *dvert = wn_data->dvert;
|
|
const int defgrp_index = wn_data->defgrp_index;
|
|
const bool use_invert_vgroup = wn_data->use_invert_vgroup;
|
|
|
|
const float weight = wn_data->weight;
|
|
|
|
float *cached_inverse_powers_of_weight = wn_data->cached_inverse_powers_of_weight;
|
|
|
|
const bool has_vgroup = dvert != nullptr;
|
|
const bool vert_of_group = has_vgroup &&
|
|
BKE_defvert_find_index(&dvert[mv_index], defgrp_index) != nullptr;
|
|
|
|
if (has_vgroup &&
|
|
((vert_of_group && use_invert_vgroup) || (!vert_of_group && !use_invert_vgroup))) {
|
|
return;
|
|
}
|
|
|
|
if (use_face_influence && !check_item_poly_strength(wn_data, item_data, mp_index)) {
|
|
return;
|
|
}
|
|
|
|
/* If item's curr_val is 0 init it to present value. */
|
|
if (item_data->curr_val == 0.0f) {
|
|
item_data->curr_val = curr_val;
|
|
}
|
|
if (!compare_ff(item_data->curr_val, curr_val, wnmd->thresh)) {
|
|
/* item's curr_val and present value differ more than threshold, update. */
|
|
item_data->loops_num++;
|
|
item_data->curr_val = curr_val;
|
|
}
|
|
|
|
/* Exponentially divided weight for each normal
|
|
* (since a few values will be used by most cases, we cache those). */
|
|
const int loops_num = item_data->loops_num;
|
|
if (loops_num < NUM_CACHED_INVERSE_POWERS_OF_WEIGHT &&
|
|
cached_inverse_powers_of_weight[loops_num] == 0.0f) {
|
|
cached_inverse_powers_of_weight[loops_num] = 1.0f / powf(weight, loops_num);
|
|
}
|
|
const float inverted_n_weight = loops_num < NUM_CACHED_INVERSE_POWERS_OF_WEIGHT ?
|
|
cached_inverse_powers_of_weight[loops_num] :
|
|
1.0f / powf(weight, loops_num);
|
|
|
|
madd_v3_v3fl(item_data->normal, poly_normals[mp_index], curr_val * inverted_n_weight);
|
|
}
|
|
|
|
static void apply_weights_vertex_normal(WeightedNormalModifierData *wnmd,
|
|
WeightedNormalData *wn_data)
|
|
{
|
|
using namespace blender;
|
|
const int verts_num = wn_data->verts_num;
|
|
|
|
const float(*positions)[3] = wn_data->vert_positions;
|
|
const blender::Span<MEdge> edges = wn_data->edges;
|
|
const blender::Span<MPoly> polys = wn_data->polys;
|
|
const blender::Span<MLoop> loops = wn_data->loops;
|
|
|
|
short(*clnors)[2] = wn_data->clnors;
|
|
const Span<int> loop_to_poly = wn_data->loop_to_poly;
|
|
|
|
const float(*poly_normals)[3] = wn_data->poly_normals;
|
|
const int *poly_strength = wn_data->poly_strength;
|
|
|
|
const MDeformVert *dvert = wn_data->dvert;
|
|
|
|
const short mode = wn_data->mode;
|
|
ModePair *mode_pair = wn_data->mode_pair;
|
|
|
|
const bool has_clnors = wn_data->has_clnors;
|
|
const float split_angle = wn_data->split_angle;
|
|
MLoopNorSpaceArray lnors_spacearr = {nullptr};
|
|
|
|
const bool keep_sharp = (wnmd->flag & MOD_WEIGHTEDNORMAL_KEEP_SHARP) != 0;
|
|
const bool use_face_influence = (wnmd->flag & MOD_WEIGHTEDNORMAL_FACE_INFLUENCE) != 0 &&
|
|
poly_strength != nullptr;
|
|
const bool has_vgroup = dvert != nullptr;
|
|
|
|
float(*loop_normals)[3] = nullptr;
|
|
|
|
WeightedNormalDataAggregateItem *items_data = nullptr;
|
|
int items_num = 0;
|
|
if (keep_sharp) {
|
|
BLI_bitmap *done_loops = BLI_BITMAP_NEW(loops.size(), __func__);
|
|
|
|
/* This will give us loop normal spaces,
|
|
* we do not actually care about computed loop_normals for now... */
|
|
loop_normals = static_cast<float(*)[3]>(
|
|
MEM_calloc_arrayN(size_t(loops.size()), sizeof(*loop_normals), __func__));
|
|
BKE_mesh_normals_loop_split(positions,
|
|
wn_data->vert_normals,
|
|
verts_num,
|
|
edges.data(),
|
|
edges.size(),
|
|
loops.data(),
|
|
loop_normals,
|
|
loops.size(),
|
|
polys.data(),
|
|
poly_normals,
|
|
polys.size(),
|
|
true,
|
|
split_angle,
|
|
wn_data->sharp_edges,
|
|
loop_to_poly.data(),
|
|
&lnors_spacearr,
|
|
has_clnors ? clnors : nullptr);
|
|
|
|
items_num = lnors_spacearr.spaces_num;
|
|
items_data = static_cast<WeightedNormalDataAggregateItem *>(
|
|
MEM_calloc_arrayN(size_t(items_num), sizeof(*items_data), __func__));
|
|
|
|
/* In this first loop, we assign each WeightedNormalDataAggregateItem
|
|
* to its smooth fan of loops (aka lnor space). */
|
|
int mp_index;
|
|
int item_index;
|
|
for (mp_index = 0, item_index = 0; mp_index < polys.size(); mp_index++) {
|
|
int ml_index = polys[mp_index].loopstart;
|
|
const int ml_end_index = ml_index + polys[mp_index].totloop;
|
|
|
|
for (; ml_index < ml_end_index; ml_index++) {
|
|
if (BLI_BITMAP_TEST(done_loops, ml_index)) {
|
|
/* Smooth fan of this loop has already been processed, skip it. */
|
|
continue;
|
|
}
|
|
BLI_assert(item_index < items_num);
|
|
|
|
WeightedNormalDataAggregateItem *itdt = &items_data[item_index];
|
|
itdt->curr_strength = FACE_STRENGTH_WEAK;
|
|
|
|
MLoopNorSpace *lnor_space = lnors_spacearr.lspacearr[ml_index];
|
|
lnor_space->user_data = itdt;
|
|
|
|
if (!(lnor_space->flags & MLNOR_SPACE_IS_SINGLE)) {
|
|
for (LinkNode *lnode = lnor_space->loops; lnode; lnode = lnode->next) {
|
|
const int ml_fan_index = POINTER_AS_INT(lnode->link);
|
|
BLI_BITMAP_ENABLE(done_loops, ml_fan_index);
|
|
}
|
|
}
|
|
else {
|
|
BLI_BITMAP_ENABLE(done_loops, ml_index);
|
|
}
|
|
|
|
item_index++;
|
|
}
|
|
}
|
|
|
|
MEM_freeN(done_loops);
|
|
}
|
|
else {
|
|
items_num = verts_num;
|
|
items_data = static_cast<WeightedNormalDataAggregateItem *>(
|
|
MEM_calloc_arrayN(size_t(items_num), sizeof(*items_data), __func__));
|
|
if (use_face_influence) {
|
|
for (int item_index = 0; item_index < items_num; item_index++) {
|
|
items_data[item_index].curr_strength = FACE_STRENGTH_WEAK;
|
|
}
|
|
}
|
|
}
|
|
wn_data->items_data = items_data;
|
|
|
|
switch (mode) {
|
|
case MOD_WEIGHTEDNORMAL_MODE_FACE:
|
|
for (int i = 0; i < polys.size(); i++) {
|
|
const int mp_index = mode_pair[i].index;
|
|
const float mp_val = mode_pair[i].val;
|
|
|
|
int ml_index = polys[mp_index].loopstart;
|
|
const int ml_index_end = ml_index + polys[mp_index].totloop;
|
|
for (; ml_index < ml_index_end; ml_index++) {
|
|
const int mv_index = loops[ml_index].v;
|
|
WeightedNormalDataAggregateItem *item_data =
|
|
keep_sharp ? static_cast<WeightedNormalDataAggregateItem *>(
|
|
lnors_spacearr.lspacearr[ml_index]->user_data) :
|
|
&items_data[mv_index];
|
|
|
|
aggregate_item_normal(
|
|
wnmd, wn_data, item_data, mv_index, mp_index, mp_val, use_face_influence);
|
|
}
|
|
}
|
|
break;
|
|
case MOD_WEIGHTEDNORMAL_MODE_ANGLE:
|
|
case MOD_WEIGHTEDNORMAL_MODE_FACE_ANGLE:
|
|
for (int i = 0; i < loops.size(); i++) {
|
|
const int ml_index = mode_pair[i].index;
|
|
const float ml_val = mode_pair[i].val;
|
|
|
|
const int mp_index = loop_to_poly[ml_index];
|
|
const int mv_index = loops[ml_index].v;
|
|
WeightedNormalDataAggregateItem *item_data =
|
|
keep_sharp ? static_cast<WeightedNormalDataAggregateItem *>(
|
|
lnors_spacearr.lspacearr[ml_index]->user_data) :
|
|
&items_data[mv_index];
|
|
|
|
aggregate_item_normal(
|
|
wnmd, wn_data, item_data, mv_index, mp_index, ml_val, use_face_influence);
|
|
}
|
|
break;
|
|
default:
|
|
BLI_assert_unreachable();
|
|
}
|
|
|
|
/* Validate computed weighted normals. */
|
|
for (int item_index = 0; item_index < items_num; item_index++) {
|
|
if (normalize_v3(items_data[item_index].normal) < CLNORS_VALID_VEC_LEN) {
|
|
zero_v3(items_data[item_index].normal);
|
|
}
|
|
}
|
|
|
|
if (keep_sharp) {
|
|
/* Set loop normals for normal computed for each lnor space (smooth fan).
|
|
* Note that loop_normals is already populated with clnors
|
|
* (before this modifier is applied, at start of this function),
|
|
* so no need to recompute them here. */
|
|
for (int ml_index = 0; ml_index < loops.size(); ml_index++) {
|
|
WeightedNormalDataAggregateItem *item_data = static_cast<WeightedNormalDataAggregateItem *>(
|
|
lnors_spacearr.lspacearr[ml_index]->user_data);
|
|
if (!is_zero_v3(item_data->normal)) {
|
|
copy_v3_v3(loop_normals[ml_index], item_data->normal);
|
|
}
|
|
}
|
|
|
|
BKE_mesh_normals_loop_custom_set(positions,
|
|
wn_data->vert_normals,
|
|
verts_num,
|
|
edges.data(),
|
|
edges.size(),
|
|
loops.data(),
|
|
loop_normals,
|
|
loops.size(),
|
|
polys.data(),
|
|
poly_normals,
|
|
polys.size(),
|
|
wn_data->sharp_edges,
|
|
clnors);
|
|
}
|
|
else {
|
|
/* TODO: Ideally, we could add an option to `BKE_mesh_normals_loop_custom_[from_verts_]set()`
|
|
* to keep current clnors instead of resetting them to default auto-computed ones,
|
|
* when given new custom normal is zero-vec.
|
|
* But this is not exactly trivial change, better to keep this optimization for later...
|
|
*/
|
|
if (!has_vgroup) {
|
|
/* NOTE: in theory, we could avoid this extra allocation & copying...
|
|
* But think we can live with it for now,
|
|
* and it makes code simpler & cleaner. */
|
|
float(*vert_normals)[3] = static_cast<float(*)[3]>(
|
|
MEM_calloc_arrayN(size_t(verts_num), sizeof(*loop_normals), __func__));
|
|
|
|
for (int ml_index = 0; ml_index < loops.size(); ml_index++) {
|
|
const int mv_index = loops[ml_index].v;
|
|
copy_v3_v3(vert_normals[mv_index], items_data[mv_index].normal);
|
|
}
|
|
|
|
BKE_mesh_normals_loop_custom_from_verts_set(positions,
|
|
wn_data->vert_normals,
|
|
vert_normals,
|
|
verts_num,
|
|
edges.data(),
|
|
edges.size(),
|
|
loops.data(),
|
|
loops.size(),
|
|
polys.data(),
|
|
poly_normals,
|
|
polys.size(),
|
|
wn_data->sharp_edges,
|
|
clnors);
|
|
|
|
MEM_freeN(vert_normals);
|
|
}
|
|
else {
|
|
loop_normals = static_cast<float(*)[3]>(
|
|
MEM_calloc_arrayN(size_t(loops.size()), sizeof(*loop_normals), __func__));
|
|
|
|
BKE_mesh_normals_loop_split(positions,
|
|
wn_data->vert_normals,
|
|
verts_num,
|
|
edges.data(),
|
|
edges.size(),
|
|
loops.data(),
|
|
loop_normals,
|
|
loops.size(),
|
|
polys.data(),
|
|
poly_normals,
|
|
polys.size(),
|
|
true,
|
|
split_angle,
|
|
wn_data->sharp_edges,
|
|
loop_to_poly.data(),
|
|
nullptr,
|
|
has_clnors ? clnors : nullptr);
|
|
|
|
for (int ml_index = 0; ml_index < loops.size(); ml_index++) {
|
|
const int item_index = loops[ml_index].v;
|
|
if (!is_zero_v3(items_data[item_index].normal)) {
|
|
copy_v3_v3(loop_normals[ml_index], items_data[item_index].normal);
|
|
}
|
|
}
|
|
|
|
BKE_mesh_normals_loop_custom_set(positions,
|
|
wn_data->vert_normals,
|
|
verts_num,
|
|
edges.data(),
|
|
edges.size(),
|
|
loops.data(),
|
|
loop_normals,
|
|
loops.size(),
|
|
polys.data(),
|
|
poly_normals,
|
|
polys.size(),
|
|
wn_data->sharp_edges,
|
|
clnors);
|
|
}
|
|
}
|
|
|
|
if (keep_sharp) {
|
|
BKE_lnor_spacearr_free(&lnors_spacearr);
|
|
}
|
|
MEM_SAFE_FREE(loop_normals);
|
|
}
|
|
|
|
static void wn_face_area(WeightedNormalModifierData *wnmd, WeightedNormalData *wn_data)
|
|
{
|
|
const float(*positions)[3] = wn_data->vert_positions;
|
|
const blender::Span<MPoly> polys = wn_data->polys;
|
|
const blender::Span<MLoop> loops = wn_data->loops;
|
|
|
|
ModePair *face_area = static_cast<ModePair *>(
|
|
MEM_malloc_arrayN(size_t(polys.size()), sizeof(*face_area), __func__));
|
|
|
|
ModePair *f_area = face_area;
|
|
for (const int i : polys.index_range()) {
|
|
f_area->val = BKE_mesh_calc_poly_area(&polys[i], &loops[polys[i].loopstart], positions);
|
|
f_area->index = i;
|
|
}
|
|
|
|
qsort(face_area, polys.size(), sizeof(*face_area), modepair_cmp_by_val_inverse);
|
|
|
|
wn_data->mode_pair = face_area;
|
|
apply_weights_vertex_normal(wnmd, wn_data);
|
|
}
|
|
|
|
static void wn_corner_angle(WeightedNormalModifierData *wnmd, WeightedNormalData *wn_data)
|
|
{
|
|
const float(*positions)[3] = wn_data->vert_positions;
|
|
const blender::Span<MPoly> polys = wn_data->polys;
|
|
const blender::Span<MLoop> loops = wn_data->loops;
|
|
|
|
ModePair *corner_angle = static_cast<ModePair *>(
|
|
MEM_malloc_arrayN(loops.size(), sizeof(*corner_angle), __func__));
|
|
|
|
for (const int i : polys.index_range()) {
|
|
const MPoly &poly = polys[i];
|
|
float *index_angle = static_cast<float *>(
|
|
MEM_malloc_arrayN(poly.totloop, sizeof(*index_angle), __func__));
|
|
BKE_mesh_calc_poly_angles(&poly, &loops[poly.loopstart], positions, index_angle);
|
|
|
|
ModePair *c_angl = &corner_angle[poly.loopstart];
|
|
float *angl = index_angle;
|
|
for (int ml_index = poly.loopstart; ml_index < poly.loopstart + poly.totloop;
|
|
ml_index++, c_angl++, angl++) {
|
|
c_angl->val = float(M_PI) - *angl;
|
|
c_angl->index = ml_index;
|
|
}
|
|
MEM_freeN(index_angle);
|
|
}
|
|
|
|
qsort(corner_angle, loops.size(), sizeof(*corner_angle), modepair_cmp_by_val_inverse);
|
|
|
|
wn_data->mode_pair = corner_angle;
|
|
apply_weights_vertex_normal(wnmd, wn_data);
|
|
}
|
|
|
|
static void wn_face_with_angle(WeightedNormalModifierData *wnmd, WeightedNormalData *wn_data)
|
|
{
|
|
const float(*positions)[3] = wn_data->vert_positions;
|
|
const blender::Span<MPoly> polys = wn_data->polys;
|
|
const blender::Span<MLoop> loops = wn_data->loops;
|
|
|
|
ModePair *combined = static_cast<ModePair *>(
|
|
MEM_malloc_arrayN(loops.size(), sizeof(*combined), __func__));
|
|
|
|
for (const int i : polys.index_range()) {
|
|
const MPoly &poly = polys[i];
|
|
float face_area = BKE_mesh_calc_poly_area(&poly, &loops[poly.loopstart], positions);
|
|
float *index_angle = static_cast<float *>(
|
|
MEM_malloc_arrayN(size_t(poly.totloop), sizeof(*index_angle), __func__));
|
|
BKE_mesh_calc_poly_angles(&poly, &loops[poly.loopstart], positions, index_angle);
|
|
|
|
ModePair *cmbnd = &combined[poly.loopstart];
|
|
float *angl = index_angle;
|
|
for (int ml_index = poly.loopstart; ml_index < poly.loopstart + poly.totloop;
|
|
ml_index++, cmbnd++, angl++) {
|
|
/* In this case val is product of corner angle and face area. */
|
|
cmbnd->val = (float(M_PI) - *angl) * face_area;
|
|
cmbnd->index = ml_index;
|
|
}
|
|
MEM_freeN(index_angle);
|
|
}
|
|
|
|
qsort(combined, loops.size(), sizeof(*combined), modepair_cmp_by_val_inverse);
|
|
|
|
wn_data->mode_pair = combined;
|
|
apply_weights_vertex_normal(wnmd, wn_data);
|
|
}
|
|
|
|
static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh)
|
|
{
|
|
using namespace blender;
|
|
WeightedNormalModifierData *wnmd = (WeightedNormalModifierData *)md;
|
|
Object *ob = ctx->object;
|
|
|
|
/* XXX TODO(Rohan Rathi):
|
|
* Once we fully switch to Mesh evaluation of modifiers,
|
|
* we can expect to get that flag from the COW copy.
|
|
* But for now, it is lost in the DM intermediate step,
|
|
* so we need to directly check orig object's data. */
|
|
#if 0
|
|
if (!(mesh->flag & ME_AUTOSMOOTH))
|
|
#else
|
|
if (!(((Mesh *)ob->data)->flag & ME_AUTOSMOOTH))
|
|
#endif
|
|
{
|
|
BKE_modifier_set_error(
|
|
ctx->object, (ModifierData *)wnmd, "Enable 'Auto Smooth' in Object Data Properties");
|
|
return mesh;
|
|
}
|
|
|
|
Mesh *result;
|
|
result = (Mesh *)BKE_id_copy_ex(nullptr, &mesh->id, nullptr, LIB_ID_COPY_LOCALIZE);
|
|
|
|
const int verts_num = result->totvert;
|
|
const float(*positions)[3] = BKE_mesh_vert_positions(result);
|
|
const blender::Span<MEdge> edges = mesh->edges();
|
|
const blender::Span<MPoly> polys = mesh->polys();
|
|
const blender::Span<MLoop> loops = mesh->loops();
|
|
|
|
/* Right now:
|
|
* If weight = 50 then all faces are given equal weight.
|
|
* If weight > 50 then more weight given to faces with larger vals (face area / corner angle).
|
|
* If weight < 50 then more weight given to faces with lesser vals. However current calculation
|
|
* does not converge to min/max.
|
|
*/
|
|
float weight = float(wnmd->weight) / 50.0f;
|
|
if (wnmd->weight == 100) {
|
|
weight = float(SHRT_MAX);
|
|
}
|
|
else if (wnmd->weight == 1) {
|
|
weight = 1 / float(SHRT_MAX);
|
|
}
|
|
else if ((weight - 1) * 25 > 1) {
|
|
weight = (weight - 1) * 25;
|
|
}
|
|
|
|
const float split_angle = mesh->smoothresh;
|
|
short(*clnors)[2] = static_cast<short(*)[2]>(
|
|
CustomData_get_layer_for_write(&result->ldata, CD_CUSTOMLOOPNORMAL, mesh->totloop));
|
|
|
|
/* Keep info whether we had clnors,
|
|
* it helps when generating clnor spaces and default normals. */
|
|
const bool has_clnors = clnors != nullptr;
|
|
if (!clnors) {
|
|
clnors = static_cast<short(*)[2]>(CustomData_add_layer(
|
|
&result->ldata, CD_CUSTOMLOOPNORMAL, CD_SET_DEFAULT, nullptr, loops.size()));
|
|
}
|
|
|
|
const MDeformVert *dvert;
|
|
int defgrp_index;
|
|
MOD_get_vgroup(ctx->object, mesh, wnmd->defgrp_name, &dvert, &defgrp_index);
|
|
|
|
const Array<int> loop_to_poly_map = bke::mesh_topology::build_loop_to_poly_map(result->polys(),
|
|
result->totloop);
|
|
|
|
bke::MutableAttributeAccessor attributes = result->attributes_for_write();
|
|
bke::SpanAttributeWriter<bool> sharp_edges = attributes.lookup_or_add_for_write_span<bool>(
|
|
"sharp_edge", ATTR_DOMAIN_EDGE);
|
|
|
|
WeightedNormalData wn_data{};
|
|
wn_data.verts_num = verts_num;
|
|
|
|
wn_data.vert_positions = positions;
|
|
wn_data.vert_normals = BKE_mesh_vert_normals_ensure(result);
|
|
wn_data.edges = edges;
|
|
wn_data.sharp_edges = sharp_edges.span.data();
|
|
|
|
wn_data.loops = loops;
|
|
wn_data.loop_to_poly = loop_to_poly_map;
|
|
wn_data.clnors = clnors;
|
|
wn_data.has_clnors = has_clnors;
|
|
wn_data.split_angle = split_angle;
|
|
|
|
wn_data.polys = polys;
|
|
wn_data.poly_normals = BKE_mesh_poly_normals_ensure(mesh);
|
|
wn_data.poly_strength = static_cast<const int *>(CustomData_get_layer_named(
|
|
&result->pdata, CD_PROP_INT32, MOD_WEIGHTEDNORMALS_FACEWEIGHT_CDLAYER_ID));
|
|
|
|
wn_data.dvert = dvert;
|
|
wn_data.defgrp_index = defgrp_index;
|
|
wn_data.use_invert_vgroup = (wnmd->flag & MOD_WEIGHTEDNORMAL_INVERT_VGROUP) != 0;
|
|
|
|
wn_data.weight = weight;
|
|
wn_data.mode = wnmd->mode;
|
|
|
|
switch (wnmd->mode) {
|
|
case MOD_WEIGHTEDNORMAL_MODE_FACE:
|
|
wn_face_area(wnmd, &wn_data);
|
|
break;
|
|
case MOD_WEIGHTEDNORMAL_MODE_ANGLE:
|
|
wn_corner_angle(wnmd, &wn_data);
|
|
break;
|
|
case MOD_WEIGHTEDNORMAL_MODE_FACE_ANGLE:
|
|
wn_face_with_angle(wnmd, &wn_data);
|
|
break;
|
|
}
|
|
|
|
MEM_SAFE_FREE(wn_data.mode_pair);
|
|
MEM_SAFE_FREE(wn_data.items_data);
|
|
|
|
result->runtime->is_original_bmesh = false;
|
|
|
|
sharp_edges.finish();
|
|
|
|
return result;
|
|
}
|
|
|
|
static void initData(ModifierData *md)
|
|
{
|
|
WeightedNormalModifierData *wnmd = (WeightedNormalModifierData *)md;
|
|
|
|
BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(wnmd, modifier));
|
|
|
|
MEMCPY_STRUCT_AFTER(wnmd, DNA_struct_default_get(WeightedNormalModifierData), modifier);
|
|
}
|
|
|
|
static void requiredDataMask(ModifierData *md, CustomData_MeshMasks *r_cddata_masks)
|
|
{
|
|
WeightedNormalModifierData *wnmd = (WeightedNormalModifierData *)md;
|
|
|
|
r_cddata_masks->lmask = CD_MASK_CUSTOMLOOPNORMAL;
|
|
|
|
if (wnmd->defgrp_name[0] != '\0') {
|
|
r_cddata_masks->vmask |= CD_MASK_MDEFORMVERT;
|
|
}
|
|
|
|
if (wnmd->flag & MOD_WEIGHTEDNORMAL_FACE_INFLUENCE) {
|
|
r_cddata_masks->pmask |= CD_MASK_PROP_INT32;
|
|
}
|
|
}
|
|
|
|
static bool dependsOnNormals(ModifierData * /*md*/)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
static void panel_draw(const bContext * /*C*/, Panel *panel)
|
|
{
|
|
uiLayout *col;
|
|
uiLayout *layout = panel->layout;
|
|
|
|
PointerRNA ob_ptr;
|
|
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr);
|
|
|
|
uiLayoutSetPropSep(layout, true);
|
|
|
|
uiItemR(layout, ptr, "mode", 0, nullptr, ICON_NONE);
|
|
|
|
uiItemR(layout, ptr, "weight", 0, IFACE_("Weight"), ICON_NONE);
|
|
uiItemR(layout, ptr, "thresh", 0, IFACE_("Threshold"), ICON_NONE);
|
|
|
|
col = uiLayoutColumn(layout, false);
|
|
uiItemR(col, ptr, "keep_sharp", 0, nullptr, ICON_NONE);
|
|
uiItemR(col, ptr, "use_face_influence", 0, nullptr, ICON_NONE);
|
|
|
|
modifier_vgroup_ui(layout, ptr, &ob_ptr, "vertex_group", "invert_vertex_group", nullptr);
|
|
|
|
modifier_panel_end(layout, ptr);
|
|
}
|
|
|
|
static void panelRegister(ARegionType *region_type)
|
|
{
|
|
modifier_panel_register(region_type, eModifierType_WeightedNormal, panel_draw);
|
|
}
|
|
|
|
ModifierTypeInfo modifierType_WeightedNormal = {
|
|
/*name*/ N_("WeightedNormal"),
|
|
/*structName*/ "WeightedNormalModifierData",
|
|
/*structSize*/ sizeof(WeightedNormalModifierData),
|
|
/*srna*/ &RNA_WeightedNormalModifier,
|
|
/*type*/ eModifierTypeType_Constructive,
|
|
/*flags*/ eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_SupportsMapping |
|
|
eModifierTypeFlag_SupportsEditmode | eModifierTypeFlag_EnableInEditmode,
|
|
/*icon*/ ICON_MOD_NORMALEDIT,
|
|
|
|
/*copyData*/ BKE_modifier_copydata_generic,
|
|
|
|
/*deformVerts*/ nullptr,
|
|
/*deformMatrices*/ nullptr,
|
|
/*deformVertsEM*/ nullptr,
|
|
/*deformMatricesEM*/ nullptr,
|
|
/*modifyMesh*/ modifyMesh,
|
|
/*modifyGeometrySet*/ nullptr,
|
|
|
|
/*initData*/ initData,
|
|
/*requiredDataMask*/ requiredDataMask,
|
|
/*freeData*/ nullptr,
|
|
/*isDisabled*/ nullptr,
|
|
/*updateDepsgraph*/ nullptr,
|
|
/*dependsOnTime*/ nullptr,
|
|
/*dependsOnNormals*/ dependsOnNormals,
|
|
/*foreachIDLink*/ nullptr,
|
|
/*foreachTexLink*/ nullptr,
|
|
/*freeRuntimeData*/ nullptr,
|
|
/*panelRegister*/ panelRegister,
|
|
/*blendWrite*/ nullptr,
|
|
/*blendRead*/ nullptr,
|
|
};
|