Geometry: add utility to check for bad geometry element index dependence #113030

Merged
Jacques Lucke merged 21 commits from JacquesLucke/blender:index-dependence-check into blender-v4.0-release 2023-09-29 21:44:45 +02:00
32 changed files with 423 additions and 0 deletions

View File

@ -129,6 +129,15 @@ typedef struct Global {
*/
int debug;
/**
* When true, various geometry processing algorithms randomize the order of elements (e.g.
* vertices or edges) in the output. In many cases, we don't make guarantees about the exact
* order of elements. So if users depend on the indices with e.g. geometry nodes, their file can
* break in a different Blender version. Explicitly turning on randomization can help protect
* oneself against such breakages.
*/
bool randomize_geometry_element_order;
/**
* Control behavior of file reading/writing.
*

View File

@ -24,6 +24,7 @@ set(INC_SYS
set(SRC
geometry_attributes.cc
geometry_ops.cc
geometry_randomization.cc
node_group_operator.cc
geometry_intern.hh

View File

@ -21,6 +21,7 @@ void GEOMETRY_OT_color_attribute_render_set(wmOperatorType *ot);
void GEOMETRY_OT_color_attribute_duplicate(wmOperatorType *ot);
void GEOMETRY_OT_attribute_convert(wmOperatorType *ot);
void GEOMETRY_OT_color_attribute_convert(wmOperatorType *ot);
void GEOMETRY_OT_geometry_randomization(wmOperatorType *ot);
void GEOMETRY_OT_execute_node_group(wmOperatorType *ot);

View File

@ -27,4 +27,5 @@ void ED_operatortypes_geometry()
WM_operatortype_append(GEOMETRY_OT_attribute_convert);
WM_operatortype_append(GEOMETRY_OT_color_attribute_convert);
WM_operatortype_append(GEOMETRY_OT_execute_node_group);
WM_operatortype_append(GEOMETRY_OT_geometry_randomization);
}

View File

@ -0,0 +1,58 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "WM_api.hh"
#include "BKE_context.h"
#include "BKE_global.h"
#include "BKE_main.h"
#include "DEG_depsgraph.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "geometry_intern.hh"
namespace blender::ed::geometry {
static int geometry_randomization_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
RNA_boolean_set(op->ptr, "value", G.randomize_geometry_element_order);
return WM_operator_props_popup(C, op, event);
}
static int geometry_randomization_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
G.randomize_geometry_element_order = RNA_boolean_get(op->ptr, "value");
LISTBASE_FOREACH (Object *, object, &bmain->objects) {
DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY);
}
WM_event_add_notifier(C, NC_WINDOW, nullptr);
return OPERATOR_FINISHED;
}
void GEOMETRY_OT_geometry_randomization(wmOperatorType *ot)
{
ot->name = "Set Geometry Randomization";
ot->idname = "GEOMETRY_OT_geometry_randomization";
ot->description = "Toggle geometry randomization for debugging purposes";
ot->exec = geometry_randomization_exec;
ot->invoke = geometry_randomization_invoke;
ot->flag |= OPTYPE_UNDO | OPTYPE_REGISTER;
RNA_def_boolean(ot->srna,
"value",
false,
"Value",
"Randomize the order of geometry elements (e.g. vertices or edges) after some "
"operations where there are no guarantees about the order. This avoids "
"accidentally depending on something that may change in the future");
JacquesLucke marked this conversation as resolved Outdated

is likely to -> may?

`is likely to` -> `may`?
}
} // namespace blender::ed::geometry

View File

@ -30,6 +30,7 @@ set(SRC
intern/mesh_to_volume.cc
intern/point_merge_by_distance.cc
intern/points_to_volume.cc
intern/randomize.cc
intern/realize_instances.cc
intern/resample_curves.cc
intern/reverse_uv_sampler.cc
@ -54,6 +55,7 @@ set(SRC
GEO_mesh_to_volume.hh
GEO_point_merge_by_distance.hh
GEO_points_to_volume.hh
GEO_randomize.hh
GEO_realize_instances.hh
GEO_resample_curves.hh
GEO_reverse_uv_sampler.hh

View File

@ -0,0 +1,24 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
struct Mesh;
struct PointCloud;
namespace blender::bke {
class CurvesGeometry;
}
namespace blender::geometry {
bool use_debug_randomization();
JacquesLucke marked this conversation as resolved
Review

I guess this is mostly an aesthetic thing, but I'd like to see see these functions have a debug_ prefix, and do the use_debug_randomization themselves. That way the calling code can be nicer without the if statements.

I guess this is mostly an aesthetic thing, but I'd like to see see these functions have a `debug_` prefix, and do the `use_debug_randomization` themselves. That way the calling code can be nicer without the if statements.
void debug_randomize_vertex_order(Mesh *mesh);
void debug_randomize_edge_order(Mesh *mesh);
void debug_randomize_face_order(Mesh *mesh);
void debug_randomize_mesh_order(Mesh *mesh);
void debug_randomize_point_order(PointCloud *pointcloud);
void debug_randomize_curve_order(bke::CurvesGeometry *curves);
}; // namespace blender::geometry

View File

@ -21,6 +21,7 @@
#include "BKE_mesh.hh"
#include "GEO_mesh_merge_by_distance.hh"
#include "GEO_randomize.hh"
#ifdef USE_WELD_DEBUG_TIME
# include "BLI_timeit.hh"
@ -1680,6 +1681,8 @@ static Mesh *create_merged_mesh(const Mesh &mesh,
BLI_assert(int(r_i) == result_nfaces);
BLI_assert(loop_cur == result_nloops);
debug_randomize_mesh_order(result);
return result;
}

View File

@ -12,6 +12,7 @@
#include "BKE_mesh_mapping.hh"
#include "GEO_mesh_split_edges.hh"
#include "GEO_randomize.hh"
namespace blender::geometry {
@ -591,6 +592,8 @@ void split_edges(Mesh &mesh,
propagate_vert_attributes(mesh, vert_map);
BKE_mesh_tag_edges_split(&mesh);
debug_randomize_mesh_order(&mesh);
}
} // namespace blender::geometry

View File

@ -17,6 +17,7 @@
#include "BKE_mesh.hh"
#include "GEO_mesh_to_curve.hh"
#include "GEO_randomize.hh"
namespace blender::geometry {
@ -74,6 +75,8 @@ BLI_NOINLINE bke::CurvesGeometry create_curve_from_vert_indices(
return true;
});
debug_randomize_curve_order(&curves);
return curves;
}

View File

@ -13,6 +13,7 @@
#include "BKE_pointcloud.h"
#include "GEO_point_merge_by_distance.hh"
#include "GEO_randomize.hh"
namespace blender::geometry {
@ -154,6 +155,8 @@ PointCloud *point_merge_by_distance(const PointCloud &src_points,
});
}
debug_randomize_point_order(dst_pointcloud);
return dst_pointcloud;
}

View File

@ -0,0 +1,231 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <algorithm>
#include <iostream>
#include <random>
#include "GEO_randomize.hh"
#include "DNA_curves_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_pointcloud_types.h"
#include "BKE_attribute.hh"
#include "BKE_attribute_math.hh"
#include "BKE_curves.hh"
#include "BKE_customdata.h"
#include "BKE_global.h"
#include "BKE_mesh.hh"
#include "BLI_array.hh"
namespace blender::geometry {
static Array<int> get_permutation(const int length, const int seed)
{
Array<int> data(length);
for (const int i : IndexRange(length)) {
data[i] = i;
}
std::shuffle(data.begin(), data.end(), std::default_random_engine(seed));
return data;
}
static Array<int> invert_permutation(const Span<int> permutation)
{
Array<int> data(permutation.size());
for (const int i : permutation.index_range()) {
data[permutation[i]] = i;
}
return data;
}
/**
* We can't use a fully random seed, because then the randomization wouldn't be deterministic,
* which is important to avoid causing issues when determinism is expected. Using a single constant
* seed is not ideal either, because then two geometries might be randomized equally or very
* similar. Ideally, the seed would be a hash of everything that feeds into the geometry processing
* algorithm before the randomization, but that's too expensive. Just use something simple but
* correct for now.
*/
static int seed_from_mesh(const Mesh &mesh)
{
return mesh.totvert;
}
static int seed_from_pointcloud(const PointCloud &pointcloud)
{
return pointcloud.totpoint;
}
static int seed_from_curves(const bke::CurvesGeometry &curves)
{
return curves.point_num;
}
static void reorder_customdata(CustomData &data, const Span<int> new_by_old_map)
{
CustomData new_data;
CustomData_copy_layout(&data, &new_data, CD_MASK_ALL, CD_CONSTRUCT, new_by_old_map.size());
for (const int old_i : new_by_old_map.index_range()) {
const int new_i = new_by_old_map[old_i];
CustomData_copy_data(&data, &new_data, old_i, new_i, 1);
}
CustomData_free(&data, new_by_old_map.size());
data = new_data;
}
void debug_randomize_vertex_order(Mesh *mesh)
{
if (mesh == nullptr || !use_debug_randomization()) {
return;
}
const int seed = seed_from_mesh(*mesh);
const Array<int> new_by_old_map = get_permutation(mesh->totvert, seed);
reorder_customdata(mesh->vert_data, new_by_old_map);
for (int &v : mesh->edges_for_write().cast<int>()) {
v = new_by_old_map[v];
}
for (int &v : mesh->corner_verts_for_write()) {
v = new_by_old_map[v];
}
BKE_mesh_tag_topology_changed(mesh);
}
void debug_randomize_edge_order(Mesh *mesh)
{
if (mesh == nullptr || !use_debug_randomization()) {
return;
}
const int seed = seed_from_mesh(*mesh);
const Array<int> new_by_old_map = get_permutation(mesh->totedge, seed);
reorder_customdata(mesh->edge_data, new_by_old_map);
for (int &e : mesh->corner_edges_for_write()) {
e = new_by_old_map[e];
}
BKE_mesh_tag_topology_changed(mesh);
}
static Array<int> make_new_offset_indices(const OffsetIndices<int> old_offsets,
const Span<int> old_by_new_map)
{
Array<int> new_offsets(old_offsets.data().size());
new_offsets[0] = 0;
for (const int new_i : old_offsets.index_range()) {
const int old_i = old_by_new_map[new_i];
new_offsets[new_i + 1] = new_offsets[new_i] + old_offsets[old_i].size();
}
return new_offsets;
}
static void reorder_customdata_groups(CustomData &data,
const OffsetIndices<int> old_offsets,
const OffsetIndices<int> new_offsets,
const Span<int> new_by_old_map)
{
const int elements_num = new_offsets.total_size();
const int groups_num = new_by_old_map.size();
CustomData new_data;
CustomData_copy_layout(&data, &new_data, CD_MASK_ALL, CD_CONSTRUCT, elements_num);
for (const int old_i : IndexRange(groups_num)) {
const int new_i = new_by_old_map[old_i];
const IndexRange old_range = old_offsets[old_i];
const IndexRange new_range = new_offsets[new_i];
BLI_assert(old_range.size() == new_range.size());
CustomData_copy_data(&data, &new_data, old_range.start(), new_range.start(), old_range.size());
}
CustomData_free(&data, elements_num);
data = new_data;
}
void debug_randomize_face_order(Mesh *mesh)
{
if (mesh == nullptr || !use_debug_randomization()) {
return;
}
const int seed = seed_from_mesh(*mesh);
const Array<int> new_by_old_map = get_permutation(mesh->faces_num, seed);
const Array<int> old_by_new_map = invert_permutation(new_by_old_map);
reorder_customdata(mesh->face_data, new_by_old_map);
const OffsetIndices old_faces = mesh->faces();
Array<int> new_face_offsets = make_new_offset_indices(old_faces, old_by_new_map);
const OffsetIndices<int> new_faces = new_face_offsets.as_span();
reorder_customdata_groups(mesh->loop_data, old_faces, new_faces, new_by_old_map);
mesh->face_offsets_for_write().copy_from(new_face_offsets);
BKE_mesh_tag_topology_changed(mesh);
}
void debug_randomize_point_order(PointCloud *pointcloud)
{
if (pointcloud == nullptr || !use_debug_randomization()) {
return;
}
const int seed = seed_from_pointcloud(*pointcloud);
const Array<int> new_by_old_map = get_permutation(pointcloud->totpoint, seed);
reorder_customdata(pointcloud->pdata, new_by_old_map);
pointcloud->tag_positions_changed();
pointcloud->tag_radii_changed();
}
void debug_randomize_curve_order(bke::CurvesGeometry *curves)
{
if (curves == nullptr || !use_debug_randomization()) {
return;
}
const int seed = seed_from_curves(*curves);
const Array<int> new_by_old_map = get_permutation(curves->curve_num, seed);
const Array<int> old_by_new_map = invert_permutation(new_by_old_map);
reorder_customdata(curves->curve_data, new_by_old_map);
const OffsetIndices old_points_by_curve = curves->points_by_curve();
Array<int> new_curve_offsets = make_new_offset_indices(old_points_by_curve, old_by_new_map);
const OffsetIndices<int> new_points_by_curve = new_curve_offsets.as_span();
reorder_customdata_groups(
curves->point_data, old_points_by_curve, new_points_by_curve, new_by_old_map);
curves->offsets_for_write().copy_from(new_curve_offsets);
curves->tag_topology_changed();
}
void debug_randomize_mesh_order(Mesh *mesh)
{
if (mesh == nullptr || !use_debug_randomization()) {
return;
}
debug_randomize_vertex_order(mesh);
debug_randomize_edge_order(mesh);
debug_randomize_face_order(mesh);
}
bool use_debug_randomization()
{
return G.randomize_geometry_element_order;
}
} // namespace blender::geometry

View File

@ -39,6 +39,8 @@
#include "BLO_read_write.hh"
#include "GEO_randomize.hh"
#include "bmesh.h"
#include "bmesh_tools.h"
@ -230,6 +232,8 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh
BM_mesh_free(bm);
blender::geometry::debug_randomize_mesh_order(result);
return result;
}

View File

@ -51,6 +51,8 @@
#include "MEM_guardedalloc.h"
#include "GEO_randomize.hh"
#include "bmesh.h"
#include "bmesh_tools.h"
#include "tools/bmesh_boolean.h"
@ -501,6 +503,8 @@ static Mesh *exact_boolean_mesh(BooleanModifierData *bmd,
MutableSpan(result->mat, result->totcol).copy_from(materials);
}
blender::geometry::debug_randomize_mesh_order(result);
return result;
}
#endif
@ -598,6 +602,8 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh
FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
}
blender::geometry::debug_randomize_mesh_order(result);
return result;
}

View File

@ -32,6 +32,8 @@
#include "DEG_depsgraph_query.hh"
#include "GEO_randomize.hh"
#include "bmesh.h"
#include "bmesh_tools.h"
@ -207,6 +209,8 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh
BM_mesh_free(bm);
blender::geometry::debug_randomize_mesh_order(result);
#ifdef USE_TIMEIT
TIMEIT_END(decim);
#endif

View File

@ -38,6 +38,8 @@
#include "MOD_modifiertypes.hh"
#include "MOD_ui_common.hh"
#include "GEO_randomize.hh"
/* For edge split modifier node. */
Mesh *doEdgeSplit(const Mesh *mesh, EdgeSplitModifierData *emd);
@ -103,6 +105,8 @@ Mesh *doEdgeSplit(const Mesh *mesh, EdgeSplitModifierData *emd)
result = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, mesh);
BM_mesh_free(bm);
blender::geometry::debug_randomize_mesh_order(result);
return result;
}

View File

@ -36,6 +36,8 @@
#include "MOD_modifiertypes.hh"
#include "MOD_ui_common.hh"
#include "GEO_randomize.hh"
#include <cstdlib>
#include <cstring>
@ -198,6 +200,9 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext * /*ctx*/,
BKE_mesh_copy_parameters_for_eval(result, mesh);
BKE_mesh_calc_edges(result, true, false);
blender::geometry::debug_randomize_mesh_order(result);
return result;
}

View File

@ -13,6 +13,8 @@
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "GEO_randomize.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_boolean_cc {
@ -145,6 +147,8 @@ static void node_geo_exec(GeoNodeExecParams params)
selection.finish();
}
geometry::debug_randomize_mesh_order(result);
params.set_output("Mesh", GeometrySet::from_mesh(result));
#else
params.error_message_add(NodeWarningType::Error,

View File

@ -10,6 +10,8 @@
#include "BKE_material.h"
#include "BKE_mesh.hh"
#include "GEO_randomize.hh"
#include "node_geometry_util.hh"
#ifdef WITH_BULLET
@ -215,6 +217,9 @@ static void node_geo_exec(GeoNodeExecParams params)
geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
Mesh *mesh = compute_hull(geometry_set);
if (mesh) {
geometry::debug_randomize_mesh_order(mesh);
}
geometry_set.replace_mesh(mesh);
geometry_set.keep_only_during_modify({GeometryComponent::Type::Mesh});
});

View File

@ -9,6 +9,8 @@
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "GEO_randomize.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_to_mesh_cc {
@ -37,11 +39,13 @@ static void geometry_set_curve_to_mesh(GeometrySet &geometry_set,
if (profile_curves == nullptr) {
Mesh *mesh = bke::curve_to_wire_mesh(curves.geometry.wrap(), propagation_info);
geometry::debug_randomize_mesh_order(mesh);
geometry_set.replace_mesh(mesh);
}
else {
Mesh *mesh = bke::curve_to_mesh_sweep(
curves.geometry.wrap(), profile_curves->geometry.wrap(), fill_caps, propagation_info);
geometry::debug_randomize_mesh_order(mesh);
geometry_set.replace_mesh(mesh);
}
}

View File

@ -22,6 +22,8 @@
#include "DEG_depsgraph_query.hh"
#include "GEO_randomize.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_distribute_points_in_volume_cc {
@ -253,6 +255,8 @@ static void node_geo_exec(GeoNodeExecParams params)
point_radii.span.fill(0.05f);
point_radii.finish();
geometry::debug_randomize_point_order(pointcloud);
geometry_set.replace_pointcloud(pointcloud);
geometry_set.keep_only_during_modify({GeometryComponent::Type::PointCloud});
});

View File

@ -24,6 +24,8 @@
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "GEO_randomize.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_distribute_points_on_faces_cc {
@ -556,6 +558,8 @@ static void point_distribution_calculate(GeometrySet &geometry_set,
const bool use_legacy_normal = params.node().custom2 != 0;
compute_attribute_outputs(
mesh, *pointcloud, bary_coords, looptri_indices, attribute_outputs, use_legacy_normal);
geometry::debug_randomize_point_order(pointcloud);
}
static void node_geo_exec(GeoNodeExecParams params)

View File

@ -12,6 +12,8 @@
#include "BKE_mesh.hh"
#include "BKE_mesh_mapping.hh"
#include "GEO_randomize.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_dual_mesh_cc {
@ -921,6 +923,7 @@ static void node_geo_exec(GeoNodeExecParams params)
if (const Mesh *mesh = geometry_set.get_mesh()) {
Mesh *new_mesh = calc_dual_mesh(
*mesh, keep_boundaries, params.get_output_propagation_info("Dual Mesh"));
geometry::debug_randomize_mesh_order(new_mesh);
geometry_set.replace_mesh(new_mesh);
}
});

View File

@ -15,6 +15,8 @@
#include "BKE_mesh_mapping.hh"
#include "BKE_mesh_runtime.hh"
#include "GEO_randomize.hh"
#include "NOD_rna_define.hh"
#include "UI_interface.hh"
@ -1431,6 +1433,8 @@ static void node_geo_exec(GeoNodeExecParams params)
break;
}
}
geometry::debug_randomize_mesh_order(mesh);
}
});

View File

@ -14,6 +14,8 @@
#include "BKE_curves.hh"
#include "BKE_curves_utils.hh"
#include "GEO_randomize.hh"
#include "DNA_pointcloud_types.h"
namespace blender::nodes::node_geo_interpolate_curves_cc {
@ -759,6 +761,8 @@ static GeometrySet generate_interpolated_curves(
child_curves_id->totcol = guide_curves_id.totcol;
}
geometry::debug_randomize_curve_order(&child_curves);
return GeometrySet::from_curves(child_curves_id);
}

View File

@ -8,6 +8,8 @@
#include "BKE_material.h"
#include "BKE_mesh.hh"
#include "GEO_randomize.hh"
#include "bmesh.h"
#include "node_geometry_util.hh"
@ -107,6 +109,8 @@ static Mesh *create_ico_sphere_mesh(const int subdivisions,
}
attributes.remove("UVMap");
geometry::debug_randomize_mesh_order(mesh);
mesh->bounds_set_eager(calculate_bounds_ico_sphere(radius, subdivisions));
return mesh;

View File

@ -9,6 +9,8 @@
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "GEO_randomize.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_mesh_subdivide_cc {
@ -48,6 +50,7 @@ static Mesh *simple_subdivide_mesh(const Mesh &mesh, const int level)
BKE_subdiv_free(subdiv);
geometry::debug_randomize_mesh_order(result);
return result;
}
#endif /* WITH_OPENSUBDIV */

View File

@ -15,6 +15,8 @@
#include "BLI_sort.hh"
#include "BLI_task.hh"
#include "GEO_randomize.hh"
#include "BKE_geometry_set.hh"
namespace blender::nodes::node_geo_points_to_curves_cc {
@ -151,6 +153,8 @@ static Curves *curves_from_points(const PointCloud &points,
{},
indices,
curves.attributes_for_write());
geometry::debug_randomize_curve_order(&curves);
return curves_id;
}

View File

@ -20,6 +20,8 @@
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "GEO_randomize.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_string_to_curves_cc {
@ -296,6 +298,8 @@ static Map<int, int> create_curve_instances(GeoNodeExecParams &params,
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
BKE_nurbList_free(&cu.nurb);
geometry::debug_randomize_curve_order(&curves);
float4x4 size_matrix = math::from_scale<float4x4>(float3(layout.final_font_size));
curves.transform(size_matrix);

View File

@ -20,6 +20,8 @@
#include "NOD_rna_define.hh"
#include "GEO_randomize.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_subdivision_surface_cc {
@ -159,6 +161,8 @@ static Mesh *mesh_subsurf_calc(const Mesh *mesh,
BKE_id_free(nullptr, mesh_copy);
}
geometry::debug_randomize_mesh_order(result);
return result;
}

View File

@ -15,6 +15,8 @@
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "GEO_randomize.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_triangulate_cc {
@ -67,6 +69,10 @@ static Mesh *triangulate_mesh_selection(const Mesh &mesh,
/* Positions are not changed by the triangulation operation, so the bounds are the same. */
result->runtime->bounds_cache = mesh.runtime->bounds_cache;
/* Vertex order is not affected. */
geometry::debug_randomize_edge_order(result);
geometry::debug_randomize_face_order(result);
return result;
}

View File

@ -26,6 +26,8 @@
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "GEO_randomize.hh"
namespace blender::nodes::node_geo_volume_to_mesh_cc {
NODE_STORAGE_FUNCS(NodeGeometryVolumeToMesh)
@ -151,6 +153,8 @@ static Mesh *create_mesh_from_volume_grids(Span<openvdb::GridBase::ConstPtr> gri
BKE_mesh_calc_edges(mesh, false, false);
BKE_mesh_smooth_flag_set(mesh, false);
geometry::debug_randomize_mesh_order(mesh);
return mesh;
}