1
1

Compare commits

...

4 Commits

19 changed files with 636 additions and 29 deletions

View File

@@ -411,6 +411,8 @@ class DATA_PT_shape_keys(MeshButtonsPanel, Panel):
row.active = enable_edit_value
row.prop(key, "eval_time")
layout.prop(ob, "add_rest_position_attribute")
class DATA_PT_uv_texture(MeshButtonsPanel, Panel):
bl_label = "UV Maps"

View File

@@ -73,6 +73,7 @@ def curve_node_items(context):
yield NodeItem("GeometryNodeCurveLength")
yield NodeItem("GeometryNodeCurveToMesh")
yield NodeItem("GeometryNodeCurveToPoints")
yield NodeItem("GeometryNodeDeformCurvesWithSurface")
yield NodeItem("GeometryNodeFillCurve")
yield NodeItem("GeometryNodeFilletCurve")
yield NodeItem("GeometryNodeResampleCurve")

View File

@@ -710,7 +710,8 @@ void BKE_mesh_calc_normals_split(struct Mesh *mesh);
* to split geometry along sharp edges.
*/
void BKE_mesh_calc_normals_split_ex(struct Mesh *mesh,
struct MLoopNorSpaceArray *r_lnors_spacearr);
struct MLoopNorSpaceArray *r_lnors_spacearr,
float (*r_corner_normals)[3]);
/**
* Higher level functions hiding most of the code needed around call to

View File

@@ -1501,6 +1501,7 @@ struct TexResult;
#define GEO_NODE_MESH_TO_VOLUME 1164
#define GEO_NODE_UV_UNWRAP 1165
#define GEO_NODE_UV_PACK_ISLANDS 1166
#define GEO_NODE_DEFORM_CURVES_ON_SURFACE 1167
/** \} */

View File

@@ -66,6 +66,9 @@
# include "DNA_userdef_types.h"
#endif
using blender::float3;
using blender::IndexRange;
/* very slow! enable for testing only! */
//#define USE_MODIFIER_VALIDATE
@@ -814,6 +817,25 @@ static void mesh_calc_modifiers(struct Depsgraph *depsgraph,
/* Clear errors before evaluation. */
BKE_modifiers_clear_errors(ob);
if (ob->modifier_flag & OB_MODIFIER_FLAG_ADD_REST_POSITION) {
if (mesh_final == nullptr) {
mesh_final = BKE_mesh_copy_for_eval(mesh_input, true);
ASSERT_IS_VALID_MESH(mesh_final);
}
float3 *rest_positions = static_cast<float3 *>(CustomData_add_layer_named(&mesh_final->vdata,
CD_PROP_FLOAT3,
CD_DEFAULT,
nullptr,
mesh_final->totvert,
"rest_position"));
blender::threading::parallel_for(
IndexRange(mesh_final->totvert), 1024, [&](const IndexRange range) {
for (const int i : range) {
rest_positions[i] = mesh_final->mvert[i].co;
}
});
}
/* Apply all leading deform modifiers. */
if (use_deform) {
for (; md; md = md->next, md_datamask = md_datamask->next) {

View File

@@ -1922,9 +1922,25 @@ void BKE_mesh_vert_coords_apply_with_mat4(Mesh *mesh,
BKE_mesh_tag_coords_changed(mesh);
}
void BKE_mesh_calc_normals_split_ex(Mesh *mesh, MLoopNorSpaceArray *r_lnors_spacearr)
static float (*ensure_corner_normal_layer(Mesh &mesh))[3]
{
float(*r_loopnors)[3];
if (CustomData_has_layer(&mesh.ldata, CD_NORMAL)) {
r_loopnors = (float(*)[3])CustomData_get_layer(&mesh.ldata, CD_NORMAL);
memset(r_loopnors, 0, sizeof(float[3]) * mesh.totloop);
}
else {
r_loopnors = (float(*)[3])CustomData_add_layer(
&mesh.ldata, CD_NORMAL, CD_CALLOC, nullptr, mesh.totloop);
CustomData_set_layer_flag(&mesh.ldata, CD_NORMAL, CD_FLAG_TEMPORARY);
}
return r_loopnors;
}
void BKE_mesh_calc_normals_split_ex(Mesh *mesh,
MLoopNorSpaceArray *r_lnors_spacearr,
float (*r_corner_normals)[3])
{
short(*clnors)[2] = nullptr;
/* Note that we enforce computing clnors when the clnor space array is requested by caller here.
@@ -1934,16 +1950,6 @@ void BKE_mesh_calc_normals_split_ex(Mesh *mesh, MLoopNorSpaceArray *r_lnors_spac
((mesh->flag & ME_AUTOSMOOTH) != 0);
const float split_angle = (mesh->flag & ME_AUTOSMOOTH) != 0 ? mesh->smoothresh : (float)M_PI;
if (CustomData_has_layer(&mesh->ldata, CD_NORMAL)) {
r_loopnors = (float(*)[3])CustomData_get_layer(&mesh->ldata, CD_NORMAL);
memset(r_loopnors, 0, sizeof(float[3]) * mesh->totloop);
}
else {
r_loopnors = (float(*)[3])CustomData_add_layer(
&mesh->ldata, CD_NORMAL, CD_CALLOC, nullptr, mesh->totloop);
CustomData_set_layer_flag(&mesh->ldata, CD_NORMAL, CD_FLAG_TEMPORARY);
}
/* may be nullptr */
clnors = (short(*)[2])CustomData_get_layer(&mesh->ldata, CD_CUSTOMLOOPNORMAL);
@@ -1953,7 +1959,7 @@ void BKE_mesh_calc_normals_split_ex(Mesh *mesh, MLoopNorSpaceArray *r_lnors_spac
mesh->medge,
mesh->totedge,
mesh->mloop,
r_loopnors,
r_corner_normals,
mesh->totloop,
mesh->mpoly,
BKE_mesh_poly_normals_ensure(mesh),
@@ -1969,7 +1975,7 @@ void BKE_mesh_calc_normals_split_ex(Mesh *mesh, MLoopNorSpaceArray *r_lnors_spac
void BKE_mesh_calc_normals_split(Mesh *mesh)
{
BKE_mesh_calc_normals_split_ex(mesh, nullptr);
BKE_mesh_calc_normals_split_ex(mesh, nullptr, ensure_corner_normal_layer(*mesh));
}
/* Split faces helper functions. */
@@ -2188,7 +2194,8 @@ void BKE_mesh_split_faces(Mesh *mesh, bool free_loop_normals)
MLoopNorSpaceArray lnors_spacearr = {nullptr};
/* Compute loop normals and loop normal spaces (a.k.a. smooth fans of faces around vertices). */
BKE_mesh_calc_normals_split_ex(mesh, &lnors_spacearr);
BKE_mesh_calc_normals_split_ex(mesh, &lnors_spacearr, ensure_corner_normal_layer(*mesh));
/* Stealing memarena from loop normals space array. */
MemArena *memarena = lnors_spacearr.mem;

View File

@@ -4749,6 +4749,7 @@ static void registerGeometryNodes()
register_node_type_geo_curve_to_mesh();
register_node_type_geo_curve_to_points();
register_node_type_geo_curve_trim();
register_node_type_geo_deform_curves_on_surface();
register_node_type_geo_delete_geometry();
register_node_type_geo_duplicate_elements();
register_node_type_geo_distribute_points_on_faces();

View File

@@ -6,12 +6,96 @@
#include "BLI_rand.hh"
#include "BKE_context.h"
#include "BKE_curves.hh"
#include "BKE_node.h"
#include "BKE_node_runtime.hh"
#include "ED_curves.h"
#include "ED_node.h"
#include "ED_object.h"
#include "DNA_modifier_types.h"
#include "DNA_node_types.h"
#include "DNA_object_types.h"
namespace blender::ed::curves {
static bool has_surface_deformation_node(const bNodeTree &ntree)
{
LISTBASE_FOREACH (const bNode *, node, &ntree.nodes) {
if (node->type == GEO_NODE_DEFORM_CURVES_ON_SURFACE) {
return true;
}
if (node->type == NODE_GROUP) {
if (node->id != nullptr) {
if (has_surface_deformation_node(*reinterpret_cast<const bNodeTree *>(node->id))) {
return true;
}
}
}
}
return false;
}
static bool has_surface_deformation_node(const Object &curves_ob)
{
LISTBASE_FOREACH (const ModifierData *, md, &curves_ob.modifiers) {
if (md->type != eModifierType_Nodes) {
continue;
}
const NodesModifierData *nmd = reinterpret_cast<const NodesModifierData *>(md);
if (nmd->node_group == nullptr) {
continue;
}
if (has_surface_deformation_node(*nmd->node_group)) {
return true;
}
}
return false;
}
void ensure_surface_deformation_node_exists(bContext &C, Object &curves_ob)
{
if (has_surface_deformation_node(curves_ob)) {
return;
}
Main *bmain = CTX_data_main(&C);
Scene *scene = CTX_data_scene(&C);
ModifierData *md = ED_object_modifier_add(
nullptr, bmain, scene, &curves_ob, "Surface Deform", eModifierType_Nodes);
NodesModifierData &nmd = *reinterpret_cast<NodesModifierData *>(md);
nmd.node_group = ntreeAddTree(bmain, "Surface Deform", "GeometryNodeTree");
bNodeTree *ntree = nmd.node_group;
ntreeAddSocketInterface(ntree, SOCK_IN, "NodeSocketGeometry", "Geometry");
ntreeAddSocketInterface(ntree, SOCK_OUT, "NodeSocketGeometry", "Geometry");
bNode *group_input = nodeAddStaticNode(&C, ntree, NODE_GROUP_INPUT);
bNode *group_output = nodeAddStaticNode(&C, ntree, NODE_GROUP_OUTPUT);
bNode *deform_node = nodeAddStaticNode(&C, ntree, GEO_NODE_DEFORM_CURVES_ON_SURFACE);
ED_node_tree_propagate_change(&C, bmain, nmd.node_group);
nodeAddLink(ntree,
group_input,
static_cast<bNodeSocket *>(group_input->outputs.first),
deform_node,
nodeFindSocket(deform_node, SOCK_IN, "Curves"));
nodeAddLink(ntree,
deform_node,
nodeFindSocket(deform_node, SOCK_OUT, "Curves"),
group_output,
static_cast<bNodeSocket *>(group_output->inputs.first));
group_input->locx = -200;
group_output->locx = 200;
deform_node->locx = 0;
ED_node_tree_propagate_change(&C, bmain, nmd.node_group);
}
bke::CurvesGeometry primitive_random_sphere(const int curves_size, const int points_per_curve)
{
bke::CurvesGeometry curves(points_per_curve * curves_size, curves_size);

View File

@@ -944,6 +944,88 @@ static void SCULPT_CURVES_OT_select_all(wmOperatorType *ot)
WM_operator_properties_select_all(ot);
}
namespace surface_set {
static bool surface_set_poll(bContext *C)
{
const Object *object = CTX_data_active_object(C);
if (object == nullptr) {
return false;
}
if (object->type != OB_MESH) {
return false;
}
return true;
}
static int surface_set_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
Object &new_surface_ob = *CTX_data_active_object(C);
Mesh &new_surface_mesh = *static_cast<Mesh *>(new_surface_ob.data);
const char *new_uv_map_name = CustomData_get_active_layer_name(&new_surface_mesh.ldata,
CD_MLOOPUV);
CTX_DATA_BEGIN (C, Object *, selected_ob, selected_objects) {
if (selected_ob->type != OB_CURVES) {
continue;
}
Object &curves_ob = *selected_ob;
Curves &curves_id = *static_cast<Curves *>(curves_ob.data);
MEM_SAFE_FREE(curves_id.surface_uv_map);
if (new_uv_map_name != nullptr) {
curves_id.surface_uv_map = BLI_strdup(new_uv_map_name);
}
bool missing_uvs;
bool invalid_uvs;
snap_curves_to_surface::snap_curves_to_surface_exec_object(
curves_ob,
new_surface_ob,
snap_curves_to_surface::AttachMode::Nearest,
&invalid_uvs,
&missing_uvs);
/* Add deformation modifier if necessary. */
blender::ed::curves::ensure_surface_deformation_node_exists(*C, curves_ob);
/* TODO: Not sure if I have to do id reference counting here. */
curves_id.surface = &new_surface_ob;
ED_object_parent_set(
op->reports, C, scene, &curves_ob, &new_surface_ob, PAR_OBJECT, false, true, nullptr);
DEG_id_tag_update(&curves_ob.id, ID_RECALC_TRANSFORM);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, &curves_id);
/* Required for deformation. */
new_surface_ob.modifier_flag |= OB_MODIFIER_FLAG_ADD_REST_POSITION;
DEG_id_tag_update(&new_surface_ob.id, ID_RECALC_GEOMETRY);
}
CTX_DATA_END;
DEG_relations_tag_update(bmain);
return OPERATOR_FINISHED;
}
} // namespace surface_set
static void CURVES_OT_surface_set(wmOperatorType *ot)
{
ot->name = "Set Curves Surface Object";
ot->idname = __func__;
ot->description = "Use the active object as surface for selected curves objects";
ot->exec = surface_set::surface_set_exec;
ot->poll = surface_set::surface_set_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
} // namespace blender::ed::curves
void ED_operatortypes_curves()
@@ -955,4 +1037,5 @@ void ED_operatortypes_curves()
WM_operatortype_append(CURVES_OT_set_selection_domain);
WM_operatortype_append(SCULPT_CURVES_OT_select_all);
WM_operatortype_append(CURVES_OT_disable_selection);
WM_operatortype_append(CURVES_OT_surface_set);
}

View File

@@ -29,6 +29,7 @@ bke::CurvesGeometry primitive_random_sphere(int curves_size, int points_per_curv
bool selection_operator_poll(bContext *C);
bool has_anything_selected(const Curves &curves_id);
VectorSet<Curves *> get_unique_editable_curves(const bContext &C);
void ensure_surface_deformation_node_exists(bContext &C, Object &curves_ob);
} // namespace blender::ed::curves
#endif

View File

@@ -24,6 +24,7 @@
#include "DNA_material_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meta_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_fluidsim_types.h"
#include "DNA_object_force_types.h"
#include "DNA_object_types.h"
@@ -72,6 +73,7 @@
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_nla.h"
#include "BKE_node.h"
#include "BKE_object.h"
#include "BKE_particle.h"
#include "BKE_pointcloud.h"
@@ -114,6 +116,10 @@
#include "object_intern.h"
using blender::float3;
using blender::float4x4;
using blender::Vector;
/* -------------------------------------------------------------------- */
/** \name Local Enum Declarations
* \{ */
@@ -2071,30 +2077,44 @@ void OBJECT_OT_curves_random_add(wmOperatorType *ot)
static int object_curves_empty_hair_add_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
ushort local_view_bits;
float loc[3], rot[3];
blender::float3 loc, rot;
if (!ED_object_add_generic_get_opts(
C, op, 'Z', loc, rot, nullptr, nullptr, &local_view_bits, nullptr)) {
return OPERATOR_CANCELLED;
}
Object *surface_ob = CTX_data_active_object(C);
BLI_assert(surface_ob != nullptr);
Object *object = ED_object_add_type(C, OB_CURVES, nullptr, loc, rot, false, local_view_bits);
object->dtx |= OB_DRAWBOUNDOX; /* TODO: remove once there is actual drawing. */
Object *curves_ob = ED_object_add_type(C, OB_CURVES, nullptr, loc, rot, false, local_view_bits);
curves_ob->dtx |= OB_DRAWBOUNDOX; /* TODO: remove once there is actual drawing. */
if (surface_ob != nullptr && surface_ob->type == OB_MESH) {
Curves *curves_id = static_cast<Curves *>(object->data);
curves_id->surface = surface_ob;
id_us_plus(&surface_ob->id);
/* Set surface object. */
Curves *curves_id = static_cast<Curves *>(curves_ob->data);
curves_id->surface = surface_ob;
id_us_plus(&surface_ob->id);
Mesh *surface_mesh = static_cast<Mesh *>(surface_ob->data);
const char *uv_name = CustomData_get_active_layer_name(&surface_mesh->ldata, CD_MLOOPUV);
if (uv_name != nullptr) {
curves_id->surface_uv_map = BLI_strdup(uv_name);
}
/* Parent to surface object. */
ED_object_parent_set(
op->reports, C, scene, curves_ob, surface_ob, PAR_OBJECT, false, true, nullptr);
/* Decide which UV map to use for attachment. */
Mesh *surface_mesh = static_cast<Mesh *>(surface_ob->data);
const char *uv_name = CustomData_get_active_layer_name(&surface_mesh->ldata, CD_MLOOPUV);
if (uv_name != nullptr) {
curves_id->surface_uv_map = BLI_strdup(uv_name);
}
/* Add deformation modifier. */
blender::ed::curves::ensure_surface_deformation_node_exists(*C, *curves_ob);
/* Make sure the surface object has a rest position attribute which is necessary for
* deformations. */
surface_ob->modifier_flag |= OB_MODIFIER_FLAG_ADD_REST_POSITION;
return OPERATOR_FINISHED;
}

View File

@@ -951,7 +951,7 @@ static int parent_set_invoke_menu(bContext *C, wmOperatorType *ot)
1);
struct {
bool mesh, gpencil;
bool mesh, gpencil, curves;
} has_children_of_type = {0};
CTX_DATA_BEGIN (C, Object *, child, selected_editable_objects) {
@@ -964,6 +964,9 @@ static int parent_set_invoke_menu(bContext *C, wmOperatorType *ot)
if (child->type == OB_GPENCIL) {
has_children_of_type.gpencil = true;
}
if (child->type == OB_CURVES) {
has_children_of_type.curves = true;
}
}
CTX_DATA_END;
@@ -987,6 +990,11 @@ static int parent_set_invoke_menu(bContext *C, wmOperatorType *ot)
else if (parent->type == OB_LATTICE) {
uiItemEnumO_ptr(layout, ot, NULL, 0, "type", PAR_LATTICE);
}
else if (parent->type == OB_MESH) {
if (has_children_of_type.curves) {
uiItemO(layout, "Object (Attach Curves to Surface)", ICON_NONE, "CURVES_OT_surface_set");
}
}
/* vertex parenting */
if (OB_TYPE_SUPPORT_PARVERT(parent->type)) {

View File

@@ -434,7 +434,10 @@ typedef struct Object {
char empty_image_visibility_flag;
char empty_image_depth;
char empty_image_flag;
char _pad8[5];
/** ObjectModifierFlag */
uint8_t modifier_flag;
char _pad8[4];
struct PreviewImage *preview;
@@ -788,6 +791,10 @@ enum {
OB_EMPTY_IMAGE_USE_ALPHA_BLEND = 1 << 0,
};
typedef enum ObjectModifierFlag {
OB_MODIFIER_FLAG_ADD_REST_POSITION = 1 << 0,
} ObjectModifierFlag;
#define MAX_DUPLI_RECUR 8
#ifdef __cplusplus

View File

@@ -3579,6 +3579,14 @@ static void rna_def_object(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Empty Image Side", "Show front/back side");
RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, NULL);
prop = RNA_def_property(srna, "add_rest_position_attribute", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "modifier_flag", OB_MODIFIER_FLAG_ADD_REST_POSITION);
RNA_def_property_ui_text(prop,
"Add Rest Position",
"Add a \"rest_position\" attribute that is a copy of the position "
"attribute before shape keys and modifiers are evaluated");
RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_Object_internal_update_data");
/* render */
prop = RNA_def_property(srna, "pass_index", PROP_INT, PROP_UNSIGNED);
RNA_def_property_int_sdna(prop, NULL, "index");

View File

@@ -21,6 +21,7 @@
#include "BLI_utildefines.h"
#include "DNA_collection_types.h"
#include "DNA_curves_types.h"
#include "DNA_defaults.h"
#include "DNA_material_types.h"
#include "DNA_mesh_types.h"
@@ -190,6 +191,10 @@ static bool node_needs_own_transform_relation(const bNode &node)
return storage.transform_space == GEO_NODE_TRANSFORM_SPACE_RELATIVE;
}
if (node.type == GEO_NODE_DEFORM_CURVES_ON_SURFACE) {
return true;
}
return false;
}
@@ -269,6 +274,14 @@ static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphConte
Set<ID *> used_ids;
find_used_ids_from_settings(nmd->settings, used_ids);
process_nodes_for_depsgraph(*nmd->node_group, used_ids, needs_own_transform_relation);
if (ctx->object->type == OB_CURVES) {
Curves *curves_id = static_cast<Curves *>(ctx->object->data);
if (curves_id->surface != nullptr) {
used_ids.add(&curves_id->surface->id);
}
}
for (ID *id : used_ids) {
switch ((ID_Type)GS(id->name)) {
case ID_OB: {

View File

@@ -47,6 +47,7 @@ void register_node_type_geo_curve_subdivide(void);
void register_node_type_geo_curve_to_mesh(void);
void register_node_type_geo_curve_to_points(void);
void register_node_type_geo_curve_trim(void);
void register_node_type_geo_deform_curves_on_surface(void);
void register_node_type_geo_delete_geometry(void);
void register_node_type_geo_duplicate_elements(void);
void register_node_type_geo_distribute_points_on_faces(void);

View File

@@ -301,6 +301,7 @@ DefNode(GeometryNode, GEO_NODE_CURVE_SPLINE_PARAMETER, 0, "SPLINE_PARAMETER", Sp
DefNode(GeometryNode, GEO_NODE_CURVE_SPLINE_TYPE, def_geo_curve_spline_type, "CURVE_SPLINE_TYPE", CurveSplineType, "Set Spline Type", "")
DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "")
DefNode(GeometryNode, GEO_NODE_CURVE_TO_POINTS, def_geo_curve_to_points, "CURVE_TO_POINTS", CurveToPoints, "Curve to Points", "")
DefNode(GeometryNode, GEO_NODE_DEFORM_CURVES_ON_SURFACE, 0, "DEFORM_CURVES_ON_SURFACE", DeformCurvesOnSurface, "Deform Curves on Surface", "")
DefNode(GeometryNode, GEO_NODE_DELETE_GEOMETRY, def_geo_delete_geometry, "DELETE_GEOMETRY", DeleteGeometry, "Delete Geometry", "")
DefNode(GeometryNode, GEO_NODE_DUPLICATE_ELEMENTS, def_geo_duplicate_elements, "DUPLICATE_ELEMENTS", DuplicateElements, "Duplicate Elements", "")
DefNode(GeometryNode, GEO_NODE_DISTRIBUTE_POINTS_ON_FACES, def_geo_distribute_points_on_faces, "DISTRIBUTE_POINTS_ON_FACES", DistributePointsOnFaces, "Distribute Points on Faces", "")

View File

@@ -57,6 +57,7 @@ set(SRC
nodes/node_geo_curve_to_mesh.cc
nodes/node_geo_curve_to_points.cc
nodes/node_geo_curve_trim.cc
nodes/node_geo_deform_curves_with_surface.cc
nodes/node_geo_delete_geometry.cc
nodes/node_geo_distribute_points_on_faces.cc
nodes/node_geo_dual_mesh.cc

View File

@@ -0,0 +1,345 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_attribute_math.hh"
#include "BKE_curves.hh"
#include "BKE_editmesh.h"
#include "BKE_lib_id.h"
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_mesh_wrapper.h"
#include "BKE_modifier.h"
#include "BKE_type_conversions.hh"
#include "BLI_float3x3.hh"
#include "BLI_task.hh"
#include "UI_interface.h"
#include "UI_resources.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "NOD_socket_search_link.hh"
#include "GEO_reverse_uv_sampler.hh"
#include "DEG_depsgraph_query.h"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_deform_curves_with_surface_cc {
using attribute_math::mix3;
using bke::CurvesGeometry;
using geometry::ReverseUVSampler;
NODE_STORAGE_FUNCS(NodeGeometryCurveTrim)
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>(N_("Curves")).supported_type(GEO_COMPONENT_TYPE_CURVE);
b.add_output<decl::Geometry>(N_("Curves"));
}
static void deform_curves(CurvesGeometry &curves,
const Mesh &surface_mesh_old,
const Mesh &surface_mesh_new,
const Span<float2> curve_attachment_uvs,
const ReverseUVSampler &reverse_uv_sampler_old,
const ReverseUVSampler &reverse_uv_sampler_new,
const Span<float3> corner_normals_old,
const Span<float3> corner_normals_new,
const Span<float3> rest_positions,
const float4x4 &surface_to_curves,
std::atomic<int> &r_invalid_uv_count)
{
/* Find attachment points on old and new mesh. */
const int curves_num = curves.curves_num();
Array<ReverseUVSampler::Result> surface_samples_old(curves_num);
Array<ReverseUVSampler::Result> surface_samples_new(curves_num);
threading::parallel_invoke(
[&]() { reverse_uv_sampler_old.sample_many(curve_attachment_uvs, surface_samples_old); },
[&]() { reverse_uv_sampler_new.sample_many(curve_attachment_uvs, surface_samples_new); });
MutableSpan<float3> positions = curves.positions_for_write();
const float4x4 curves_to_surface = surface_to_curves.inverted();
threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange range) {
for (const int curve_i : range) {
const ReverseUVSampler::Result &surface_sample_old = surface_samples_old[curve_i];
if (surface_sample_old.type != ReverseUVSampler::ResultType::Ok) {
r_invalid_uv_count++;
continue;
}
const ReverseUVSampler::Result &surface_sample_new = surface_samples_new[curve_i];
if (surface_sample_new.type != ReverseUVSampler::ResultType::Ok) {
r_invalid_uv_count++;
continue;
}
const MLoopTri &looptri_old = *surface_sample_old.looptri;
const MLoopTri &looptri_new = *surface_sample_new.looptri;
const float3 &bary_weights_old = surface_sample_old.bary_weights;
const float3 &bary_weights_new = surface_sample_new.bary_weights;
const int corner_0_old = looptri_old.tri[0];
const int corner_1_old = looptri_old.tri[1];
const int corner_2_old = looptri_old.tri[2];
const int corner_0_new = looptri_new.tri[0];
const int corner_1_new = looptri_new.tri[1];
const int corner_2_new = looptri_new.tri[2];
const int vert_0_old = surface_mesh_old.mloop[corner_0_old].v;
const int vert_1_old = surface_mesh_old.mloop[corner_1_old].v;
const int vert_2_old = surface_mesh_old.mloop[corner_2_old].v;
const int vert_0_new = surface_mesh_new.mloop[corner_0_new].v;
const int vert_1_new = surface_mesh_new.mloop[corner_1_new].v;
const int vert_2_new = surface_mesh_new.mloop[corner_2_new].v;
const float3 &normal_0_old = corner_normals_old[corner_0_old];
const float3 &normal_1_old = corner_normals_old[corner_1_old];
const float3 &normal_2_old = corner_normals_old[corner_2_old];
const float3 normal_old = math::normalize(
mix3(bary_weights_old, normal_0_old, normal_1_old, normal_2_old));
const float3 &normal_0_new = corner_normals_new[corner_0_new];
const float3 &normal_1_new = corner_normals_new[corner_1_new];
const float3 &normal_2_new = corner_normals_new[corner_2_new];
const float3 normal_new = math::normalize(
mix3(bary_weights_new, normal_0_new, normal_1_new, normal_2_new));
const float3 &pos_0_old = surface_mesh_old.mvert[vert_0_old].co;
const float3 &pos_1_old = surface_mesh_old.mvert[vert_1_old].co;
const float3 &pos_2_old = surface_mesh_old.mvert[vert_2_old].co;
const float3 pos_old = mix3(bary_weights_old, pos_0_old, pos_1_old, pos_2_old);
const float3 &pos_0_new = surface_mesh_new.mvert[vert_0_new].co;
const float3 &pos_1_new = surface_mesh_new.mvert[vert_1_new].co;
const float3 &pos_2_new = surface_mesh_new.mvert[vert_2_new].co;
const float3 pos_new = mix3(bary_weights_new, pos_0_new, pos_1_new, pos_2_new);
/* The translation is just the difference between the old and new position on the surface. */
const float3 translation = pos_new - pos_old;
const float3 &rest_pos_0 = rest_positions[vert_0_new];
const float3 &rest_pos_1 = rest_positions[vert_1_new];
/* The tangent reference direction is used to determine the rotation of the surface point
* around its normal axis. It's important that the old and new tangent reference are computed
* in a consistent way. If the surface has not been rotated, the old and new tangent
* reference have to have the same direction. For that reason, the old tangent reference is
* computed based on the rest position attribute instead of positions on the old mesh. This
* way the old and new tangent reference use the same topology.
*
* TODO: Figure out if this can be smoothly interpolated across the surface as well.
* Currently, this is a source of discontinuity in the deformation, because the vector
* changes intantly from one triangle to the next. */
const float3 tangent_reference_dir_old = rest_pos_1 - rest_pos_0;
const float3 tangent_reference_dir_new = pos_1_new - pos_0_new;
/* Compute first local tangent based on the (potentially smoothed) normal and the tangent
* reference. */
const float3 tangent_x_old = math::normalize(
math::cross(normal_old, tangent_reference_dir_old));
const float3 tangent_x_new = math::normalize(
math::cross(normal_new, tangent_reference_dir_new));
/* The second tangent defined by the normal and first tangent. */
const float3 tangent_y_old = math::normalize(math::cross(normal_old, tangent_x_old));
const float3 tangent_y_new = math::normalize(math::cross(normal_new, tangent_x_new));
/* Construct rotation matrix that encodes the orientation of the old surface position. */
float3x3 rotation_old;
copy_v3_v3(rotation_old.values[0], tangent_x_old);
copy_v3_v3(rotation_old.values[1], tangent_y_old);
copy_v3_v3(rotation_old.values[2], normal_old);
/* Construct rotation matrix that encodes the orientation of the new surface position. */
float3x3 rotation_new;
copy_v3_v3(rotation_new.values[0], tangent_x_new);
copy_v3_v3(rotation_new.values[1], tangent_y_new);
copy_v3_v3(rotation_new.values[2], normal_new);
/* Can use transpose instead of inverse because the matrix is orthonormal. */
const float3x3 rotation_old_inv = rotation_old.transposed();
/* Compute a rotation matrix that rotates points from the old to the new surface
* orientation. */
const float3x3 rotation = rotation_new * rotation_old_inv;
float4x4 rotation_4x4;
copy_m4_m3(rotation_4x4.values, rotation.values);
/* Construction transformation matrix for this surface position that includes rotation and
* translation. */
float4x4 surface_transform = float4x4::identity();
sub_v3_v3(surface_transform.values[3], pos_old);
mul_m4_m4_pre(surface_transform.values, rotation_4x4.values);
add_v3_v3(surface_transform.values[3], translation);
add_v3_v3(surface_transform.values[3], pos_old);
/* Change the basis of the transformation so to that it can be applied in the local space of
* the curves. */
const float4x4 curve_transform = surface_to_curves * surface_transform * curves_to_surface;
/* Actually transform all points. */
const IndexRange points = curves.points_for_curve(curve_i);
for (const int point_i : points) {
const float3 old_point_pos = positions[point_i];
const float3 new_point_pos = curve_transform * old_point_pos;
positions[point_i] = new_point_pos;
}
}
});
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet curves_geometry = params.extract_input<GeometrySet>("Curves");
Mesh *surface_mesh_orig = nullptr;
bool free_suface_mesh_orig = false;
auto pass_through_input = [&]() {
params.set_output("Curves", std::move(curves_geometry));
if (free_suface_mesh_orig) {
BKE_id_free(nullptr, surface_mesh_orig);
}
};
const Object *self_ob_eval = params.self_object();
if (self_ob_eval == nullptr || self_ob_eval->type != OB_CURVES) {
pass_through_input();
return;
}
const Curves *self_curves_eval = static_cast<const Curves *>(self_ob_eval->data);
Object *surface_ob_eval = self_curves_eval->surface;
const StringRef uv_map_name = self_curves_eval->surface_uv_map;
const StringRef rest_position_name = "rest_position";
if (!curves_geometry.has_curves()) {
pass_through_input();
return;
}
if (surface_ob_eval == nullptr || surface_ob_eval->type != OB_MESH) {
pass_through_input();
return;
}
Object *surface_ob_orig = DEG_get_original_object(surface_ob_eval);
Mesh &surface_object_data = *static_cast<Mesh *>(surface_ob_orig->data);
if (BMEditMesh *em = surface_object_data.edit_mesh) {
surface_mesh_orig = BKE_mesh_from_bmesh_for_eval_nomain(em->bm, NULL, &surface_object_data);
free_suface_mesh_orig = true;
}
else {
surface_mesh_orig = &surface_object_data;
}
Mesh *surface_mesh_eval = BKE_modifier_get_evaluated_mesh_from_evaluated_object(surface_ob_eval,
false);
if (surface_mesh_eval == nullptr) {
pass_through_input();
return;
}
BKE_mesh_wrapper_ensure_mdata(surface_mesh_eval);
MeshComponent mesh_eval;
mesh_eval.replace(surface_mesh_eval, GeometryOwnershipType::ReadOnly);
MeshComponent mesh_orig;
mesh_orig.replace(surface_mesh_orig, GeometryOwnershipType::ReadOnly);
Curves &curves_id = *curves_geometry.get_curves_for_write();
CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry);
if (!mesh_orig.attribute_exists(uv_map_name)) {
pass_through_input();
return;
}
if (!mesh_eval.attribute_exists(uv_map_name)) {
pass_through_input();
return;
}
if (!mesh_eval.attribute_exists(rest_position_name)) {
pass_through_input();
return;
}
if (curves.surface_uv_coords().is_empty()) {
pass_through_input();
return;
}
const VArraySpan<float2> uv_map_orig = mesh_orig.attribute_get_for_read<float2>(
uv_map_name, ATTR_DOMAIN_CORNER, {0.0f, 0.0f});
const VArraySpan<float2> uv_map_eval = mesh_eval.attribute_get_for_read<float2>(
uv_map_name, ATTR_DOMAIN_CORNER, {0.0f, 0.0f});
const VArraySpan<float3> rest_positions = mesh_eval.attribute_get_for_read<float3>(
rest_position_name, ATTR_DOMAIN_POINT, {0.0f, 0.0f, 0.0f});
const Span<float2> surface_uv_coords = curves.surface_uv_coords();
const Span<MLoopTri> looptris_orig{BKE_mesh_runtime_looptri_ensure(surface_mesh_orig),
BKE_mesh_runtime_looptri_len(surface_mesh_orig)};
const Span<MLoopTri> looptris_eval{BKE_mesh_runtime_looptri_ensure(surface_mesh_eval),
BKE_mesh_runtime_looptri_len(surface_mesh_eval)};
const ReverseUVSampler reverse_uv_sampler_orig{uv_map_orig, looptris_orig};
const ReverseUVSampler reverse_uv_sampler_eval{uv_map_eval, looptris_eval};
/* Retrieve face corner normals from each mesh. It's necessary to use face corner normals
* because face normals or vertex normals may lose information (custom normals, auto smooth) in
* some cases. It isn't yet possible to retrieve lazily calculated face corner normals from a
* const mesh, so they are calculated here every time. */
Array<float3> corner_normals_orig(surface_mesh_orig->totloop);
Array<float3> corner_normals_eval(surface_mesh_eval->totloop);
BKE_mesh_calc_normals_split_ex(
surface_mesh_orig, nullptr, reinterpret_cast<float(*)[3]>(corner_normals_orig.data()));
BKE_mesh_calc_normals_split_ex(
surface_mesh_eval, nullptr, reinterpret_cast<float(*)[3]>(corner_normals_eval.data()));
std::atomic<int> invalid_uv_count = 0;
const bke::CurvesSurfaceTransforms transforms{*self_ob_eval, surface_ob_eval};
deform_curves(curves,
*surface_mesh_orig,
*surface_mesh_eval,
surface_uv_coords,
reverse_uv_sampler_orig,
reverse_uv_sampler_eval,
corner_normals_orig,
corner_normals_eval,
rest_positions,
transforms.surface_to_curves,
invalid_uv_count);
curves.tag_positions_changed();
if (invalid_uv_count) {
char *error = BLI_sprintfN(
TIP_("Invalid surface attachment UV coordinates found on %d curves"),
invalid_uv_count.load());
params.error_message_add(NodeWarningType::Warning, error);
MEM_freeN(error);
}
if (free_suface_mesh_orig) {
BKE_id_free(nullptr, surface_mesh_orig);
}
params.set_output("Curves", curves_geometry);
}
} // namespace blender::nodes::node_geo_deform_curves_with_surface_cc
void register_node_type_geo_deform_curves_on_surface()
{
namespace file_ns = blender::nodes::node_geo_deform_curves_with_surface_cc;
static bNodeType ntype;
geo_node_type_base(
&ntype, GEO_NODE_DEFORM_CURVES_ON_SURFACE, "Deform Curves on Surface", NODE_CLASS_GEOMETRY);
ntype.geometry_node_execute = file_ns::node_geo_exec;
ntype.declare = file_ns::node_declare;
node_type_size(&ntype, 170, 120, 700);
nodeRegisterType(&ntype);
}