2355 lines
77 KiB
C++
2355 lines
77 KiB
C++
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup bke
|
|
*
|
|
* Functions to convert mesh data to and from legacy formats like #MFace.
|
|
*/
|
|
|
|
#define DNA_DEPRECATED_ALLOW
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "DNA_mesh_types.h"
|
|
#include "DNA_meshdata_types.h"
|
|
#include "DNA_object_types.h"
|
|
|
|
#include "BLI_array_utils.hh"
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_map.hh"
|
|
#include "BLI_math_geom.h"
|
|
#include "BLI_math_matrix.h"
|
|
#include "BLI_math_rotation.h"
|
|
#include "BLI_math_vector_types.hh"
|
|
#include "BLI_memarena.h"
|
|
#include "BLI_multi_value_map.hh"
|
|
#include "BLI_polyfill_2d.h"
|
|
#include "BLI_resource_scope.hh"
|
|
#include "BLI_string.h"
|
|
#include "BLI_task.hh"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "BKE_attribute.hh"
|
|
#include "BKE_customdata.hh"
|
|
#include "BKE_global.h"
|
|
#include "BKE_idprop.hh"
|
|
#include "BKE_main.h"
|
|
#include "BKE_mesh.hh"
|
|
#include "BKE_mesh_legacy_convert.hh"
|
|
#include "BKE_modifier.hh"
|
|
#include "BKE_multires.hh"
|
|
#include "BKE_node.hh"
|
|
#include "BKE_node_runtime.hh"
|
|
#include "BKE_node_tree_update.hh"
|
|
|
|
#include "BLT_translation.h"
|
|
|
|
using blender::MutableSpan;
|
|
using blender::Span;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Legacy Edge Calculation
|
|
* \{ */
|
|
|
|
struct EdgeSort {
|
|
uint v1, v2;
|
|
char is_loose, is_draw;
|
|
};
|
|
|
|
/* edges have to be added with lowest index first for sorting */
|
|
static void to_edgesort(EdgeSort *ed, uint v1, uint v2, char is_loose, short is_draw)
|
|
{
|
|
if (v1 < v2) {
|
|
ed->v1 = v1;
|
|
ed->v2 = v2;
|
|
}
|
|
else {
|
|
ed->v1 = v2;
|
|
ed->v2 = v1;
|
|
}
|
|
ed->is_loose = is_loose;
|
|
ed->is_draw = is_draw;
|
|
}
|
|
|
|
static int vergedgesort(const void *v1, const void *v2)
|
|
{
|
|
const EdgeSort *x1 = static_cast<const EdgeSort *>(v1);
|
|
const EdgeSort *x2 = static_cast<const EdgeSort *>(v2);
|
|
|
|
if (x1->v1 > x2->v1) {
|
|
return 1;
|
|
}
|
|
if (x1->v1 < x2->v1) {
|
|
return -1;
|
|
}
|
|
if (x1->v2 > x2->v2) {
|
|
return 1;
|
|
}
|
|
if (x1->v2 < x2->v2) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Create edges based on known verts and faces,
|
|
* this function is only used when loading very old blend files */
|
|
static void mesh_calc_edges_mdata(const MVert * /*allvert*/,
|
|
const MFace *allface,
|
|
MLoop *allloop,
|
|
const MPoly *allpoly,
|
|
int /*totvert*/,
|
|
int totface,
|
|
int /*totloop*/,
|
|
int faces_num,
|
|
MEdge **r_medge,
|
|
int *r_totedge)
|
|
{
|
|
const MPoly *mpoly;
|
|
const MFace *mface;
|
|
MEdge *edges, *edge;
|
|
EdgeSort *edsort, *ed;
|
|
int a, totedge = 0;
|
|
uint totedge_final = 0;
|
|
uint edge_index;
|
|
|
|
/* we put all edges in array, sort them, and detect doubles that way */
|
|
|
|
for (a = totface, mface = allface; a > 0; a--, mface++) {
|
|
if (mface->v4) {
|
|
totedge += 4;
|
|
}
|
|
else if (mface->v3) {
|
|
totedge += 3;
|
|
}
|
|
else {
|
|
totedge += 1;
|
|
}
|
|
}
|
|
|
|
if (totedge == 0) {
|
|
/* flag that mesh has edges */
|
|
(*r_medge) = (MEdge *)MEM_callocN(0, __func__);
|
|
(*r_totedge) = 0;
|
|
return;
|
|
}
|
|
|
|
ed = edsort = (EdgeSort *)MEM_mallocN(totedge * sizeof(EdgeSort), "EdgeSort");
|
|
|
|
for (a = totface, mface = allface; a > 0; a--, mface++) {
|
|
to_edgesort(ed++, mface->v1, mface->v2, !mface->v3, mface->edcode & ME_V1V2);
|
|
if (mface->v4) {
|
|
to_edgesort(ed++, mface->v2, mface->v3, 0, mface->edcode & ME_V2V3);
|
|
to_edgesort(ed++, mface->v3, mface->v4, 0, mface->edcode & ME_V3V4);
|
|
to_edgesort(ed++, mface->v4, mface->v1, 0, mface->edcode & ME_V4V1);
|
|
}
|
|
else if (mface->v3) {
|
|
to_edgesort(ed++, mface->v2, mface->v3, 0, mface->edcode & ME_V2V3);
|
|
to_edgesort(ed++, mface->v3, mface->v1, 0, mface->edcode & ME_V3V1);
|
|
}
|
|
}
|
|
|
|
qsort(edsort, totedge, sizeof(EdgeSort), vergedgesort);
|
|
|
|
/* count final amount */
|
|
for (a = totedge, ed = edsort; a > 1; a--, ed++) {
|
|
/* edge is unique when it differs from next edge, or is last */
|
|
if (ed->v1 != (ed + 1)->v1 || ed->v2 != (ed + 1)->v2) {
|
|
totedge_final++;
|
|
}
|
|
}
|
|
totedge_final++;
|
|
|
|
edges = (MEdge *)MEM_callocN(sizeof(MEdge) * totedge_final, __func__);
|
|
|
|
for (a = totedge, edge = edges, ed = edsort; a > 1; a--, ed++) {
|
|
/* edge is unique when it differs from next edge, or is last */
|
|
if (ed->v1 != (ed + 1)->v1 || ed->v2 != (ed + 1)->v2) {
|
|
edge->v1 = ed->v1;
|
|
edge->v2 = ed->v2;
|
|
|
|
/* order is swapped so extruding this edge as a surface won't flip face normals
|
|
* with cyclic curves */
|
|
if (ed->v1 + 1 != ed->v2) {
|
|
std::swap(edge->v1, edge->v2);
|
|
}
|
|
edge++;
|
|
}
|
|
else {
|
|
/* Equal edge, merge the draw-flag. */
|
|
(ed + 1)->is_draw |= ed->is_draw;
|
|
}
|
|
}
|
|
/* last edge */
|
|
edge->v1 = ed->v1;
|
|
edge->v2 = ed->v2;
|
|
|
|
MEM_freeN(edsort);
|
|
|
|
/* set edge members of mloops */
|
|
blender::Map<blender::OrderedEdge, int> hash;
|
|
hash.reserve(totedge_final);
|
|
for (edge_index = 0, edge = edges; edge_index < totedge_final; edge_index++, edge++) {
|
|
hash.add({edge->v1, edge->v2}, edge_index);
|
|
}
|
|
|
|
mpoly = allpoly;
|
|
for (a = 0; a < faces_num; a++, mpoly++) {
|
|
MLoop *ml, *ml_next;
|
|
int i = mpoly->totloop;
|
|
|
|
ml_next = allloop + mpoly->loopstart; /* first loop */
|
|
ml = &ml_next[i - 1]; /* last loop */
|
|
|
|
while (i-- != 0) {
|
|
ml->e = hash.lookup({ml->v, ml_next->v});
|
|
ml = ml_next;
|
|
ml_next++;
|
|
}
|
|
}
|
|
|
|
*r_medge = edges;
|
|
*r_totedge = totedge_final;
|
|
}
|
|
|
|
void BKE_mesh_calc_edges_legacy(Mesh *me)
|
|
{
|
|
using namespace blender;
|
|
MEdge *edges;
|
|
int totedge = 0;
|
|
const Span<MVert> verts(
|
|
static_cast<const MVert *>(CustomData_get_layer(&me->vert_data, CD_MVERT)), me->totvert);
|
|
|
|
mesh_calc_edges_mdata(
|
|
verts.data(),
|
|
me->mface,
|
|
static_cast<MLoop *>(CustomData_get_layer_for_write(&me->loop_data, CD_MLOOP, me->totloop)),
|
|
static_cast<const MPoly *>(CustomData_get_layer(&me->face_data, CD_MPOLY)),
|
|
verts.size(),
|
|
me->totface_legacy,
|
|
me->totloop,
|
|
me->faces_num,
|
|
&edges,
|
|
&totedge);
|
|
|
|
if (totedge == 0) {
|
|
/* flag that mesh has edges */
|
|
me->totedge = 0;
|
|
return;
|
|
}
|
|
|
|
edges = (MEdge *)CustomData_add_layer_with_data(
|
|
&me->edge_data, CD_MEDGE, edges, totedge, nullptr);
|
|
me->totedge = totedge;
|
|
|
|
BKE_mesh_tag_topology_changed(me);
|
|
BKE_mesh_strip_loose_faces(me);
|
|
}
|
|
|
|
void BKE_mesh_strip_loose_faces(Mesh *me)
|
|
{
|
|
/* NOTE: We need to keep this for edge creation (for now?), and some old `readfile.cc` code. */
|
|
MFace *f;
|
|
int a, b;
|
|
MFace *mfaces = me->mface;
|
|
|
|
for (a = b = 0, f = mfaces; a < me->totface_legacy; a++, f++) {
|
|
if (f->v3) {
|
|
if (a != b) {
|
|
memcpy(&mfaces[b], f, sizeof(mfaces[b]));
|
|
CustomData_copy_data(&me->fdata_legacy, &me->fdata_legacy, a, b, 1);
|
|
}
|
|
b++;
|
|
}
|
|
}
|
|
if (a != b) {
|
|
CustomData_free_elem(&me->fdata_legacy, b, a - b);
|
|
me->totface_legacy = b;
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name CD Flag Initialization
|
|
* \{ */
|
|
|
|
void BKE_mesh_do_versions_cd_flag_init(Mesh *mesh)
|
|
{
|
|
using namespace blender;
|
|
if (UNLIKELY(mesh->cd_flag)) {
|
|
return;
|
|
}
|
|
|
|
const Span<MVert> verts(
|
|
static_cast<const MVert *>(CustomData_get_layer(&mesh->vert_data, CD_MVERT)), mesh->totvert);
|
|
const Span<MEdge> edges(
|
|
static_cast<const MEdge *>(CustomData_get_layer(&mesh->edge_data, CD_MEDGE)), mesh->totedge);
|
|
|
|
for (const MVert &vert : verts) {
|
|
if (vert.bweight_legacy != 0) {
|
|
mesh->cd_flag |= ME_CDFLAG_VERT_BWEIGHT;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (const MEdge &edge : edges) {
|
|
if (edge.bweight_legacy != 0) {
|
|
mesh->cd_flag |= ME_CDFLAG_EDGE_BWEIGHT;
|
|
if (mesh->cd_flag & ME_CDFLAG_EDGE_CREASE) {
|
|
break;
|
|
}
|
|
}
|
|
if (edge.crease_legacy != 0) {
|
|
mesh->cd_flag |= ME_CDFLAG_EDGE_CREASE;
|
|
if (mesh->cd_flag & ME_CDFLAG_EDGE_BWEIGHT) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name NGon Tessellation (NGon to MFace Conversion)
|
|
* \{ */
|
|
|
|
#define MESH_MLOOPCOL_FROM_MCOL(_mloopcol, _mcol) \
|
|
{ \
|
|
MLoopCol *mloopcol__tmp = _mloopcol; \
|
|
const MCol *mcol__tmp = _mcol; \
|
|
mloopcol__tmp->r = mcol__tmp->b; \
|
|
mloopcol__tmp->g = mcol__tmp->g; \
|
|
mloopcol__tmp->b = mcol__tmp->r; \
|
|
mloopcol__tmp->a = mcol__tmp->a; \
|
|
} \
|
|
(void)0
|
|
|
|
static void bm_corners_to_loops_ex(ID *id,
|
|
CustomData *fdata_legacy,
|
|
const int totface,
|
|
CustomData *ldata,
|
|
MFace *mface,
|
|
int totloop,
|
|
int findex,
|
|
int loopstart,
|
|
int numTex,
|
|
int numCol)
|
|
{
|
|
MFace *mf = mface + findex;
|
|
|
|
for (int i = 0; i < numTex; i++) {
|
|
const MTFace *texface = (const MTFace *)CustomData_get_n_for_write(
|
|
fdata_legacy, CD_MTFACE, findex, i, totface);
|
|
|
|
blender::float2 *uv = static_cast<blender::float2 *>(
|
|
CustomData_get_n_for_write(ldata, CD_PROP_FLOAT2, loopstart, i, totloop));
|
|
copy_v2_v2(*uv, texface->uv[0]);
|
|
uv++;
|
|
copy_v2_v2(*uv, texface->uv[1]);
|
|
uv++;
|
|
copy_v2_v2(*uv, texface->uv[2]);
|
|
uv++;
|
|
|
|
if (mf->v4) {
|
|
copy_v2_v2(*uv, texface->uv[3]);
|
|
uv++;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < numCol; i++) {
|
|
MLoopCol *mloopcol = (MLoopCol *)CustomData_get_n_for_write(
|
|
ldata, CD_PROP_BYTE_COLOR, loopstart, i, totloop);
|
|
const MCol *mcol = (const MCol *)CustomData_get_n_for_write(
|
|
fdata_legacy, CD_MCOL, findex, i, totface);
|
|
|
|
MESH_MLOOPCOL_FROM_MCOL(mloopcol, &mcol[0]);
|
|
mloopcol++;
|
|
MESH_MLOOPCOL_FROM_MCOL(mloopcol, &mcol[1]);
|
|
mloopcol++;
|
|
MESH_MLOOPCOL_FROM_MCOL(mloopcol, &mcol[2]);
|
|
mloopcol++;
|
|
if (mf->v4) {
|
|
MESH_MLOOPCOL_FROM_MCOL(mloopcol, &mcol[3]);
|
|
mloopcol++;
|
|
}
|
|
}
|
|
|
|
if (CustomData_has_layer(fdata_legacy, CD_TESSLOOPNORMAL)) {
|
|
float(*loop_normals)[3] = (float(*)[3])CustomData_get_for_write(
|
|
ldata, loopstart, CD_NORMAL, totloop);
|
|
const short(*tessloop_normals)[3] = (short(*)[3])CustomData_get_for_write(
|
|
fdata_legacy, findex, CD_TESSLOOPNORMAL, totface);
|
|
const int max = mf->v4 ? 4 : 3;
|
|
|
|
for (int i = 0; i < max; i++, loop_normals++, tessloop_normals++) {
|
|
normal_short_to_float_v3(*loop_normals, *tessloop_normals);
|
|
}
|
|
}
|
|
|
|
if (CustomData_has_layer(fdata_legacy, CD_MDISPS)) {
|
|
MDisps *ld = (MDisps *)CustomData_get_for_write(ldata, loopstart, CD_MDISPS, totloop);
|
|
const MDisps *fd = (const MDisps *)CustomData_get_for_write(
|
|
fdata_legacy, findex, CD_MDISPS, totface);
|
|
const float(*disps)[3] = fd->disps;
|
|
int tot = mf->v4 ? 4 : 3;
|
|
int corners;
|
|
|
|
if (CustomData_external_test(fdata_legacy, CD_MDISPS)) {
|
|
if (id && fdata_legacy->external) {
|
|
CustomData_external_add(ldata, id, CD_MDISPS, totloop, fdata_legacy->external->filepath);
|
|
}
|
|
}
|
|
|
|
corners = multires_mdisp_corners(fd);
|
|
|
|
if (corners == 0) {
|
|
/* Empty #MDisp layers appear in at least one of the `sintel.blend` files.
|
|
* Not sure why this happens, but it seems fine to just ignore them here.
|
|
* If `corners == 0` for a non-empty layer though, something went wrong. */
|
|
BLI_assert(fd->totdisp == 0);
|
|
}
|
|
else {
|
|
const int side = int(sqrtf(float(fd->totdisp / corners)));
|
|
const int side_sq = side * side;
|
|
|
|
for (int i = 0; i < tot; i++, disps += side_sq, ld++) {
|
|
ld->totdisp = side_sq;
|
|
ld->level = int(logf(float(side) - 1.0f) / float(M_LN2)) + 1;
|
|
|
|
if (ld->disps) {
|
|
MEM_freeN(ld->disps);
|
|
}
|
|
|
|
ld->disps = (float(*)[3])MEM_malloc_arrayN(
|
|
size_t(side_sq), sizeof(float[3]), "converted loop mdisps");
|
|
if (fd->disps) {
|
|
memcpy(ld->disps, disps, size_t(side_sq) * sizeof(float[3]));
|
|
}
|
|
else {
|
|
memset(ld->disps, 0, size_t(side_sq) * sizeof(float[3]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void CustomData_to_bmeshpoly(CustomData *fdata_legacy, CustomData *ldata, int totloop)
|
|
{
|
|
for (int i = 0; i < fdata_legacy->totlayer; i++) {
|
|
if (fdata_legacy->layers[i].type == CD_MTFACE) {
|
|
CustomData_add_layer_named(
|
|
ldata, CD_PROP_FLOAT2, CD_SET_DEFAULT, totloop, fdata_legacy->layers[i].name);
|
|
}
|
|
else if (fdata_legacy->layers[i].type == CD_MCOL) {
|
|
CustomData_add_layer_named(
|
|
ldata, CD_PROP_BYTE_COLOR, CD_SET_DEFAULT, totloop, fdata_legacy->layers[i].name);
|
|
}
|
|
else if (fdata_legacy->layers[i].type == CD_MDISPS) {
|
|
CustomData_add_layer_named(
|
|
ldata, CD_MDISPS, CD_SET_DEFAULT, totloop, fdata_legacy->layers[i].name);
|
|
}
|
|
else if (fdata_legacy->layers[i].type == CD_TESSLOOPNORMAL) {
|
|
CustomData_add_layer_named(
|
|
ldata, CD_NORMAL, CD_SET_DEFAULT, totloop, fdata_legacy->layers[i].name);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void convert_mfaces_to_mpolys(ID *id,
|
|
CustomData *fdata_legacy,
|
|
CustomData *ldata,
|
|
CustomData *pdata,
|
|
int totedge_i,
|
|
int totface_i,
|
|
int totloop_i,
|
|
int faces_num_i,
|
|
blender::int2 *edges,
|
|
MFace *mface,
|
|
int *r_totloop,
|
|
int *r_faces_num)
|
|
{
|
|
MFace *mf;
|
|
MLoop *ml, *mloop;
|
|
MPoly *poly, *mpoly;
|
|
int numTex, numCol;
|
|
int i, j, totloop, faces_num, *polyindex;
|
|
|
|
/* just in case some of these layers are filled in (can happen with python created meshes) */
|
|
CustomData_free(ldata, totloop_i);
|
|
CustomData_free(pdata, faces_num_i);
|
|
|
|
faces_num = totface_i;
|
|
mpoly = (MPoly *)CustomData_add_layer(pdata, CD_MPOLY, CD_SET_DEFAULT, faces_num);
|
|
int *material_indices = static_cast<int *>(
|
|
CustomData_get_layer_named_for_write(pdata, CD_PROP_INT32, "material_index", faces_num));
|
|
if (material_indices == nullptr) {
|
|
material_indices = static_cast<int *>(CustomData_add_layer_named(
|
|
pdata, CD_PROP_INT32, CD_SET_DEFAULT, faces_num, "material_index"));
|
|
}
|
|
bool *sharp_faces = static_cast<bool *>(
|
|
CustomData_get_layer_named_for_write(pdata, CD_PROP_BOOL, "sharp_face", faces_num));
|
|
if (!sharp_faces) {
|
|
sharp_faces = static_cast<bool *>(
|
|
CustomData_add_layer_named(pdata, CD_PROP_BOOL, CD_SET_DEFAULT, faces_num, "sharp_face"));
|
|
}
|
|
|
|
numTex = CustomData_number_of_layers(fdata_legacy, CD_MTFACE);
|
|
numCol = CustomData_number_of_layers(fdata_legacy, CD_MCOL);
|
|
|
|
totloop = 0;
|
|
mf = mface;
|
|
for (i = 0; i < totface_i; i++, mf++) {
|
|
totloop += mf->v4 ? 4 : 3;
|
|
}
|
|
|
|
mloop = (MLoop *)CustomData_add_layer(ldata, CD_MLOOP, CD_SET_DEFAULT, totloop);
|
|
|
|
CustomData_to_bmeshpoly(fdata_legacy, ldata, totloop);
|
|
|
|
if (id) {
|
|
/* ensure external data is transferred */
|
|
/* TODO(sergey): Use multiresModifier_ensure_external_read(). */
|
|
CustomData_external_read(fdata_legacy, id, CD_MASK_MDISPS, totface_i);
|
|
}
|
|
|
|
blender::Map<blender::OrderedEdge, int> eh;
|
|
eh.reserve(totedge_i);
|
|
|
|
/* build edge hash */
|
|
for (i = 0; i < totedge_i; i++) {
|
|
eh.add(edges[i], i);
|
|
}
|
|
|
|
polyindex = (int *)CustomData_get_layer(fdata_legacy, CD_ORIGINDEX);
|
|
|
|
j = 0; /* current loop index */
|
|
ml = mloop;
|
|
mf = mface;
|
|
poly = mpoly;
|
|
for (i = 0; i < totface_i; i++, mf++, poly++) {
|
|
poly->loopstart = j;
|
|
|
|
poly->totloop = mf->v4 ? 4 : 3;
|
|
|
|
material_indices[i] = mf->mat_nr;
|
|
sharp_faces[i] = (mf->flag & ME_SMOOTH) == 0;
|
|
|
|
#define ML(v1, v2) \
|
|
{ \
|
|
ml->v = mf->v1; \
|
|
ml->e = eh.lookup({mf->v1, mf->v2}); \
|
|
ml++; \
|
|
j++; \
|
|
} \
|
|
(void)0
|
|
|
|
ML(v1, v2);
|
|
ML(v2, v3);
|
|
if (mf->v4) {
|
|
ML(v3, v4);
|
|
ML(v4, v1);
|
|
}
|
|
else {
|
|
ML(v3, v1);
|
|
}
|
|
|
|
#undef ML
|
|
|
|
bm_corners_to_loops_ex(
|
|
id, fdata_legacy, totface_i, ldata, mface, totloop, i, poly->loopstart, numTex, numCol);
|
|
|
|
if (polyindex) {
|
|
*polyindex = i;
|
|
polyindex++;
|
|
}
|
|
}
|
|
|
|
/* NOTE: we don't convert NGons at all, these are not even real ngons,
|
|
* they have their own UVs, colors etc - it's more an editing feature. */
|
|
|
|
*r_faces_num = faces_num;
|
|
*r_totloop = totloop;
|
|
}
|
|
|
|
static void update_active_fdata_layers(Mesh &mesh, CustomData *fdata_legacy, CustomData *ldata)
|
|
{
|
|
int act;
|
|
|
|
if (CustomData_has_layer(ldata, CD_PROP_FLOAT2)) {
|
|
act = CustomData_get_active_layer(ldata, CD_PROP_FLOAT2);
|
|
CustomData_set_layer_active(fdata_legacy, CD_MTFACE, act);
|
|
|
|
act = CustomData_get_render_layer(ldata, CD_PROP_FLOAT2);
|
|
CustomData_set_layer_render(fdata_legacy, CD_MTFACE, act);
|
|
|
|
act = CustomData_get_clone_layer(ldata, CD_PROP_FLOAT2);
|
|
CustomData_set_layer_clone(fdata_legacy, CD_MTFACE, act);
|
|
|
|
act = CustomData_get_stencil_layer(ldata, CD_PROP_FLOAT2);
|
|
CustomData_set_layer_stencil(fdata_legacy, CD_MTFACE, act);
|
|
}
|
|
|
|
if (CustomData_has_layer(ldata, CD_PROP_BYTE_COLOR)) {
|
|
if (mesh.active_color_attribute != nullptr) {
|
|
act = CustomData_get_named_layer(ldata, CD_PROP_BYTE_COLOR, mesh.active_color_attribute);
|
|
CustomData_set_layer_active(fdata_legacy, CD_MCOL, act);
|
|
}
|
|
|
|
if (mesh.default_color_attribute != nullptr) {
|
|
act = CustomData_get_named_layer(ldata, CD_PROP_BYTE_COLOR, mesh.default_color_attribute);
|
|
CustomData_set_layer_render(fdata_legacy, CD_MCOL, act);
|
|
}
|
|
|
|
act = CustomData_get_clone_layer(ldata, CD_PROP_BYTE_COLOR);
|
|
CustomData_set_layer_clone(fdata_legacy, CD_MCOL, act);
|
|
|
|
act = CustomData_get_stencil_layer(ldata, CD_PROP_BYTE_COLOR);
|
|
CustomData_set_layer_stencil(fdata_legacy, CD_MCOL, act);
|
|
}
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
/**
|
|
* Debug check, used to assert when we expect layers to be in/out of sync.
|
|
*
|
|
* \param fallback: Use when there are no layers to handle,
|
|
* since callers may expect success or failure.
|
|
*/
|
|
static bool check_matching_legacy_layer_counts(CustomData *fdata_legacy,
|
|
CustomData *ldata,
|
|
bool fallback)
|
|
{
|
|
int a_num = 0, b_num = 0;
|
|
# define LAYER_CMP(l_a, t_a, l_b, t_b) \
|
|
((a_num += CustomData_number_of_layers(l_a, t_a)) == \
|
|
(b_num += CustomData_number_of_layers(l_b, t_b)))
|
|
|
|
if (!LAYER_CMP(ldata, CD_PROP_FLOAT2, fdata_legacy, CD_MTFACE)) {
|
|
return false;
|
|
}
|
|
if (!LAYER_CMP(ldata, CD_PROP_BYTE_COLOR, fdata_legacy, CD_MCOL)) {
|
|
return false;
|
|
}
|
|
if (!LAYER_CMP(ldata, CD_PREVIEW_MLOOPCOL, fdata_legacy, CD_PREVIEW_MCOL)) {
|
|
return false;
|
|
}
|
|
if (!LAYER_CMP(ldata, CD_ORIGSPACE_MLOOP, fdata_legacy, CD_ORIGSPACE)) {
|
|
return false;
|
|
}
|
|
if (!LAYER_CMP(ldata, CD_NORMAL, fdata_legacy, CD_TESSLOOPNORMAL)) {
|
|
return false;
|
|
}
|
|
if (!LAYER_CMP(ldata, CD_TANGENT, fdata_legacy, CD_TANGENT)) {
|
|
return false;
|
|
}
|
|
|
|
# undef LAYER_CMP
|
|
|
|
/* if no layers are on either CustomData's,
|
|
* then there was nothing to do... */
|
|
return a_num ? true : fallback;
|
|
}
|
|
#endif
|
|
|
|
static void add_mface_layers(Mesh &mesh, CustomData *fdata_legacy, CustomData *ldata, int total)
|
|
{
|
|
/* avoid accumulating extra layers */
|
|
BLI_assert(!check_matching_legacy_layer_counts(fdata_legacy, ldata, false));
|
|
|
|
for (int i = 0; i < ldata->totlayer; i++) {
|
|
if (ldata->layers[i].type == CD_PROP_FLOAT2) {
|
|
CustomData_add_layer_named(
|
|
fdata_legacy, CD_MTFACE, CD_SET_DEFAULT, total, ldata->layers[i].name);
|
|
}
|
|
if (ldata->layers[i].type == CD_PROP_BYTE_COLOR) {
|
|
CustomData_add_layer_named(
|
|
fdata_legacy, CD_MCOL, CD_SET_DEFAULT, total, ldata->layers[i].name);
|
|
}
|
|
else if (ldata->layers[i].type == CD_PREVIEW_MLOOPCOL) {
|
|
CustomData_add_layer_named(
|
|
fdata_legacy, CD_PREVIEW_MCOL, CD_SET_DEFAULT, total, ldata->layers[i].name);
|
|
}
|
|
else if (ldata->layers[i].type == CD_ORIGSPACE_MLOOP) {
|
|
CustomData_add_layer_named(
|
|
fdata_legacy, CD_ORIGSPACE, CD_SET_DEFAULT, total, ldata->layers[i].name);
|
|
}
|
|
else if (ldata->layers[i].type == CD_NORMAL) {
|
|
CustomData_add_layer_named(
|
|
fdata_legacy, CD_TESSLOOPNORMAL, CD_SET_DEFAULT, total, ldata->layers[i].name);
|
|
}
|
|
else if (ldata->layers[i].type == CD_TANGENT) {
|
|
CustomData_add_layer_named(
|
|
fdata_legacy, CD_TANGENT, CD_SET_DEFAULT, total, ldata->layers[i].name);
|
|
}
|
|
}
|
|
|
|
update_active_fdata_layers(mesh, fdata_legacy, ldata);
|
|
}
|
|
|
|
static void mesh_ensure_tessellation_customdata(Mesh *me)
|
|
{
|
|
if (UNLIKELY((me->totface_legacy != 0) && (me->faces_num == 0))) {
|
|
/* Pass, otherwise this function clears 'mface' before
|
|
* versioning 'mface -> mpoly' code kicks in #30583.
|
|
*
|
|
* Callers could also check but safer to do here - campbell */
|
|
}
|
|
else {
|
|
const int tottex_original = CustomData_number_of_layers(&me->loop_data, CD_PROP_FLOAT2);
|
|
const int totcol_original = CustomData_number_of_layers(&me->loop_data, CD_PROP_BYTE_COLOR);
|
|
|
|
const int tottex_tessface = CustomData_number_of_layers(&me->fdata_legacy, CD_MTFACE);
|
|
const int totcol_tessface = CustomData_number_of_layers(&me->fdata_legacy, CD_MCOL);
|
|
|
|
if (tottex_tessface != tottex_original || totcol_tessface != totcol_original) {
|
|
BKE_mesh_tessface_clear(me);
|
|
|
|
add_mface_layers(*me, &me->fdata_legacy, &me->loop_data, me->totface_legacy);
|
|
|
|
/* TODO: add some `--debug-mesh` option. */
|
|
if (G.debug & G_DEBUG) {
|
|
/* NOTE(campbell): this warning may be un-called for if we are initializing the mesh for
|
|
* the first time from #BMesh, rather than giving a warning about this we could be smarter
|
|
* and check if there was any data to begin with, for now just print the warning with
|
|
* some info to help troubleshoot what's going on. */
|
|
printf(
|
|
"%s: warning! Tessellation uvs or vcol data got out of sync, "
|
|
"had to reset!\n CD_MTFACE: %d != CD_PROP_FLOAT2: %d || CD_MCOL: %d != "
|
|
"CD_PROP_BYTE_COLOR: "
|
|
"%d\n",
|
|
__func__,
|
|
tottex_tessface,
|
|
tottex_original,
|
|
totcol_tessface,
|
|
totcol_original);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BKE_mesh_convert_mfaces_to_mpolys(Mesh *mesh)
|
|
{
|
|
convert_mfaces_to_mpolys(&mesh->id,
|
|
&mesh->fdata_legacy,
|
|
&mesh->loop_data,
|
|
&mesh->face_data,
|
|
mesh->totedge,
|
|
mesh->totface_legacy,
|
|
mesh->totloop,
|
|
mesh->faces_num,
|
|
mesh->edges_for_write().data(),
|
|
(MFace *)CustomData_get_layer(&mesh->fdata_legacy, CD_MFACE),
|
|
&mesh->totloop,
|
|
&mesh->faces_num);
|
|
BKE_mesh_legacy_convert_loops_to_corners(mesh);
|
|
BKE_mesh_legacy_convert_polys_to_offsets(mesh);
|
|
|
|
mesh_ensure_tessellation_customdata(mesh);
|
|
}
|
|
|
|
/**
|
|
* Update active indices for active/render/clone/stencil custom data layers
|
|
* based on indices from fdata_legacy layers
|
|
* used when creating fdata_legacy and ldata for pre-bmesh
|
|
* meshes and needed to preserve active/render/clone/stencil flags set in pre-bmesh files.
|
|
*/
|
|
static void CustomData_bmesh_do_versions_update_active_layers(CustomData *fdata_legacy,
|
|
CustomData *loop_data)
|
|
{
|
|
int act;
|
|
|
|
if (CustomData_has_layer(fdata_legacy, CD_MTFACE)) {
|
|
act = CustomData_get_active_layer(fdata_legacy, CD_MTFACE);
|
|
CustomData_set_layer_active(loop_data, CD_PROP_FLOAT2, act);
|
|
|
|
act = CustomData_get_render_layer(fdata_legacy, CD_MTFACE);
|
|
CustomData_set_layer_render(loop_data, CD_PROP_FLOAT2, act);
|
|
|
|
act = CustomData_get_clone_layer(fdata_legacy, CD_MTFACE);
|
|
CustomData_set_layer_clone(loop_data, CD_PROP_FLOAT2, act);
|
|
|
|
act = CustomData_get_stencil_layer(fdata_legacy, CD_MTFACE);
|
|
CustomData_set_layer_stencil(loop_data, CD_PROP_FLOAT2, act);
|
|
}
|
|
|
|
if (CustomData_has_layer(fdata_legacy, CD_MCOL)) {
|
|
act = CustomData_get_active_layer(fdata_legacy, CD_MCOL);
|
|
CustomData_set_layer_active(loop_data, CD_PROP_BYTE_COLOR, act);
|
|
|
|
act = CustomData_get_render_layer(fdata_legacy, CD_MCOL);
|
|
CustomData_set_layer_render(loop_data, CD_PROP_BYTE_COLOR, act);
|
|
|
|
act = CustomData_get_clone_layer(fdata_legacy, CD_MCOL);
|
|
CustomData_set_layer_clone(loop_data, CD_PROP_BYTE_COLOR, act);
|
|
|
|
act = CustomData_get_stencil_layer(fdata_legacy, CD_MCOL);
|
|
CustomData_set_layer_stencil(loop_data, CD_PROP_BYTE_COLOR, act);
|
|
}
|
|
}
|
|
|
|
void BKE_mesh_do_versions_convert_mfaces_to_mpolys(Mesh *mesh)
|
|
{
|
|
convert_mfaces_to_mpolys(&mesh->id,
|
|
&mesh->fdata_legacy,
|
|
&mesh->loop_data,
|
|
&mesh->face_data,
|
|
mesh->totedge,
|
|
mesh->totface_legacy,
|
|
mesh->totloop,
|
|
mesh->faces_num,
|
|
mesh->edges_for_write().data(),
|
|
(MFace *)CustomData_get_layer(&mesh->fdata_legacy, CD_MFACE),
|
|
&mesh->totloop,
|
|
&mesh->faces_num);
|
|
BKE_mesh_legacy_convert_loops_to_corners(mesh);
|
|
BKE_mesh_legacy_convert_polys_to_offsets(mesh);
|
|
|
|
CustomData_bmesh_do_versions_update_active_layers(&mesh->fdata_legacy, &mesh->loop_data);
|
|
|
|
mesh_ensure_tessellation_customdata(mesh);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name MFace Tessellation
|
|
*
|
|
* #MFace is a legacy data-structure that should be avoided, use #MLoopTri instead.
|
|
* \{ */
|
|
|
|
#define MESH_MLOOPCOL_TO_MCOL(_mloopcol, _mcol) \
|
|
{ \
|
|
const MLoopCol *mloopcol__tmp = _mloopcol; \
|
|
MCol *mcol__tmp = _mcol; \
|
|
mcol__tmp->b = mloopcol__tmp->r; \
|
|
mcol__tmp->g = mloopcol__tmp->g; \
|
|
mcol__tmp->r = mloopcol__tmp->b; \
|
|
mcol__tmp->a = mloopcol__tmp->a; \
|
|
} \
|
|
(void)0
|
|
|
|
/**
|
|
* Convert all CD layers from loop/poly to tessface data.
|
|
*
|
|
* \param loopindices: is an array of an int[4] per tessface,
|
|
* mapping tessface's verts to loops indices.
|
|
*
|
|
* \note when mface is not null, mface[face_index].v4
|
|
* is used to test quads, else, loopindices[face_index][3] is used.
|
|
*/
|
|
static void mesh_loops_to_tessdata(CustomData *fdata_legacy,
|
|
CustomData *loop_data,
|
|
MFace *mface,
|
|
const int *polyindices,
|
|
uint (*loopindices)[4],
|
|
const int num_faces)
|
|
{
|
|
/* NOTE(mont29): performances are sub-optimal when we get a null #MFace,
|
|
* we could be ~25% quicker with dedicated code.
|
|
* The issue is, unless having two different functions with nearly the same code,
|
|
* there's not much ways to solve this. Better IMHO to live with it for now (sigh). */
|
|
const int numUV = CustomData_number_of_layers(loop_data, CD_PROP_FLOAT2);
|
|
const int numCol = CustomData_number_of_layers(loop_data, CD_PROP_BYTE_COLOR);
|
|
const bool hasPCol = CustomData_has_layer(loop_data, CD_PREVIEW_MLOOPCOL);
|
|
const bool hasOrigSpace = CustomData_has_layer(loop_data, CD_ORIGSPACE_MLOOP);
|
|
const bool hasLoopNormal = CustomData_has_layer(loop_data, CD_NORMAL);
|
|
const bool hasLoopTangent = CustomData_has_layer(loop_data, CD_TANGENT);
|
|
int findex, i, j;
|
|
const int *pidx;
|
|
uint(*lidx)[4];
|
|
|
|
for (i = 0; i < numUV; i++) {
|
|
MTFace *texface = (MTFace *)CustomData_get_layer_n_for_write(
|
|
fdata_legacy, CD_MTFACE, i, num_faces);
|
|
const blender::float2 *uv = static_cast<const blender::float2 *>(
|
|
CustomData_get_layer_n(loop_data, CD_PROP_FLOAT2, i));
|
|
|
|
for (findex = 0, pidx = polyindices, lidx = loopindices; findex < num_faces;
|
|
pidx++, lidx++, findex++, texface++)
|
|
{
|
|
for (j = (mface ? mface[findex].v4 : (*lidx)[3]) ? 4 : 3; j--;) {
|
|
copy_v2_v2(texface->uv[j], uv[(*lidx)[j]]);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < numCol; i++) {
|
|
MCol(*mcol)[4] = (MCol(*)[4])CustomData_get_layer_n_for_write(
|
|
fdata_legacy, CD_MCOL, i, num_faces);
|
|
const MLoopCol *mloopcol = (const MLoopCol *)CustomData_get_layer_n(
|
|
loop_data, CD_PROP_BYTE_COLOR, i);
|
|
|
|
for (findex = 0, lidx = loopindices; findex < num_faces; lidx++, findex++, mcol++) {
|
|
for (j = (mface ? mface[findex].v4 : (*lidx)[3]) ? 4 : 3; j--;) {
|
|
MESH_MLOOPCOL_TO_MCOL(&mloopcol[(*lidx)[j]], &(*mcol)[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hasPCol) {
|
|
MCol(*mcol)[4] = (MCol(*)[4])CustomData_get_layer(fdata_legacy, CD_PREVIEW_MCOL);
|
|
const MLoopCol *mloopcol = (const MLoopCol *)CustomData_get_layer(loop_data,
|
|
CD_PREVIEW_MLOOPCOL);
|
|
|
|
for (findex = 0, lidx = loopindices; findex < num_faces; lidx++, findex++, mcol++) {
|
|
for (j = (mface ? mface[findex].v4 : (*lidx)[3]) ? 4 : 3; j--;) {
|
|
MESH_MLOOPCOL_TO_MCOL(&mloopcol[(*lidx)[j]], &(*mcol)[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hasOrigSpace) {
|
|
OrigSpaceFace *of = (OrigSpaceFace *)CustomData_get_layer(fdata_legacy, CD_ORIGSPACE);
|
|
const OrigSpaceLoop *lof = (const OrigSpaceLoop *)CustomData_get_layer(loop_data,
|
|
CD_ORIGSPACE_MLOOP);
|
|
|
|
for (findex = 0, lidx = loopindices; findex < num_faces; lidx++, findex++, of++) {
|
|
for (j = (mface ? mface[findex].v4 : (*lidx)[3]) ? 4 : 3; j--;) {
|
|
copy_v2_v2(of->uv[j], lof[(*lidx)[j]].uv);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hasLoopNormal) {
|
|
short(*face_normals)[4][3] = (short(*)[4][3])CustomData_get_layer(fdata_legacy,
|
|
CD_TESSLOOPNORMAL);
|
|
const float(*loop_normals)[3] = (const float(*)[3])CustomData_get_layer(loop_data, CD_NORMAL);
|
|
|
|
for (findex = 0, lidx = loopindices; findex < num_faces; lidx++, findex++, face_normals++) {
|
|
for (j = (mface ? mface[findex].v4 : (*lidx)[3]) ? 4 : 3; j--;) {
|
|
normal_float_to_short_v3((*face_normals)[j], loop_normals[(*lidx)[j]]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hasLoopTangent) {
|
|
/* Need to do for all UV maps at some point. */
|
|
float(*ftangents)[4] = (float(*)[4])CustomData_get_layer(fdata_legacy, CD_TANGENT);
|
|
const float(*ltangents)[4] = (const float(*)[4])CustomData_get_layer(loop_data, CD_TANGENT);
|
|
|
|
for (findex = 0, pidx = polyindices, lidx = loopindices; findex < num_faces;
|
|
pidx++, lidx++, findex++)
|
|
{
|
|
int nverts = (mface ? mface[findex].v4 : (*lidx)[3]) ? 4 : 3;
|
|
for (j = nverts; j--;) {
|
|
copy_v4_v4(ftangents[findex * 4 + j], ltangents[(*lidx)[j]]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int BKE_mesh_mface_index_validate(MFace *mface, CustomData *fdata_legacy, int mfindex, int nr)
|
|
{
|
|
/* first test if the face is legal */
|
|
if ((mface->v3 || nr == 4) && mface->v3 == mface->v4) {
|
|
mface->v4 = 0;
|
|
nr--;
|
|
}
|
|
if ((mface->v2 || mface->v4) && mface->v2 == mface->v3) {
|
|
mface->v3 = mface->v4;
|
|
mface->v4 = 0;
|
|
nr--;
|
|
}
|
|
if (mface->v1 == mface->v2) {
|
|
mface->v2 = mface->v3;
|
|
mface->v3 = mface->v4;
|
|
mface->v4 = 0;
|
|
nr--;
|
|
}
|
|
|
|
/* Check corrupt cases, bow-tie geometry,
|
|
* can't handle these because edge data won't exist so just return 0. */
|
|
if (nr == 3) {
|
|
if (
|
|
/* real edges */
|
|
mface->v1 == mface->v2 || mface->v2 == mface->v3 || mface->v3 == mface->v1)
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
else if (nr == 4) {
|
|
if (
|
|
/* real edges */
|
|
mface->v1 == mface->v2 || mface->v2 == mface->v3 || mface->v3 == mface->v4 ||
|
|
mface->v4 == mface->v1 ||
|
|
/* across the face */
|
|
mface->v1 == mface->v3 || mface->v2 == mface->v4)
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* prevent a zero at wrong index location */
|
|
if (nr == 3) {
|
|
if (mface->v3 == 0) {
|
|
static int corner_indices[4] = {1, 2, 0, 3};
|
|
|
|
std::swap(mface->v1, mface->v2);
|
|
std::swap(mface->v2, mface->v3);
|
|
|
|
if (fdata_legacy) {
|
|
CustomData_swap_corners(fdata_legacy, mfindex, corner_indices);
|
|
}
|
|
}
|
|
}
|
|
else if (nr == 4) {
|
|
if (mface->v3 == 0 || mface->v4 == 0) {
|
|
static int corner_indices[4] = {2, 3, 0, 1};
|
|
|
|
std::swap(mface->v1, mface->v3);
|
|
std::swap(mface->v2, mface->v4);
|
|
|
|
if (fdata_legacy) {
|
|
CustomData_swap_corners(fdata_legacy, mfindex, corner_indices);
|
|
}
|
|
}
|
|
}
|
|
|
|
return nr;
|
|
}
|
|
|
|
static int mesh_tessface_calc(Mesh &mesh,
|
|
CustomData *fdata_legacy,
|
|
CustomData *ldata,
|
|
CustomData *pdata,
|
|
float (*positions)[3],
|
|
int totface,
|
|
int totloop,
|
|
int faces_num)
|
|
{
|
|
#define USE_TESSFACE_SPEEDUP
|
|
#define USE_TESSFACE_QUADS
|
|
|
|
/* We abuse #MFace.edcode to tag quad faces. See below for details. */
|
|
#define TESSFACE_IS_QUAD 1
|
|
|
|
const int looptri_num = poly_to_tri_count(faces_num, totloop);
|
|
|
|
MFace *mface, *mf;
|
|
MemArena *arena = nullptr;
|
|
int *mface_to_poly_map;
|
|
uint(*lindices)[4];
|
|
int poly_index, mface_index;
|
|
uint j;
|
|
|
|
const blender::OffsetIndices faces = mesh.faces();
|
|
const Span<int> corner_verts = mesh.corner_verts();
|
|
const int *material_indices = static_cast<const int *>(
|
|
CustomData_get_layer_named(pdata, CD_PROP_INT32, "material_index"));
|
|
const bool *sharp_faces = static_cast<const bool *>(
|
|
CustomData_get_layer_named(pdata, CD_PROP_BOOL, "sharp_face"));
|
|
|
|
/* Allocate the length of `totfaces`, avoid many small reallocation's,
|
|
* if all faces are triangles it will be correct, `quads == 2x` allocations. */
|
|
/* Take care since memory is _not_ zeroed so be sure to initialize each field. */
|
|
mface_to_poly_map = (int *)MEM_malloc_arrayN(
|
|
size_t(looptri_num), sizeof(*mface_to_poly_map), __func__);
|
|
mface = (MFace *)MEM_malloc_arrayN(size_t(looptri_num), sizeof(*mface), __func__);
|
|
lindices = (uint(*)[4])MEM_malloc_arrayN(size_t(looptri_num), sizeof(*lindices), __func__);
|
|
|
|
mface_index = 0;
|
|
for (poly_index = 0; poly_index < faces_num; poly_index++) {
|
|
const uint mp_loopstart = uint(faces[poly_index].start());
|
|
const uint mp_totloop = uint(faces[poly_index].size());
|
|
uint l1, l2, l3, l4;
|
|
uint *lidx;
|
|
if (mp_totloop < 3) {
|
|
/* Do nothing. */
|
|
}
|
|
|
|
#ifdef USE_TESSFACE_SPEEDUP
|
|
|
|
# define ML_TO_MF(i1, i2, i3) \
|
|
mface_to_poly_map[mface_index] = poly_index; \
|
|
mf = &mface[mface_index]; \
|
|
lidx = lindices[mface_index]; \
|
|
/* Set loop indices, transformed to vert indices later. */ \
|
|
l1 = mp_loopstart + i1; \
|
|
l2 = mp_loopstart + i2; \
|
|
l3 = mp_loopstart + i3; \
|
|
mf->v1 = corner_verts[l1]; \
|
|
mf->v2 = corner_verts[l2]; \
|
|
mf->v3 = corner_verts[l3]; \
|
|
mf->v4 = 0; \
|
|
lidx[0] = l1; \
|
|
lidx[1] = l2; \
|
|
lidx[2] = l3; \
|
|
lidx[3] = 0; \
|
|
mf->mat_nr = material_indices ? material_indices[poly_index] : 0; \
|
|
mf->flag = (sharp_faces && sharp_faces[poly_index]) ? 0 : ME_SMOOTH; \
|
|
mf->edcode = 0; \
|
|
(void)0
|
|
|
|
/* ALMOST IDENTICAL TO DEFINE ABOVE (see EXCEPTION) */
|
|
# define ML_TO_MF_QUAD() \
|
|
mface_to_poly_map[mface_index] = poly_index; \
|
|
mf = &mface[mface_index]; \
|
|
lidx = lindices[mface_index]; \
|
|
/* Set loop indices, transformed to vert indices later. */ \
|
|
l1 = mp_loopstart + 0; /* EXCEPTION */ \
|
|
l2 = mp_loopstart + 1; /* EXCEPTION */ \
|
|
l3 = mp_loopstart + 2; /* EXCEPTION */ \
|
|
l4 = mp_loopstart + 3; /* EXCEPTION */ \
|
|
mf->v1 = corner_verts[l1]; \
|
|
mf->v2 = corner_verts[l2]; \
|
|
mf->v3 = corner_verts[l3]; \
|
|
mf->v4 = corner_verts[l4]; \
|
|
lidx[0] = l1; \
|
|
lidx[1] = l2; \
|
|
lidx[2] = l3; \
|
|
lidx[3] = l4; \
|
|
mf->mat_nr = material_indices ? material_indices[poly_index] : 0; \
|
|
mf->flag = (sharp_faces && sharp_faces[poly_index]) ? 0 : ME_SMOOTH; \
|
|
mf->edcode = TESSFACE_IS_QUAD; \
|
|
(void)0
|
|
|
|
else if (mp_totloop == 3) {
|
|
ML_TO_MF(0, 1, 2);
|
|
mface_index++;
|
|
}
|
|
else if (mp_totloop == 4) {
|
|
# ifdef USE_TESSFACE_QUADS
|
|
ML_TO_MF_QUAD();
|
|
mface_index++;
|
|
# else
|
|
ML_TO_MF(0, 1, 2);
|
|
mface_index++;
|
|
ML_TO_MF(0, 2, 3);
|
|
mface_index++;
|
|
# endif
|
|
}
|
|
#endif /* USE_TESSFACE_SPEEDUP */
|
|
else {
|
|
const float *co_curr, *co_prev;
|
|
|
|
float normal[3];
|
|
|
|
float axis_mat[3][3];
|
|
float(*projverts)[2];
|
|
uint(*tris)[3];
|
|
|
|
const uint totfilltri = mp_totloop - 2;
|
|
|
|
if (UNLIKELY(arena == nullptr)) {
|
|
arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
|
|
}
|
|
|
|
tris = (uint(*)[3])BLI_memarena_alloc(arena, sizeof(*tris) * size_t(totfilltri));
|
|
projverts = (float(*)[2])BLI_memarena_alloc(arena, sizeof(*projverts) * size_t(mp_totloop));
|
|
|
|
zero_v3(normal);
|
|
|
|
/* Calculate the normal, flipped: to get a positive 2D cross product. */
|
|
co_prev = positions[corner_verts[mp_loopstart + mp_totloop - 1]];
|
|
for (j = 0; j < mp_totloop; j++) {
|
|
const int vert = corner_verts[mp_loopstart + j];
|
|
co_curr = positions[vert];
|
|
add_newell_cross_v3_v3v3(normal, co_prev, co_curr);
|
|
co_prev = co_curr;
|
|
}
|
|
if (UNLIKELY(normalize_v3(normal) == 0.0f)) {
|
|
normal[2] = 1.0f;
|
|
}
|
|
|
|
/* Project verts to 2D. */
|
|
axis_dominant_v3_to_m3_negate(axis_mat, normal);
|
|
|
|
for (j = 0; j < mp_totloop; j++) {
|
|
const int vert = corner_verts[mp_loopstart + j];
|
|
mul_v2_m3v3(projverts[j], axis_mat, positions[vert]);
|
|
}
|
|
|
|
BLI_polyfill_calc_arena(projverts, mp_totloop, 1, tris, arena);
|
|
|
|
/* Apply fill. */
|
|
for (j = 0; j < totfilltri; j++) {
|
|
uint *tri = tris[j];
|
|
lidx = lindices[mface_index];
|
|
|
|
mface_to_poly_map[mface_index] = poly_index;
|
|
mf = &mface[mface_index];
|
|
|
|
/* Set loop indices, transformed to vert indices later. */
|
|
l1 = mp_loopstart + tri[0];
|
|
l2 = mp_loopstart + tri[1];
|
|
l3 = mp_loopstart + tri[2];
|
|
|
|
mf->v1 = corner_verts[l1];
|
|
mf->v2 = corner_verts[l2];
|
|
mf->v3 = corner_verts[l3];
|
|
mf->v4 = 0;
|
|
|
|
lidx[0] = l1;
|
|
lidx[1] = l2;
|
|
lidx[2] = l3;
|
|
lidx[3] = 0;
|
|
|
|
mf->mat_nr = material_indices ? material_indices[poly_index] : 0;
|
|
mf->edcode = 0;
|
|
|
|
mface_index++;
|
|
}
|
|
|
|
BLI_memarena_clear(arena);
|
|
}
|
|
}
|
|
|
|
if (arena) {
|
|
BLI_memarena_free(arena);
|
|
arena = nullptr;
|
|
}
|
|
|
|
CustomData_free(fdata_legacy, totface);
|
|
totface = mface_index;
|
|
|
|
BLI_assert(totface <= looptri_num);
|
|
|
|
/* Not essential but without this we store over-allocated memory in the #CustomData layers. */
|
|
if (LIKELY(looptri_num != totface)) {
|
|
mface = (MFace *)MEM_reallocN(mface, sizeof(*mface) * size_t(totface));
|
|
mface_to_poly_map = (int *)MEM_reallocN(mface_to_poly_map,
|
|
sizeof(*mface_to_poly_map) * size_t(totface));
|
|
}
|
|
|
|
CustomData_add_layer_with_data(fdata_legacy, CD_MFACE, mface, totface, nullptr);
|
|
|
|
/* #CD_ORIGINDEX will contain an array of indices from tessellation-faces to the polygons
|
|
* they are directly tessellated from. */
|
|
CustomData_add_layer_with_data(fdata_legacy, CD_ORIGINDEX, mface_to_poly_map, totface, nullptr);
|
|
add_mface_layers(mesh, fdata_legacy, ldata, totface);
|
|
|
|
/* NOTE: quad detection issue - fourth vertex-index vs fourth loop-index:
|
|
* Polygons take care of their loops ordering, hence not of their vertices ordering.
|
|
* Currently, the #TFace fourth vertex index might be 0 even for a quad.
|
|
* However, we know our fourth loop index is never 0 for quads
|
|
* (because they are sorted for polygons, and our quads are still mere copies of their polygons).
|
|
* So we pass nullptr as #MFace pointer, and #mesh_loops_to_tessdata
|
|
* will use the fourth loop index as quad test. */
|
|
mesh_loops_to_tessdata(fdata_legacy, ldata, nullptr, mface_to_poly_map, lindices, totface);
|
|
|
|
/* NOTE: quad detection issue - fourth vert-index vs fourth loop-index:
|
|
* ...However, most #TFace code uses `MFace->v4 == 0` test to check whether it is a tri or quad.
|
|
* BKE_mesh_mface_index_validate() will check this and rotate the tessellated face if needed.
|
|
*/
|
|
#ifdef USE_TESSFACE_QUADS
|
|
mf = mface;
|
|
for (mface_index = 0; mface_index < totface; mface_index++, mf++) {
|
|
if (mf->edcode == TESSFACE_IS_QUAD) {
|
|
BKE_mesh_mface_index_validate(mf, fdata_legacy, mface_index, 4);
|
|
mf->edcode = 0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
MEM_freeN(lindices);
|
|
|
|
return totface;
|
|
|
|
#undef USE_TESSFACE_SPEEDUP
|
|
#undef USE_TESSFACE_QUADS
|
|
|
|
#undef ML_TO_MF
|
|
#undef ML_TO_MF_QUAD
|
|
}
|
|
|
|
void BKE_mesh_tessface_calc(Mesh *mesh)
|
|
{
|
|
mesh->totface_legacy = mesh_tessface_calc(
|
|
*mesh,
|
|
&mesh->fdata_legacy,
|
|
&mesh->loop_data,
|
|
&mesh->face_data,
|
|
reinterpret_cast<float(*)[3]>(mesh->vert_positions_for_write().data()),
|
|
mesh->totface_legacy,
|
|
mesh->totloop,
|
|
mesh->faces_num);
|
|
|
|
mesh_ensure_tessellation_customdata(mesh);
|
|
}
|
|
|
|
void BKE_mesh_tessface_ensure(Mesh *mesh)
|
|
{
|
|
if (mesh->faces_num && mesh->totface_legacy == 0) {
|
|
BKE_mesh_tessface_calc(mesh);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Sharp Edge Conversion
|
|
* \{ */
|
|
|
|
void BKE_mesh_legacy_sharp_faces_from_flags(Mesh *mesh)
|
|
{
|
|
using namespace blender;
|
|
using namespace blender::bke;
|
|
MutableAttributeAccessor attributes = mesh->attributes_for_write();
|
|
if (attributes.contains("sharp_face") || !CustomData_get_layer(&mesh->face_data, CD_MPOLY)) {
|
|
return;
|
|
}
|
|
const Span<MPoly> polys(
|
|
static_cast<const MPoly *>(CustomData_get_layer(&mesh->face_data, CD_MPOLY)),
|
|
mesh->faces_num);
|
|
if (std::any_of(polys.begin(), polys.end(), [](const MPoly &poly) {
|
|
return !(poly.flag_legacy & ME_SMOOTH);
|
|
}))
|
|
{
|
|
SpanAttributeWriter<bool> sharp_faces = attributes.lookup_or_add_for_write_only_span<bool>(
|
|
"sharp_face", ATTR_DOMAIN_FACE);
|
|
threading::parallel_for(polys.index_range(), 4096, [&](const IndexRange range) {
|
|
for (const int i : range) {
|
|
sharp_faces.span[i] = !(polys[i].flag_legacy & ME_SMOOTH);
|
|
}
|
|
});
|
|
sharp_faces.finish();
|
|
}
|
|
else {
|
|
attributes.remove("sharp_face");
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Face Set Conversion
|
|
* \{ */
|
|
|
|
void BKE_mesh_legacy_face_set_to_generic(Mesh *mesh)
|
|
{
|
|
using namespace blender;
|
|
if (mesh->attributes().contains(".sculpt_face_set")) {
|
|
return;
|
|
}
|
|
void *faceset_data = nullptr;
|
|
const ImplicitSharingInfo *faceset_sharing_info = nullptr;
|
|
for (const int i : IndexRange(mesh->face_data.totlayer)) {
|
|
CustomDataLayer &layer = mesh->face_data.layers[i];
|
|
if (layer.type == CD_SCULPT_FACE_SETS) {
|
|
faceset_data = layer.data;
|
|
faceset_sharing_info = layer.sharing_info;
|
|
layer.data = nullptr;
|
|
layer.sharing_info = nullptr;
|
|
CustomData_free_layer(&mesh->face_data, CD_SCULPT_FACE_SETS, mesh->faces_num, i);
|
|
break;
|
|
}
|
|
}
|
|
if (faceset_data != nullptr) {
|
|
CustomData_add_layer_named_with_data(&mesh->face_data,
|
|
CD_PROP_INT32,
|
|
faceset_data,
|
|
mesh->faces_num,
|
|
".sculpt_face_set",
|
|
faceset_sharing_info);
|
|
}
|
|
if (faceset_sharing_info != nullptr) {
|
|
faceset_sharing_info->remove_user_and_delete_if_last();
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Face Map Conversion
|
|
* \{ */
|
|
|
|
static void move_face_map_data_to_attributes(Mesh *mesh)
|
|
{
|
|
using namespace blender;
|
|
if (mesh->attributes().contains("face_maps")) {
|
|
return;
|
|
}
|
|
int *data = nullptr;
|
|
const ImplicitSharingInfo *sharing_info = nullptr;
|
|
for (const int i : IndexRange(mesh->face_data.totlayer)) {
|
|
CustomDataLayer &layer = mesh->face_data.layers[i];
|
|
if (layer.type == CD_FACEMAP) {
|
|
data = static_cast<int *>(layer.data);
|
|
sharing_info = layer.sharing_info;
|
|
layer.data = nullptr;
|
|
layer.sharing_info = nullptr;
|
|
CustomData_free_layer(&mesh->face_data, CD_FACEMAP, mesh->faces_num, i);
|
|
break;
|
|
}
|
|
}
|
|
if (!data) {
|
|
return;
|
|
}
|
|
|
|
CustomData_add_layer_named_with_data(
|
|
&mesh->face_data, CD_PROP_INT32, data, mesh->faces_num, "face_maps", sharing_info);
|
|
if (sharing_info != nullptr) {
|
|
sharing_info->remove_user_and_delete_if_last();
|
|
}
|
|
|
|
MultiValueMap<int, int> groups;
|
|
for (const int i : IndexRange(mesh->faces_num)) {
|
|
if (data[i] == -1) {
|
|
/* -1 values "didn't have" a face map. */
|
|
continue;
|
|
}
|
|
groups.add(data[i], i);
|
|
}
|
|
|
|
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
|
|
for (const auto item : groups.items()) {
|
|
bke::SpanAttributeWriter<bool> attribute = attributes.lookup_or_add_for_write_span<bool>(
|
|
".temp_face_map_" + std::to_string(item.key), ATTR_DOMAIN_FACE);
|
|
if (attribute) {
|
|
attribute.span.fill_indices(item.value.as_span(), true);
|
|
attribute.finish();
|
|
}
|
|
}
|
|
}
|
|
|
|
void BKE_mesh_legacy_face_map_to_generic(Main *bmain)
|
|
{
|
|
LISTBASE_FOREACH (Mesh *, mesh, &bmain->meshes) {
|
|
move_face_map_data_to_attributes(mesh);
|
|
}
|
|
|
|
LISTBASE_FOREACH (Object *, object, &bmain->objects) {
|
|
if (object->type != OB_MESH) {
|
|
continue;
|
|
}
|
|
Mesh *mesh = static_cast<Mesh *>(object->data);
|
|
int i;
|
|
LISTBASE_FOREACH_INDEX (bFaceMap *, face_map, &object->fmaps, i) {
|
|
mesh->attributes_for_write().rename(".temp_face_map_" + std::to_string(i), face_map->name);
|
|
}
|
|
BLI_freelistN(&object->fmaps);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Bevel Weight Conversion
|
|
* \{ */
|
|
|
|
void BKE_mesh_legacy_bevel_weight_to_layers(Mesh *mesh)
|
|
{
|
|
using namespace blender;
|
|
if (mesh->mvert && !CustomData_has_layer(&mesh->vert_data, CD_BWEIGHT)) {
|
|
const Span<MVert> verts(mesh->mvert, mesh->totvert);
|
|
if (mesh->cd_flag & ME_CDFLAG_VERT_BWEIGHT) {
|
|
float *weights = static_cast<float *>(
|
|
CustomData_add_layer(&mesh->vert_data, CD_BWEIGHT, CD_CONSTRUCT, verts.size()));
|
|
for (const int i : verts.index_range()) {
|
|
weights[i] = verts[i].bweight_legacy / 255.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mesh->medge && !CustomData_has_layer(&mesh->edge_data, CD_BWEIGHT)) {
|
|
const Span<MEdge> edges(mesh->medge, mesh->totedge);
|
|
if (mesh->cd_flag & ME_CDFLAG_EDGE_BWEIGHT) {
|
|
float *weights = static_cast<float *>(
|
|
CustomData_add_layer(&mesh->edge_data, CD_BWEIGHT, CD_CONSTRUCT, edges.size()));
|
|
for (const int i : edges.index_range()) {
|
|
weights[i] = edges[i].bweight_legacy / 255.0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void replace_custom_data_layer_with_named(CustomData &custom_data,
|
|
const eCustomDataType old_type,
|
|
const eCustomDataType new_type,
|
|
const int elems_num,
|
|
const char *new_name)
|
|
{
|
|
using namespace blender;
|
|
void *data = nullptr;
|
|
const ImplicitSharingInfo *sharing_info = nullptr;
|
|
for (const int i : IndexRange(custom_data.totlayer)) {
|
|
CustomDataLayer &layer = custom_data.layers[i];
|
|
if (layer.type == old_type) {
|
|
data = layer.data;
|
|
sharing_info = layer.sharing_info;
|
|
layer.data = nullptr;
|
|
layer.sharing_info = nullptr;
|
|
CustomData_free_layer(&custom_data, old_type, elems_num, i);
|
|
break;
|
|
}
|
|
}
|
|
if (data != nullptr) {
|
|
CustomData_add_layer_named_with_data(
|
|
&custom_data, new_type, data, elems_num, new_name, sharing_info);
|
|
}
|
|
if (sharing_info != nullptr) {
|
|
sharing_info->remove_user_and_delete_if_last();
|
|
}
|
|
}
|
|
|
|
void BKE_mesh_legacy_bevel_weight_to_generic(Mesh *mesh)
|
|
{
|
|
if (!mesh->attributes().contains("bevel_weight_vert")) {
|
|
replace_custom_data_layer_with_named(
|
|
mesh->vert_data, CD_BWEIGHT, CD_PROP_FLOAT, mesh->totvert, "bevel_weight_vert");
|
|
}
|
|
if (!mesh->attributes().contains("bevel_weight_edge")) {
|
|
replace_custom_data_layer_with_named(
|
|
mesh->edge_data, CD_BWEIGHT, CD_PROP_FLOAT, mesh->totedge, "bevel_weight_edge");
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Crease Conversion
|
|
* \{ */
|
|
|
|
void BKE_mesh_legacy_edge_crease_to_layers(Mesh *mesh)
|
|
{
|
|
using namespace blender;
|
|
if (!mesh->medge) {
|
|
return;
|
|
}
|
|
if (CustomData_has_layer(&mesh->edge_data, CD_CREASE)) {
|
|
return;
|
|
}
|
|
const Span<MEdge> edges(mesh->medge, mesh->totedge);
|
|
if (mesh->cd_flag & ME_CDFLAG_EDGE_CREASE) {
|
|
float *creases = static_cast<float *>(
|
|
CustomData_add_layer(&mesh->edge_data, CD_CREASE, CD_CONSTRUCT, edges.size()));
|
|
for (const int i : edges.index_range()) {
|
|
creases[i] = edges[i].crease_legacy / 255.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
void BKE_mesh_legacy_crease_to_generic(Mesh *mesh)
|
|
{
|
|
if (!mesh->attributes().contains("crease_vert")) {
|
|
replace_custom_data_layer_with_named(
|
|
mesh->vert_data, CD_CREASE, CD_PROP_FLOAT, mesh->totvert, "crease_vert");
|
|
}
|
|
if (!mesh->attributes().contains("crease_edge")) {
|
|
replace_custom_data_layer_with_named(
|
|
mesh->edge_data, CD_CREASE, CD_PROP_FLOAT, mesh->totedge, "crease_edge");
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Sharp Edge Conversion
|
|
* \{ */
|
|
|
|
void BKE_mesh_legacy_sharp_edges_from_flags(Mesh *mesh)
|
|
{
|
|
using namespace blender;
|
|
using namespace blender::bke;
|
|
if (!mesh->medge) {
|
|
return;
|
|
}
|
|
const Span<MEdge> edges(mesh->medge, mesh->totedge);
|
|
MutableAttributeAccessor attributes = mesh->attributes_for_write();
|
|
if (attributes.contains("sharp_edge")) {
|
|
return;
|
|
}
|
|
if (std::any_of(edges.begin(), edges.end(), [](const MEdge &edge) {
|
|
return edge.flag_legacy & ME_SHARP;
|
|
}))
|
|
{
|
|
SpanAttributeWriter<bool> sharp_edges = attributes.lookup_or_add_for_write_only_span<bool>(
|
|
"sharp_edge", ATTR_DOMAIN_EDGE);
|
|
threading::parallel_for(edges.index_range(), 4096, [&](const IndexRange range) {
|
|
for (const int i : range) {
|
|
sharp_edges.span[i] = edges[i].flag_legacy & ME_SHARP;
|
|
}
|
|
});
|
|
sharp_edges.finish();
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name UV Seam Conversion
|
|
* \{ */
|
|
|
|
void BKE_mesh_legacy_uv_seam_from_flags(Mesh *mesh)
|
|
{
|
|
using namespace blender;
|
|
using namespace blender::bke;
|
|
if (!mesh->medge) {
|
|
return;
|
|
}
|
|
MutableSpan<MEdge> edges(mesh->medge, mesh->totedge);
|
|
MutableAttributeAccessor attributes = mesh->attributes_for_write();
|
|
if (attributes.contains(".uv_seam")) {
|
|
return;
|
|
}
|
|
if (std::any_of(edges.begin(), edges.end(), [](const MEdge &edge) {
|
|
return edge.flag_legacy & ME_SEAM;
|
|
}))
|
|
{
|
|
SpanAttributeWriter<bool> uv_seams = attributes.lookup_or_add_for_write_only_span<bool>(
|
|
".uv_seam", ATTR_DOMAIN_EDGE);
|
|
threading::parallel_for(edges.index_range(), 4096, [&](const IndexRange range) {
|
|
for (const int i : range) {
|
|
uv_seams.span[i] = edges[i].flag_legacy & ME_SEAM;
|
|
}
|
|
});
|
|
uv_seams.finish();
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Hide Attribute and Legacy Flag Conversion
|
|
* \{ */
|
|
|
|
void BKE_mesh_legacy_convert_flags_to_hide_layers(Mesh *mesh)
|
|
{
|
|
using namespace blender;
|
|
using namespace blender::bke;
|
|
MutableAttributeAccessor attributes = mesh->attributes_for_write();
|
|
if (!mesh->mvert || attributes.contains(".hide_vert") || attributes.contains(".hide_edge") ||
|
|
attributes.contains(".hide_poly"))
|
|
{
|
|
return;
|
|
}
|
|
const Span<MVert> verts(mesh->mvert, mesh->totvert);
|
|
if (std::any_of(verts.begin(), verts.end(), [](const MVert &vert) {
|
|
return vert.flag_legacy & ME_HIDE;
|
|
}))
|
|
{
|
|
SpanAttributeWriter<bool> hide_vert = attributes.lookup_or_add_for_write_only_span<bool>(
|
|
".hide_vert", ATTR_DOMAIN_POINT);
|
|
threading::parallel_for(verts.index_range(), 4096, [&](IndexRange range) {
|
|
for (const int i : range) {
|
|
hide_vert.span[i] = verts[i].flag_legacy & ME_HIDE;
|
|
}
|
|
});
|
|
hide_vert.finish();
|
|
}
|
|
|
|
if (mesh->medge) {
|
|
const Span<MEdge> edges(mesh->medge, mesh->totedge);
|
|
if (std::any_of(edges.begin(), edges.end(), [](const MEdge &edge) {
|
|
return edge.flag_legacy & ME_HIDE;
|
|
}))
|
|
{
|
|
SpanAttributeWriter<bool> hide_edge = attributes.lookup_or_add_for_write_only_span<bool>(
|
|
".hide_edge", ATTR_DOMAIN_EDGE);
|
|
threading::parallel_for(edges.index_range(), 4096, [&](IndexRange range) {
|
|
for (const int i : range) {
|
|
hide_edge.span[i] = edges[i].flag_legacy & ME_HIDE;
|
|
}
|
|
});
|
|
hide_edge.finish();
|
|
}
|
|
}
|
|
|
|
const Span<MPoly> polys(
|
|
static_cast<const MPoly *>(CustomData_get_layer(&mesh->face_data, CD_MPOLY)),
|
|
mesh->faces_num);
|
|
if (std::any_of(polys.begin(), polys.end(), [](const MPoly &poly) {
|
|
return poly.flag_legacy & ME_HIDE;
|
|
}))
|
|
{
|
|
SpanAttributeWriter<bool> hide_poly = attributes.lookup_or_add_for_write_only_span<bool>(
|
|
".hide_poly", ATTR_DOMAIN_FACE);
|
|
threading::parallel_for(polys.index_range(), 4096, [&](IndexRange range) {
|
|
for (const int i : range) {
|
|
hide_poly.span[i] = polys[i].flag_legacy & ME_HIDE;
|
|
}
|
|
});
|
|
hide_poly.finish();
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Material Index Conversion
|
|
* \{ */
|
|
|
|
void BKE_mesh_legacy_convert_mpoly_to_material_indices(Mesh *mesh)
|
|
{
|
|
using namespace blender;
|
|
using namespace blender::bke;
|
|
MutableAttributeAccessor attributes = mesh->attributes_for_write();
|
|
if (!CustomData_has_layer(&mesh->face_data, CD_MPOLY) || attributes.contains("material_index")) {
|
|
return;
|
|
}
|
|
const Span<MPoly> polys(
|
|
static_cast<const MPoly *>(CustomData_get_layer(&mesh->face_data, CD_MPOLY)),
|
|
mesh->faces_num);
|
|
if (std::any_of(
|
|
polys.begin(), polys.end(), [](const MPoly &poly) { return poly.mat_nr_legacy != 0; }))
|
|
{
|
|
SpanAttributeWriter<int> material_indices = attributes.lookup_or_add_for_write_only_span<int>(
|
|
"material_index", ATTR_DOMAIN_FACE);
|
|
threading::parallel_for(polys.index_range(), 4096, [&](IndexRange range) {
|
|
for (const int i : range) {
|
|
material_indices.span[i] = polys[i].mat_nr_legacy;
|
|
}
|
|
});
|
|
material_indices.finish();
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Generic UV Map Conversion
|
|
* \{ */
|
|
|
|
void BKE_mesh_legacy_convert_uvs_to_generic(Mesh *mesh)
|
|
{
|
|
using namespace blender;
|
|
using namespace blender::bke;
|
|
if (!CustomData_has_layer(&mesh->loop_data, CD_MLOOPUV)) {
|
|
return;
|
|
}
|
|
|
|
/* Store layer names since they will be removed, used to set the active status of new layers.
|
|
* Use intermediate #StringRef because the names can be null. */
|
|
|
|
Array<std::string> uv_names(CustomData_number_of_layers(&mesh->loop_data, CD_MLOOPUV));
|
|
for (const int i : uv_names.index_range()) {
|
|
uv_names[i] = CustomData_get_layer_name(&mesh->loop_data, CD_MLOOPUV, i);
|
|
}
|
|
const int active_name_i = uv_names.as_span().first_index_try(
|
|
StringRef(CustomData_get_active_layer_name(&mesh->loop_data, CD_MLOOPUV)));
|
|
const int default_name_i = uv_names.as_span().first_index_try(
|
|
StringRef(CustomData_get_render_layer_name(&mesh->loop_data, CD_MLOOPUV)));
|
|
|
|
for (const int i : uv_names.index_range()) {
|
|
const MLoopUV *mloopuv = static_cast<const MLoopUV *>(
|
|
CustomData_get_layer_named(&mesh->loop_data, CD_MLOOPUV, uv_names[i].c_str()));
|
|
const uint32_t needed_boolean_attributes = threading::parallel_reduce(
|
|
IndexRange(mesh->totloop),
|
|
4096,
|
|
0,
|
|
[&](const IndexRange range, uint32_t init) {
|
|
for (const int i : range) {
|
|
init |= mloopuv[i].flag;
|
|
}
|
|
return init;
|
|
},
|
|
[](const uint32_t a, const uint32_t b) { return a | b; });
|
|
|
|
float2 *coords = static_cast<float2 *>(
|
|
MEM_malloc_arrayN(mesh->totloop, sizeof(float2), __func__));
|
|
bool *vert_selection = nullptr;
|
|
bool *edge_selection = nullptr;
|
|
bool *pin = nullptr;
|
|
if (needed_boolean_attributes & MLOOPUV_VERTSEL) {
|
|
vert_selection = static_cast<bool *>(
|
|
MEM_malloc_arrayN(mesh->totloop, sizeof(bool), __func__));
|
|
}
|
|
if (needed_boolean_attributes & MLOOPUV_EDGESEL) {
|
|
edge_selection = static_cast<bool *>(
|
|
MEM_malloc_arrayN(mesh->totloop, sizeof(bool), __func__));
|
|
}
|
|
if (needed_boolean_attributes & MLOOPUV_PINNED) {
|
|
pin = static_cast<bool *>(MEM_malloc_arrayN(mesh->totloop, sizeof(bool), __func__));
|
|
}
|
|
|
|
threading::parallel_for(IndexRange(mesh->totloop), 4096, [&](IndexRange range) {
|
|
for (const int i : range) {
|
|
coords[i] = mloopuv[i].uv;
|
|
}
|
|
if (vert_selection) {
|
|
for (const int i : range) {
|
|
vert_selection[i] = mloopuv[i].flag & MLOOPUV_VERTSEL;
|
|
}
|
|
}
|
|
if (edge_selection) {
|
|
for (const int i : range) {
|
|
edge_selection[i] = mloopuv[i].flag & MLOOPUV_EDGESEL;
|
|
}
|
|
}
|
|
if (pin) {
|
|
for (const int i : range) {
|
|
pin[i] = mloopuv[i].flag & MLOOPUV_PINNED;
|
|
}
|
|
}
|
|
});
|
|
|
|
CustomData_free_layer_named(&mesh->loop_data, uv_names[i].c_str(), mesh->totloop);
|
|
|
|
char new_name[MAX_CUSTOMDATA_LAYER_NAME];
|
|
BKE_id_attribute_calc_unique_name(&mesh->id, uv_names[i].c_str(), new_name);
|
|
uv_names[i] = new_name;
|
|
|
|
CustomData_add_layer_named_with_data(
|
|
&mesh->loop_data, CD_PROP_FLOAT2, coords, mesh->totloop, new_name, nullptr);
|
|
char buffer[MAX_CUSTOMDATA_LAYER_NAME];
|
|
if (vert_selection) {
|
|
CustomData_add_layer_named_with_data(&mesh->loop_data,
|
|
CD_PROP_BOOL,
|
|
vert_selection,
|
|
mesh->totloop,
|
|
BKE_uv_map_vert_select_name_get(new_name, buffer),
|
|
nullptr);
|
|
}
|
|
if (edge_selection) {
|
|
CustomData_add_layer_named_with_data(&mesh->loop_data,
|
|
CD_PROP_BOOL,
|
|
edge_selection,
|
|
mesh->totloop,
|
|
BKE_uv_map_edge_select_name_get(new_name, buffer),
|
|
nullptr);
|
|
}
|
|
if (pin) {
|
|
CustomData_add_layer_named_with_data(&mesh->loop_data,
|
|
CD_PROP_BOOL,
|
|
pin,
|
|
mesh->totloop,
|
|
BKE_uv_map_pin_name_get(new_name, buffer),
|
|
nullptr);
|
|
}
|
|
}
|
|
|
|
if (active_name_i != -1) {
|
|
CustomData_set_layer_active_index(
|
|
&mesh->loop_data,
|
|
CD_PROP_FLOAT2,
|
|
CustomData_get_named_layer_index(
|
|
&mesh->loop_data, CD_PROP_FLOAT2, uv_names[active_name_i].c_str()));
|
|
}
|
|
if (default_name_i != -1) {
|
|
CustomData_set_layer_render_index(
|
|
&mesh->loop_data,
|
|
CD_PROP_FLOAT2,
|
|
CustomData_get_named_layer_index(
|
|
&mesh->loop_data, CD_PROP_FLOAT2, uv_names[default_name_i].c_str()));
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/** \name Selection Attribute and Legacy Flag Conversion
|
|
* \{ */
|
|
|
|
void BKE_mesh_legacy_convert_flags_to_selection_layers(Mesh *mesh)
|
|
{
|
|
using namespace blender;
|
|
using namespace blender::bke;
|
|
MutableAttributeAccessor attributes = mesh->attributes_for_write();
|
|
if (!mesh->mvert || attributes.contains(".select_vert") || attributes.contains(".select_edge") ||
|
|
attributes.contains(".select_poly"))
|
|
{
|
|
return;
|
|
}
|
|
|
|
const Span<MVert> verts(mesh->mvert, mesh->totvert);
|
|
if (std::any_of(
|
|
verts.begin(), verts.end(), [](const MVert &vert) { return vert.flag_legacy & SELECT; }))
|
|
{
|
|
SpanAttributeWriter<bool> select_vert = attributes.lookup_or_add_for_write_only_span<bool>(
|
|
".select_vert", ATTR_DOMAIN_POINT);
|
|
threading::parallel_for(verts.index_range(), 4096, [&](IndexRange range) {
|
|
for (const int i : range) {
|
|
select_vert.span[i] = verts[i].flag_legacy & SELECT;
|
|
}
|
|
});
|
|
select_vert.finish();
|
|
}
|
|
|
|
if (mesh->medge) {
|
|
const Span<MEdge> edges(mesh->medge, mesh->totedge);
|
|
if (std::any_of(edges.begin(), edges.end(), [](const MEdge &edge) {
|
|
return edge.flag_legacy & SELECT;
|
|
}))
|
|
{
|
|
SpanAttributeWriter<bool> select_edge = attributes.lookup_or_add_for_write_only_span<bool>(
|
|
".select_edge", ATTR_DOMAIN_EDGE);
|
|
threading::parallel_for(edges.index_range(), 4096, [&](IndexRange range) {
|
|
for (const int i : range) {
|
|
select_edge.span[i] = edges[i].flag_legacy & SELECT;
|
|
}
|
|
});
|
|
select_edge.finish();
|
|
}
|
|
}
|
|
|
|
const Span<MPoly> polys(
|
|
static_cast<const MPoly *>(CustomData_get_layer(&mesh->face_data, CD_MPOLY)),
|
|
mesh->faces_num);
|
|
if (std::any_of(polys.begin(), polys.end(), [](const MPoly &poly) {
|
|
return poly.flag_legacy & ME_FACE_SEL;
|
|
}))
|
|
{
|
|
SpanAttributeWriter<bool> select_poly = attributes.lookup_or_add_for_write_only_span<bool>(
|
|
".select_poly", ATTR_DOMAIN_FACE);
|
|
threading::parallel_for(polys.index_range(), 4096, [&](IndexRange range) {
|
|
for (const int i : range) {
|
|
select_poly.span[i] = polys[i].flag_legacy & ME_FACE_SEL;
|
|
}
|
|
});
|
|
select_poly.finish();
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Vertex and Position Conversion
|
|
* \{ */
|
|
|
|
void BKE_mesh_legacy_convert_verts_to_positions(Mesh *mesh)
|
|
{
|
|
using namespace blender;
|
|
using namespace blender::bke;
|
|
const MVert *mvert = static_cast<const MVert *>(
|
|
CustomData_get_layer(&mesh->vert_data, CD_MVERT));
|
|
if (!mvert || CustomData_has_layer_named(&mesh->vert_data, CD_PROP_FLOAT3, "position")) {
|
|
return;
|
|
}
|
|
|
|
const Span<MVert> verts(mvert, mesh->totvert);
|
|
MutableSpan<float3> positions(
|
|
static_cast<float3 *>(CustomData_add_layer_named(
|
|
&mesh->vert_data, CD_PROP_FLOAT3, CD_CONSTRUCT, mesh->totvert, "position")),
|
|
mesh->totvert);
|
|
threading::parallel_for(verts.index_range(), 2048, [&](IndexRange range) {
|
|
for (const int i : range) {
|
|
positions[i] = verts[i].co_legacy;
|
|
}
|
|
});
|
|
|
|
CustomData_free_layers(&mesh->vert_data, CD_MVERT, mesh->totvert);
|
|
mesh->mvert = nullptr;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name MEdge and int2 conversion
|
|
* \{ */
|
|
|
|
void BKE_mesh_legacy_convert_edges_to_generic(Mesh *mesh)
|
|
{
|
|
using namespace blender;
|
|
using namespace blender::bke;
|
|
const MEdge *medge = static_cast<const MEdge *>(
|
|
CustomData_get_layer(&mesh->edge_data, CD_MEDGE));
|
|
if (!medge || CustomData_has_layer_named(&mesh->edge_data, CD_PROP_INT32_2D, ".edge_verts")) {
|
|
return;
|
|
}
|
|
|
|
const Span<MEdge> legacy_edges(medge, mesh->totedge);
|
|
MutableSpan<int2> edges(
|
|
static_cast<int2 *>(CustomData_add_layer_named(
|
|
&mesh->edge_data, CD_PROP_INT32_2D, CD_CONSTRUCT, mesh->totedge, ".edge_verts")),
|
|
mesh->totedge);
|
|
threading::parallel_for(legacy_edges.index_range(), 2048, [&](IndexRange range) {
|
|
for (const int i : range) {
|
|
edges[i] = int2(legacy_edges[i].v1, legacy_edges[i].v2);
|
|
}
|
|
});
|
|
|
|
CustomData_free_layers(&mesh->edge_data, CD_MEDGE, mesh->totedge);
|
|
mesh->medge = nullptr;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Attribute Active Flag to String Conversion
|
|
* \{ */
|
|
|
|
void BKE_mesh_legacy_attribute_flags_to_strings(Mesh *mesh)
|
|
{
|
|
using namespace blender;
|
|
/* It's not clear whether the active/render status was stored in the dedicated flags or in the
|
|
* generic CustomData layer indices, so convert from both, preferring the explicit flags. */
|
|
|
|
auto active_from_flags = [&](const CustomData &data) {
|
|
if (!mesh->active_color_attribute) {
|
|
for (const int i : IndexRange(data.totlayer)) {
|
|
if (data.layers[i].flag & CD_FLAG_COLOR_ACTIVE) {
|
|
mesh->active_color_attribute = BLI_strdup(data.layers[i].name);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
auto active_from_indices = [&](const CustomData &data) {
|
|
if (!mesh->active_color_attribute) {
|
|
const int i = CustomData_get_active_layer_index(&data, CD_PROP_COLOR);
|
|
if (i != -1) {
|
|
mesh->active_color_attribute = BLI_strdup(data.layers[i].name);
|
|
}
|
|
}
|
|
if (!mesh->active_color_attribute) {
|
|
const int i = CustomData_get_active_layer_index(&data, CD_PROP_BYTE_COLOR);
|
|
if (i != -1) {
|
|
mesh->active_color_attribute = BLI_strdup(data.layers[i].name);
|
|
}
|
|
}
|
|
};
|
|
auto default_from_flags = [&](const CustomData &data) {
|
|
if (!mesh->default_color_attribute) {
|
|
for (const int i : IndexRange(data.totlayer)) {
|
|
if (data.layers[i].flag & CD_FLAG_COLOR_RENDER) {
|
|
mesh->default_color_attribute = BLI_strdup(data.layers[i].name);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
auto default_from_indices = [&](const CustomData &data) {
|
|
if (!mesh->default_color_attribute) {
|
|
const int i = CustomData_get_render_layer_index(&data, CD_PROP_COLOR);
|
|
if (i != -1) {
|
|
mesh->default_color_attribute = BLI_strdup(data.layers[i].name);
|
|
}
|
|
}
|
|
if (!mesh->default_color_attribute) {
|
|
const int i = CustomData_get_render_layer_index(&data, CD_PROP_BYTE_COLOR);
|
|
if (i != -1) {
|
|
mesh->default_color_attribute = BLI_strdup(data.layers[i].name);
|
|
}
|
|
}
|
|
};
|
|
|
|
active_from_flags(mesh->vert_data);
|
|
active_from_flags(mesh->loop_data);
|
|
active_from_indices(mesh->vert_data);
|
|
active_from_indices(mesh->loop_data);
|
|
|
|
default_from_flags(mesh->vert_data);
|
|
default_from_flags(mesh->loop_data);
|
|
default_from_indices(mesh->vert_data);
|
|
default_from_indices(mesh->loop_data);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Face Corner Conversion
|
|
* \{ */
|
|
|
|
void BKE_mesh_legacy_convert_loops_to_corners(Mesh *mesh)
|
|
{
|
|
using namespace blender;
|
|
if (CustomData_has_layer_named(&mesh->loop_data, CD_PROP_INT32, ".corner_vert") &&
|
|
CustomData_has_layer_named(&mesh->loop_data, CD_PROP_INT32, ".corner_edge"))
|
|
{
|
|
return;
|
|
}
|
|
const Span<MLoop> loops(
|
|
static_cast<const MLoop *>(CustomData_get_layer(&mesh->loop_data, CD_MLOOP)), mesh->totloop);
|
|
MutableSpan<int> corner_verts(
|
|
static_cast<int *>(CustomData_add_layer_named(
|
|
&mesh->loop_data, CD_PROP_INT32, CD_CONSTRUCT, mesh->totloop, ".corner_vert")),
|
|
mesh->totloop);
|
|
MutableSpan<int> corner_edges(
|
|
static_cast<int *>(CustomData_add_layer_named(
|
|
&mesh->loop_data, CD_PROP_INT32, CD_CONSTRUCT, mesh->totloop, ".corner_edge")),
|
|
mesh->totloop);
|
|
threading::parallel_for(loops.index_range(), 2048, [&](IndexRange range) {
|
|
for (const int i : range) {
|
|
corner_verts[i] = loops[i].v;
|
|
corner_edges[i] = loops[i].e;
|
|
}
|
|
});
|
|
|
|
CustomData_free_layers(&mesh->loop_data, CD_MLOOP, mesh->totloop);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Poly Offset Conversion
|
|
* \{ */
|
|
|
|
static bool poly_loops_orders_match(const Span<MPoly> polys)
|
|
{
|
|
for (const int i : polys.index_range().drop_back(1)) {
|
|
if (polys[i].loopstart > polys[i + 1].loopstart) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void BKE_mesh_legacy_convert_polys_to_offsets(Mesh *mesh)
|
|
{
|
|
using namespace blender;
|
|
if (mesh->face_offset_indices) {
|
|
return;
|
|
}
|
|
const Span<MPoly> polys(
|
|
static_cast<const MPoly *>(CustomData_get_layer(&mesh->face_data, CD_MPOLY)),
|
|
mesh->faces_num);
|
|
|
|
BKE_mesh_face_offsets_ensure_alloc(mesh);
|
|
MutableSpan<int> offsets = mesh->face_offsets_for_write();
|
|
|
|
if (poly_loops_orders_match(polys)) {
|
|
for (const int i : polys.index_range()) {
|
|
offsets[i] = polys[i].loopstart;
|
|
}
|
|
}
|
|
else {
|
|
/* Reorder mesh polygons to match the order of their loops. */
|
|
Array<int> orig_indices(polys.size());
|
|
array_utils::fill_index_range<int>(orig_indices);
|
|
std::stable_sort(orig_indices.begin(), orig_indices.end(), [polys](const int a, const int b) {
|
|
return polys[a].loopstart < polys[b].loopstart;
|
|
});
|
|
CustomData old_poly_data = mesh->face_data;
|
|
CustomData_reset(&mesh->face_data);
|
|
CustomData_copy_layout(
|
|
&old_poly_data, &mesh->face_data, CD_MASK_MESH.pmask, CD_CONSTRUCT, mesh->faces_num);
|
|
|
|
int offset = 0;
|
|
for (const int i : orig_indices.index_range()) {
|
|
offsets[i] = offset;
|
|
offset += polys[orig_indices[i]].totloop;
|
|
}
|
|
|
|
threading::parallel_for(orig_indices.index_range(), 1024, [&](const IndexRange range) {
|
|
for (const int i : range) {
|
|
CustomData_copy_data(&old_poly_data, &mesh->face_data, orig_indices[i], i, 1);
|
|
}
|
|
});
|
|
|
|
CustomData_free(&old_poly_data, mesh->faces_num);
|
|
}
|
|
|
|
CustomData_free_layers(&mesh->face_data, CD_MPOLY, mesh->faces_num);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Auto Smooth Conversion
|
|
* \{ */
|
|
|
|
static bNodeTree *add_auto_smooth_node_tree(Main &bmain)
|
|
{
|
|
bNodeTree *group = ntreeAddTree(&bmain, DATA_("Auto Smooth"), "GeometryNodeTree");
|
|
if (!group->geometry_node_asset_traits) {
|
|
group->geometry_node_asset_traits = MEM_new<GeometryNodeAssetTraits>(__func__);
|
|
}
|
|
group->geometry_node_asset_traits->flag |= GEO_NODE_ASSET_MODIFIER;
|
|
|
|
group->tree_interface.add_socket(DATA_("Geometry"),
|
|
"",
|
|
"NodeSocketGeometry",
|
|
NODE_INTERFACE_SOCKET_INPUT | NODE_INTERFACE_SOCKET_OUTPUT,
|
|
nullptr);
|
|
bNodeTreeInterfaceSocket *angle_io_socket = group->tree_interface.add_socket(
|
|
DATA_("Angle"), "", "NodeSocketFloat", NODE_INTERFACE_SOCKET_INPUT, nullptr);
|
|
auto &angle_data = *static_cast<bNodeSocketValueFloat *>(angle_io_socket->socket_data);
|
|
angle_data.min = 0.0f;
|
|
angle_data.max = DEG2RADF(180.0f);
|
|
angle_data.subtype = PROP_ANGLE;
|
|
|
|
bNode *group_output = nodeAddNode(nullptr, group, "NodeGroupOutput");
|
|
group_output->locx = 480.0f;
|
|
group_output->locy = -100.0f;
|
|
bNode *group_input_angle = nodeAddNode(nullptr, group, "NodeGroupInput");
|
|
group_input_angle->locx = -420.0f;
|
|
group_input_angle->locy = -300.0f;
|
|
LISTBASE_FOREACH (bNodeSocket *, socket, &group_input_angle->outputs) {
|
|
if (!STREQ(socket->identifier, "Socket_1")) {
|
|
socket->flag |= SOCK_HIDDEN;
|
|
}
|
|
}
|
|
bNode *group_input_mesh = nodeAddNode(nullptr, group, "NodeGroupInput");
|
|
group_input_mesh->locx = -60.0f;
|
|
group_input_mesh->locy = -100.0f;
|
|
LISTBASE_FOREACH (bNodeSocket *, socket, &group_input_mesh->outputs) {
|
|
if (!STREQ(socket->identifier, "Socket_0")) {
|
|
socket->flag |= SOCK_HIDDEN;
|
|
}
|
|
}
|
|
bNode *shade_smooth_edge = nodeAddNode(nullptr, group, "GeometryNodeSetShadeSmooth");
|
|
shade_smooth_edge->custom1 = ATTR_DOMAIN_EDGE;
|
|
shade_smooth_edge->locx = 120.0f;
|
|
shade_smooth_edge->locy = -100.0f;
|
|
bNode *shade_smooth_face = nodeAddNode(nullptr, group, "GeometryNodeSetShadeSmooth");
|
|
shade_smooth_face->custom1 = ATTR_DOMAIN_FACE;
|
|
shade_smooth_face->locx = 300.0f;
|
|
shade_smooth_face->locy = -100.0f;
|
|
bNode *edge_angle = nodeAddNode(nullptr, group, "GeometryNodeInputMeshEdgeAngle");
|
|
edge_angle->locx = -420.0f;
|
|
edge_angle->locy = -220.0f;
|
|
bNode *edge_smooth = nodeAddNode(nullptr, group, "GeometryNodeInputEdgeSmooth");
|
|
edge_smooth->locx = -60.0f;
|
|
edge_smooth->locy = -160.0f;
|
|
bNode *face_smooth = nodeAddNode(nullptr, group, "GeometryNodeInputShadeSmooth");
|
|
face_smooth->locx = -240.0f;
|
|
face_smooth->locy = -340.0f;
|
|
bNode *boolean_and = nodeAddNode(nullptr, group, "FunctionNodeBooleanMath");
|
|
boolean_and->custom1 = NODE_BOOLEAN_MATH_AND;
|
|
boolean_and->locx = -60.0f;
|
|
boolean_and->locy = -220.0f;
|
|
bNode *less_than_or_equal = nodeAddNode(nullptr, group, "FunctionNodeCompare");
|
|
static_cast<NodeFunctionCompare *>(less_than_or_equal->storage)->operation =
|
|
NODE_COMPARE_LESS_EQUAL;
|
|
less_than_or_equal->locx = -240.0f;
|
|
less_than_or_equal->locy = -180.0f;
|
|
|
|
nodeAddLink(group,
|
|
edge_angle,
|
|
nodeFindSocket(edge_angle, SOCK_OUT, "Unsigned Angle"),
|
|
less_than_or_equal,
|
|
nodeFindSocket(less_than_or_equal, SOCK_IN, "A"));
|
|
nodeAddLink(group,
|
|
shade_smooth_face,
|
|
nodeFindSocket(shade_smooth_face, SOCK_OUT, "Geometry"),
|
|
group_output,
|
|
nodeFindSocket(group_output, SOCK_IN, "Socket_0"));
|
|
nodeAddLink(group,
|
|
group_input_angle,
|
|
nodeFindSocket(group_input_angle, SOCK_OUT, "Socket_1"),
|
|
less_than_or_equal,
|
|
nodeFindSocket(less_than_or_equal, SOCK_IN, "B"));
|
|
nodeAddLink(group,
|
|
less_than_or_equal,
|
|
nodeFindSocket(less_than_or_equal, SOCK_OUT, "Result"),
|
|
boolean_and,
|
|
nodeFindSocket(boolean_and, SOCK_IN, "Boolean"));
|
|
nodeAddLink(group,
|
|
face_smooth,
|
|
nodeFindSocket(face_smooth, SOCK_OUT, "Smooth"),
|
|
boolean_and,
|
|
nodeFindSocket(boolean_and, SOCK_IN, "Boolean_001"));
|
|
nodeAddLink(group,
|
|
group_input_mesh,
|
|
nodeFindSocket(group_input_mesh, SOCK_OUT, "Socket_0"),
|
|
shade_smooth_edge,
|
|
nodeFindSocket(shade_smooth_edge, SOCK_IN, "Geometry"));
|
|
nodeAddLink(group,
|
|
edge_smooth,
|
|
nodeFindSocket(edge_smooth, SOCK_OUT, "Smooth"),
|
|
shade_smooth_edge,
|
|
nodeFindSocket(shade_smooth_edge, SOCK_IN, "Selection"));
|
|
nodeAddLink(group,
|
|
shade_smooth_edge,
|
|
nodeFindSocket(shade_smooth_edge, SOCK_OUT, "Geometry"),
|
|
shade_smooth_face,
|
|
nodeFindSocket(shade_smooth_face, SOCK_IN, "Geometry"));
|
|
nodeAddLink(group,
|
|
boolean_and,
|
|
nodeFindSocket(boolean_and, SOCK_OUT, "Boolean"),
|
|
shade_smooth_edge,
|
|
nodeFindSocket(shade_smooth_edge, SOCK_IN, "Shade Smooth"));
|
|
|
|
LISTBASE_FOREACH (bNode *, node, &group->nodes) {
|
|
nodeSetSelected(node, false);
|
|
}
|
|
|
|
return group;
|
|
}
|
|
|
|
void BKE_main_mesh_legacy_convert_auto_smooth(Main &bmain)
|
|
{
|
|
using namespace blender;
|
|
bNodeTree *auto_smooth_node_tree = nullptr;
|
|
LISTBASE_FOREACH (Object *, object, &bmain.objects) {
|
|
if (object->type != OB_MESH) {
|
|
continue;
|
|
}
|
|
Mesh *mesh = static_cast<Mesh *>(object->data);
|
|
if (!(mesh->flag & ME_AUTOSMOOTH_LEGACY)) {
|
|
continue;
|
|
}
|
|
if (CustomData_has_layer(&mesh->loop_data, CD_CUSTOMLOOPNORMAL)) {
|
|
continue;
|
|
}
|
|
if (!auto_smooth_node_tree) {
|
|
auto_smooth_node_tree = add_auto_smooth_node_tree(bmain);
|
|
BKE_ntree_update_main_tree(&bmain, auto_smooth_node_tree, nullptr);
|
|
}
|
|
auto *md = reinterpret_cast<NodesModifierData *>(BKE_modifier_new(eModifierType_Nodes));
|
|
STRNCPY(md->modifier.name, DATA_("Auto Smooth"));
|
|
BKE_modifier_unique_name(&object->modifiers, &md->modifier);
|
|
md->node_group = auto_smooth_node_tree;
|
|
if (!BLI_listbase_is_empty(&object->modifiers) &&
|
|
static_cast<ModifierData *>(object->modifiers.last)->type == eModifierType_Subsurf)
|
|
{
|
|
/* Add the auto smooth node group before the last subdivision surface modifier if possible.
|
|
* Subdivision surface modifiers have special handling for interpolating face corner normals,
|
|
* and recalculating them afterwards isn't usually helpful and can be much slower. */
|
|
BLI_insertlinkbefore(&object->modifiers, object->modifiers.last, md);
|
|
}
|
|
else {
|
|
BLI_addtail(&object->modifiers, md);
|
|
}
|
|
|
|
md->settings.properties = bke::idprop::create_group("Nodes Modifier Settings").release();
|
|
IDProperty *angle_prop =
|
|
bke::idprop::create(DATA_("Socket_1"), mesh->smoothresh_legacy).release();
|
|
auto *ui_data = reinterpret_cast<IDPropertyUIDataFloat *>(IDP_ui_data_ensure(angle_prop));
|
|
ui_data->base.rna_subtype = PROP_ANGLE;
|
|
ui_data->soft_min = 0.0f;
|
|
ui_data->soft_max = DEG2RADF(180.0f);
|
|
IDP_AddToGroup(md->settings.properties, angle_prop);
|
|
IDP_AddToGroup(md->settings.properties,
|
|
bke::idprop::create(DATA_("Input_1_use_attribute"), 0).release());
|
|
IDP_AddToGroup(md->settings.properties,
|
|
bke::idprop::create(DATA_("Input_1_attribute_name"), "").release());
|
|
}
|
|
}
|
|
|
|
namespace blender::bke {
|
|
|
|
void mesh_sculpt_mask_to_legacy(MutableSpan<CustomDataLayer> vert_layers)
|
|
{
|
|
bool changed = false;
|
|
for (CustomDataLayer &layer : vert_layers) {
|
|
if (StringRef(layer.name) == ".sculpt_mask") {
|
|
layer.type = CD_PAINT_MASK;
|
|
layer.name[0] = '\0';
|
|
changed = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!changed) {
|
|
return;
|
|
}
|
|
/* #CustomData expects the layers to be sorted in increasing order based on type. */
|
|
std::stable_sort(
|
|
vert_layers.begin(),
|
|
vert_layers.end(),
|
|
[](const CustomDataLayer &a, const CustomDataLayer &b) { return a.type < b.type; });
|
|
}
|
|
|
|
void mesh_sculpt_mask_to_generic(Mesh &mesh)
|
|
{
|
|
if (mesh.attributes().contains(".sculpt_mask")) {
|
|
return;
|
|
}
|
|
void *data = nullptr;
|
|
const ImplicitSharingInfo *sharing_info = nullptr;
|
|
for (const int i : IndexRange(mesh.vert_data.totlayer)) {
|
|
CustomDataLayer &layer = mesh.vert_data.layers[i];
|
|
if (layer.type == CD_PAINT_MASK) {
|
|
data = layer.data;
|
|
sharing_info = layer.sharing_info;
|
|
layer.data = nullptr;
|
|
layer.sharing_info = nullptr;
|
|
CustomData_free_layer(&mesh.vert_data, CD_PAINT_MASK, mesh.totvert, i);
|
|
break;
|
|
}
|
|
}
|
|
if (data != nullptr) {
|
|
CustomData_add_layer_named_with_data(
|
|
&mesh.vert_data, CD_PROP_FLOAT, data, mesh.totvert, ".sculpt_mask", sharing_info);
|
|
}
|
|
if (sharing_info != nullptr) {
|
|
sharing_info->remove_user_and_delete_if_last();
|
|
}
|
|
}
|
|
|
|
//
|
|
} // namespace blender::bke
|
|
|
|
/** \} */
|