Curves: support sculpting on deformed curves

Previously, curves sculpt tools only worked on original data. This was
very limiting, because one could effectively only sculpt the curves when
all procedural effects were turned off. This patch adds support for curves
sculpting while looking the result of procedural effects (like deformation
based on the surface mesh). This functionality is also known as "crazy space"
support in Blender.

For more details see D15407.

Differential Revision: https://developer.blender.org/D15407
This commit is contained in:
Jacques Lucke 2022-07-22 15:39:41 +02:00
parent 98bf714b37
commit 1f94b56d77
Notes: blender-bot 2023-02-14 07:39:46 +01:00
Referenced by commit 30113e54d2, Fix curves sculpting in deformed space when using Subdivide node
Referenced by issue #99936, Regression: Unable to add curve hair on the left side of the model.
51 changed files with 1647 additions and 759 deletions

View File

@ -0,0 +1,53 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#pragma once
#include "BLI_float3x3.hh"
#include "BLI_math_vec_types.hh"
#include "BLI_span.hh"
struct Depsgraph;
struct Object;
namespace blender::bke::crazyspace {
/**
* Contains information about how points have been deformed during evaluation.
* This allows mapping edits on evaluated data back to original data in some cases.
*/
struct GeometryDeformation {
/**
* Positions of the deformed points. This may also point to the original position if no
* deformation data is available.
*/
Span<float3> positions;
/**
* Matrices that transform point translations on original data into corresponding translations in
* evaluated data. This may be empty if not available.
*/
Span<float3x3> deform_mats;
float3 translation_from_deformed_to_original(const int position_i,
const float3 &translation) const
{
if (this->deform_mats.is_empty()) {
return translation;
}
const float3x3 &deform_mat = this->deform_mats[position_i];
return deform_mat.inverted() * translation;
}
};
/**
* During evaluation of the object, deformation data may have been generated for this object. This
* function either retrieves the deformation data from the evaluated object, or falls back to
* returning the original data.
*/
GeometryDeformation get_evaluated_curves_deformation(const Depsgraph &depsgraph,
const Object &ob_orig);
} // namespace blender::bke::crazyspace

View File

@ -11,6 +11,7 @@
#include <mutex>
#include "BLI_float3x3.hh"
#include "BLI_float4x4.hh"
#include "BLI_generic_virtual_array.hh"
#include "BLI_index_mask.hh"
@ -414,6 +415,38 @@ class CurvesGeometry : public ::CurvesGeometry {
}
};
/**
* Used to propagate deformation data through modifier evaluation so that sculpt tools can work on
* evaluated data.
*/
class CurvesEditHints {
public:
/**
* Original data that the edit hints below are meant to be used for.
*/
const Curves &curves_id_orig;
/**
* Evaluated positions for the points in #curves_orig. If this is empty, the positions from the
* evaluated #Curves should be used if possible.
*/
std::optional<Array<float3>> positions;
/**
* Matrices which transform point movement vectors from original data to corresponding movements
* of evaluated data.
*/
std::optional<Array<float3x3>> deform_mats;
CurvesEditHints(const Curves &curves_id_orig) : curves_id_orig(curves_id_orig)
{
}
/**
* The edit hints have to correspond to the original curves, i.e. the number of deformed points
* is the same as the number of original points.
*/
bool is_valid() const;
};
namespace curves {
/* -------------------------------------------------------------------- */

View File

@ -23,9 +23,10 @@ typedef enum GeometryComponentType {
GEO_COMPONENT_TYPE_INSTANCES = 2,
GEO_COMPONENT_TYPE_VOLUME = 3,
GEO_COMPONENT_TYPE_CURVE = 4,
GEO_COMPONENT_TYPE_EDIT = 5,
} GeometryComponentType;
#define GEO_COMPONENT_TYPE_ENUM_SIZE 5
#define GEO_COMPONENT_TYPE_ENUM_SIZE 6
void BKE_geometry_set_free(struct GeometrySet *geometry_set);

View File

@ -43,7 +43,8 @@ enum class GeometryOwnershipType {
namespace blender::bke {
class ComponentAttributeProviders;
}
class CurvesEditHints;
} // namespace blender::bke
class GeometryComponent;
@ -168,6 +169,12 @@ struct GeometrySet {
* Remove all geometry components with types that are not in the provided list.
*/
void keep_only(const blender::Span<GeometryComponentType> component_types);
/**
* Keeps the provided geometry types, but also instances and edit data.
* Instances must not be removed while using #modify_geometry_sets.
*/
void keep_only_during_modify(const blender::Span<GeometryComponentType> component_types);
void remove_geometry_during_modify();
void add(const GeometryComponent &component);
@ -287,6 +294,10 @@ struct GeometrySet {
* Returns a read-only curves data-block or null.
*/
const Curves *get_curves_for_read() const;
/**
* Returns read-only curve edit hints or null.
*/
const blender::bke::CurvesEditHints *get_curve_edit_hints_for_read() const;
/**
* Returns a mutable mesh or null. No ownership is transferred.
@ -304,6 +315,10 @@ struct GeometrySet {
* Returns a mutable curves data-block or null. No ownership is transferred.
*/
Curves *get_curves_for_write();
/**
* Returns mutable curve edit hints or null.
*/
blender::bke::CurvesEditHints *get_curve_edit_hints_for_write();
/* Utility methods for replacement. */
/**
@ -825,3 +840,37 @@ class VolumeComponent : public GeometryComponent {
static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_VOLUME;
};
/**
* When the original data is in some edit mode, we want to propagate some additional information
* through object evaluation. This information can be used by edit modes to support working on
* evaluated data.
*
* This component is added at the beginning of modifier evaluation.
*/
class GeometryComponentEditData final : public GeometryComponent {
public:
/**
* Information about how original curves are manipulated during evaluation. This data is used so
* that curve sculpt tools can work on evaluated data. It is not stored in #CurveComponent
* because the data remains valid even when there is no actual curves geometry anymore, for
* example, when the curves have been converted to a mesh.
*/
std::unique_ptr<blender::bke::CurvesEditHints> curves_edit_hints_;
GeometryComponentEditData();
GeometryComponent *copy() const final;
bool owns_direct_data() const final;
void ensure_owns_direct_data() final;
/**
* The first node that does topology changing operations on curves should store the curve point
* positions it retrieved as input. Without this, information about the deformed positions is
* lost, which would make curves sculpt mode fall back to using original curve positions instead
* of deformed ones.
*/
static void remember_deformed_curve_positions_if_necessary(GeometrySet &geometry);
static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_EDIT;
};

View File

@ -137,4 +137,15 @@ float3 compute_bary_coord_in_triangle(const Mesh &mesh,
const MLoopTri &looptri,
const float3 &position);
template<typename T>
inline T sample_corner_attrribute_with_bary_coords(const float3 &bary_weights,
const MLoopTri &looptri,
const Span<T> corner_attribute)
{
return attribute_math::mix3(bary_weights,
corner_attribute[looptri.tri[0]],
corner_attribute[looptri.tri[1]],
corner_attribute[looptri.tri[2]]);
}
} // namespace blender::bke::mesh_surface_sample

View File

@ -139,6 +139,7 @@ set(SRC
intern/freestyle.c
intern/geometry_component_curve.cc
intern/geometry_component_curves.cc
intern/geometry_component_edit_data.cc
intern/geometry_component_instances.cc
intern/geometry_component_mesh.cc
intern/geometry_component_pointcloud.cc
@ -353,6 +354,7 @@ set(SRC
BKE_constraint.h
BKE_context.h
BKE_crazyspace.h
BKE_crazyspace.hh
BKE_cryptomatte.h
BKE_cryptomatte.hh
BKE_curve.h

View File

@ -19,7 +19,10 @@
#include "BKE_DerivedMesh.h"
#include "BKE_crazyspace.h"
#include "BKE_crazyspace.hh"
#include "BKE_curves.hh"
#include "BKE_editmesh.h"
#include "BKE_geometry_set.hh"
#include "BKE_lib_id.h"
#include "BKE_mesh.h"
#include "BKE_mesh_wrapper.h"
@ -586,3 +589,64 @@ void BKE_crazyspace_api_eval_clear(Object *object)
}
/** \} */
namespace blender::bke::crazyspace {
GeometryDeformation get_evaluated_curves_deformation(const Depsgraph &depsgraph,
const Object &ob_orig)
{
BLI_assert(ob_orig.type == OB_CURVES);
const Curves &curves_id_orig = *static_cast<const Curves *>(ob_orig.data);
const CurvesGeometry &curves_orig = CurvesGeometry::wrap(curves_id_orig.geometry);
const int points_num = curves_orig.points_num();
GeometryDeformation deformation;
/* Use the undeformed positions by default. */
deformation.positions = curves_orig.positions();
const Object *ob_eval = DEG_get_evaluated_object(&depsgraph, const_cast<Object *>(&ob_orig));
if (ob_eval == nullptr) {
return deformation;
}
const GeometrySet *geometry_eval = ob_eval->runtime.geometry_set_eval;
if (geometry_eval == nullptr) {
return deformation;
}
/* If available, use deformation information generated during evaluation. */
const GeometryComponentEditData *edit_component_eval =
geometry_eval->get_component_for_read<GeometryComponentEditData>();
bool uses_extra_positions = false;
if (edit_component_eval != nullptr) {
const CurvesEditHints *edit_hints = edit_component_eval->curves_edit_hints_.get();
if (edit_hints != nullptr && &edit_hints->curves_id_orig == &curves_id_orig) {
if (edit_hints->positions.has_value()) {
BLI_assert(edit_hints->positions->size() == points_num);
deformation.positions = *edit_hints->positions;
uses_extra_positions = true;
}
if (edit_hints->deform_mats.has_value()) {
BLI_assert(edit_hints->deform_mats->size() == points_num);
deformation.deform_mats = *edit_hints->deform_mats;
}
}
}
/* Use the positions of the evaluated curves directly, if the number of points matches. */
if (!uses_extra_positions) {
const CurveComponent *curves_component_eval =
geometry_eval->get_component_for_read<CurveComponent>();
if (curves_component_eval != nullptr) {
const Curves *curves_id_eval = curves_component_eval->get_for_read();
if (curves_id_eval != nullptr) {
const CurvesGeometry &curves_eval = CurvesGeometry::wrap(curves_id_eval->geometry);
if (curves_eval.points_num() == points_num) {
deformation.positions = curves_eval.positions();
}
}
}
}
return deformation;
}
} // namespace blender::bke::crazyspace

View File

@ -319,6 +319,14 @@ void BKE_curves_data_update(struct Depsgraph *depsgraph, struct Scene *scene, Ob
Curves *curves = static_cast<Curves *>(object->data);
GeometrySet geometry_set = GeometrySet::create_with_curves(curves,
GeometryOwnershipType::ReadOnly);
if (object->mode == OB_MODE_SCULPT_CURVES) {
/* Try to propagate deformation data through modifier evaluation, so that sculpt mode can work
* on evaluated curves. */
GeometryComponentEditData &edit_component =
geometry_set.get_component_for_write<GeometryComponentEditData>();
edit_component.curves_edit_hints_ = std::make_unique<blender::bke::CurvesEditHints>(
*static_cast<const Curves *>(DEG_get_original_object(object)->data));
}
curves_evaluate_modifiers(depsgraph, scene, object, geometry_set);
/* Assign evaluated object. */
@ -409,4 +417,20 @@ CurvesSurfaceTransforms::CurvesSurfaceTransforms(const Object &curves_ob, const
}
}
bool CurvesEditHints::is_valid() const
{
const int point_num = this->curves_id_orig.geometry.point_num;
if (this->positions.has_value()) {
if (this->positions->size() != point_num) {
return false;
}
}
if (this->deform_mats.has_value()) {
if (this->deform_mats->size() != point_num) {
return false;
}
}
return true;
}
} // namespace blender::bke

View File

@ -0,0 +1,58 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_curves.hh"
#include "BKE_geometry_set.hh"
using namespace blender;
using namespace blender::bke;
GeometryComponentEditData::GeometryComponentEditData() : GeometryComponent(GEO_COMPONENT_TYPE_EDIT)
{
}
GeometryComponent *GeometryComponentEditData::copy() const
{
GeometryComponentEditData *new_component = new GeometryComponentEditData();
if (curves_edit_hints_) {
new_component->curves_edit_hints_ = std::make_unique<CurvesEditHints>(*curves_edit_hints_);
}
return new_component;
}
bool GeometryComponentEditData::owns_direct_data() const
{
return true;
}
void GeometryComponentEditData::ensure_owns_direct_data()
{
/* Nothing to do. */
}
void GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(
GeometrySet &geometry)
{
/* This component should be created at the start of object evaluation if it's necessary. */
if (!geometry.has<GeometryComponentEditData>()) {
return;
}
GeometryComponentEditData &edit_component =
geometry.get_component_for_write<GeometryComponentEditData>();
if (!edit_component.curves_edit_hints_) {
return;
}
if (edit_component.curves_edit_hints_->positions.has_value()) {
return;
}
const Curves *curves_id = geometry.get_curves_for_read();
if (curves_id == nullptr) {
return;
}
const bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id->geometry);
const int points_num = curves.points_num();
if (points_num != edit_component.curves_edit_hints_->curves_id_orig.geometry.point_num) {
return;
}
edit_component.curves_edit_hints_->positions.emplace(points_num);
edit_component.curves_edit_hints_->positions->as_mutable_span().copy_from(curves.positions());
}

View File

@ -53,6 +53,8 @@ GeometryComponent *GeometryComponent::create(GeometryComponentType component_typ
return new VolumeComponent();
case GEO_COMPONENT_TYPE_CURVE:
return new CurveComponent();
case GEO_COMPONENT_TYPE_EDIT:
return new GeometryComponentEditData();
}
BLI_assert_unreachable();
return nullptr;
@ -175,6 +177,20 @@ void GeometrySet::keep_only(const blender::Span<GeometryComponentType> component
}
}
void GeometrySet::keep_only_during_modify(
const blender::Span<GeometryComponentType> component_types)
{
Vector<GeometryComponentType> extended_types = component_types;
extended_types.append_non_duplicates(GEO_COMPONENT_TYPE_INSTANCES);
extended_types.append_non_duplicates(GEO_COMPONENT_TYPE_EDIT);
this->keep_only(extended_types);
}
void GeometrySet::remove_geometry_during_modify()
{
this->keep_only_during_modify({});
}
void GeometrySet::add(const GeometryComponent &component)
{
BLI_assert(!components_[component.type()]);
@ -290,6 +306,13 @@ const Curves *GeometrySet::get_curves_for_read() const
return (component == nullptr) ? nullptr : component->get_for_read();
}
const blender::bke::CurvesEditHints *GeometrySet::get_curve_edit_hints_for_read() const
{
const GeometryComponentEditData *component =
this->get_component_for_read<GeometryComponentEditData>();
return (component == nullptr) ? nullptr : component->curves_edit_hints_.get();
}
bool GeometrySet::has_pointcloud() const
{
const PointCloudComponent *component = this->get_component_for_read<PointCloudComponent>();
@ -453,6 +476,16 @@ Curves *GeometrySet::get_curves_for_write()
return component == nullptr ? nullptr : component->get_for_write();
}
blender::bke::CurvesEditHints *GeometrySet::get_curve_edit_hints_for_write()
{
if (!this->has<GeometryComponentEditData>()) {
return nullptr;
}
GeometryComponentEditData &component =
this->get_component_for_write<GeometryComponentEditData>();
return component.curves_edit_hints_.get();
}
void GeometrySet::attribute_foreach(const Span<GeometryComponentType> component_types,
const bool include_instances,
const AttributeForeachCallback callback) const
@ -679,6 +712,8 @@ bool BKE_object_has_geometry_set_instances(const Object *ob)
case GEO_COMPONENT_TYPE_CURVE:
is_instance = !ELEM(ob->type, OB_CURVES_LEGACY, OB_FONT);
break;
case GEO_COMPONENT_TYPE_EDIT:
break;
}
if (is_instance) {
return true;

View File

@ -332,6 +332,29 @@ extern const float bvhtree_kdop_axes[13][3];
namespace blender {
using BVHTree_RayCastCallback_CPP =
FunctionRef<void(int index, const BVHTreeRay &ray, BVHTreeRayHit &hit)>;
inline void BLI_bvhtree_ray_cast_all_cpp(BVHTree &tree,
const float3 co,
const float3 dir,
float radius,
float hit_dist,
BVHTree_RayCastCallback_CPP fn)
{
BLI_bvhtree_ray_cast_all(
&tree,
co,
dir,
radius,
hit_dist,
[](void *userdata, int index, const BVHTreeRay *ray, BVHTreeRayHit *hit) {
BVHTree_RayCastCallback_CPP fn = *static_cast<BVHTree_RayCastCallback_CPP *>(userdata);
fn(index, *ray, *hit);
},
&fn);
}
using BVHTree_RangeQuery_CPP = FunctionRef<void(int index, const float3 &co, float dist_sq)>;
inline void BLI_bvhtree_range_query_cpp(BVHTree &tree,

View File

@ -23,6 +23,8 @@
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_mesh_sample.hh"
#include "BKE_modifier.h"
#include "BKE_object.h"
#include "BKE_paint.h"
#include "BKE_report.h"
@ -42,6 +44,8 @@
#include "WM_api.h"
#include "DEG_depsgraph_query.h"
/**
* The code below uses a suffix naming convention to indicate the coordinate space:
* cu: Local space of the curves object that is being edited.
@ -80,13 +84,15 @@ struct AddOperationExecutor {
AddOperation *self_ = nullptr;
CurvesSculptCommonContext ctx_;
Object *object_ = nullptr;
Curves *curves_id_ = nullptr;
CurvesGeometry *curves_ = nullptr;
Object *curves_ob_orig_ = nullptr;
Curves *curves_id_orig_ = nullptr;
CurvesGeometry *curves_orig_ = nullptr;
Object *surface_ob_ = nullptr;
Mesh *surface_ = nullptr;
Span<MLoopTri> surface_looptris_;
Object *surface_ob_eval_ = nullptr;
Mesh *surface_eval_ = nullptr;
Span<MLoopTri> surface_looptris_eval_;
VArraySpan<float2> surface_uv_map_eval_;
BVHTreeFromMesh surface_bvh_eval_;
const CurvesSculpt *curves_sculpt_ = nullptr;
const Brush *brush_ = nullptr;
@ -99,14 +105,6 @@ struct AddOperationExecutor {
CurvesSurfaceTransforms transforms_;
BVHTreeFromMesh surface_bvh_;
struct AddedPoints {
Vector<float3> positions_cu;
Vector<float3> bary_coords;
Vector<int> looptri_indices;
};
AddOperationExecutor(const bContext &C) : ctx_(C)
{
}
@ -114,19 +112,26 @@ struct AddOperationExecutor {
void execute(AddOperation &self, const bContext &C, const StrokeExtension &stroke_extension)
{
self_ = &self;
object_ = CTX_data_active_object(&C);
curves_ob_orig_ = CTX_data_active_object(&C);
curves_id_ = static_cast<Curves *>(object_->data);
curves_ = &CurvesGeometry::wrap(curves_id_->geometry);
curves_id_orig_ = static_cast<Curves *>(curves_ob_orig_->data);
curves_orig_ = &CurvesGeometry::wrap(curves_id_orig_->geometry);
if (curves_id_->surface == nullptr || curves_id_->surface->type != OB_MESH) {
if (curves_id_orig_->surface == nullptr || curves_id_orig_->surface->type != OB_MESH) {
report_missing_surface(stroke_extension.reports);
return;
}
transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface);
transforms_ = CurvesSurfaceTransforms(*curves_ob_orig_, curves_id_orig_->surface);
surface_ob_ = curves_id_->surface;
surface_ = static_cast<Mesh *>(surface_ob_->data);
Object &surface_ob_orig = *curves_id_orig_->surface;
Mesh &surface_orig = *static_cast<Mesh *>(surface_ob_orig.data);
surface_ob_eval_ = DEG_get_evaluated_object(ctx_.depsgraph, &surface_ob_orig);
if (surface_ob_eval_ == nullptr) {
return;
}
surface_eval_ = BKE_object_get_evaluated_mesh(surface_ob_eval_);
curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt;
brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint);
@ -143,56 +148,70 @@ struct AddOperationExecutor {
return;
}
/* Find UV map. */
VArraySpan<float2> surface_uv_map;
if (curves_id_orig_->surface_uv_map != nullptr) {
surface_uv_map = bke::mesh_attributes(surface_orig)
.lookup<float2>(curves_id_orig_->surface_uv_map, ATTR_DOMAIN_CORNER);
surface_uv_map_eval_ = bke::mesh_attributes(*surface_eval_)
.lookup<float2>(curves_id_orig_->surface_uv_map,
ATTR_DOMAIN_CORNER);
}
if (surface_uv_map.is_empty()) {
report_missing_uv_map_on_original_surface(stroke_extension.reports);
return;
}
if (surface_uv_map_eval_.is_empty()) {
report_missing_uv_map_on_evaluated_surface(stroke_extension.reports);
return;
}
const double time = PIL_check_seconds_timer() * 1000000.0;
/* Use a pointer cast to avoid overflow warnings. */
RandomNumberGenerator rng{*(uint32_t *)(&time)};
BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2);
BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); });
BKE_bvhtree_from_mesh_get(&surface_bvh_eval_, surface_eval_, BVHTREE_FROM_LOOPTRI, 2);
BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_eval_); });
surface_looptris_ = {BKE_mesh_runtime_looptri_ensure(surface_),
BKE_mesh_runtime_looptri_len(surface_)};
surface_looptris_eval_ = {BKE_mesh_runtime_looptri_ensure(surface_eval_),
BKE_mesh_runtime_looptri_len(surface_eval_)};
/* Sample points on the surface using one of multiple strategies. */
AddedPoints added_points;
Vector<float2> sampled_uvs;
if (add_amount_ == 1) {
this->sample_in_center_with_symmetry(added_points);
this->sample_in_center_with_symmetry(sampled_uvs);
}
else if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
this->sample_projected_with_symmetry(rng, added_points);
this->sample_projected_with_symmetry(rng, sampled_uvs);
}
else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
this->sample_spherical_with_symmetry(rng, added_points);
this->sample_spherical_with_symmetry(rng, sampled_uvs);
}
else {
BLI_assert_unreachable();
}
if (added_points.bary_coords.is_empty()) {
if (sampled_uvs.is_empty()) {
/* No new points have been added. */
return;
}
/* Find UV map. */
VArraySpan<float2> surface_uv_map;
if (curves_id_->surface_uv_map != nullptr) {
const bke::AttributeAccessor surface_attributes = bke::mesh_attributes(*surface_);
surface_uv_map = surface_attributes.lookup<float2>(curves_id_->surface_uv_map,
ATTR_DOMAIN_CORNER);
}
const Span<MLoopTri> surface_looptris_orig = {BKE_mesh_runtime_looptri_ensure(&surface_orig),
BKE_mesh_runtime_looptri_len(&surface_orig)};
/* Find normals. */
if (!CustomData_has_layer(&surface_->ldata, CD_NORMAL)) {
BKE_mesh_calc_normals_split(surface_);
if (!CustomData_has_layer(&surface_orig.ldata, CD_NORMAL)) {
BKE_mesh_calc_normals_split(&surface_orig);
}
const Span<float3> corner_normals_su = {
reinterpret_cast<const float3 *>(CustomData_get_layer(&surface_->ldata, CD_NORMAL)),
surface_->totloop};
reinterpret_cast<const float3 *>(CustomData_get_layer(&surface_orig.ldata, CD_NORMAL)),
surface_orig.totloop};
const geometry::ReverseUVSampler reverse_uv_sampler{surface_uv_map, surface_looptris_orig};
geometry::AddCurvesOnMeshInputs add_inputs;
add_inputs.root_positions_cu = added_points.positions_cu;
add_inputs.bary_coords = added_points.bary_coords;
add_inputs.looptri_indices = added_points.looptri_indices;
add_inputs.uvs = sampled_uvs;
add_inputs.interpolate_length = brush_settings_->flag &
BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_LENGTH;
add_inputs.interpolate_shape = brush_settings_->flag &
@ -201,13 +220,10 @@ struct AddOperationExecutor {
BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_POINT_COUNT;
add_inputs.fallback_curve_length = brush_settings_->curve_length;
add_inputs.fallback_point_count = std::max(2, brush_settings_->points_per_curve);
add_inputs.surface = surface_;
add_inputs.surface_bvh = &surface_bvh_;
add_inputs.surface_looptris = surface_looptris_;
add_inputs.surface_uv_map = surface_uv_map;
add_inputs.transforms = &transforms_;
add_inputs.reverse_uv_sampler = &reverse_uv_sampler;
add_inputs.surface = &surface_orig;
add_inputs.corner_normals_su = corner_normals_su;
add_inputs.curves_to_surface_mat = transforms_.curves_to_surface;
add_inputs.surface_to_curves_normal_mat = transforms_.surface_to_curves_normal;
if (add_inputs.interpolate_length || add_inputs.interpolate_shape ||
add_inputs.interpolate_point_count) {
@ -215,17 +231,17 @@ struct AddOperationExecutor {
add_inputs.old_roots_kdtree = self_->curve_roots_kdtree_;
}
geometry::add_curves_on_mesh(*curves_, add_inputs);
geometry::add_curves_on_mesh(*curves_orig_, add_inputs);
DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id);
DEG_id_tag_update(&curves_id_orig_->id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_orig_->id);
ED_region_tag_redraw(ctx_.region);
}
/**
* Sample a single point exactly at the mouse position.
*/
void sample_in_center_with_symmetry(AddedPoints &r_added_points)
void sample_in_center_with_symmetry(Vector<float2> &r_sampled_uvs)
{
float3 ray_start_wo, ray_end_wo;
ED_view3d_win_to_segment_clipped(
@ -234,15 +250,15 @@ struct AddOperationExecutor {
const float3 ray_end_cu = transforms_.world_to_curves * ray_end_wo;
const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
eCurvesSymmetryType(curves_id_->symmetry));
eCurvesSymmetryType(curves_id_orig_->symmetry));
for (const float4x4 &brush_transform : symmetry_brush_transforms) {
const float4x4 transform = transforms_.curves_to_surface * brush_transform;
this->sample_in_center(r_added_points, transform * ray_start_cu, transform * ray_end_cu);
this->sample_in_center(r_sampled_uvs, transform * ray_start_cu, transform * ray_end_cu);
}
}
void sample_in_center(AddedPoints &r_added_points,
void sample_in_center(Vector<float2> &r_sampled_uvs,
const float3 &ray_start_su,
const float3 &ray_end_su)
{
@ -251,58 +267,61 @@ struct AddOperationExecutor {
BVHTreeRayHit ray_hit;
ray_hit.dist = FLT_MAX;
ray_hit.index = -1;
BLI_bvhtree_ray_cast(surface_bvh_.tree,
BLI_bvhtree_ray_cast(surface_bvh_eval_.tree,
ray_start_su,
ray_direction_su,
0.0f,
&ray_hit,
surface_bvh_.raycast_callback,
&surface_bvh_);
surface_bvh_eval_.raycast_callback,
&surface_bvh_eval_);
if (ray_hit.index == -1) {
return;
}
const int looptri_index = ray_hit.index;
const MLoopTri &looptri = surface_looptris_eval_[looptri_index];
const float3 brush_pos_su = ray_hit.co;
const float3 bary_coords = bke::mesh_surface_sample::compute_bary_coord_in_triangle(
*surface_, surface_looptris_[looptri_index], brush_pos_su);
*surface_eval_, looptri, brush_pos_su);
const float3 brush_pos_cu = transforms_.surface_to_curves * brush_pos_su;
r_added_points.positions_cu.append(brush_pos_cu);
r_added_points.bary_coords.append(bary_coords);
r_added_points.looptri_indices.append(looptri_index);
const float2 uv = bke::mesh_surface_sample::sample_corner_attrribute_with_bary_coords(
bary_coords, looptri, surface_uv_map_eval_);
r_sampled_uvs.append(uv);
}
/**
* Sample points by shooting rays within the brush radius in the 3D view.
*/
void sample_projected_with_symmetry(RandomNumberGenerator &rng, AddedPoints &r_added_points)
void sample_projected_with_symmetry(RandomNumberGenerator &rng, Vector<float2> &r_sampled_uvs)
{
const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
eCurvesSymmetryType(curves_id_->symmetry));
eCurvesSymmetryType(curves_id_orig_->symmetry));
for (const float4x4 &brush_transform : symmetry_brush_transforms) {
this->sample_projected(rng, r_added_points, brush_transform);
this->sample_projected(rng, r_sampled_uvs, brush_transform);
}
}
void sample_projected(RandomNumberGenerator &rng,
AddedPoints &r_added_points,
Vector<float2> &r_sampled_uvs,
const float4x4 &brush_transform)
{
const int old_amount = r_added_points.bary_coords.size();
const int old_amount = r_sampled_uvs.size();
const int max_iterations = 100;
int current_iteration = 0;
while (r_added_points.bary_coords.size() < old_amount + add_amount_) {
while (r_sampled_uvs.size() < old_amount + add_amount_) {
if (current_iteration++ >= max_iterations) {
break;
}
const int missing_amount = add_amount_ + old_amount - r_added_points.bary_coords.size();
Vector<float3> bary_coords;
Vector<int> looptri_indices;
Vector<float3> positions_su;
const int missing_amount = add_amount_ + old_amount - r_sampled_uvs.size();
const int new_points = bke::mesh_surface_sample::sample_surface_points_projected(
rng,
*surface_,
surface_bvh_,
*surface_eval_,
surface_bvh_eval_,
brush_pos_re_,
brush_radius_re_,
[&](const float2 &pos_re, float3 &r_start_su, float3 &r_end_su) {
@ -317,11 +336,14 @@ struct AddOperationExecutor {
use_front_face_,
add_amount_,
missing_amount,
r_added_points.bary_coords,
r_added_points.looptri_indices,
r_added_points.positions_cu);
for (float3 &pos : r_added_points.positions_cu.as_mutable_span().take_back(new_points)) {
pos = transforms_.surface_to_curves * pos;
bary_coords,
looptri_indices,
positions_su);
for (const int i : IndexRange(new_points)) {
const float2 uv = bke::mesh_surface_sample::sample_corner_attrribute_with_bary_coords(
bary_coords[i], surface_looptris_eval_[looptri_indices[i]], surface_uv_map_eval_);
r_sampled_uvs.append(uv);
}
}
}
@ -329,13 +351,13 @@ struct AddOperationExecutor {
/**
* Sample points in a 3D sphere around the surface position that the mouse hovers over.
*/
void sample_spherical_with_symmetry(RandomNumberGenerator &rng, AddedPoints &r_added_points)
void sample_spherical_with_symmetry(RandomNumberGenerator &rng, Vector<float2> &r_sampled_uvs)
{
const std::optional<CurvesBrush3D> brush_3d = sample_curves_surface_3d_brush(*ctx_.depsgraph,
*ctx_.region,
*ctx_.v3d,
transforms_,
surface_bvh_,
surface_bvh_eval_,
brush_pos_re_,
brush_radius_re_);
if (!brush_3d.has_value()) {
@ -355,7 +377,7 @@ struct AddOperationExecutor {
const float3 view_ray_end_cu = transforms_.world_to_curves * view_ray_end_wo;
const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
eCurvesSymmetryType(curves_id_->symmetry));
eCurvesSymmetryType(curves_id_orig_->symmetry));
for (const float4x4 &brush_transform : symmetry_brush_transforms) {
const float4x4 transform = transforms_.curves_to_surface * brush_transform;
@ -365,13 +387,12 @@ struct AddOperationExecutor {
const float brush_radius_su = transform_brush_radius(
transform, brush_3d->position_cu, brush_3d->radius_cu);
this->sample_spherical(
rng, r_added_points, brush_pos_su, brush_radius_su, view_direction_su);
this->sample_spherical(rng, r_sampled_uvs, brush_pos_su, brush_radius_su, view_direction_su);
}
}
void sample_spherical(RandomNumberGenerator &rng,
AddedPoints &r_added_points,
Vector<float2> &r_sampled_uvs,
const float3 &brush_pos_su,
const float brush_radius_su,
const float3 &view_direction_su)
@ -379,32 +400,32 @@ struct AddOperationExecutor {
const float brush_radius_sq_su = pow2f(brush_radius_su);
/* Find surface triangles within brush radius. */
Vector<int> looptri_indices;
Vector<int> selected_looptri_indices;
if (use_front_face_) {
BLI_bvhtree_range_query_cpp(
*surface_bvh_.tree,
*surface_bvh_eval_.tree,
brush_pos_su,
brush_radius_su,
[&](const int index, const float3 &UNUSED(co), const float UNUSED(dist_sq)) {
const MLoopTri &looptri = surface_looptris_[index];
const float3 v0_su = surface_->mvert[surface_->mloop[looptri.tri[0]].v].co;
const float3 v1_su = surface_->mvert[surface_->mloop[looptri.tri[1]].v].co;
const float3 v2_su = surface_->mvert[surface_->mloop[looptri.tri[2]].v].co;
const MLoopTri &looptri = surface_looptris_eval_[index];
const float3 v0_su = surface_eval_->mvert[surface_eval_->mloop[looptri.tri[0]].v].co;
const float3 v1_su = surface_eval_->mvert[surface_eval_->mloop[looptri.tri[1]].v].co;
const float3 v2_su = surface_eval_->mvert[surface_eval_->mloop[looptri.tri[2]].v].co;
float3 normal_su;
normal_tri_v3(normal_su, v0_su, v1_su, v2_su);
if (math::dot(normal_su, view_direction_su) >= 0.0f) {
return;
}
looptri_indices.append(index);
selected_looptri_indices.append(index);
});
}
else {
BLI_bvhtree_range_query_cpp(
*surface_bvh_.tree,
*surface_bvh_eval_.tree,
brush_pos_su,
brush_radius_su,
[&](const int index, const float3 &UNUSED(co), const float UNUSED(dist_sq)) {
looptri_indices.append(index);
selected_looptri_indices.append(index);
});
}
@ -418,42 +439,45 @@ struct AddOperationExecutor {
const int max_iterations = 5;
int current_iteration = 0;
const int old_amount = r_added_points.bary_coords.size();
while (r_added_points.bary_coords.size() < old_amount + add_amount_) {
const int old_amount = r_sampled_uvs.size();
while (r_sampled_uvs.size() < old_amount + add_amount_) {
if (current_iteration++ >= max_iterations) {
break;
}
Vector<float3> bary_coords;
Vector<int> looptri_indices;
Vector<float3> positions_su;
const int new_points = bke::mesh_surface_sample::sample_surface_points_spherical(
rng,
*surface_,
looptri_indices,
*surface_eval_,
selected_looptri_indices,
brush_pos_su,
brush_radius_su,
approximate_density_su,
r_added_points.bary_coords,
r_added_points.looptri_indices,
r_added_points.positions_cu);
for (float3 &pos : r_added_points.positions_cu.as_mutable_span().take_back(new_points)) {
pos = transforms_.surface_to_curves * pos;
bary_coords,
looptri_indices,
positions_su);
for (const int i : IndexRange(new_points)) {
const float2 uv = bke::mesh_surface_sample::sample_corner_attrribute_with_bary_coords(
bary_coords[i], surface_looptris_eval_[looptri_indices[i]], surface_uv_map_eval_);
r_sampled_uvs.append(uv);
}
}
/* Remove samples when there are too many. */
while (r_added_points.bary_coords.size() > old_amount + add_amount_) {
while (r_sampled_uvs.size() > old_amount + add_amount_) {
const int index_to_remove = rng.get_int32(add_amount_) + old_amount;
r_added_points.bary_coords.remove_and_reorder(index_to_remove);
r_added_points.looptri_indices.remove_and_reorder(index_to_remove);
r_added_points.positions_cu.remove_and_reorder(index_to_remove);
r_sampled_uvs.remove_and_reorder(index_to_remove);
}
}
void ensure_curve_roots_kdtree()
{
if (self_->curve_roots_kdtree_ == nullptr) {
self_->curve_roots_kdtree_ = BLI_kdtree_3d_new(curves_->curves_num());
for (const int curve_i : curves_->curves_range()) {
const int root_point_i = curves_->offsets()[curve_i];
const float3 &root_pos_cu = curves_->positions()[root_point_i];
self_->curve_roots_kdtree_ = BLI_kdtree_3d_new(curves_orig_->curves_num());
for (const int curve_i : curves_orig_->curves_range()) {
const int root_point_i = curves_orig_->offsets()[curve_i];
const float3 &root_pos_cu = curves_orig_->positions()[root_point_i];
BLI_kdtree_3d_insert(self_->curve_roots_kdtree_, curve_i, root_pos_cu);
}
BLI_kdtree_3d_balance(self_->curve_roots_kdtree_);
@ -467,17 +491,8 @@ void AddOperation::on_stroke_extended(const bContext &C, const StrokeExtension &
executor.execute(*this, C, stroke_extension);
}
std::unique_ptr<CurvesSculptStrokeOperation> new_add_operation(const bContext &C,
ReportList *reports)
std::unique_ptr<CurvesSculptStrokeOperation> new_add_operation()
{
const Object &ob_active = *CTX_data_active_object(&C);
BLI_assert(ob_active.type == OB_CURVES);
const Curves &curves_id = *static_cast<Curves *>(ob_active.data);
if (curves_id.surface == nullptr || curves_id.surface->type != OB_MESH) {
BKE_report(reports, RPT_WARNING, "Can not use Add brush when there is no surface mesh");
return {};
}
return std::make_unique<AddOperation>();
}

View File

@ -8,6 +8,9 @@
#include "BKE_bvhutils.h"
#include "BKE_context.h"
#include "BKE_curves.hh"
#include "BKE_modifier.h"
#include "BKE_object.h"
#include "BKE_report.h"
#include "ED_view3d.h"
@ -20,6 +23,10 @@
#include "BLI_length_parameterize.hh"
#include "BLI_task.hh"
#include "DEG_depsgraph_query.h"
#include "BLT_translation.h"
/**
* The code below uses a prefix naming convention to indicate the coordinate space:
* cu: Local space of the curves object that is being edited.
@ -48,7 +55,8 @@ static std::optional<float3> find_curves_brush_position(const CurvesGeometry &cu
const float brush_radius_re,
const ARegion &region,
const RegionView3D &rv3d,
const Object &object)
const Object &object,
const Span<float3> positions)
{
/* This value might have to be adjusted based on user feedback. */
const float brush_inner_radius_re = std::min<float>(brush_radius_re, (float)UI_UNIT_X / 3.0f);
@ -88,8 +96,6 @@ static std::optional<float3> find_curves_brush_position(const CurvesGeometry &cu
}
};
const Span<float3> positions = curves.positions();
BrushPositionCandidate best_candidate = threading::parallel_reduce(
curves.curves_range(),
128,
@ -175,20 +181,21 @@ std::optional<CurvesBrush3D> sample_curves_3d_brush(const Depsgraph &depsgraph,
{
const Curves &curves_id = *static_cast<Curves *>(curves_object.data);
const CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry);
const Object *surface_object = curves_id.surface;
Object *surface_object = curves_id.surface;
Object *surface_object_eval = DEG_get_evaluated_object(&depsgraph, surface_object);
float3 center_ray_start_wo, center_ray_end_wo;
ED_view3d_win_to_segment_clipped(
&depsgraph, &region, &v3d, brush_pos_re, center_ray_start_wo, center_ray_end_wo, true);
/* Shorten ray when the surface object is hit. */
if (surface_object != nullptr) {
if (surface_object_eval != nullptr) {
const float4x4 surface_to_world_mat = surface_object->obmat;
const float4x4 world_to_surface_mat = surface_to_world_mat.inverted();
Mesh &surface = *static_cast<Mesh *>(surface_object->data);
Mesh *surface_eval = BKE_object_get_evaluated_mesh(surface_object_eval);
BVHTreeFromMesh surface_bvh;
BKE_bvhtree_from_mesh_get(&surface_bvh, &surface, BVHTREE_FROM_LOOPTRI, 2);
BKE_bvhtree_from_mesh_get(&surface_bvh, surface_eval, BVHTREE_FROM_LOOPTRI, 2);
BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh); });
const float3 center_ray_start_su = world_to_surface_mat * center_ray_start_wo;
@ -222,6 +229,9 @@ std::optional<CurvesBrush3D> sample_curves_3d_brush(const Depsgraph &depsgraph,
const float3 center_ray_start_cu = world_to_curves_mat * center_ray_start_wo;
const float3 center_ray_end_cu = world_to_curves_mat * center_ray_end_wo;
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_curves_deformation(depsgraph, curves_object);
const std::optional<float3> brush_position_optional_cu = find_curves_brush_position(
curves,
center_ray_start_cu,
@ -229,7 +239,8 @@ std::optional<CurvesBrush3D> sample_curves_3d_brush(const Depsgraph &depsgraph,
brush_radius_re,
region,
rv3d,
curves_object);
curves_object,
deformation.positions);
if (!brush_position_optional_cu.has_value()) {
/* Nothing found. */
return std::nullopt;
@ -380,4 +391,21 @@ CurvesSculptCommonContext::CurvesSculptCommonContext(const bContext &C)
this->rv3d = CTX_wm_region_view3d(&C);
}
void report_missing_surface(ReportList *reports)
{
BKE_report(reports, RPT_WARNING, TIP_("Missing surface mesh"));
}
void report_missing_uv_map_on_original_surface(ReportList *reports)
{
BKE_report(
reports, RPT_WARNING, TIP_("Missing UV map for attaching curves on original surface"));
}
void report_missing_uv_map_on_evaluated_surface(ReportList *reports)
{
BKE_report(
reports, RPT_WARNING, TIP_("Missing UV map for attaching curves on evaluated surface"));
}
} // namespace blender::ed::sculpt_paint

View File

@ -13,12 +13,15 @@
#include "PIL_time.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
#include "BKE_attribute_math.hh"
#include "BKE_brush.h"
#include "BKE_bvhutils.h"
#include "BKE_context.h"
#include "BKE_crazyspace.hh"
#include "BKE_curves.hh"
#include "BKE_geometry_set.hh"
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_paint.h"
@ -88,9 +91,9 @@ struct CombOperationExecutor {
eBrushFalloffShape falloff_shape_;
Object *object_ = nullptr;
Curves *curves_id_ = nullptr;
CurvesGeometry *curves_ = nullptr;
Object *curves_ob_orig_ = nullptr;
Curves *curves_id_orig_ = nullptr;
CurvesGeometry *curves_orig_ = nullptr;
VArray<float> point_factors_;
Vector<int64_t> selected_curve_indices_;
@ -112,7 +115,12 @@ struct CombOperationExecutor {
BLI_SCOPED_DEFER([&]() { self_->brush_pos_last_re_ = stroke_extension.mouse_position; });
object_ = CTX_data_active_object(&C);
curves_ob_orig_ = CTX_data_active_object(&C);
curves_id_orig_ = static_cast<Curves *>(curves_ob_orig_->data);
curves_orig_ = &CurvesGeometry::wrap(curves_id_orig_->geometry);
if (curves_orig_->curves_num() == 0) {
return;
}
curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt;
brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint);
@ -122,16 +130,10 @@ struct CombOperationExecutor {
falloff_shape_ = static_cast<eBrushFalloffShape>(brush_->falloff_shape);
curves_id_ = static_cast<Curves *>(object_->data);
curves_ = &CurvesGeometry::wrap(curves_id_->geometry);
if (curves_->curves_num() == 0) {
return;
}
transforms_ = CurvesSurfaceTransforms(*curves_ob_orig_, curves_id_orig_->surface);
transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface);
point_factors_ = get_point_selection(*curves_id_);
curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_);
point_factors_ = get_point_selection(*curves_id_orig_);
curve_selection_ = retrieve_selected_curves(*curves_id_orig_, selected_curve_indices_);
brush_pos_prev_re_ = self_->brush_pos_last_re_;
brush_pos_re_ = stroke_extension.mouse_position;
@ -160,9 +162,9 @@ struct CombOperationExecutor {
this->restore_segment_lengths(changed_curves);
curves_->tag_positions_changed();
DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id);
curves_orig_->tag_positions_changed();
DEG_id_tag_update(&curves_id_orig_->id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_orig_->id);
ED_region_tag_redraw(ctx_.region);
}
@ -172,7 +174,7 @@ struct CombOperationExecutor {
void comb_projected_with_symmetry(EnumerableThreadSpecific<Vector<int>> &r_changed_curves)
{
const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
eCurvesSymmetryType(curves_id_->symmetry));
eCurvesSymmetryType(curves_id_orig_->symmetry));
for (const float4x4 &brush_transform : symmetry_brush_transforms) {
this->comb_projected(r_changed_curves, brush_transform);
}
@ -183,10 +185,12 @@ struct CombOperationExecutor {
{
const float4x4 brush_transform_inv = brush_transform.inverted();
MutableSpan<float3> positions_cu = curves_->positions_for_write();
MutableSpan<float3> positions_cu_orig = curves_orig_->positions_for_write();
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *curves_ob_orig_);
float4x4 projection;
ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values);
ED_view3d_ob_project_mat_get(ctx_.rv3d, curves_ob_orig_, projection.values);
const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
const float brush_radius_sq_re = pow2f(brush_radius_re);
@ -195,16 +199,18 @@ struct CombOperationExecutor {
Vector<int> &local_changed_curves = r_changed_curves.local();
for (const int curve_i : curve_selection_.slice(range)) {
bool curve_changed = false;
const IndexRange points = curves_->points_for_curve(curve_i);
const IndexRange points = curves_orig_->points_for_curve(curve_i);
for (const int point_i : points.drop_front(1)) {
const float3 old_pos_cu = brush_transform_inv * positions_cu[point_i];
const float3 old_pos_cu = deformation.positions[point_i];
const float3 old_symm_pos_cu = brush_transform_inv * old_pos_cu;
/* Find the position of the point in screen space. */
float2 old_pos_re;
ED_view3d_project_float_v2_m4(ctx_.region, old_pos_cu, old_pos_re, projection.values);
float2 old_symm_pos_re;
ED_view3d_project_float_v2_m4(
ctx_.region, old_symm_pos_cu, old_symm_pos_re, projection.values);
const float distance_to_brush_sq_re = dist_squared_to_line_segment_v2(
old_pos_re, brush_pos_prev_re_, brush_pos_re_);
old_symm_pos_re, brush_pos_prev_re_, brush_pos_re_);
if (distance_to_brush_sq_re > brush_radius_sq_re) {
/* Ignore the point because it's too far away. */
continue;
@ -219,16 +225,20 @@ struct CombOperationExecutor {
/* Offset the old point position in screen space and transform it back into 3D space.
*/
const float2 new_position_re = old_pos_re + brush_pos_diff_re_ * weight;
float3 new_position_wo;
const float2 new_symm_pos_re = old_symm_pos_re + brush_pos_diff_re_ * weight;
float3 new_symm_pos_wo;
ED_view3d_win_to_3d(ctx_.v3d,
ctx_.region,
transforms_.curves_to_world * old_pos_cu,
new_position_re,
new_position_wo);
const float3 new_position_cu = brush_transform *
(transforms_.world_to_curves * new_position_wo);
positions_cu[point_i] = new_position_cu;
transforms_.curves_to_world * old_symm_pos_cu,
new_symm_pos_re,
new_symm_pos_wo);
const float3 new_pos_cu = brush_transform *
(transforms_.world_to_curves * new_symm_pos_wo);
const float3 translation_eval = new_pos_cu - old_pos_cu;
const float3 translation_orig = deformation.translation_from_deformed_to_original(
point_i, translation_eval);
positions_cu_orig[point_i] += translation_orig;
curve_changed = true;
}
@ -245,7 +255,7 @@ struct CombOperationExecutor {
void comb_spherical_with_symmetry(EnumerableThreadSpecific<Vector<int>> &r_changed_curves)
{
float4x4 projection;
ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values);
ED_view3d_ob_project_mat_get(ctx_.rv3d, curves_ob_orig_, projection.values);
float3 brush_start_wo, brush_end_wo;
ED_view3d_win_to_3d(ctx_.v3d,
@ -264,7 +274,7 @@ struct CombOperationExecutor {
const float brush_radius_cu = self_->brush_3d_.radius_cu * brush_radius_factor_;
const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
eCurvesSymmetryType(curves_id_->symmetry));
eCurvesSymmetryType(curves_id_orig_->symmetry));
for (const float4x4 &brush_transform : symmetry_brush_transforms) {
this->comb_spherical(r_changed_curves,
brush_transform * brush_start_cu,
@ -278,17 +288,20 @@ struct CombOperationExecutor {
const float3 &brush_end_cu,
const float brush_radius_cu)
{
MutableSpan<float3> positions_cu = curves_->positions_for_write();
MutableSpan<float3> positions_cu = curves_orig_->positions_for_write();
const float brush_radius_sq_cu = pow2f(brush_radius_cu);
const float3 brush_diff_cu = brush_end_cu - brush_start_cu;
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *curves_ob_orig_);
threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) {
Vector<int> &local_changed_curves = r_changed_curves.local();
for (const int curve_i : curve_selection_.slice(range)) {
bool curve_changed = false;
const IndexRange points = curves_->points_for_curve(curve_i);
const IndexRange points = curves_orig_->points_for_curve(curve_i);
for (const int point_i : points.drop_front(1)) {
const float3 pos_old_cu = positions_cu[point_i];
const float3 pos_old_cu = deformation.positions[point_i];
/* Compute distance to the brush. */
const float distance_to_brush_sq_cu = dist_squared_to_line_segment_v3(
@ -306,8 +319,12 @@ struct CombOperationExecutor {
/* Combine the falloff and brush strength. */
const float weight = brush_strength_ * radius_falloff * point_factors_[point_i];
const float3 translation_eval_cu = weight * brush_diff_cu;
const float3 translation_orig_cu = deformation.translation_from_deformed_to_original(
point_i, translation_eval_cu);
/* Update the point position. */
positions_cu[point_i] = pos_old_cu + weight * brush_diff_cu;
positions_cu[point_i] += translation_orig_cu;
curve_changed = true;
}
if (curve_changed) {
@ -326,7 +343,7 @@ struct CombOperationExecutor {
*ctx_.region,
*ctx_.v3d,
*ctx_.rv3d,
*object_,
*curves_ob_orig_,
brush_pos_re_,
brush_radius_base_re_);
if (brush_3d.has_value()) {
@ -340,11 +357,11 @@ struct CombOperationExecutor {
*/
void initialize_segment_lengths()
{
const Span<float3> positions_cu = curves_->positions();
self_->segment_lengths_cu_.reinitialize(curves_->points_num());
threading::parallel_for(curves_->curves_range(), 128, [&](const IndexRange range) {
const Span<float3> positions_cu = curves_orig_->positions();
self_->segment_lengths_cu_.reinitialize(curves_orig_->points_num());
threading::parallel_for(curves_orig_->curves_range(), 128, [&](const IndexRange range) {
for (const int curve_i : range) {
const IndexRange points = curves_->points_for_curve(curve_i);
const IndexRange points = curves_orig_->points_for_curve(curve_i);
for (const int point_i : points.drop_back(1)) {
const float3 &p1_cu = positions_cu[point_i];
const float3 &p2_cu = positions_cu[point_i + 1];
@ -361,12 +378,12 @@ struct CombOperationExecutor {
void restore_segment_lengths(EnumerableThreadSpecific<Vector<int>> &changed_curves)
{
const Span<float> expected_lengths_cu = self_->segment_lengths_cu_;
MutableSpan<float3> positions_cu = curves_->positions_for_write();
MutableSpan<float3> positions_cu = curves_orig_->positions_for_write();
threading::parallel_for_each(changed_curves, [&](const Vector<int> &changed_curves) {
threading::parallel_for(changed_curves.index_range(), 256, [&](const IndexRange range) {
for (const int curve_i : changed_curves.as_span().slice(range)) {
const IndexRange points = curves_->points_for_curve(curve_i);
const IndexRange points = curves_orig_->points_for_curve(curve_i);
for (const int segment_i : points.drop_back(1)) {
const float3 &p1_cu = positions_cu[segment_i];
float3 &p2_cu = positions_cu[segment_i + 1];

View File

@ -51,6 +51,12 @@ using blender::bke::CurvesGeometry;
class DeleteOperation : public CurvesSculptStrokeOperation {
private:
CurvesBrush3D brush_3d_;
/**
* Need to store those in case the brush is evaluated more than once before the curves are
* evaluated again. This can happen when the mouse is moved quickly and the brush spacing is
* small.
*/
Vector<float3> deformed_positions_;
friend struct DeleteOperationExecutor;
@ -109,6 +115,9 @@ struct DeleteOperationExecutor {
if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
this->initialize_spherical_brush_reference_point();
}
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_);
self_->deformed_positions_ = deformation.positions;
}
Array<bool> curves_to_delete(curves_->curves_num(), false);
@ -123,12 +132,22 @@ struct DeleteOperationExecutor {
}
Vector<int64_t> indices;
const IndexMask mask = index_mask_ops::find_indices_based_on_predicate(
const IndexMask mask_to_delete = index_mask_ops::find_indices_based_on_predicate(
curves_->curves_range(), 4096, indices, [&](const int curve_i) {
return curves_to_delete[curve_i];
});
curves_->remove_curves(mask);
/* Remove deleted curves from the stored deformed positions. */
const Vector<IndexRange> ranges_to_keep = mask_to_delete.extract_ranges_invert(
curves_->curves_range());
Vector<float3> new_deformed_positions;
for (const IndexRange curves_range : ranges_to_keep) {
new_deformed_positions.extend(
self_->deformed_positions_.as_span().slice(curves_->points_for_curves(curves_range)));
}
self_->deformed_positions_ = std::move(new_deformed_positions);
curves_->remove_curves(mask_to_delete);
DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id);
@ -151,8 +170,6 @@ struct DeleteOperationExecutor {
float4x4 projection;
ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values);
Span<float3> positions_cu = curves_->positions();
const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
const float brush_radius_sq_re = pow2f(brush_radius_re);
@ -160,7 +177,7 @@ struct DeleteOperationExecutor {
for (const int curve_i : curve_selection_.slice(range)) {
const IndexRange points = curves_->points_for_curve(curve_i);
if (points.size() == 1) {
const float3 pos_cu = brush_transform_inv * positions_cu[points.first()];
const float3 pos_cu = brush_transform_inv * self_->deformed_positions_[points.first()];
float2 pos_re;
ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values);
@ -171,8 +188,8 @@ struct DeleteOperationExecutor {
}
for (const int segment_i : points.drop_back(1)) {
const float3 pos1_cu = brush_transform_inv * positions_cu[segment_i];
const float3 pos2_cu = brush_transform_inv * positions_cu[segment_i + 1];
const float3 pos1_cu = brush_transform_inv * self_->deformed_positions_[segment_i];
const float3 pos2_cu = brush_transform_inv * self_->deformed_positions_[segment_i + 1];
float2 pos1_re, pos2_re;
ED_view3d_project_float_v2_m4(ctx_.region, pos1_cu, pos1_re, projection.values);
@ -212,8 +229,6 @@ struct DeleteOperationExecutor {
void delete_spherical(const float3 &brush_cu, MutableSpan<bool> curves_to_delete)
{
Span<float3> positions_cu = curves_->positions();
const float brush_radius_cu = self_->brush_3d_.radius_cu * brush_radius_factor_;
const float brush_radius_sq_cu = pow2f(brush_radius_cu);
@ -222,7 +237,7 @@ struct DeleteOperationExecutor {
const IndexRange points = curves_->points_for_curve(curve_i);
if (points.size() == 1) {
const float3 &pos_cu = positions_cu[points.first()];
const float3 &pos_cu = self_->deformed_positions_[points.first()];
const float distance_sq_cu = math::distance_squared(pos_cu, brush_cu);
if (distance_sq_cu < brush_radius_sq_cu) {
curves_to_delete[curve_i] = true;
@ -231,8 +246,8 @@ struct DeleteOperationExecutor {
}
for (const int segment_i : points.drop_back(1)) {
const float3 &pos1_cu = positions_cu[segment_i];
const float3 &pos2_cu = positions_cu[segment_i + 1];
const float3 &pos1_cu = self_->deformed_positions_[segment_i];
const float3 &pos2_cu = self_->deformed_positions_[segment_i + 1];
const float distance_sq_cu = dist_squared_to_line_segment_v3(brush_cu, pos1_cu, pos2_cu);
if (distance_sq_cu > brush_radius_sq_cu) {

View File

@ -2,6 +2,7 @@
#include <numeric>
#include "BKE_attribute_math.hh"
#include "BKE_brush.h"
#include "BKE_bvhutils.h"
#include "BKE_context.h"
@ -9,11 +10,15 @@
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_mesh_sample.hh"
#include "BKE_modifier.h"
#include "BKE_object.h"
#include "BKE_report.h"
#include "ED_screen.h"
#include "ED_view3d.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
#include "BLI_index_mask_ops.hh"
#include "BLI_kdtree.h"
@ -36,7 +41,11 @@ namespace blender::ed::sculpt_paint {
class DensityAddOperation : public CurvesSculptStrokeOperation {
private:
/** Used when some data should be interpolated from existing curves. */
KDTree_3d *curve_roots_kdtree_ = nullptr;
KDTree_3d *original_curve_roots_kdtree_ = nullptr;
/** Contains curve roots of all curves that existed before the brush started. */
KDTree_3d *deformed_curve_roots_kdtree_ = nullptr;
/** Root positions of curves that have been added in the current brush stroke. */
Vector<float3> new_deformed_root_positions_;
int original_curve_num_ = 0;
friend struct DensityAddOperationExecutor;
@ -44,8 +53,11 @@ class DensityAddOperation : public CurvesSculptStrokeOperation {
public:
~DensityAddOperation() override
{
if (curve_roots_kdtree_ != nullptr) {
BLI_kdtree_3d_free(curve_roots_kdtree_);
if (original_curve_roots_kdtree_ != nullptr) {
BLI_kdtree_3d_free(original_curve_roots_kdtree_);
}
if (deformed_curve_roots_kdtree_ != nullptr) {
BLI_kdtree_3d_free(deformed_curve_roots_kdtree_);
}
}
@ -56,14 +68,18 @@ struct DensityAddOperationExecutor {
DensityAddOperation *self_ = nullptr;
CurvesSculptCommonContext ctx_;
Object *object_ = nullptr;
Curves *curves_id_ = nullptr;
CurvesGeometry *curves_ = nullptr;
Object *curves_ob_orig_ = nullptr;
Curves *curves_id_orig_ = nullptr;
CurvesGeometry *curves_orig_ = nullptr;
Object *surface_ob_ = nullptr;
Mesh *surface_ = nullptr;
Span<MLoopTri> surface_looptris_;
Span<float3> corner_normals_su_;
Object *surface_ob_orig_ = nullptr;
Mesh *surface_orig_ = nullptr;
Object *surface_ob_eval_ = nullptr;
Mesh *surface_eval_ = nullptr;
Span<MLoopTri> surface_looptris_eval_;
VArraySpan<float2> surface_uv_map_eval_;
BVHTreeFromMesh surface_bvh_eval_;
const CurvesSculpt *curves_sculpt_ = nullptr;
const Brush *brush_ = nullptr;
@ -75,8 +91,6 @@ struct DensityAddOperationExecutor {
CurvesSurfaceTransforms transforms_;
BVHTreeFromMesh surface_bvh_;
DensityAddOperationExecutor(const bContext &C) : ctx_(C)
{
}
@ -86,32 +100,51 @@ struct DensityAddOperationExecutor {
const StrokeExtension &stroke_extension)
{
self_ = &self;
object_ = CTX_data_active_object(&C);
curves_id_ = static_cast<Curves *>(object_->data);
curves_ = &CurvesGeometry::wrap(curves_id_->geometry);
curves_ob_orig_ = CTX_data_active_object(&C);
curves_id_orig_ = static_cast<Curves *>(curves_ob_orig_->data);
curves_orig_ = &CurvesGeometry::wrap(curves_id_orig_->geometry);
if (stroke_extension.is_first) {
self_->original_curve_num_ = curves_->curves_num();
self_->original_curve_num_ = curves_orig_->curves_num();
}
if (curves_id_->surface == nullptr || curves_id_->surface->type != OB_MESH) {
if (curves_id_orig_->surface == nullptr || curves_id_orig_->surface->type != OB_MESH) {
report_missing_surface(stroke_extension.reports);
return;
}
surface_ob_ = curves_id_->surface;
surface_ = static_cast<Mesh *>(surface_ob_->data);
surface_ob_orig_ = curves_id_orig_->surface;
surface_orig_ = static_cast<Mesh *>(surface_ob_orig_->data);
surface_looptris_ = {BKE_mesh_runtime_looptri_ensure(surface_),
BKE_mesh_runtime_looptri_len(surface_)};
transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface);
if (!CustomData_has_layer(&surface_->ldata, CD_NORMAL)) {
BKE_mesh_calc_normals_split(surface_);
surface_ob_eval_ = DEG_get_evaluated_object(ctx_.depsgraph, surface_ob_orig_);
if (surface_ob_eval_ == nullptr) {
return;
}
corner_normals_su_ = {
reinterpret_cast<const float3 *>(CustomData_get_layer(&surface_->ldata, CD_NORMAL)),
surface_->totloop};
surface_eval_ = BKE_object_get_evaluated_mesh(surface_ob_eval_);
BKE_bvhtree_from_mesh_get(&surface_bvh_eval_, surface_eval_, BVHTREE_FROM_LOOPTRI, 2);
BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_eval_); });
surface_looptris_eval_ = {BKE_mesh_runtime_looptri_ensure(surface_eval_),
BKE_mesh_runtime_looptri_len(surface_eval_)};
/* Find UV map. */
VArraySpan<float2> surface_uv_map;
if (curves_id_orig_->surface_uv_map != nullptr) {
surface_uv_map = bke::mesh_attributes(*surface_orig_)
.lookup<float2>(curves_id_orig_->surface_uv_map, ATTR_DOMAIN_CORNER);
surface_uv_map_eval_ = bke::mesh_attributes(*surface_eval_)
.lookup<float2>(curves_id_orig_->surface_uv_map,
ATTR_DOMAIN_CORNER);
}
if (surface_uv_map.is_empty()) {
report_missing_uv_map_on_original_surface(stroke_extension.reports);
return;
}
if (surface_uv_map_eval_.is_empty()) {
report_missing_uv_map_on_evaluated_surface(stroke_extension.reports);
return;
}
transforms_ = CurvesSurfaceTransforms(*curves_ob_orig_, curves_id_orig_->surface);
curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt;
brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint);
@ -123,23 +156,17 @@ struct DensityAddOperationExecutor {
const eBrushFalloffShape falloff_shape = static_cast<eBrushFalloffShape>(
brush_->falloff_shape);
BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2);
BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); });
Vector<float3> new_bary_coords;
Vector<int> new_looptri_indices;
Vector<float3> new_positions_cu;
Vector<float2> new_uvs;
const double time = PIL_check_seconds_timer() * 1000000.0;
RandomNumberGenerator rng{*(uint32_t *)(&time)};
/* Find potential new curve root points. */
if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
this->sample_projected_with_symmetry(
rng, new_bary_coords, new_looptri_indices, new_positions_cu);
this->sample_projected_with_symmetry(rng, new_uvs, new_positions_cu);
}
else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
this->sample_spherical_with_symmetry(
rng, new_bary_coords, new_looptri_indices, new_positions_cu);
this->sample_spherical_with_symmetry(rng, new_uvs, new_positions_cu);
}
else {
BLI_assert_unreachable();
@ -148,9 +175,11 @@ struct DensityAddOperationExecutor {
pos = transforms_.surface_to_curves * pos;
}
this->ensure_curve_roots_kdtree();
if (stroke_extension.is_first) {
this->prepare_curve_roots_kdtrees();
}
const int already_added_curves = curves_->curves_num() - self_->original_curve_num_;
const int already_added_curves = self_->new_deformed_root_positions_.size();
KDTree_3d *new_roots_kdtree = BLI_kdtree_3d_new(already_added_curves +
new_positions_cu.size());
BLI_SCOPED_DEFER([&]() { BLI_kdtree_3d_free(new_roots_kdtree); });
@ -161,15 +190,12 @@ struct DensityAddOperationExecutor {
threading::parallel_invoke(
/* Build kdtree from root points created by the current stroke. */
[&]() {
const Span<float3> positions_cu = curves_->positions();
for (const int curve_i : curves_->curves_range().take_back(already_added_curves)) {
const float3 &root_pos_cu = positions_cu[curves_->offsets()[curve_i]];
BLI_kdtree_3d_insert(new_roots_kdtree, curve_i, root_pos_cu);
for (const int i : IndexRange(already_added_curves)) {
BLI_kdtree_3d_insert(new_roots_kdtree, -1, self_->new_deformed_root_positions_[i]);
}
for (const int new_i : new_positions_cu.index_range()) {
const int index_in_kdtree = curves_->curves_num() + new_i;
const float3 &root_pos_cu = new_positions_cu[new_i];
BLI_kdtree_3d_insert(new_roots_kdtree, index_in_kdtree, root_pos_cu);
BLI_kdtree_3d_insert(new_roots_kdtree, new_i, root_pos_cu);
}
BLI_kdtree_3d_balance(new_roots_kdtree);
},
@ -183,7 +209,7 @@ struct DensityAddOperationExecutor {
KDTreeNearest_3d nearest;
nearest.dist = FLT_MAX;
BLI_kdtree_3d_find_nearest(
self_->curve_roots_kdtree_, new_root_pos_cu, &nearest);
self_->deformed_curve_roots_kdtree_, new_root_pos_cu, &nearest);
if (nearest.dist < brush_settings_->minimum_distance) {
new_curve_skipped[new_i] = true;
}
@ -201,12 +227,11 @@ struct DensityAddOperationExecutor {
new_roots_kdtree,
root_pos_cu,
brush_settings_->minimum_distance,
[&](const int other_i, const float *UNUSED(co), float UNUSED(dist_sq)) {
if (other_i < curves_->curves_num()) {
[&](const int other_new_i, const float *UNUSED(co), float UNUSED(dist_sq)) {
if (other_new_i == -1) {
new_curve_skipped[new_i] = true;
return false;
}
const int other_new_i = other_i - curves_->curves_num();
if (new_i == other_new_i) {
return true;
}
@ -219,31 +244,25 @@ struct DensityAddOperationExecutor {
for (int64_t i = new_positions_cu.size() - 1; i >= 0; i--) {
if (new_curve_skipped[i]) {
new_positions_cu.remove_and_reorder(i);
new_bary_coords.remove_and_reorder(i);
new_looptri_indices.remove_and_reorder(i);
new_uvs.remove_and_reorder(i);
}
}
/* Find UV map. */
VArraySpan<float2> surface_uv_map;
if (curves_id_->surface_uv_map != nullptr) {
bke::AttributeAccessor surface_attributes = bke::mesh_attributes(*surface_);
surface_uv_map = surface_attributes.lookup<float2>(curves_id_->surface_uv_map,
ATTR_DOMAIN_CORNER);
}
self_->new_deformed_root_positions_.extend(new_positions_cu);
/* Find normals. */
if (!CustomData_has_layer(&surface_->ldata, CD_NORMAL)) {
BKE_mesh_calc_normals_split(surface_);
if (!CustomData_has_layer(&surface_orig_->ldata, CD_NORMAL)) {
BKE_mesh_calc_normals_split(surface_orig_);
}
const Span<float3> corner_normals_su = {
reinterpret_cast<const float3 *>(CustomData_get_layer(&surface_->ldata, CD_NORMAL)),
surface_->totloop};
reinterpret_cast<const float3 *>(CustomData_get_layer(&surface_orig_->ldata, CD_NORMAL)),
surface_orig_->totloop};
const Span<MLoopTri> surface_looptris_orig = {BKE_mesh_runtime_looptri_ensure(surface_orig_),
BKE_mesh_runtime_looptri_len(surface_orig_)};
const geometry::ReverseUVSampler reverse_uv_sampler{surface_uv_map, surface_looptris_orig};
geometry::AddCurvesOnMeshInputs add_inputs;
add_inputs.root_positions_cu = new_positions_cu;
add_inputs.bary_coords = new_bary_coords;
add_inputs.looptri_indices = new_looptri_indices;
add_inputs.uvs = new_uvs;
add_inputs.interpolate_length = brush_settings_->flag &
BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_LENGTH;
add_inputs.interpolate_shape = brush_settings_->flag &
@ -252,53 +271,67 @@ struct DensityAddOperationExecutor {
BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_POINT_COUNT;
add_inputs.fallback_curve_length = brush_settings_->curve_length;
add_inputs.fallback_point_count = std::max(2, brush_settings_->points_per_curve);
add_inputs.surface = surface_;
add_inputs.surface_bvh = &surface_bvh_;
add_inputs.surface_looptris = surface_looptris_;
add_inputs.surface_uv_map = surface_uv_map;
add_inputs.transforms = &transforms_;
add_inputs.surface = surface_orig_;
add_inputs.corner_normals_su = corner_normals_su;
add_inputs.curves_to_surface_mat = transforms_.curves_to_surface;
add_inputs.surface_to_curves_normal_mat = transforms_.surface_to_curves_normal;
add_inputs.old_roots_kdtree = self_->curve_roots_kdtree_;
add_inputs.reverse_uv_sampler = &reverse_uv_sampler;
add_inputs.old_roots_kdtree = self_->original_curve_roots_kdtree_;
geometry::add_curves_on_mesh(*curves_, add_inputs);
geometry::add_curves_on_mesh(*curves_orig_, add_inputs);
DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id);
DEG_id_tag_update(&curves_id_orig_->id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_orig_->id);
ED_region_tag_redraw(ctx_.region);
}
void ensure_curve_roots_kdtree()
void prepare_curve_roots_kdtrees()
{
if (self_->curve_roots_kdtree_ == nullptr) {
self_->curve_roots_kdtree_ = BLI_kdtree_3d_new(curves_->curves_num());
for (const int curve_i : curves_->curves_range()) {
const int root_point_i = curves_->offsets()[curve_i];
const float3 &root_pos_cu = curves_->positions()[root_point_i];
BLI_kdtree_3d_insert(self_->curve_roots_kdtree_, curve_i, root_pos_cu);
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *curves_ob_orig_);
const Span<int> curve_offsets = curves_orig_->offsets();
const Span<float3> original_positions = curves_orig_->positions();
const Span<float3> deformed_positions = deformation.positions;
BLI_assert(original_positions.size() == deformed_positions.size());
auto roots_kdtree_from_positions = [&](const Span<float3> positions) {
KDTree_3d *kdtree = BLI_kdtree_3d_new(curves_orig_->curves_num());
for (const int curve_i : curves_orig_->curves_range()) {
const int root_point_i = curve_offsets[curve_i];
BLI_kdtree_3d_insert(kdtree, curve_i, positions[root_point_i]);
}
BLI_kdtree_3d_balance(self_->curve_roots_kdtree_);
}
BLI_kdtree_3d_balance(kdtree);
return kdtree;
};
threading::parallel_invoke(
[&]() {
self_->original_curve_roots_kdtree_ = roots_kdtree_from_positions(original_positions);
},
[&]() {
self_->deformed_curve_roots_kdtree_ = roots_kdtree_from_positions(deformed_positions);
});
}
void sample_projected_with_symmetry(RandomNumberGenerator &rng,
Vector<float3> &r_bary_coords,
Vector<int> &r_looptri_indices,
Vector<float2> &r_uvs,
Vector<float3> &r_positions_su)
{
float4x4 projection;
ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values);
ED_view3d_ob_project_mat_get(ctx_.rv3d, curves_ob_orig_, projection.values);
const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
eCurvesSymmetryType(curves_id_->symmetry));
eCurvesSymmetryType(curves_id_orig_->symmetry));
for (const float4x4 &brush_transform : symmetry_brush_transforms) {
const float4x4 brush_transform_inv = brush_transform.inverted();
const float4x4 transform = transforms_.curves_to_surface * brush_transform *
transforms_.world_to_curves;
Vector<float3> positions_su;
Vector<float3> bary_coords;
Vector<int> looptri_indices;
const int new_points = bke::mesh_surface_sample::sample_surface_points_projected(
rng,
*surface_,
surface_bvh_,
*surface_eval_,
surface_bvh_eval_,
brush_pos_re_,
brush_radius_re_,
[&](const float2 &pos_re, float3 &r_start_su, float3 &r_end_su) {
@ -311,14 +344,13 @@ struct DensityAddOperationExecutor {
true,
brush_settings_->density_add_attempts,
brush_settings_->density_add_attempts,
r_bary_coords,
r_looptri_indices,
r_positions_su);
bary_coords,
looptri_indices,
positions_su);
/* Remove some sampled points randomly based on the brush falloff and strength. */
const int old_points = r_bary_coords.size() - new_points;
for (int i = r_bary_coords.size() - 1; i >= old_points; i--) {
const float3 pos_su = r_positions_su[i];
for (int i = new_points - 1; i >= 0; i--) {
const float3 pos_su = positions_su[i];
const float3 pos_cu = brush_transform_inv * transforms_.surface_to_curves * pos_su;
float2 pos_re;
ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values);
@ -327,24 +359,30 @@ struct DensityAddOperationExecutor {
brush_, dist_to_brush_re, brush_radius_re_);
const float weight = brush_strength_ * radius_falloff;
if (rng.get_float() > weight) {
r_bary_coords.remove_and_reorder(i);
r_looptri_indices.remove_and_reorder(i);
r_positions_su.remove_and_reorder(i);
bary_coords.remove_and_reorder(i);
looptri_indices.remove_and_reorder(i);
positions_su.remove_and_reorder(i);
}
}
for (const int i : bary_coords.index_range()) {
const float2 uv = bke::mesh_surface_sample::sample_corner_attrribute_with_bary_coords(
bary_coords[i], surface_looptris_eval_[looptri_indices[i]], surface_uv_map_eval_);
r_uvs.append(uv);
}
r_positions_su.extend(positions_su);
}
}
void sample_spherical_with_symmetry(RandomNumberGenerator &rng,
Vector<float3> &r_bary_coords,
Vector<int> &r_looptri_indices,
Vector<float2> &r_uvs,
Vector<float3> &r_positions_su)
{
const std::optional<CurvesBrush3D> brush_3d = sample_curves_surface_3d_brush(*ctx_.depsgraph,
*ctx_.region,
*ctx_.v3d,
transforms_,
surface_bvh_,
surface_bvh_eval_,
brush_pos_re_,
brush_radius_re_);
if (!brush_3d.has_value()) {
@ -352,7 +390,7 @@ struct DensityAddOperationExecutor {
}
const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
eCurvesSymmetryType(curves_id_->symmetry));
eCurvesSymmetryType(curves_id_orig_->symmetry));
for (const float4x4 &brush_transform : symmetry_brush_transforms) {
const float3 brush_pos_cu = brush_transform * brush_3d->position_cu;
const float3 brush_pos_su = transforms_.curves_to_surface * brush_pos_cu;
@ -360,45 +398,54 @@ struct DensityAddOperationExecutor {
transforms_.curves_to_surface, brush_pos_cu, brush_3d->radius_cu);
const float brush_radius_sq_su = pow2f(brush_radius_su);
Vector<int> looptri_indices;
Vector<int> selected_looptri_indices;
BLI_bvhtree_range_query_cpp(
*surface_bvh_.tree,
*surface_bvh_eval_.tree,
brush_pos_su,
brush_radius_su,
[&](const int index, const float3 &UNUSED(co), const float UNUSED(dist_sq)) {
looptri_indices.append(index);
selected_looptri_indices.append(index);
});
const float brush_plane_area_su = M_PI * brush_radius_sq_su;
const float approximate_density_su = brush_settings_->density_add_attempts /
brush_plane_area_su;
Vector<float3> positions_su;
Vector<float3> bary_coords;
Vector<int> looptri_indices;
const int new_points = bke::mesh_surface_sample::sample_surface_points_spherical(
rng,
*surface_,
looptri_indices,
*surface_eval_,
selected_looptri_indices,
brush_pos_su,
brush_radius_su,
approximate_density_su,
r_bary_coords,
r_looptri_indices,
r_positions_su);
bary_coords,
looptri_indices,
positions_su);
/* Remove some sampled points randomly based on the brush falloff and strength. */
const int old_points = r_bary_coords.size() - new_points;
for (int i = r_bary_coords.size() - 1; i >= old_points; i--) {
const float3 pos_su = r_positions_su[i];
for (int i = new_points - 1; i >= 0; i--) {
const float3 pos_su = positions_su[i];
const float3 pos_cu = transforms_.surface_to_curves * pos_su;
const float dist_to_brush_cu = math::distance(pos_cu, brush_pos_cu);
const float radius_falloff = BKE_brush_curve_strength(
brush_, dist_to_brush_cu, brush_3d->radius_cu);
const float weight = brush_strength_ * radius_falloff;
if (rng.get_float() > weight) {
r_bary_coords.remove_and_reorder(i);
r_looptri_indices.remove_and_reorder(i);
r_positions_su.remove_and_reorder(i);
bary_coords.remove_and_reorder(i);
looptri_indices.remove_and_reorder(i);
positions_su.remove_and_reorder(i);
}
}
for (const int i : bary_coords.index_range()) {
const float2 uv = bke::mesh_surface_sample::sample_corner_attrribute_with_bary_coords(
bary_coords[i], surface_looptris_eval_[looptri_indices[i]], surface_uv_map_eval_);
r_uvs.append(uv);
}
r_positions_su.extend(positions_su);
}
}
};
@ -414,6 +461,13 @@ class DensitySubtractOperation : public CurvesSculptStrokeOperation {
private:
friend struct DensitySubtractOperationExecutor;
/**
* Deformed root positions of curves that still exist. This has to be stored in case the brush is
* executed more than once before the curves are evaluated again. This can happen when the mouse
* is moved quickly and the brush spacing is small.
*/
Vector<float3> deformed_root_positions_;
public:
void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override;
};
@ -433,8 +487,12 @@ struct DensitySubtractOperationExecutor {
Vector<int64_t> selected_curve_indices_;
IndexMask curve_selection_;
Object *surface_ob_ = nullptr;
Mesh *surface_ = nullptr;
Object *surface_ob_orig_ = nullptr;
Mesh *surface_orig_ = nullptr;
Object *surface_ob_eval_ = nullptr;
Mesh *surface_eval_ = nullptr;
BVHTreeFromMesh surface_bvh_eval_;
const CurvesSculpt *curves_sculpt_ = nullptr;
const Brush *brush_ = nullptr;
@ -446,7 +504,6 @@ struct DensitySubtractOperationExecutor {
float minimum_distance_;
CurvesSurfaceTransforms transforms_;
BVHTreeFromMesh surface_bvh_;
KDTree_3d *root_points_kdtree_;
@ -468,11 +525,20 @@ struct DensitySubtractOperationExecutor {
return;
}
surface_ob_ = curves_id_->surface;
if (surface_ob_ == nullptr) {
surface_ob_orig_ = curves_id_->surface;
if (surface_ob_orig_ == nullptr) {
return;
}
surface_ = static_cast<Mesh *>(surface_ob_->data);
surface_orig_ = static_cast<Mesh *>(surface_ob_orig_->data);
surface_ob_eval_ = DEG_get_evaluated_object(ctx_.depsgraph, surface_ob_orig_);
if (surface_ob_eval_ == nullptr) {
return;
}
surface_eval_ = BKE_object_get_evaluated_mesh(surface_ob_eval_);
BKE_bvhtree_from_mesh_get(&surface_bvh_eval_, surface_eval_, BVHTREE_FROM_LOOPTRI, 2);
BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_eval_); });
curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt;
brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint);
@ -488,16 +554,20 @@ struct DensitySubtractOperationExecutor {
transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface);
const eBrushFalloffShape falloff_shape = static_cast<eBrushFalloffShape>(
brush_->falloff_shape);
BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2);
BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); });
const Span<float3> positions_cu = curves_->positions();
if (stroke_extension.is_first) {
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_);
for (const int curve_i : curves_->curves_range()) {
const int first_point_i = curves_->offsets()[curve_i];
self_->deformed_root_positions_.append(deformation.positions[first_point_i]);
}
}
root_points_kdtree_ = BLI_kdtree_3d_new(curve_selection_.size());
BLI_SCOPED_DEFER([&]() { BLI_kdtree_3d_free(root_points_kdtree_); });
for (const int curve_i : curve_selection_) {
const int first_point_i = curves_->offsets()[curve_i];
const float3 &pos_cu = positions_cu[first_point_i];
const float3 &pos_cu = self_->deformed_root_positions_[curve_i];
BLI_kdtree_3d_insert(root_points_kdtree_, curve_i, pos_cu);
}
BLI_kdtree_3d_balance(root_points_kdtree_);
@ -515,12 +585,23 @@ struct DensitySubtractOperationExecutor {
}
Vector<int64_t> indices;
const IndexMask mask = index_mask_ops::find_indices_based_on_predicate(
const IndexMask mask_to_delete = index_mask_ops::find_indices_based_on_predicate(
curves_->curves_range(), 4096, indices, [&](const int curve_i) {
return curves_to_delete[curve_i];
});
curves_->remove_curves(mask);
/* Remove deleted curves fromt he stored deformed root positions. */
const Vector<IndexRange> ranges_to_keep = mask_to_delete.extract_ranges_invert(
curves_->curves_range());
BLI_assert(curves_->curves_num() == self_->deformed_root_positions_.size());
Vector<float3> new_deformed_positions;
for (const IndexRange range : ranges_to_keep) {
new_deformed_positions.extend(self_->deformed_root_positions_.as_span().slice(range));
}
self_->deformed_root_positions_ = std::move(new_deformed_positions);
curves_->remove_curves(mask_to_delete);
BLI_assert(curves_->curves_num() == self_->deformed_root_positions_.size());
DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id);
@ -539,15 +620,12 @@ struct DensitySubtractOperationExecutor {
void reduce_density_projected(const float4x4 &brush_transform,
MutableSpan<bool> curves_to_delete)
{
const Span<float3> positions_cu = curves_->positions();
const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
const float brush_radius_sq_re = pow2f(brush_radius_re);
float4x4 projection;
ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values);
const Span<int> offsets = curves_->offsets();
/* Randomly select the curves that are allowed to be removed, based on the brush radius and
* strength. */
Array<bool> allow_remove_curve(curves_->curves_num(), false);
@ -559,8 +637,7 @@ struct DensitySubtractOperationExecutor {
allow_remove_curve[curve_i] = true;
continue;
}
const int first_point_i = offsets[curve_i];
const float3 pos_cu = brush_transform * positions_cu[first_point_i];
const float3 pos_cu = brush_transform * self_->deformed_root_positions_[curve_i];
float2 pos_re;
ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values);
@ -586,8 +663,7 @@ struct DensitySubtractOperationExecutor {
if (!allow_remove_curve[curve_i]) {
continue;
}
const int first_point_i = offsets[curve_i];
const float3 orig_pos_cu = positions_cu[first_point_i];
const float3 orig_pos_cu = self_->deformed_root_positions_[curve_i];
const float3 pos_cu = brush_transform * orig_pos_cu;
float2 pos_re;
ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values);
@ -618,7 +694,7 @@ struct DensitySubtractOperationExecutor {
*ctx_.region,
*ctx_.v3d,
transforms_,
surface_bvh_,
surface_bvh_eval_,
brush_pos_re_,
brush_radius_re);
if (!brush_3d.has_value()) {
@ -638,8 +714,6 @@ struct DensitySubtractOperationExecutor {
MutableSpan<bool> curves_to_delete)
{
const float brush_radius_sq_cu = pow2f(brush_radius_cu);
const Span<float3> positions_cu = curves_->positions();
const Span<int> offsets = curves_->offsets();
/* Randomly select the curves that are allowed to be removed, based on the brush radius and
* strength. */
@ -652,8 +726,7 @@ struct DensitySubtractOperationExecutor {
allow_remove_curve[curve_i] = true;
continue;
}
const int first_point_i = offsets[curve_i];
const float3 pos_cu = positions_cu[first_point_i];
const float3 pos_cu = self_->deformed_root_positions_[curve_i];
const float dist_to_brush_sq_cu = math::distance_squared(brush_pos_cu, pos_cu);
if (dist_to_brush_sq_cu > brush_radius_sq_cu) {
@ -677,8 +750,7 @@ struct DensitySubtractOperationExecutor {
if (!allow_remove_curve[curve_i]) {
continue;
}
const int first_point_i = offsets[curve_i];
const float3 &pos_cu = positions_cu[first_point_i];
const float3 &pos_cu = self_->deformed_root_positions_[curve_i];
const float dist_to_brush_sq_cu = math::distance_squared(pos_cu, brush_pos_cu);
if (dist_to_brush_sq_cu > brush_radius_sq_cu) {
continue;
@ -717,6 +789,10 @@ static bool use_add_density_mode(const BrushStrokeMode brush_mode,
{
const Scene &scene = *CTX_data_scene(&C);
const Brush &brush = *BKE_paint_brush_for_read(&scene.toolsettings->curves_sculpt->paint);
const Depsgraph &depsgraph = *CTX_data_depsgraph_on_load(&C);
const ARegion &region = *CTX_wm_region(&C);
const View3D &v3d = *CTX_wm_view3d(&C);
const eBrushCurvesSculptDensityMode density_mode = static_cast<eBrushCurvesSculptDensityMode>(
brush.curves_sculpt_settings->density_mode);
const bool use_invert = brush_mode == BRUSH_STROKE_INVERT;
@ -728,26 +804,29 @@ static bool use_add_density_mode(const BrushStrokeMode brush_mode,
return use_invert;
}
const Object &curves_ob = *CTX_data_active_object(&C);
const Curves &curves_id = *static_cast<Curves *>(curves_ob.data);
const CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry);
if (curves_id.surface == nullptr) {
/* The brush won't do anything in this case anyway. */
const Object &curves_ob_orig = *CTX_data_active_object(&C);
const Curves &curves_id_orig = *static_cast<Curves *>(curves_ob_orig.data);
Object *surface_ob_orig = curves_id_orig.surface;
if (surface_ob_orig == nullptr) {
return true;
}
Object *surface_ob_eval = DEG_get_evaluated_object(&depsgraph, surface_ob_orig);
if (surface_ob_eval == nullptr) {
return true;
}
const CurvesGeometry &curves = CurvesGeometry::wrap(curves_id_orig.geometry);
if (curves.curves_num() <= 1) {
return true;
}
const Mesh *surface_mesh_eval = BKE_object_get_evaluated_mesh(surface_ob_eval);
if (surface_mesh_eval == nullptr) {
return true;
}
const CurvesSurfaceTransforms transforms(curves_ob, curves_id.surface);
BVHTreeFromMesh surface_bvh;
BKE_bvhtree_from_mesh_get(
&surface_bvh, static_cast<const Mesh *>(curves_id.surface->data), BVHTREE_FROM_LOOPTRI, 2);
BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh); });
const Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(&C);
const ARegion &region = *CTX_wm_region(&C);
const View3D &v3d = *CTX_wm_view3d(&C);
const CurvesSurfaceTransforms transforms(curves_ob_orig, curves_id_orig.surface);
BVHTreeFromMesh surface_bvh_eval;
BKE_bvhtree_from_mesh_get(&surface_bvh_eval, surface_mesh_eval, BVHTREE_FROM_LOOPTRI, 2);
BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_eval); });
const float2 brush_pos_re = stroke_start.mouse_position;
/* Reduce radius so that only an inner circle is used to determine the existing density. */
@ -755,7 +834,7 @@ static bool use_add_density_mode(const BrushStrokeMode brush_mode,
/* Find the surface point under the brush. */
const std::optional<CurvesBrush3D> brush_3d = sample_curves_surface_3d_brush(
depsgraph, region, v3d, transforms, surface_bvh, brush_pos_re, brush_radius_re);
depsgraph, region, v3d, transforms, surface_bvh_eval, brush_pos_re, brush_radius_re);
if (!brush_3d.has_value()) {
return true;
}
@ -764,8 +843,9 @@ static bool use_add_density_mode(const BrushStrokeMode brush_mode,
const float brush_radius_cu = brush_3d->radius_cu;
const float brush_radius_sq_cu = pow2f(brush_radius_cu);
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_curves_deformation(depsgraph, curves_ob_orig);
const Span<int> offsets = curves.offsets();
const Span<float3> positions_cu = curves.positions();
/* Compute distance from brush to curve roots. */
Array<std::pair<float, int>> distances_sq_to_brush(curves.curves_num());
@ -774,7 +854,7 @@ static bool use_add_density_mode(const BrushStrokeMode brush_mode,
int &valid_curve_count = valid_curve_count_by_thread.local();
for (const int curve_i : range) {
const int root_point_i = offsets[curve_i];
const float3 &root_pos_cu = positions_cu[root_point_i];
const float3 &root_pos_cu = deformation.positions[root_point_i];
const float dist_sq_cu = math::distance_squared(root_pos_cu, brush_pos_cu);
if (dist_sq_cu < brush_radius_sq_cu) {
distances_sq_to_brush[curve_i] = {math::distance_squared(root_pos_cu, brush_pos_cu),
@ -799,9 +879,9 @@ static bool use_add_density_mode(const BrushStrokeMode brush_mode,
* center. */
float min_dist_sq_cu = FLT_MAX;
for (const int i : IndexRange(check_curve_count)) {
const float3 &pos_i = positions_cu[offsets[distances_sq_to_brush[i].second]];
const float3 &pos_i = deformation.positions[offsets[distances_sq_to_brush[i].second]];
for (int j = i + 1; j < check_curve_count; j++) {
const float3 &pos_j = positions_cu[offsets[distances_sq_to_brush[j].second]];
const float3 &pos_j = deformation.positions[offsets[distances_sq_to_brush[j].second]];
const float dist_sq_cu = math::distance_squared(pos_i, pos_j);
math::min_inplace(min_dist_sq_cu, dist_sq_cu);
}

View File

@ -342,7 +342,8 @@ struct CurvesEffectOperationExecutor {
void gather_influences_projected(
threading::EnumerableThreadSpecific<Influences> &influences_for_thread)
{
const Span<float3> positions_cu = curves_->positions();
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_);
float4x4 projection;
ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values);
@ -368,8 +369,8 @@ struct CurvesEffectOperationExecutor {
float max_move_distance_cu = 0.0f;
for (const float4x4 &brush_transform_inv : symmetry_brush_transforms_inv) {
for (const int segment_i : points.drop_back(1)) {
const float3 p1_cu = brush_transform_inv * positions_cu[segment_i];
const float3 p2_cu = brush_transform_inv * positions_cu[segment_i + 1];
const float3 p1_cu = brush_transform_inv * deformation.positions[segment_i];
const float3 p2_cu = brush_transform_inv * deformation.positions[segment_i + 1];
float2 p1_re, p2_re;
ED_view3d_project_float_v2_m4(ctx_.region, p1_cu, p1_re, projection.values);
@ -430,7 +431,8 @@ struct CurvesEffectOperationExecutor {
void gather_influences_spherical(
threading::EnumerableThreadSpecific<Influences> &influences_for_thread)
{
const Span<float3> positions_cu = curves_->positions();
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_);
float3 brush_pos_start_wo, brush_pos_end_wo;
ED_view3d_win_to_3d(ctx_.v3d,
@ -468,8 +470,8 @@ struct CurvesEffectOperationExecutor {
const float3 brush_pos_end_transformed_cu = brush_transform * brush_pos_end_cu;
for (const int segment_i : points.drop_back(1)) {
const float3 &p1_cu = positions_cu[segment_i];
const float3 &p2_cu = positions_cu[segment_i + 1];
const float3 &p1_cu = deformation.positions[segment_i];
const float3 &p2_cu = deformation.positions[segment_i + 1];
float3 closest_on_segment_cu;
float3 closest_on_brush_cu;

View File

@ -12,6 +12,7 @@
#include "BLI_virtual_array.hh"
#include "BKE_attribute.h"
#include "BKE_crazyspace.hh"
#include "BKE_curves.hh"
#include "ED_curves_sculpt.h"
@ -24,6 +25,7 @@ struct Object;
struct Brush;
struct Scene;
struct BVHTreeFromMesh;
struct ReportList;
namespace blender::ed::sculpt_paint {
@ -34,6 +36,7 @@ struct StrokeExtension {
bool is_first;
float2 mouse_position;
float pressure;
ReportList *reports = nullptr;
};
float brush_radius_factor(const Brush &brush, const StrokeExtension &stroke_extension);
@ -55,8 +58,7 @@ class CurvesSculptStrokeOperation {
virtual void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) = 0;
};
std::unique_ptr<CurvesSculptStrokeOperation> new_add_operation(const bContext &C,
ReportList *reports);
std::unique_ptr<CurvesSculptStrokeOperation> new_add_operation();
std::unique_ptr<CurvesSculptStrokeOperation> new_comb_operation();
std::unique_ptr<CurvesSculptStrokeOperation> new_delete_operation();
std::unique_ptr<CurvesSculptStrokeOperation> new_snake_hook_operation();
@ -126,4 +128,8 @@ float transform_brush_radius(const float4x4 &transform,
const float3 &brush_position,
const float old_radius);
void report_missing_surface(ReportList *reports);
void report_missing_uv_map_on_original_surface(ReportList *reports);
void report_missing_uv_map_on_evaluated_surface(ReportList *reports);
} // namespace blender::ed::sculpt_paint

View File

@ -9,6 +9,8 @@
#include "BKE_bvhutils.h"
#include "BKE_context.h"
#include "BKE_curves.hh"
#include "BKE_modifier.h"
#include "BKE_object.h"
#include "BKE_paint.h"
#include "WM_api.h"
@ -24,6 +26,7 @@
#include "ED_view3d.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
#include "DNA_brush_types.h"
#include "DNA_curves_types.h"
@ -122,7 +125,7 @@ static std::unique_ptr<CurvesSculptStrokeOperation> start_brush_operation(
case CURVES_SCULPT_TOOL_SNAKE_HOOK:
return new_snake_hook_operation();
case CURVES_SCULPT_TOOL_ADD:
return new_add_operation(C, op.reports);
return new_add_operation();
case CURVES_SCULPT_TOOL_GROW_SHRINK:
return new_grow_shrink_operation(mode, C);
case CURVES_SCULPT_TOOL_SELECTION_PAINT:
@ -176,6 +179,7 @@ static void stroke_update_step(bContext *C,
StrokeExtension stroke_extension;
RNA_float_get_array(stroke_element, "mouse", stroke_extension.mouse_position);
stroke_extension.pressure = RNA_float_get(stroke_element, "pressure");
stroke_extension.reports = op->reports;
if (!op_data->operation) {
stroke_extension.is_first = true;
@ -1129,14 +1133,21 @@ static int min_distance_edit_invoke(bContext *C, wmOperator *op, const wmEvent *
View3D *v3d = CTX_wm_view3d(C);
Scene *scene = CTX_data_scene(C);
Object &curves_ob = *CTX_data_active_object(C);
Curves &curves_id = *static_cast<Curves *>(curves_ob.data);
Object &surface_ob = *curves_id.surface;
Mesh &surface_me = *static_cast<Mesh *>(surface_ob.data);
Object &curves_ob_orig = *CTX_data_active_object(C);
Curves &curves_id_orig = *static_cast<Curves *>(curves_ob_orig.data);
Object &surface_ob_orig = *curves_id_orig.surface;
Object *surface_ob_eval = DEG_get_evaluated_object(depsgraph, &surface_ob_orig);
if (surface_ob_eval == nullptr) {
return OPERATOR_CANCELLED;
}
Mesh *surface_me_eval = BKE_object_get_evaluated_mesh(surface_ob_eval);
if (surface_me_eval == nullptr) {
return OPERATOR_CANCELLED;
}
BVHTreeFromMesh surface_bvh;
BKE_bvhtree_from_mesh_get(&surface_bvh, &surface_me, BVHTREE_FROM_LOOPTRI, 2);
BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh); });
BVHTreeFromMesh surface_bvh_eval;
BKE_bvhtree_from_mesh_get(&surface_bvh_eval, surface_me_eval, BVHTREE_FROM_LOOPTRI, 2);
BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_eval); });
const int2 mouse_pos_int_re{event->mval};
const float2 mouse_pos_re{mouse_pos_int_re};
@ -1145,23 +1156,22 @@ static int min_distance_edit_invoke(bContext *C, wmOperator *op, const wmEvent *
ED_view3d_win_to_segment_clipped(
depsgraph, region, v3d, mouse_pos_re, ray_start_wo, ray_end_wo, true);
const float4x4 surface_to_world_mat = surface_ob.obmat;
const float4x4 world_to_surface_mat = surface_to_world_mat.inverted();
const CurvesSurfaceTransforms transforms{curves_ob_orig, &surface_ob_orig};
const float3 ray_start_su = world_to_surface_mat * ray_start_wo;
const float3 ray_end_su = world_to_surface_mat * ray_end_wo;
const float3 ray_start_su = transforms.world_to_surface * ray_start_wo;
const float3 ray_end_su = transforms.world_to_surface * ray_end_wo;
const float3 ray_direction_su = math::normalize(ray_end_su - ray_start_su);
BVHTreeRayHit ray_hit;
ray_hit.dist = FLT_MAX;
ray_hit.index = -1;
BLI_bvhtree_ray_cast(surface_bvh.tree,
BLI_bvhtree_ray_cast(surface_bvh_eval.tree,
ray_start_su,
ray_direction_su,
0.0f,
&ray_hit,
surface_bvh.raycast_callback,
&surface_bvh);
surface_bvh_eval.raycast_callback,
&surface_bvh_eval);
if (ray_hit.index == -1) {
WM_report(RPT_ERROR, "Cursor must be over the surface mesh");
return OPERATOR_CANCELLED;
@ -1169,16 +1179,13 @@ static int min_distance_edit_invoke(bContext *C, wmOperator *op, const wmEvent *
const float3 hit_pos_su = ray_hit.co;
const float3 hit_normal_su = ray_hit.no;
const float4x4 curves_to_world_mat = curves_ob.obmat;
const float4x4 world_to_curves_mat = curves_to_world_mat.inverted();
const float4x4 surface_to_curves_mat = world_to_curves_mat * surface_to_world_mat;
const float4x4 surface_to_curves_normal_mat = surface_to_curves_mat.inverted().transposed();
const float3 hit_pos_cu = surface_to_curves_mat * hit_pos_su;
const float3 hit_normal_cu = math::normalize(surface_to_curves_normal_mat * hit_normal_su);
const float3 hit_pos_cu = transforms.surface_to_curves * hit_pos_su;
const float3 hit_normal_cu = math::normalize(transforms.surface_to_curves_normal *
hit_normal_su);
MinDistanceEditData *op_data = MEM_new<MinDistanceEditData>(__func__);
op_data->curves_to_world_mat = curves_to_world_mat;
op_data->curves_to_world_mat = transforms.curves_to_world;
op_data->normal_cu = hit_normal_cu;
op_data->pos_cu = hit_pos_cu;
op_data->initial_mouse = event->xy;

View File

@ -157,6 +157,9 @@ struct PinchOperationExecutor {
{
const float4x4 brush_transform_inv = brush_transform.inverted();
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_);
float4x4 projection;
ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values);
MutableSpan<float3> positions_cu = curves_->positions_for_write();
@ -167,11 +170,13 @@ struct PinchOperationExecutor {
for (const int curve_i : curve_selection_.slice(range)) {
const IndexRange points = curves_->points_for_curve(curve_i);
for (const int point_i : points.drop_front(1)) {
const float3 old_pos_cu = brush_transform_inv * positions_cu[point_i];
float2 old_pos_re;
ED_view3d_project_float_v2_m4(ctx_.region, old_pos_cu, old_pos_re, projection.values);
const float3 old_pos_cu = deformation.positions[point_i];
const float3 old_symm_pos_cu = brush_transform_inv * old_pos_cu;
float2 old_symm_pos_re;
ED_view3d_project_float_v2_m4(
ctx_.region, old_symm_pos_cu, old_symm_pos_re, projection.values);
const float dist_to_brush_sq_re = math::distance_squared(old_pos_re, brush_pos_re_);
const float dist_to_brush_sq_re = math::distance_squared(old_symm_pos_re, brush_pos_re_);
if (dist_to_brush_sq_re > brush_radius_sq_re) {
continue;
}
@ -182,14 +187,21 @@ struct PinchOperationExecutor {
const float weight = invert_factor_ * 0.1f * brush_strength_ * radius_falloff *
point_factors_[point_i];
const float2 new_pos_re = math::interpolate(old_pos_re, brush_pos_re_, weight);
const float2 new_symm_pos_re = math::interpolate(old_symm_pos_re, brush_pos_re_, weight);
const float3 old_pos_wo = transforms_.curves_to_world * old_pos_cu;
float3 new_pos_wo;
ED_view3d_win_to_3d(ctx_.v3d, ctx_.region, old_pos_wo, new_pos_re, new_pos_wo);
float3 new_symm_pos_wo;
ED_view3d_win_to_3d(ctx_.v3d,
ctx_.region,
transforms_.curves_to_world * old_symm_pos_cu,
new_symm_pos_re,
new_symm_pos_wo);
const float3 new_pos_cu = transforms_.world_to_curves * new_pos_wo;
positions_cu[point_i] = brush_transform * new_pos_cu;
const float3 new_pos_cu = brush_transform * transforms_.world_to_curves *
new_symm_pos_wo;
const float3 translation_eval = new_pos_cu - old_pos_cu;
const float3 translation_orig = deformation.translation_from_deformed_to_original(
point_i, translation_eval);
positions_cu[point_i] += translation_orig;
r_changed_curves[curve_i] = true;
}
}
@ -221,11 +233,14 @@ struct PinchOperationExecutor {
MutableSpan<float3> positions_cu = curves_->positions_for_write();
const float brush_radius_sq_cu = pow2f(brush_radius_cu);
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_);
threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) {
for (const int curve_i : curve_selection_.slice(range)) {
const IndexRange points = curves_->points_for_curve(curve_i);
for (const int point_i : points.drop_front(1)) {
const float3 old_pos_cu = positions_cu[point_i];
const float3 old_pos_cu = deformation.positions[point_i];
const float dist_to_brush_sq_cu = math::distance_squared(old_pos_cu, brush_pos_cu);
if (dist_to_brush_sq_cu > brush_radius_sq_cu) {
@ -239,7 +254,10 @@ struct PinchOperationExecutor {
point_factors_[point_i];
const float3 new_pos_cu = math::interpolate(old_pos_cu, brush_pos_cu, weight);
positions_cu[point_i] = new_pos_cu;
const float3 translation_eval = new_pos_cu - old_pos_cu;
const float3 translation_orig = deformation.translation_from_deformed_to_original(
point_i, translation_eval);
positions_cu[point_i] += translation_orig;
r_changed_curves[curve_i] = true;
}

View File

@ -4,6 +4,7 @@
#include "BKE_brush.h"
#include "BKE_bvhutils.h"
#include "BKE_context.h"
#include "BKE_crazyspace.hh"
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
@ -20,6 +21,8 @@
#include "BLI_length_parameterize.hh"
#include "GEO_add_curves_on_mesh.hh"
#include "curves_sculpt_intern.hh"
namespace blender::ed::sculpt_paint {
@ -38,23 +41,6 @@ class PuffOperation : public CurvesSculptStrokeOperation {
void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override;
};
static float3 compute_surface_point_normal(const MLoopTri &looptri,
const float3 &bary_coord,
const Span<float3> corner_normals)
{
const int l0 = looptri.tri[0];
const int l1 = looptri.tri[1];
const int l2 = looptri.tri[2];
const float3 &l0_normal = corner_normals[l0];
const float3 &l1_normal = corner_normals[l1];
const float3 &l2_normal = corner_normals[l2];
const float3 normal = math::normalize(
attribute_math::mix3(bary_coord, l0_normal, l1_normal, l2_normal));
return normal;
}
/**
* Utility class that actually executes the update when the stroke is updated. That's useful
* because it avoids passing a very large number of parameters between functions.
@ -183,8 +169,6 @@ struct PuffOperationExecutor {
void find_curve_weights_projected(const float4x4 &brush_transform,
MutableSpan<float> r_curve_weights)
{
Span<float3> positions_cu = curves_->positions();
const float4x4 brush_transform_inv = brush_transform.inverted();
float4x4 projection;
@ -193,15 +177,18 @@ struct PuffOperationExecutor {
const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
const float brush_radius_sq_re = pow2f(brush_radius_re);
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_);
threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) {
for (const int curve_selection_i : range) {
const int curve_i = curve_selection_[curve_selection_i];
const IndexRange points = curves_->points_for_curve(curve_i);
const float3 first_pos_cu = brush_transform_inv * positions_cu[points[0]];
const float3 first_pos_cu = brush_transform_inv * deformation.positions[points[0]];
float2 prev_pos_re;
ED_view3d_project_float_v2_m4(ctx_.region, first_pos_cu, prev_pos_re, projection.values);
for (const int point_i : points.drop_front(1)) {
const float3 pos_cu = brush_transform_inv * positions_cu[point_i];
const float3 pos_cu = brush_transform_inv * deformation.positions[point_i];
float2 pos_re;
ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values);
BLI_SCOPED_DEFER([&]() { prev_pos_re = pos_re; });
@ -248,16 +235,18 @@ struct PuffOperationExecutor {
const float brush_radius_cu,
MutableSpan<float> r_curve_weights)
{
const Span<float3> positions_cu = curves_->positions();
const float brush_radius_sq_cu = pow2f(brush_radius_cu);
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_);
threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) {
for (const int curve_selection_i : range) {
const int curve_i = curve_selection_[curve_selection_i];
const IndexRange points = curves_->points_for_curve(curve_i);
for (const int point_i : points.drop_front(1)) {
const float3 &prev_pos_cu = positions_cu[point_i - 1];
const float3 &pos_cu = positions_cu[point_i];
const float3 &prev_pos_cu = deformation.positions[point_i - 1];
const float3 &pos_cu = deformation.positions[point_i];
const float dist_to_brush_sq_cu = dist_squared_to_line_segment_v3(
brush_pos_cu, prev_pos_cu, pos_cu);
if (dist_to_brush_sq_cu > brush_radius_sq_cu) {
@ -305,7 +294,7 @@ struct PuffOperationExecutor {
const float3 &v2_su = surface_->mvert[surface_->mloop[looptri.tri[2]].v].co;
float3 bary_coords;
interp_weights_tri_v3(bary_coords, v0_su, v1_su, v2_su, closest_pos_su);
const float3 normal_su = compute_surface_point_normal(
const float3 normal_su = geometry::compute_surface_point_normal(
looptri, bary_coords, corner_normals_su_);
const float3 normal_cu = math::normalize(transforms_.surface_to_curves_normal * normal_su);

View File

@ -161,14 +161,15 @@ struct SelectionPaintOperationExecutor {
float4x4 projection;
ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values);
Span<float3> positions_cu = curves_->positions();
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_);
const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
const float brush_radius_sq_re = pow2f(brush_radius_re);
threading::parallel_for(curves_->points_range(), 1024, [&](const IndexRange point_range) {
for (const int point_i : point_range) {
const float3 pos_cu = brush_transform_inv * positions_cu[point_i];
const float3 pos_cu = brush_transform_inv * deformation.positions[point_i];
/* Find the position of the point in screen space. */
float2 pos_re;
@ -215,14 +216,15 @@ struct SelectionPaintOperationExecutor {
void paint_point_selection_spherical(MutableSpan<float> selection, const float3 &brush_cu)
{
Span<float3> positions_cu = curves_->positions();
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_);
const float brush_radius_cu = self_->brush_3d_.radius_cu;
const float brush_radius_sq_cu = pow2f(brush_radius_cu);
threading::parallel_for(curves_->points_range(), 1024, [&](const IndexRange point_range) {
for (const int i : point_range) {
const float3 pos_old_cu = positions_cu[i];
const float3 pos_old_cu = deformation.positions[i];
/* Compute distance to the brush. */
const float distance_to_brush_sq_cu = math::distance_squared(pos_old_cu, brush_cu);
@ -256,9 +258,11 @@ struct SelectionPaintOperationExecutor {
void paint_curve_selection_projected(const float4x4 &brush_transform,
MutableSpan<float> selection)
{
const Span<float3> positions_cu = curves_->positions();
const float4x4 brush_transform_inv = brush_transform.inverted();
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_);
float4x4 projection;
ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values);
@ -274,8 +278,8 @@ struct SelectionPaintOperationExecutor {
[&](const IndexRange segment_range, const float init) {
float max_weight = init;
for (const int segment_i : segment_range) {
const float3 pos1_cu = brush_transform_inv * positions_cu[segment_i];
const float3 pos2_cu = brush_transform_inv * positions_cu[segment_i + 1];
const float3 pos1_cu = brush_transform_inv * deformation.positions[segment_i];
const float3 pos2_cu = brush_transform_inv * deformation.positions[segment_i + 1];
float2 pos1_re;
float2 pos2_re;
@ -323,7 +327,8 @@ struct SelectionPaintOperationExecutor {
void paint_curve_selection_spherical(MutableSpan<float> selection, const float3 &brush_cu)
{
const Span<float3> positions_cu = curves_->positions();
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_);
const float brush_radius_cu = self_->brush_3d_.radius_cu;
const float brush_radius_sq_cu = pow2f(brush_radius_cu);
@ -337,8 +342,8 @@ struct SelectionPaintOperationExecutor {
[&](const IndexRange segment_range, const float init) {
float max_weight = init;
for (const int segment_i : segment_range) {
const float3 &pos1_cu = positions_cu[segment_i];
const float3 &pos2_cu = positions_cu[segment_i + 1];
const float3 &pos1_cu = deformation.positions[segment_i];
const float3 &pos2_cu = deformation.positions[segment_i + 1];
const float distance_sq_cu = dist_squared_to_line_segment_v3(
brush_cu, pos1_cu, pos2_cu);

View File

@ -4,6 +4,7 @@
#include "curves_sculpt_intern.hh"
#include "BLI_float3x3.hh"
#include "BLI_float4x4.hh"
#include "BLI_vector.hh"
@ -20,14 +21,16 @@
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_mesh_sample.hh"
#include "BKE_modifier.h"
#include "BKE_object.h"
#include "BKE_paint.h"
#include "BKE_report.h"
#include "DNA_brush_enums.h"
#include "DNA_brush_types.h"
#include "DNA_curves_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
@ -38,13 +41,27 @@
#include "WM_api.h"
#include "DEG_depsgraph_query.h"
#include "GEO_add_curves_on_mesh.hh"
#include "GEO_reverse_uv_sampler.hh"
#include "BLT_translation.h"
namespace blender::ed::sculpt_paint {
using geometry::ReverseUVSampler;
struct SlideCurveInfo {
/** Index of the curve to slide. */
int curve_i;
/** A weight based on the initial distance to the brush. */
float radius_falloff;
/**
* Normal of the surface where the curve was attached. This is used to rotate the curve if it is
* moved to a place with a different normal.
*/
float3 initial_normal_cu;
};
struct SlideInfo {
@ -55,10 +72,13 @@ struct SlideInfo {
class SlideOperation : public CurvesSculptStrokeOperation {
private:
/** Last mouse position. */
float2 brush_pos_last_re_;
float2 initial_brush_pos_re_;
/** Information about which curves to slide. This is initialized when the brush starts. */
Vector<SlideInfo> slide_info_;
/** Positions of all curve points at the start of sliding. */
Array<float3> initial_positions_cu_;
/** Deformed positions of all curve points at the start of sliding. */
Array<float3> initial_deformed_positions_cu_;
friend struct SlideOperationExecutor;
@ -80,27 +100,31 @@ struct SlideOperationExecutor {
float brush_radius_factor_;
float brush_strength_;
Object *object_ = nullptr;
Curves *curves_id_ = nullptr;
CurvesGeometry *curves_ = nullptr;
Object *curves_ob_orig_ = nullptr;
Curves *curves_id_orig_ = nullptr;
CurvesGeometry *curves_orig_ = nullptr;
Object *surface_ob_ = nullptr;
Mesh *surface_ = nullptr;
Span<MLoopTri> surface_looptris_;
VArraySpan<float2> surface_uv_map_;
Object *surface_ob_orig_ = nullptr;
Mesh *surface_orig_ = nullptr;
Span<MLoopTri> surface_looptris_orig_;
VArraySpan<float2> surface_uv_map_orig_;
Span<float3> corner_normals_orig_su_;
Object *surface_ob_eval_ = nullptr;
Mesh *surface_eval_ = nullptr;
Span<MLoopTri> surface_looptris_eval_;
VArraySpan<float2> surface_uv_map_eval_;
BVHTreeFromMesh surface_bvh_eval_;
VArray<float> curve_factors_;
VArray<float> point_factors_;
Vector<int64_t> selected_curve_indices_;
IndexMask curve_selection_;
float2 brush_pos_prev_re_;
float2 brush_pos_re_;
float2 brush_pos_diff_re_;
CurvesSurfaceTransforms transforms_;
BVHTreeFromMesh surface_bvh_;
std::atomic<bool> found_invalid_uv_mapping_{false};
SlideOperationExecutor(const bContext &C) : ctx_(C)
{
@ -111,15 +135,21 @@ struct SlideOperationExecutor {
UNUSED_VARS(C, stroke_extension);
self_ = &self;
object_ = CTX_data_active_object(&C);
curves_id_ = static_cast<Curves *>(object_->data);
curves_ = &CurvesGeometry::wrap(curves_id_->geometry);
if (curves_id_->surface == nullptr || curves_id_->surface->type != OB_MESH) {
curves_ob_orig_ = CTX_data_active_object(&C);
curves_id_orig_ = static_cast<Curves *>(curves_ob_orig_->data);
curves_orig_ = &CurvesGeometry::wrap(curves_id_orig_->geometry);
if (curves_id_orig_->surface == nullptr || curves_id_orig_->surface->type != OB_MESH) {
report_missing_surface(stroke_extension.reports);
return;
}
if (curves_->curves_num() == 0) {
if (curves_orig_->curves_num() == 0) {
return;
}
if (curves_id_orig_->surface_uv_map == nullptr) {
report_missing_uv_map_on_original_surface(stroke_extension.reports);
return;
}
const StringRefNull uv_map_name = curves_id_orig_->surface_uv_map;
curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt;
brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint);
@ -127,169 +157,312 @@ struct SlideOperationExecutor {
brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension);
curve_factors_ = get_curves_selection(*curves_id_);
point_factors_ = get_point_selection(*curves_id_);
curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_);
curve_factors_ = get_curves_selection(*curves_id_orig_);
curve_selection_ = retrieve_selected_curves(*curves_id_orig_, selected_curve_indices_);
brush_pos_prev_re_ = self_->brush_pos_last_re_;
brush_pos_re_ = stroke_extension.mouse_position;
brush_pos_diff_re_ = brush_pos_re_ - brush_pos_prev_re_;
BLI_SCOPED_DEFER([&]() { self_->brush_pos_last_re_ = brush_pos_re_; });
transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface);
transforms_ = CurvesSurfaceTransforms(*curves_ob_orig_, curves_id_orig_->surface);
surface_ob_ = curves_id_->surface;
surface_ = static_cast<Mesh *>(surface_ob_->data);
BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2);
BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); });
surface_looptris_ = {BKE_mesh_runtime_looptri_ensure(surface_),
BKE_mesh_runtime_looptri_len(surface_)};
if (curves_id_->surface_uv_map != nullptr) {
const bke::AttributeAccessor surface_attributes = bke::mesh_attributes(*surface_);
surface_uv_map_ = surface_attributes.lookup<float2>(curves_id_->surface_uv_map,
ATTR_DOMAIN_CORNER);
}
if (stroke_extension.is_first) {
const Vector<float4x4> brush_transforms = get_symmetry_brush_transforms(
eCurvesSymmetryType(curves_id_->symmetry));
for (const float4x4 &brush_transform : brush_transforms) {
this->detect_curves_to_slide(brush_transform);
}
surface_ob_orig_ = curves_id_orig_->surface;
surface_orig_ = static_cast<Mesh *>(surface_ob_orig_->data);
surface_looptris_orig_ = {BKE_mesh_runtime_looptri_ensure(surface_orig_),
BKE_mesh_runtime_looptri_len(surface_orig_)};
surface_uv_map_orig_ =
bke::mesh_attributes(*surface_orig_).lookup<float2>(uv_map_name, ATTR_DOMAIN_CORNER);
if (surface_uv_map_orig_.is_empty()) {
report_missing_uv_map_on_original_surface(stroke_extension.reports);
return;
}
this->slide_projected();
if (!CustomData_has_layer(&surface_orig_->ldata, CD_NORMAL)) {
BKE_mesh_calc_normals_split(surface_orig_);
}
corner_normals_orig_su_ = {
reinterpret_cast<const float3 *>(CustomData_get_layer(&surface_orig_->ldata, CD_NORMAL)),
surface_orig_->totloop};
curves_->tag_positions_changed();
DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id);
surface_ob_eval_ = DEG_get_evaluated_object(ctx_.depsgraph, surface_ob_orig_);
if (surface_ob_eval_ == nullptr) {
return;
}
surface_eval_ = BKE_object_get_evaluated_mesh(surface_ob_eval_);
if (surface_eval_ == nullptr) {
return;
}
surface_looptris_eval_ = {BKE_mesh_runtime_looptri_ensure(surface_eval_),
BKE_mesh_runtime_looptri_len(surface_eval_)};
surface_uv_map_eval_ =
bke::mesh_attributes(*surface_eval_).lookup<float2>(uv_map_name, ATTR_DOMAIN_CORNER);
if (surface_uv_map_eval_.is_empty()) {
report_missing_uv_map_on_evaluated_surface(stroke_extension.reports);
return;
}
BKE_bvhtree_from_mesh_get(&surface_bvh_eval_, surface_eval_, BVHTREE_FROM_LOOPTRI, 2);
BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_eval_); });
if (stroke_extension.is_first) {
self_->initial_brush_pos_re_ = brush_pos_re_;
/* Remember original and deformed positions of all points. Otherwise this information is lost
* when sliding starts, but it's still used. */
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *curves_ob_orig_);
self_->initial_positions_cu_ = curves_orig_->positions();
self_->initial_deformed_positions_cu_ = deformation.positions;
/* First find all curves to slide. When the mouse moves, only those curves will be moved. */
this->find_curves_to_slide_with_symmetry();
return;
}
this->slide_with_symmetry();
if (found_invalid_uv_mapping_) {
BKE_report(
stroke_extension.reports, RPT_WARNING, TIP_("UV map or surface attachment is invalid"));
}
curves_orig_->tag_positions_changed();
DEG_id_tag_update(&curves_id_orig_->id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_orig_->id);
ED_region_tag_redraw(ctx_.region);
}
void detect_curves_to_slide(const float4x4 &brush_transform)
void find_curves_to_slide_with_symmetry()
{
const float4x4 brush_transform_inv = brush_transform.inverted();
const Vector<float4x4> brush_transforms = get_symmetry_brush_transforms(
eCurvesSymmetryType(curves_id_orig_->symmetry));
const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
const float brush_radius_sq_re = pow2f(brush_radius_re);
const Span<float3> positions_cu = curves_->positions();
float4x4 projection;
ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values);
self_->slide_info_.append({brush_transform});
Vector<SlideCurveInfo> &curves_to_slide = self_->slide_info_.last().curves_to_slide;
/* Find curves in brush radius that should be moved. */
for (const int curve_i : curve_selection_) {
const int first_point_i = curves_->offsets()[curve_i];
const float3 &first_pos_cu = brush_transform_inv * positions_cu[first_point_i];
float2 first_pos_re;
ED_view3d_project_float_v2_m4(ctx_.region, first_pos_cu, first_pos_re, projection.values);
const float dist_to_brush_sq_re = math::distance_squared(first_pos_re, brush_pos_re_);
if (dist_to_brush_sq_re > brush_radius_sq_re) {
continue;
}
const float dist_to_brush_re = std::sqrt(dist_to_brush_sq_re);
const float radius_falloff = BKE_brush_curve_strength(
brush_, dist_to_brush_re, brush_radius_re);
curves_to_slide.append({curve_i, radius_falloff});
const std::optional<CurvesBrush3D> brush_3d = sample_curves_surface_3d_brush(*ctx_.depsgraph,
*ctx_.region,
*ctx_.v3d,
transforms_,
surface_bvh_eval_,
brush_pos_re_,
brush_radius_re);
if (!brush_3d.has_value()) {
return;
}
const ReverseUVSampler reverse_uv_sampler_orig{surface_uv_map_orig_, surface_looptris_orig_};
for (const float4x4 &brush_transform : brush_transforms) {
self_->slide_info_.append_as();
SlideInfo &slide_info = self_->slide_info_.last();
slide_info.brush_transform = brush_transform;
this->find_curves_to_slide(brush_transform * brush_3d->position_cu,
brush_3d->radius_cu,
reverse_uv_sampler_orig,
slide_info.curves_to_slide);
}
}
void slide_projected()
void find_curves_to_slide(const float3 &brush_pos_cu,
const float brush_radius_cu,
const ReverseUVSampler &reverse_uv_sampler_orig,
Vector<SlideCurveInfo> &r_curves_to_slide)
{
MutableSpan<float3> positions_cu = curves_->positions_for_write();
const Span<float2> surface_uv_coords = curves_orig_->surface_uv_coords();
const float brush_radius_sq_cu = pow2f(brush_radius_cu);
MutableSpan<float2> surface_uv_coords;
if (!surface_uv_map_.is_empty()) {
surface_uv_coords = curves_->surface_uv_coords_for_write();
const Span<int> offsets = curves_orig_->offsets();
for (const int curve_i : curve_selection_) {
const int first_point_i = offsets[curve_i];
const float3 old_pos_cu = self_->initial_deformed_positions_cu_[first_point_i];
const float dist_to_brush_sq_cu = math::distance_squared(old_pos_cu, brush_pos_cu);
if (dist_to_brush_sq_cu > brush_radius_sq_cu) {
/* Root point is too far away from curve center. */
continue;
}
const float dist_to_brush_cu = std::sqrt(dist_to_brush_sq_cu);
const float radius_falloff = BKE_brush_curve_strength(
brush_, dist_to_brush_cu, brush_radius_cu);
const float2 uv = surface_uv_coords[curve_i];
ReverseUVSampler::Result result = reverse_uv_sampler_orig.sample(uv);
if (result.type != ReverseUVSampler::ResultType::Ok) {
/* The curve does not have a valid surface attachment. */
found_invalid_uv_mapping_.store(true);
continue;
}
/* Compute the normal at the initial surface position. */
const float3 normal_cu = math::normalize(
transforms_.surface_to_curves_normal *
geometry::compute_surface_point_normal(
*result.looptri, result.bary_weights, corner_normals_orig_su_));
r_curves_to_slide.append({curve_i, radius_falloff, normal_cu});
}
}
void slide_with_symmetry()
{
const ReverseUVSampler reverse_uv_sampler_orig{surface_uv_map_orig_, surface_looptris_orig_};
for (const SlideInfo &slide_info : self_->slide_info_) {
this->slide(slide_info.curves_to_slide, reverse_uv_sampler_orig, slide_info.brush_transform);
}
}
void slide(const Span<SlideCurveInfo> slide_curves,
const ReverseUVSampler &reverse_uv_sampler_orig,
const float4x4 &brush_transform)
{
const float4x4 brush_transform_inv = brush_transform.inverted();
const Span<MVert> verts_orig_su{surface_orig_->mvert, surface_orig_->totvert};
const Span<MLoop> loops_orig{surface_orig_->mloop, surface_orig_->totloop};
MutableSpan<float3> positions_orig_cu = curves_orig_->positions_for_write();
MutableSpan<float2> surface_uv_coords = curves_orig_->surface_uv_coords_for_write();
float4x4 projection;
ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values);
ED_view3d_ob_project_mat_get(ctx_.rv3d, curves_ob_orig_, projection.values);
for (const SlideInfo &slide_info : self_->slide_info_) {
const float4x4 &brush_transform = slide_info.brush_transform;
const float4x4 brush_transform_inv = brush_transform.inverted();
const Span<SlideCurveInfo> curves_to_slide = slide_info.curves_to_slide;
const float2 brush_pos_diff_re = brush_pos_re_ - self_->initial_brush_pos_re_;
threading::parallel_for(curves_to_slide.index_range(), 256, [&](const IndexRange range) {
for (const SlideCurveInfo &curve_slide_info : curves_to_slide.slice(range)) {
const int curve_i = curve_slide_info.curve_i;
const IndexRange points = curves_->points_for_curve(curve_i);
const int first_point_i = points.first();
const float3 old_first_pos_cu = brush_transform_inv * positions_cu[first_point_i];
/* The brush transformation has to be applied in curves space. */
const float4x4 world_to_surface_with_symmetry_mat = transforms_.curves_to_surface *
brush_transform *
transforms_.world_to_curves;
float2 old_first_pos_re;
ED_view3d_project_float_v2_m4(
ctx_.region, old_first_pos_cu, old_first_pos_re, projection.values);
const float first_point_weight = brush_strength_ * curve_slide_info.radius_falloff;
threading::parallel_for(slide_curves.index_range(), 256, [&](const IndexRange range) {
for (const SlideCurveInfo &slide_curve_info : slide_curves.slice(range)) {
const int curve_i = slide_curve_info.curve_i;
const IndexRange points = curves_orig_->points_for_curve(curve_i);
const int first_point_i = points[0];
/* Slide root position in region space and then project it back onto the surface. */
const float2 new_first_pos_re = old_first_pos_re +
first_point_weight * brush_pos_diff_re_;
const float3 old_first_pos_eval_cu = self_->initial_deformed_positions_cu_[first_point_i];
const float3 old_first_symm_pos_eval_cu = brush_transform_inv * old_first_pos_eval_cu;
const float3 old_first_pos_eval_su = transforms_.curves_to_surface * old_first_pos_eval_cu;
float3 ray_start_wo, ray_end_wo;
ED_view3d_win_to_segment_clipped(ctx_.depsgraph,
ctx_.region,
ctx_.v3d,
new_first_pos_re,
ray_start_wo,
ray_end_wo,
true);
const float3 ray_start_su = transforms_.world_to_surface * ray_start_wo;
const float3 ray_end_su = transforms_.world_to_surface * ray_end_wo;
float2 old_first_symm_pos_eval_re;
ED_view3d_project_float_v2_m4(ctx_.region,
old_first_symm_pos_eval_cu,
old_first_symm_pos_eval_re,
projection.values);
const float3 ray_direction_su = math::normalize(ray_end_su - ray_start_su);
BVHTreeRayHit hit;
hit.dist = FLT_MAX;
hit.index = -1;
BLI_bvhtree_ray_cast(surface_bvh_.tree,
ray_start_su,
ray_direction_su,
0.0f,
&hit,
surface_bvh_.raycast_callback,
&surface_bvh_);
if (hit.index == -1) {
continue;
}
const float radius_falloff = slide_curve_info.radius_falloff;
const float curve_weight = brush_strength_ * radius_falloff * curve_factors_[curve_i];
const float2 new_first_symm_pos_eval_re = old_first_symm_pos_eval_re +
curve_weight * brush_pos_diff_re;
const int looptri_index = hit.index;
const float3 attached_pos_su = hit.co;
/* Compute the ray that will be used to find the new position on the surface. */
float3 ray_start_wo, ray_end_wo;
ED_view3d_win_to_segment_clipped(ctx_.depsgraph,
ctx_.region,
ctx_.v3d,
new_first_symm_pos_eval_re,
ray_start_wo,
ray_end_wo,
true);
const float3 ray_start_su = world_to_surface_with_symmetry_mat * ray_start_wo;
const float3 ray_end_su = world_to_surface_with_symmetry_mat * ray_end_wo;
const float3 ray_direction_su = math::normalize(ray_end_su - ray_start_su);
const float3 attached_pos_cu = transforms_.surface_to_curves * attached_pos_su;
const float3 pos_offset_cu = brush_transform * (attached_pos_cu - old_first_pos_cu);
/* Update positions. The first point doesn't have an additional weight here, because then
* it wouldn't be attached to the surface anymore. */
positions_cu[first_point_i] += pos_offset_cu;
for (const int point_i : points.drop_front(1)) {
const float weight = point_factors_[point_i];
positions_cu[point_i] += weight * pos_offset_cu;
}
/* Update surface attachment information if necessary. */
if (!surface_uv_map_.is_empty()) {
const MLoopTri &looptri = surface_looptris_[looptri_index];
const float3 bary_coord = bke::mesh_surface_sample::compute_bary_coord_in_triangle(
*surface_, looptri, attached_pos_su);
const float2 &uv0 = surface_uv_map_[looptri.tri[0]];
const float2 &uv1 = surface_uv_map_[looptri.tri[1]];
const float2 &uv2 = surface_uv_map_[looptri.tri[2]];
const float2 uv = attribute_math::mix3(bary_coord, uv0, uv1, uv2);
surface_uv_coords[curve_i] = uv;
}
/* Find the ray hit that is closest to the initial curve root position. */
int looptri_index_eval;
float3 hit_pos_eval_su;
if (!this->find_closest_ray_hit(ray_start_su,
ray_direction_su,
old_first_pos_eval_su,
looptri_index_eval,
hit_pos_eval_su)) {
continue;
}
});
/* Compute the uv of the new surface position on the evaluated mesh. */
const MLoopTri &looptri_eval = surface_looptris_eval_[looptri_index_eval];
const float3 bary_weights_eval = bke::mesh_surface_sample::compute_bary_coord_in_triangle(
*surface_eval_, looptri_eval, hit_pos_eval_su);
const float2 uv = attribute_math::mix3(bary_weights_eval,
surface_uv_map_eval_[looptri_eval.tri[0]],
surface_uv_map_eval_[looptri_eval.tri[1]],
surface_uv_map_eval_[looptri_eval.tri[2]]);
/* Try to find the same uv on the original surface. */
const ReverseUVSampler::Result result = reverse_uv_sampler_orig.sample(uv);
if (result.type != ReverseUVSampler::ResultType::Ok) {
found_invalid_uv_mapping_.store(true);
continue;
}
const MLoopTri &looptri_orig = *result.looptri;
const float3 &bary_weights_orig = result.bary_weights;
/* Gather old and new surface normal. */
const float3 &initial_normal_cu = slide_curve_info.initial_normal_cu;
const float3 new_normal_cu = math::normalize(
transforms_.surface_to_curves_normal *
geometry::compute_surface_point_normal(
*result.looptri, result.bary_weights, corner_normals_orig_su_));
/* Gather old and new surface position. */
const float3 old_first_pos_orig_cu = self_->initial_positions_cu_[first_point_i];
const float3 new_first_pos_orig_cu =
transforms_.surface_to_curves *
attribute_math::mix3<float3>(bary_weights_orig,
verts_orig_su[loops_orig[looptri_orig.tri[0]].v].co,
verts_orig_su[loops_orig[looptri_orig.tri[1]].v].co,
verts_orig_su[loops_orig[looptri_orig.tri[2]].v].co);
/* Actually transform curve points. */
const float4x4 slide_transform = this->get_slide_transform(
old_first_pos_orig_cu, new_first_pos_orig_cu, initial_normal_cu, new_normal_cu);
for (const int point_i : points) {
positions_orig_cu[point_i] = slide_transform * self_->initial_positions_cu_[point_i];
}
surface_uv_coords[curve_i] = uv;
}
});
}
bool find_closest_ray_hit(const float3 &ray_start_su,
const float3 &ray_direction_su,
const float3 &point_su,
int &r_looptri_index,
float3 &r_hit_pos)
{
float best_dist_sq_su = FLT_MAX;
int best_looptri_index_eval;
float3 best_hit_pos_su;
BLI_bvhtree_ray_cast_all_cpp(
*surface_bvh_eval_.tree,
ray_start_su,
ray_direction_su,
0.0f,
FLT_MAX,
[&](const int looptri_index, const BVHTreeRay &ray, BVHTreeRayHit &hit) {
surface_bvh_eval_.raycast_callback(&surface_bvh_eval_, looptri_index, &ray, &hit);
if (hit.index < 0) {
return;
}
const float3 &hit_pos_su = hit.co;
const float dist_sq_su = math::distance_squared(hit_pos_su, point_su);
if (dist_sq_su < best_dist_sq_su) {
best_dist_sq_su = dist_sq_su;
best_hit_pos_su = hit_pos_su;
best_looptri_index_eval = hit.index;
}
});
if (best_dist_sq_su == FLT_MAX) {
return false;
}
r_looptri_index = best_looptri_index_eval;
r_hit_pos = best_hit_pos_su;
return true;
}
float4x4 get_slide_transform(const float3 &old_root_pos,
const float3 &new_root_pos,
const float3 &old_normal,
const float3 &new_normal)
{
float3x3 rotation_3x3;
rotation_between_vecs_to_mat3(rotation_3x3.values, old_normal, new_normal);
float4x4 rotation_4x4;
copy_m4_m3(rotation_4x4.values, rotation_3x3.values);
float4x4 transform = float4x4::identity();
sub_v3_v3(transform.values[3], old_root_pos);
mul_m4_m4_pre(transform.values, rotation_4x4.values);
add_v3_v3(transform.values[3], new_root_pos);
return transform;
}
};

View File

@ -2,6 +2,7 @@
#include "BKE_brush.h"
#include "BKE_context.h"
#include "BKE_crazyspace.hh"
#include "ED_screen.h"
#include "ED_view3d.h"
@ -95,60 +96,56 @@ struct SmoothOperationExecutor {
}
}
Array<float> point_smooth_factors(curves_->points_num(), 0.0f);
if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
this->smooth_projected_with_symmetry();
this->find_projected_smooth_factors_with_symmetry(point_smooth_factors);
}
else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
this->smooth_spherical_with_symmetry();
this->find_spherical_smooth_factors_with_symmetry(point_smooth_factors);
}
else {
BLI_assert_unreachable();
}
this->smooth(point_smooth_factors);
curves_->tag_positions_changed();
DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id);
ED_region_tag_redraw(ctx_.region);
}
void smooth_projected_with_symmetry()
void find_projected_smooth_factors_with_symmetry(MutableSpan<float> r_point_smooth_factors)
{
const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
eCurvesSymmetryType(curves_id_->symmetry));
for (const float4x4 &brush_transform : symmetry_brush_transforms) {
this->smooth_projected(brush_transform);
this->find_projected_smooth_factors(brush_transform, r_point_smooth_factors);
}
}
void smooth_projected(const float4x4 &brush_transform)
void find_projected_smooth_factors(const float4x4 &brush_transform,
MutableSpan<float> r_point_smooth_factors)
{
const float4x4 brush_transform_inv = brush_transform.inverted();
MutableSpan<float3> positions_cu = curves_->positions_for_write();
const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
const float brush_radius_sq_re = pow2f(brush_radius_re);
float4x4 projection;
ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values);
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_);
threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) {
Vector<float2> old_curve_positions_re;
for (const int curve_i : curve_selection_.slice(range)) {
const IndexRange points = curves_->points_for_curve(curve_i);
/* Find position of control points in screen space. */
old_curve_positions_re.clear();
old_curve_positions_re.reserve(points.size());
for (const int point_i : points) {
const float3 &pos_cu = brush_transform_inv * positions_cu[point_i];
const float3 &pos_cu = brush_transform_inv * deformation.positions[point_i];
float2 pos_re;
ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values);
old_curve_positions_re.append_unchecked(pos_re);
}
for (const int i : IndexRange(points.size()).drop_front(1).drop_back(1)) {
const int point_i = points[i];
const float2 &old_pos_re = old_curve_positions_re[i];
const float dist_to_brush_sq_re = math::distance_squared(old_pos_re, brush_pos_re_);
const float dist_to_brush_sq_re = math::distance_squared(pos_re, brush_pos_re_);
if (dist_to_brush_sq_re > brush_radius_sq_re) {
continue;
}
@ -161,27 +158,13 @@ struct SmoothOperationExecutor {
const float weight_factor = 0.1f;
const float weight = weight_factor * brush_strength_ * radius_falloff *
point_factors_[point_i];
/* Move points towards the middle of their neighbors. */
const float2 &old_pos_prev_re = old_curve_positions_re[i - 1];
const float2 &old_pos_next_re = old_curve_positions_re[i + 1];
const float2 goal_pos_re = math::midpoint(old_pos_prev_re, old_pos_next_re);
const float2 new_pos_re = math::interpolate(old_pos_re, goal_pos_re, weight);
const float3 old_pos_cu = brush_transform_inv * positions_cu[point_i];
float3 new_pos_wo;
ED_view3d_win_to_3d(ctx_.v3d,
ctx_.region,
transforms_.curves_to_world * old_pos_cu,
new_pos_re,
new_pos_wo);
const float3 new_pos_cu = brush_transform * (transforms_.world_to_curves * new_pos_wo);
positions_cu[point_i] = new_pos_cu;
math::max_inplace(r_point_smooth_factors[point_i], weight);
}
}
});
}
void smooth_spherical_with_symmetry()
void find_spherical_smooth_factors_with_symmetry(MutableSpan<float> r_point_smooth_factors)
{
float4x4 projection;
ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values);
@ -198,27 +181,25 @@ struct SmoothOperationExecutor {
const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
eCurvesSymmetryType(curves_id_->symmetry));
for (const float4x4 &brush_transform : symmetry_brush_transforms) {
this->smooth_spherical(brush_transform * brush_pos_cu, brush_radius_cu);
this->find_spherical_smooth_factors(
brush_transform * brush_pos_cu, brush_radius_cu, r_point_smooth_factors);
}
}
void smooth_spherical(const float3 &brush_pos_cu, const float brush_radius_cu)
void find_spherical_smooth_factors(const float3 &brush_pos_cu,
const float brush_radius_cu,
MutableSpan<float> r_point_smooth_factors)
{
MutableSpan<float3> positions_cu = curves_->positions_for_write();
const float brush_radius_sq_cu = pow2f(brush_radius_cu);
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_);
threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) {
Vector<float3> old_curve_positions_cu;
for (const int curve_i : curve_selection_.slice(range)) {
const IndexRange points = curves_->points_for_curve(curve_i);
/* Remember original positions so that we don't smooth based on already smoothed points
* below. */
old_curve_positions_cu.clear();
old_curve_positions_cu.extend(positions_cu.slice(points));
for (const int i : IndexRange(points.size()).drop_front(1).drop_back(1)) {
const int point_i = points[i];
const float3 &old_pos_cu = old_curve_positions_cu[i];
const float dist_to_brush_sq_cu = math::distance_squared(old_pos_cu, brush_pos_cu);
for (const int point_i : points) {
const float3 &pos_cu = deformation.positions[point_i];
const float dist_to_brush_sq_cu = math::distance_squared(pos_cu, brush_pos_cu);
if (dist_to_brush_sq_cu > brush_radius_sq_cu) {
continue;
}
@ -231,13 +212,34 @@ struct SmoothOperationExecutor {
const float weight_factor = 0.1f;
const float weight = weight_factor * brush_strength_ * radius_falloff *
point_factors_[point_i];
math::max_inplace(r_point_smooth_factors[point_i], weight);
}
}
});
}
/* Move points towards the middle of their neighbors. */
const float3 &old_pos_prev_cu = old_curve_positions_cu[i - 1];
const float3 &old_pos_next_cu = old_curve_positions_cu[i + 1];
const float3 goal_pos_cu = math::midpoint(old_pos_prev_cu, old_pos_next_cu);
const float3 new_pos_cu = math::interpolate(old_pos_cu, goal_pos_cu, weight);
positions_cu[point_i] = new_pos_cu;
void smooth(const Span<float> point_smooth_factors)
{
MutableSpan<float3> positions = curves_->positions_for_write();
threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) {
Vector<float3> old_positions;
for (const int curve_i : curve_selection_.slice(range)) {
const IndexRange points = curves_->points_for_curve(curve_i);
old_positions.clear();
old_positions.extend(positions.slice(points));
for (const int i : IndexRange(points.size()).drop_front(1).drop_back(1)) {
const int point_i = points[i];
const float smooth_factor = point_smooth_factors[point_i];
if (smooth_factor == 0.0f) {
continue;
}
/* Move towards the middle of the neighboring points. */
const float3 old_pos = old_positions[i];
const float3 &prev_pos = old_positions[i - 1];
const float3 &next_pos = old_positions[i + 1];
const float3 goal_pos = math::midpoint(prev_pos, next_pos);
const float3 new_pos = math::interpolate(old_pos, goal_pos, smooth_factor);
positions[point_i] = new_pos;
}
}
});

View File

@ -176,6 +176,8 @@ struct SnakeHookOperatorExecutor {
void projected_snake_hook(const float4x4 &brush_transform)
{
const float4x4 brush_transform_inv = brush_transform.inverted();
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_);
MutableSpan<float3> positions_cu = curves_->positions_for_write();
@ -189,12 +191,14 @@ struct SnakeHookOperatorExecutor {
for (const int curve_i : curves_range) {
const IndexRange points = curves_->points_for_curve(curve_i);
const int last_point_i = points.last();
const float3 old_pos_cu = brush_transform_inv * positions_cu[last_point_i];
const float3 old_pos_cu = deformation.positions[last_point_i];
const float3 old_symm_pos_cu = brush_transform_inv * old_pos_cu;
float2 old_pos_re;
ED_view3d_project_float_v2_m4(ctx_.region, old_pos_cu, old_pos_re, projection.values);
float2 old_symm_pos_re;
ED_view3d_project_float_v2_m4(
ctx_.region, old_symm_pos_cu, old_symm_pos_re, projection.values);
const float distance_to_brush_sq_re = math::distance_squared(old_pos_re,
const float distance_to_brush_sq_re = math::distance_squared(old_symm_pos_re,
brush_pos_prev_re_);
if (distance_to_brush_sq_re > brush_radius_sq_re) {
continue;
@ -204,17 +208,21 @@ struct SnakeHookOperatorExecutor {
brush_, std::sqrt(distance_to_brush_sq_re), brush_radius_re);
const float weight = brush_strength_ * radius_falloff * curve_factors_[curve_i];
const float2 new_position_re = old_pos_re + brush_pos_diff_re_ * weight;
float3 new_position_wo;
const float2 new_symm_pos_re = old_symm_pos_re + brush_pos_diff_re_ * weight;
float3 new_symm_pos_wo;
ED_view3d_win_to_3d(ctx_.v3d,
ctx_.region,
transforms_.curves_to_world * old_pos_cu,
new_position_re,
new_position_wo);
const float3 new_position_cu = brush_transform *
(transforms_.world_to_curves * new_position_wo);
transforms_.curves_to_world * old_symm_pos_cu,
new_symm_pos_re,
new_symm_pos_wo);
const float3 new_pos_cu = brush_transform *
(transforms_.world_to_curves * new_symm_pos_wo);
const float3 translation_eval = new_pos_cu - old_pos_cu;
const float3 translation_orig = deformation.translation_from_deformed_to_original(
last_point_i, translation_eval);
move_last_point_and_resample(positions_cu.slice(points), new_position_cu);
move_last_point_and_resample(positions_cu.slice(points),
positions_cu[last_point_i] + translation_orig);
}
});
}
@ -252,6 +260,9 @@ struct SnakeHookOperatorExecutor {
const float3 &brush_end_cu,
const float brush_radius_cu)
{
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_);
MutableSpan<float3> positions_cu = curves_->positions_for_write();
const float3 brush_diff_cu = brush_end_cu - brush_start_cu;
const float brush_radius_sq_cu = pow2f(brush_radius_cu);
@ -260,7 +271,7 @@ struct SnakeHookOperatorExecutor {
for (const int curve_i : curves_range) {
const IndexRange points = curves_->points_for_curve(curve_i);
const int last_point_i = points.last();
const float3 old_pos_cu = positions_cu[last_point_i];
const float3 old_pos_cu = deformation.positions[last_point_i];
const float distance_to_brush_sq_cu = dist_squared_to_line_segment_v3(
old_pos_cu, brush_start_cu, brush_end_cu);
@ -274,9 +285,12 @@ struct SnakeHookOperatorExecutor {
brush_, distance_to_brush_cu, brush_radius_cu);
const float weight = brush_strength_ * radius_falloff * curve_factors_[curve_i];
const float3 new_pos_cu = old_pos_cu + weight * brush_diff_cu;
const float3 translation_eval = weight * brush_diff_cu;
const float3 translation_orig = deformation.translation_from_deformed_to_original(
last_point_i, translation_eval);
move_last_point_and_resample(positions_cu.slice(points), new_pos_cu);
move_last_point_and_resample(positions_cu.slice(points),
positions_cu[last_point_i] + translation_orig);
}
});
}

View File

@ -937,6 +937,19 @@ static void create_inspection_string_for_geometry(const geo_log::GeometryValueLo
ss << TIP_("\u2022 Volume") << line_end;
break;
}
case GEO_COMPONENT_TYPE_EDIT: {
if (value_log.edit_data_info.has_value()) {
const geo_log::GeometryValueLog::EditDataInfo &edit_info = *value_log.edit_data_info;
char line[256];
BLI_snprintf(line,
sizeof(line),
TIP_("\u2022 Edit Curves: %s, %s"),
edit_info.has_deformed_positions ? TIP_("positions") : TIP_("no positions"),
edit_info.has_deform_matrices ? TIP_("matrices") : TIP_("no matrices"));
ss << line << line_end;
}
break;
}
}
}
@ -975,6 +988,9 @@ static void create_inspection_string_for_geometry(const geo_log::GeometryValueLo
ss << TIP_("Volume");
break;
}
case GEO_COMPONENT_TYPE_EDIT: {
break;
}
}
ss << ((type == supported_types.last()) ? "" : ", ");
}

View File

@ -13,13 +13,13 @@
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "GEO_reverse_uv_sampler.hh"
namespace blender::geometry {
struct AddCurvesOnMeshInputs {
/** Information about the root points where new curves should be generated. */
Span<float3> root_positions_cu;
Span<float3> bary_coords;
Span<int> looptri_indices;
/** UV Coordinates at which the new curves should be added. */
Span<float2> uvs;
/** Determines shape of new curves. */
bool interpolate_length = false;
@ -30,14 +30,10 @@ struct AddCurvesOnMeshInputs {
/** Information about the surface that the new curves are attached to. */
const Mesh *surface = nullptr;
BVHTreeFromMesh *surface_bvh = nullptr;
Span<MLoopTri> surface_looptris;
Span<float2> surface_uv_map;
const ReverseUVSampler *reverse_uv_sampler = nullptr;
Span<float3> corner_normals_su;
/** Transformation matrices. */
float4x4 curves_to_surface_mat;
float4x4 surface_to_curves_normal_mat;
bke::CurvesSurfaceTransforms *transforms = nullptr;
/**
* KD-Tree that contains the root points of existing curves. This is only necessary when
@ -51,4 +47,8 @@ struct AddCurvesOnMeshInputs {
*/
void add_curves_on_mesh(bke::CurvesGeometry &curves, const AddCurvesOnMeshInputs &inputs);
float3 compute_surface_point_normal(const MLoopTri &looptri,
const float3 &bary_coord,
const Span<float3> corner_normals);
} // namespace blender::geometry

View File

@ -6,6 +6,7 @@
#include "BKE_mesh_sample.hh"
#include "GEO_add_curves_on_mesh.hh"
#include "GEO_reverse_uv_sampler.hh"
/**
* The code below uses a suffix naming convention to indicate the coordinate space:
@ -27,9 +28,9 @@ struct NeighborCurve {
static constexpr int max_neighbors = 5;
using NeighborCurves = Vector<NeighborCurve, max_neighbors>;
static float3 compute_surface_point_normal(const MLoopTri &looptri,
const float3 &bary_coord,
const Span<float3> corner_normals)
float3 compute_surface_point_normal(const MLoopTri &looptri,
const float3 &bary_coord,
const Span<float3> corner_normals)
{
const int l0 = looptri.tri[0];
const int l1 = looptri.tri[1];
@ -136,16 +137,15 @@ static void interpolate_position_with_interpolation(CurvesGeometry &curves,
const int old_curves_num,
const Span<float> new_lengths_cu,
const Span<float3> new_normals_su,
const float4x4 &surface_to_curves_normal_mat,
const float4x4 &curves_to_surface_mat,
const BVHTreeFromMesh &surface_bvh,
const Span<MLoopTri> surface_looptris,
const Mesh &surface,
const bke::CurvesSurfaceTransforms &transforms,
const ReverseUVSampler &reverse_uv_sampler,
const Span<float3> corner_normals_su)
{
MutableSpan<float3> positions_cu = curves.positions_for_write();
const int added_curves_num = root_positions_cu.size();
const Span<float2> uv_coords = curves.surface_uv_coords();
threading::parallel_for(IndexRange(added_curves_num), 256, [&](const IndexRange range) {
for (const int added_curve_i : range) {
const NeighborCurves &neighbors = neighbors_per_curve[added_curve_i];
@ -154,7 +154,7 @@ static void interpolate_position_with_interpolation(CurvesGeometry &curves,
const float length_cu = new_lengths_cu[added_curve_i];
const float3 &normal_su = new_normals_su[added_curve_i];
const float3 normal_cu = math::normalize(surface_to_curves_normal_mat * normal_su);
const float3 normal_cu = math::normalize(transforms.surface_to_curves_normal * normal_su);
const float3 &root_cu = root_positions_cu[added_curve_i];
@ -169,26 +169,15 @@ static void interpolate_position_with_interpolation(CurvesGeometry &curves,
for (const NeighborCurve &neighbor : neighbors) {
const int neighbor_curve_i = neighbor.index;
const float3 &neighbor_first_pos_cu = positions_cu[curves.offsets()[neighbor_curve_i]];
const float3 neighbor_first_pos_su = curves_to_surface_mat * neighbor_first_pos_cu;
BVHTreeNearest nearest;
nearest.dist_sq = FLT_MAX;
BLI_bvhtree_find_nearest(surface_bvh.tree,
neighbor_first_pos_su,
&nearest,
surface_bvh.nearest_callback,
const_cast<BVHTreeFromMesh *>(&surface_bvh));
const int neighbor_looptri_index = nearest.index;
const MLoopTri &neighbor_looptri = surface_looptris[neighbor_looptri_index];
const float3 neighbor_bary_coord =
bke::mesh_surface_sample::compute_bary_coord_in_triangle(
surface, neighbor_looptri, nearest.co);
const float2 neighbor_uv = uv_coords[neighbor_curve_i];
const ReverseUVSampler::Result result = reverse_uv_sampler.sample(neighbor_uv);
if (result.type != ReverseUVSampler::ResultType::Ok) {
continue;
}
const float3 neighbor_normal_su = compute_surface_point_normal(
surface_looptris[neighbor_looptri_index], neighbor_bary_coord, corner_normals_su);
const float3 neighbor_normal_cu = math::normalize(surface_to_curves_normal_mat *
*result.looptri, result.bary_weights, corner_normals_su);
const float3 neighbor_normal_cu = math::normalize(transforms.surface_to_curves_normal *
neighbor_normal_su);
/* The rotation matrix used to transform relative coordinates of the neighbor curve
@ -245,13 +234,37 @@ void add_curves_on_mesh(CurvesGeometry &curves, const AddCurvesOnMeshInputs &inp
const bool use_interpolation = inputs.interpolate_length || inputs.interpolate_point_count ||
inputs.interpolate_shape;
Vector<float3> root_positions_cu;
Vector<float3> bary_coords;
Vector<const MLoopTri *> looptris;
Vector<float2> used_uvs;
/* Find faces that the passed in uvs belong to. */
for (const int i : inputs.uvs.index_range()) {
const float2 &uv = inputs.uvs[i];
const ReverseUVSampler::Result result = inputs.reverse_uv_sampler->sample(uv);
if (result.type != ReverseUVSampler::ResultType::Ok) {
continue;
}
const MLoopTri &looptri = *result.looptri;
bary_coords.append(result.bary_weights);
looptris.append(&looptri);
const float3 root_position_su = attribute_math::mix3<float3>(
result.bary_weights,
inputs.surface->mvert[inputs.surface->mloop[looptri.tri[0]].v].co,
inputs.surface->mvert[inputs.surface->mloop[looptri.tri[1]].v].co,
inputs.surface->mvert[inputs.surface->mloop[looptri.tri[2]].v].co);
root_positions_cu.append(inputs.transforms->surface_to_curves * root_position_su);
used_uvs.append(uv);
}
Array<NeighborCurves> neighbors_per_curve;
if (use_interpolation) {
BLI_assert(inputs.old_roots_kdtree != nullptr);
neighbors_per_curve = find_curve_neighbors(inputs.root_positions_cu, *inputs.old_roots_kdtree);
neighbors_per_curve = find_curve_neighbors(root_positions_cu, *inputs.old_roots_kdtree);
}
const int added_curves_num = inputs.root_positions_cu.size();
const int added_curves_num = root_positions_cu.size();
const int old_points_num = curves.points_num();
const int old_curves_num = curves.curves_num();
const int new_curves_num = old_curves_num + added_curves_num;
@ -280,6 +293,10 @@ void add_curves_on_mesh(CurvesGeometry &curves, const AddCurvesOnMeshInputs &inp
curves.resize(new_points_num, new_curves_num);
MutableSpan<float3> positions_cu = curves.positions_for_write();
/* Initialize attachment information. */
MutableSpan<float2> surface_uv_coords = curves.surface_uv_coords_for_write();
surface_uv_coords.take_back(added_curves_num).copy_from(used_uvs);
/* Determine length of new curves. */
Array<float> new_lengths_cu(added_curves_num);
if (inputs.interpolate_length) {
@ -306,25 +323,11 @@ void add_curves_on_mesh(CurvesGeometry &curves, const AddCurvesOnMeshInputs &inp
Array<float3> new_normals_su(added_curves_num);
threading::parallel_for(IndexRange(added_curves_num), 256, [&](const IndexRange range) {
for (const int i : range) {
const int looptri_index = inputs.looptri_indices[i];
const float3 &bary_coord = inputs.bary_coords[i];
new_normals_su[i] = compute_surface_point_normal(
inputs.surface_looptris[looptri_index], bary_coord, inputs.corner_normals_su);
*looptris[i], bary_coords[i], inputs.corner_normals_su);
}
});
/* Propagate attachment information. */
if (!inputs.surface_uv_map.is_empty()) {
MutableSpan<float2> surface_uv_coords = curves.surface_uv_coords_for_write();
bke::mesh_surface_sample::sample_corner_attribute(
*inputs.surface,
inputs.looptri_indices,
inputs.bary_coords,
GVArray::ForSpan(inputs.surface_uv_map),
IndexRange(added_curves_num),
surface_uv_coords.take_back(added_curves_num));
}
/* Update selection arrays when available. */
const VArray<float> points_selection = curves.selection_point_float();
if (points_selection.is_span()) {
@ -340,25 +343,22 @@ void add_curves_on_mesh(CurvesGeometry &curves, const AddCurvesOnMeshInputs &inp
/* Initialize position attribute. */
if (inputs.interpolate_shape) {
interpolate_position_with_interpolation(curves,
inputs.root_positions_cu,
root_positions_cu,
neighbors_per_curve,
old_curves_num,
new_lengths_cu,
new_normals_su,
inputs.surface_to_curves_normal_mat,
inputs.curves_to_surface_mat,
*inputs.surface_bvh,
inputs.surface_looptris,
*inputs.surface,
*inputs.transforms,
*inputs.reverse_uv_sampler,
inputs.corner_normals_su);
}
else {
interpolate_position_without_interpolation(curves,
old_curves_num,
inputs.root_positions_cu,
root_positions_cu,
new_lengths_cu,
new_normals_su,
inputs.surface_to_curves_normal_mat);
inputs.transforms->surface_to_curves_normal);
}
/* Set curve types. */

View File

@ -204,7 +204,8 @@ struct GatherTasks {
/* Volumes only have very simple support currently. Only the first found volume is put into the
* output. */
UserCounter<VolumeComponent> first_volume;
UserCounter<const VolumeComponent> first_volume;
UserCounter<const GeometryComponentEditData> first_edit_data;
};
/** Current offsets while during the gather operation. */
@ -582,7 +583,16 @@ static void gather_realize_tasks_recursive(GatherTasksInfo &gather_info,
const VolumeComponent *volume_component = static_cast<const VolumeComponent *>(component);
if (!gather_info.r_tasks.first_volume) {
volume_component->user_add();
gather_info.r_tasks.first_volume = const_cast<VolumeComponent *>(volume_component);
gather_info.r_tasks.first_volume = volume_component;
}
break;
}
case GEO_COMPONENT_TYPE_EDIT: {
const GeometryComponentEditData *edit_component =
static_cast<const GeometryComponentEditData *>(component);
if (!gather_info.r_tasks.first_edit_data) {
edit_component->user_add();
gather_info.r_tasks.first_edit_data = edit_component;
}
break;
}
@ -1405,6 +1415,9 @@ GeometrySet realize_instances(GeometrySet geometry_set, const RealizeInstancesOp
if (gather_info.r_tasks.first_volume) {
new_geometry_set.add(*gather_info.r_tasks.first_volume);
}
if (gather_info.r_tasks.first_edit_data) {
new_geometry_set.add(*gather_info.r_tasks.first_edit_data);
}
return new_geometry_set;
}

View File

@ -163,6 +163,7 @@ class GeoNodeExecParams {
}
void check_input_geometry_set(StringRef identifier, const GeometrySet &geometry_set) const;
void check_output_geometry_set(const GeometrySet &geometry_set) const;
/**
* Get input as vector for multi input socket with the given identifier.
@ -231,6 +232,9 @@ class GeoNodeExecParams {
#ifdef DEBUG
this->check_output_access(identifier, type);
#endif
if constexpr (std::is_same_v<StoredT, GeometrySet>) {
this->check_output_geometry_set(value);
}
GMutablePointer gvalue = provider_->alloc_output_value(type);
new (gvalue.get()) StoredT(std::forward<T>(value));
provider_->set_output(identifier, gvalue);

View File

@ -115,11 +115,16 @@ class GeometryValueLog : public ValueLog {
struct InstancesInfo {
int instances_num;
};
struct EditDataInfo {
bool has_deformed_positions;
bool has_deform_matrices;
};
std::optional<MeshInfo> mesh_info;
std::optional<CurveInfo> curve_info;
std::optional<PointCloudInfo> pointcloud_info;
std::optional<InstancesInfo> instances_info;
std::optional<EditDataInfo> edit_data_info;
GeometryValueLog(const GeometrySet &geometry_set, bool log_full_geometry = false);

View File

@ -50,7 +50,7 @@ static void node_geo_exec(GeoNodeExecParams params)
}
if (sub_min == float3(FLT_MAX)) {
sub_geometry.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
sub_geometry.remove_geometry_during_modify();
}
else {
const float3 scale = sub_max - sub_min;
@ -58,7 +58,7 @@ static void node_geo_exec(GeoNodeExecParams params)
Mesh *mesh = geometry::create_cuboid_mesh(scale, 2, 2, 2, "uv_map");
transform_mesh(*mesh, center, float3(0), float3(1));
sub_geometry.replace_mesh(mesh);
sub_geometry.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES});
sub_geometry.keep_only_during_modify({GEO_COMPONENT_TYPE_MESH});
}
});

View File

@ -224,7 +224,7 @@ static void node_geo_exec(GeoNodeExecParams params)
geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
Mesh *mesh = compute_hull(geometry_set);
geometry_set.replace_mesh(mesh);
geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES});
geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_MESH});
});
params.set_output("Convex Hull", std::move(geometry_set));

View File

@ -60,6 +60,8 @@ static void node_geo_exec(GeoNodeExecParams params)
const Field<bool> selection = params.extract_input<Field<bool>>("Selection");
GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set);
switch (mode) {
case GEO_NODE_CURVE_RESAMPLE_COUNT: {
Field<int> count = params.extract_input<Field<int>>("Count");

View File

@ -28,9 +28,10 @@ static void geometry_set_curve_to_mesh(GeometrySet &geometry_set,
const bool fill_caps)
{
const Curves &curves = *geometry_set.get_curves_for_read();
const Curves *profile_curves = profile_set.get_curves_for_read();
GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set);
if (profile_curves == nullptr) {
Mesh *mesh = bke::curve_to_wire_mesh(bke::CurvesGeometry::wrap(curves.geometry));
geometry_set.replace_mesh(mesh);
@ -55,7 +56,7 @@ static void node_geo_exec(GeoNodeExecParams params)
has_curves = true;
geometry_set_curve_to_mesh(geometry_set, profile_set, fill_caps);
}
geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES});
geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_MESH});
});
params.set_output("Mesh", std::move(curve_set));

View File

@ -316,9 +316,11 @@ static void node_geo_exec(GeoNodeExecParams params)
attribute_outputs.normal_id = StrongAnonymousAttributeID("Normal");
attribute_outputs.rotation_id = StrongAnonymousAttributeID("Rotation");
GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set);
geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
if (!geometry_set.has_curves()) {
geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
geometry_set.remove_geometry_during_modify();
return;
}
const std::unique_ptr<CurveEval> curve = curves_to_curve_eval(
@ -329,7 +331,7 @@ static void node_geo_exec(GeoNodeExecParams params)
const Array<int> offsets = calculate_spline_point_offsets(params, mode, *curve, splines);
const int total_num = offsets.last();
if (total_num == 0) {
geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
geometry_set.remove_geometry_during_modify();
return;
}
@ -355,7 +357,7 @@ static void node_geo_exec(GeoNodeExecParams params)
point_attributes.tangents, point_attributes.normals, point_attributes.rotations);
}
geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES, GEO_COMPONENT_TYPE_POINT_CLOUD});
geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_POINT_CLOUD});
});
params.set_output("Points", std::move(geometry_set));

View File

@ -579,6 +579,7 @@ static void node_geo_exec(GeoNodeExecParams params)
const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)storage.mode;
GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve");
GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set);
if (mode == GEO_NODE_CURVE_SAMPLE_FACTOR) {
Field<float> start_field = params.extract_input<Field<float>>("Start");

View File

@ -41,7 +41,7 @@ static void node_declare(NodeDeclarationBuilder &b)
b.add_output<decl::Geometry>(N_("Curves"));
}
static void deform_curves(CurvesGeometry &curves,
static void deform_curves(const CurvesGeometry &curves,
const Mesh &surface_mesh_old,
const Mesh &surface_mesh_new,
const Span<float2> curve_attachment_uvs,
@ -51,6 +51,8 @@ static void deform_curves(CurvesGeometry &curves,
const Span<float3> corner_normals_new,
const Span<float3> rest_positions,
const float4x4 &surface_to_curves,
MutableSpan<float3> r_positions,
MutableSpan<float3x3> r_rotations,
std::atomic<int> &r_invalid_uv_count)
{
/* Find attachment points on old and new mesh. */
@ -61,8 +63,6 @@ static void deform_curves(CurvesGeometry &curves,
[&]() { 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) {
@ -190,9 +190,15 @@ static void deform_curves(CurvesGeometry &curves,
/* 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 old_point_pos = r_positions[point_i];
const float3 new_point_pos = curve_transform * old_point_pos;
positions[point_i] = new_point_pos;
r_positions[point_i] = new_point_pos;
}
if (!r_rotations.is_empty()) {
for (const int point_i : points) {
r_rotations[point_i] = rotation * r_rotations[point_i];
}
}
}
});
@ -320,17 +326,67 @@ static void node_geo_exec(GeoNodeExecParams params)
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);
bke::CurvesEditHints *edit_hints = curves_geometry.get_curve_edit_hints_for_write();
MutableSpan<float3> edit_hint_positions;
MutableSpan<float3x3> edit_hint_rotations;
if (edit_hints != nullptr) {
if (edit_hints->positions.has_value()) {
edit_hint_positions = *edit_hints->positions;
}
if (!edit_hints->deform_mats.has_value()) {
edit_hints->deform_mats.emplace(edit_hints->curves_id_orig.geometry.point_num,
float3x3::identity());
edit_hints->deform_mats->fill(float3x3::identity());
}
edit_hint_rotations = *edit_hints->deform_mats;
}
if (edit_hint_positions.is_empty()) {
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,
curves.positions_for_write(),
edit_hint_rotations,
invalid_uv_count);
}
else {
/* First deform the actual curves in the input geometry. */
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,
curves.positions_for_write(),
{},
invalid_uv_count);
/* Then also deform edit curve information for use in sculpt mode. */
const CurvesGeometry &curves_orig = CurvesGeometry::wrap(edit_hints->curves_id_orig.geometry);
deform_curves(curves_orig,
*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,
edit_hint_positions,
edit_hint_rotations,
invalid_uv_count);
}
curves.tag_positions_changed();

View File

@ -550,7 +550,7 @@ static void node_geo_exec(GeoNodeExecParams params)
geometry_set, selection_field, method, seed, attribute_outputs, params);
/* Keep instances because the original geometry set may contain instances that are processed as
* well. */
geometry_set.keep_only({GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_INSTANCES});
geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_POINT_CLOUD});
});
params.set_output("Points", std::move(geometry_set));

View File

@ -328,10 +328,11 @@ static void duplicate_curves(GeometrySet &geometry_set,
const IndexAttributes &attribute_outputs)
{
if (!geometry_set.has_curves()) {
geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
geometry_set.remove_geometry_during_modify();
return;
}
geometry_set.keep_only({GEO_COMPONENT_TYPE_CURVE, GEO_COMPONENT_TYPE_INSTANCES});
geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_CURVE});
GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set);
const CurveComponent &src_component = *geometry_set.get_component_for_read<CurveComponent>();
const Curves &curves_id = *src_component.get_for_read();
@ -516,10 +517,10 @@ static void duplicate_faces(GeometrySet &geometry_set,
const IndexAttributes &attribute_outputs)
{
if (!geometry_set.has_mesh()) {
geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
geometry_set.remove_geometry_during_modify();
return;
}
geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES});
geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_MESH});
const MeshComponent &src_component = *geometry_set.get_component_for_read<MeshComponent>();
const Mesh &mesh = *src_component.get_for_read();
@ -720,7 +721,7 @@ static void duplicate_edges(GeometrySet &geometry_set,
const IndexAttributes &attribute_outputs)
{
if (!geometry_set.has_mesh()) {
geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
geometry_set.remove_geometry_during_modify();
return;
};
const MeshComponent &src_component = *geometry_set.get_component_for_read<MeshComponent>();
@ -1032,7 +1033,7 @@ static void duplicate_points(GeometrySet &geometry_set,
}
}
component_types.append(GEO_COMPONENT_TYPE_INSTANCES);
geometry_set.keep_only(component_types);
geometry_set.keep_only_during_modify(component_types);
}
/** \} */

View File

@ -213,7 +213,7 @@ static void node_geo_exec(GeoNodeExecParams params)
}
}
geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
geometry_set.remove_geometry_during_modify();
});
/* Unused references may have been added above. Remove those now so that other nodes don't

View File

@ -94,7 +94,7 @@ static void node_geo_exec(GeoNodeExecParams params)
params.extract_input<Field<float3>>("Position"),
params.extract_input<Field<float>>("Radius"),
params.extract_input<Field<bool>>("Selection"));
geometry_set.keep_only({GEO_COMPONENT_TYPE_POINT_CLOUD});
geometry_set.keep_only({GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_EDIT});
params.set_output("Points", std::move(geometry_set));
}
else {

View File

@ -20,7 +20,7 @@ static void node_geo_exec(GeoNodeExecParams params)
geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
const Mesh *mesh = geometry_set.get_mesh_for_read();
if (mesh == nullptr) {
geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
geometry_set.remove_geometry_during_modify();
return;
}
@ -31,13 +31,13 @@ static void node_geo_exec(GeoNodeExecParams params)
evaluator.evaluate();
const IndexMask selection = evaluator.get_evaluated_as_mask(0);
if (selection.size() == 0) {
geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
geometry_set.remove_geometry_during_modify();
return;
}
bke::CurvesGeometry curves = geometry::mesh_to_curve_convert(*mesh, selection);
geometry_set.replace_curves(bke::curves_new_nomain(std::move(curves)));
geometry_set.keep_only({GEO_COMPONENT_TYPE_CURVE, GEO_COMPONENT_TYPE_INSTANCES});
geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_CURVE});
});
params.set_output("Curve", std::move(geometry_set));

View File

@ -62,13 +62,13 @@ static void geometry_set_mesh_to_points(GeometrySet &geometry_set,
{
const MeshComponent *mesh_component = geometry_set.get_component_for_read<MeshComponent>();
if (mesh_component == nullptr) {
geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
geometry_set.remove_geometry_during_modify();
return;
}
GeometryComponentFieldContext field_context{*mesh_component, domain};
const int domain_num = mesh_component->attribute_domain_size(domain);
if (domain_num == 0) {
geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
geometry_set.remove_geometry_during_modify();
return;
}
fn::FieldEvaluator evaluator{field_context, domain_num};
@ -115,7 +115,7 @@ static void geometry_set_mesh_to_points(GeometrySet &geometry_set,
}
}
geometry_set.keep_only({GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_INSTANCES});
geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_POINT_CLOUD});
}
static void node_geo_exec(GeoNodeExecParams params)

View File

@ -154,7 +154,7 @@ static void node_geo_exec(GeoNodeExecParams params)
if (geometry_set.has_mesh()) {
Volume *volume = create_volume_from_mesh(*geometry_set.get_mesh_for_read(), params);
geometry_set.replace_volume(volume);
geometry_set.keep_only({GEO_COMPONENT_TYPE_VOLUME, GEO_COMPONENT_TYPE_INSTANCES});
geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_VOLUME});
}
});
params.set_output("Volume", std::move(geometry_set));

View File

@ -25,14 +25,14 @@ static void geometry_set_points_to_vertices(GeometrySet &geometry_set,
const PointCloudComponent *point_component =
geometry_set.get_component_for_read<PointCloudComponent>();
if (point_component == nullptr) {
geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
geometry_set.remove_geometry_during_modify();
return;
}
GeometryComponentFieldContext field_context{*point_component, ATTR_DOMAIN_POINT};
const int domain_num = point_component->attribute_domain_size(ATTR_DOMAIN_POINT);
if (domain_num == 0) {
geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
geometry_set.remove_geometry_during_modify();
return;
}
@ -63,7 +63,7 @@ static void geometry_set_points_to_vertices(GeometrySet &geometry_set,
}
}
geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES});
geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_MESH});
}
static void node_geo_exec(GeoNodeExecParams params)

View File

@ -224,7 +224,7 @@ static void initialize_volume_component_from_points(GeoNodeExecParams &params,
new_grid->transform().postScale(voxel_size);
BKE_volume_grid_add_vdb(*volume, "density", std::move(new_grid));
r_geometry_set.keep_only({GEO_COMPONENT_TYPE_VOLUME, GEO_COMPONENT_TYPE_INSTANCES});
r_geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_VOLUME});
r_geometry_set.replace_volume(volume);
}
#endif

View File

@ -123,6 +123,34 @@ static void translate_volume(Volume &volume, const float3 translation, const Dep
transform_volume(volume, float4x4::from_location(translation), depsgraph);
}
static void transform_curve_edit_hints(bke::CurvesEditHints &edit_hints, const float4x4 &transform)
{
if (edit_hints.positions.has_value()) {
for (float3 &pos : *edit_hints.positions) {
pos = transform * pos;
}
}
float3x3 deform_mat;
copy_m3_m4(deform_mat.values, transform.values);
if (edit_hints.deform_mats.has_value()) {
for (float3x3 &mat : *edit_hints.deform_mats) {
mat = deform_mat * mat;
}
}
else {
edit_hints.deform_mats.emplace(edit_hints.curves_id_orig.geometry.point_num, deform_mat);
}
}
static void translate_curve_edit_hints(bke::CurvesEditHints &edit_hints, const float3 &translation)
{
if (edit_hints.positions.has_value()) {
for (float3 &pos : *edit_hints.positions) {
pos += translation;
}
}
}
static void translate_geometry_set(GeometrySet &geometry,
const float3 translation,
const Depsgraph &depsgraph)
@ -142,6 +170,9 @@ static void translate_geometry_set(GeometrySet &geometry,
if (geometry.has_instances()) {
translate_instances(geometry.get_component_for_write<InstancesComponent>(), translation);
}
if (bke::CurvesEditHints *curve_edit_hints = geometry.get_curve_edit_hints_for_write()) {
translate_curve_edit_hints(*curve_edit_hints, translation);
}
}
void transform_geometry_set(GeometrySet &geometry,
@ -163,6 +194,9 @@ void transform_geometry_set(GeometrySet &geometry,
if (geometry.has_instances()) {
transform_instances(geometry.get_component_for_write<InstancesComponent>(), transform);
}
if (bke::CurvesEditHints *curve_edit_hints = geometry.get_curve_edit_hints_for_write()) {
transform_curve_edit_hints(*curve_edit_hints, transform);
}
}
void transform_mesh(Mesh &mesh,

View File

@ -193,7 +193,7 @@ static void node_geo_exec(GeoNodeExecParams params)
geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
Mesh *mesh = create_mesh_from_volume(geometry_set, params);
geometry_set.replace_mesh(mesh);
geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES});
geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_MESH});
});
#else
params.error_message_add(NodeWarningType::Error,

View File

@ -2,6 +2,7 @@
#include "NOD_geometry_nodes_eval_log.hh"
#include "BKE_curves.hh"
#include "BKE_geometry_set_instances.hh"
#include "DNA_modifier_types.h"
@ -264,6 +265,17 @@ GeometryValueLog::GeometryValueLog(const GeometrySet &geometry_set, bool log_ful
info.instances_num = instances_component.instances_num();
break;
}
case GEO_COMPONENT_TYPE_EDIT: {
const GeometryComponentEditData &edit_component = *(
const GeometryComponentEditData *)component;
if (const bke::CurvesEditHints *curve_edit_hints =
edit_component.curves_edit_hints_.get()) {
EditDataInfo &info = this->edit_data_info.emplace();
info.has_deform_matrices = curve_edit_hints->deform_mats.has_value();
info.has_deformed_positions = curve_edit_hints->positions.has_value();
}
break;
}
case GEO_COMPONENT_TYPE_VOLUME: {
break;
}

View File

@ -4,6 +4,7 @@
#include "DEG_depsgraph_query.h"
#include "BKE_curves.hh"
#include "BKE_type_conversions.hh"
#include "NOD_geometry_exec.hh"
@ -94,11 +95,27 @@ void GeoNodeExecParams::check_input_geometry_set(StringRef identifier,
message += TIP_("Curve");
break;
}
case GEO_COMPONENT_TYPE_EDIT: {
continue;
}
}
this->error_message_add(NodeWarningType::Info, std::move(message));
}
}
void GeoNodeExecParams::check_output_geometry_set(const GeometrySet &geometry_set) const
{
UNUSED_VARS_NDEBUG(geometry_set);
#ifdef DEBUG
if (const bke::CurvesEditHints *curve_edit_hints =
geometry_set.get_curve_edit_hints_for_read()) {
/* If this is not valid, it's likely that the number of stored deformed points does not match
* the number of points in the original data. */
BLI_assert(curve_edit_hints->is_valid());
}
#endif
}
const bNodeSocket *GeoNodeExecParams::find_available_socket(const StringRef name) const
{
for (const InputSocketRef *socket : provider_->dnode->inputs()) {