Add Chromium-only Blender WebEngine parity work
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_math_quaternion.hh"
|
||||
#include "BLI_math_vector.hh"
|
||||
|
||||
namespace blender::xpbd {
|
||||
|
||||
struct AlignRotationsConstraintResult {
|
||||
float4 delta_lambda;
|
||||
math::Quaternion offset0 = math::Quaternion(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
math::Quaternion offset1 = math::Quaternion(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
float residual_error_squared = 0.0f;
|
||||
};
|
||||
|
||||
inline AlignRotationsConstraintResult evaluate_align_rotations_constraint(
|
||||
const math::Quaternion &r0,
|
||||
const math::Quaternion &r1,
|
||||
const float3 &inertia0,
|
||||
const float3 &inertia1,
|
||||
const math::Quaternion &rest_rotation,
|
||||
const float compliance_term,
|
||||
const float4 &lambda_prev)
|
||||
{
|
||||
const float inv_lumped_inertia0 = math::safe_rcp(0.5f * (inertia0.x + inertia0.y + inertia0.z));
|
||||
const float inv_lumped_inertia1 = math::safe_rcp(0.5f * (inertia1.x + inertia1.y + inertia1.z));
|
||||
if (inv_lumped_inertia0 == 0.0f && inv_lumped_inertia1 == 0.0f) {
|
||||
/* Everything is pinned, so the constraint can't do anything. */
|
||||
return {};
|
||||
}
|
||||
|
||||
const float4 rest_rot_f = float4(rest_rotation);
|
||||
|
||||
/* Note In "Position and Orientation Based Cosserat Rods" (Kugelstadt, Schoemer) the W
|
||||
* component of the Darboux vector is ignored. In "Sag-Free Initialization for Strand-Based
|
||||
* Hybrid Hair Simulation" (Hsu et al.) it is included to improve stability in cases where the
|
||||
* hair is bent at nearly 180 degrees. */
|
||||
const math::Quaternion &rot_diff = math::invert_normalized(r0) * r1;
|
||||
const float4 rot_diff_f = float4(rot_diff);
|
||||
|
||||
const float4 residual_neg = rot_diff_f - rest_rot_f;
|
||||
const float4 residual_pos = rot_diff_f + rest_rot_f;
|
||||
const float4 residual = math::length_squared(residual_neg) < math::length_squared(residual_pos) ?
|
||||
residual_neg :
|
||||
residual_pos;
|
||||
|
||||
const float error_squared = math::length_squared(residual + compliance_term * lambda_prev);
|
||||
|
||||
const float4 delta_lambda = (-residual - compliance_term * lambda_prev) /
|
||||
(inv_lumped_inertia0 + inv_lumped_inertia1 + compliance_term);
|
||||
|
||||
const math::Quaternion offset0 = r1 * math::conjugate(
|
||||
math::Quaternion(delta_lambda * inv_lumped_inertia0));
|
||||
const math::Quaternion offset1 = r0 * math::Quaternion(delta_lambda * inv_lumped_inertia1);
|
||||
|
||||
return {delta_lambda, offset0, offset1, error_squared};
|
||||
}
|
||||
|
||||
} // namespace blender::xpbd
|
||||
@@ -0,0 +1,191 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GEO_xpbd_constraint_coloring_utils.hh"
|
||||
#include "GEO_xpbd_constraint_math.hh"
|
||||
#include "GEO_xpbd_constraint_set_templated.hh"
|
||||
|
||||
namespace blender::xpbd {
|
||||
|
||||
/* Constraint implementation for static and dynamic friction is based on
|
||||
* "Detailed Rigid Body Simulation with Extended Position Based Dynamics",
|
||||
* Mueller, Macklin, et al., 2020 */
|
||||
class CollisionEdgeConstraintSet : public TemplatedConstraintSet<CollisionEdgeConstraintSet> {
|
||||
private:
|
||||
int geo_i_;
|
||||
Span<int2> point_pairs_;
|
||||
Span<float2> point_radii_;
|
||||
Span<float3> contact_points_on_edge_;
|
||||
Span<float3> contact_points_motion_;
|
||||
/* Direction of the collider edges. */
|
||||
Span<float3> edge_directions_;
|
||||
/* Normal vectors in the plane of the adjacent face.
|
||||
* The edge normal is cross(edge_direction, face_normal). */
|
||||
Span<float3> edge_normals_;
|
||||
/* Margin of the collider surface. */
|
||||
Span<float> edge_margins_;
|
||||
Span<float> compliance_terms_;
|
||||
Span<float> static_frictions_;
|
||||
Span<float> dynamic_frictions_;
|
||||
/* Scale factor for residual error. */
|
||||
Span<float> error_scales_;
|
||||
MutableSpan<bool> active_states_;
|
||||
MutableSpan<float> point_mix_factors_;
|
||||
MutableSpan<float> lambdas_normal_;
|
||||
|
||||
public:
|
||||
static constexpr StringRefNull debug_name = "Collision Plane";
|
||||
|
||||
CollisionEdgeConstraintSet(const int geo_i,
|
||||
const Span<int2> point_pairs,
|
||||
const Span<float2> point_radii,
|
||||
const Span<float3> contact_points_on_edge,
|
||||
const Span<float3> contact_points_motion,
|
||||
const Span<float3> edge_directions,
|
||||
const Span<float3> edge_normals,
|
||||
const Span<float> edge_margins,
|
||||
const Span<float> compliance_terms,
|
||||
const Span<float> static_frictions,
|
||||
const Span<float> dynamic_frictions,
|
||||
const Span<float> error_scales,
|
||||
MutableSpan<bool> active_states,
|
||||
MutableSpan<float> point_mix_factors,
|
||||
MutableSpan<float> lambdas_normal)
|
||||
: TemplatedConstraintSet<CollisionEdgeConstraintSet>(point_pairs.size(), {geo_i}),
|
||||
geo_i_(geo_i),
|
||||
point_pairs_(point_pairs),
|
||||
point_radii_(point_radii),
|
||||
contact_points_on_edge_(contact_points_on_edge),
|
||||
contact_points_motion_(contact_points_motion),
|
||||
edge_directions_(edge_directions),
|
||||
edge_normals_(edge_normals),
|
||||
edge_margins_(edge_margins),
|
||||
compliance_terms_(compliance_terms),
|
||||
static_frictions_(static_frictions),
|
||||
dynamic_frictions_(dynamic_frictions),
|
||||
error_scales_(error_scales),
|
||||
active_states_(active_states),
|
||||
point_mix_factors_(point_mix_factors),
|
||||
lambdas_normal_(lambdas_normal)
|
||||
{
|
||||
}
|
||||
|
||||
void reset_force(const int constraint_i) const
|
||||
{
|
||||
active_states_[constraint_i] = false;
|
||||
lambdas_normal_[constraint_i] = 0.0f;
|
||||
}
|
||||
|
||||
template<typename UpdaterT>
|
||||
void solve_single(const ConstraintSetParams ¶ms,
|
||||
UpdaterT &updater,
|
||||
const int constraint_i) const
|
||||
{
|
||||
const int2 &point_pair = point_pairs_[constraint_i];
|
||||
const float3 &pos0 = params.position(geo_i_, point_pair[0]);
|
||||
const float3 &pos1 = params.position(geo_i_, point_pair[1]);
|
||||
const float3 &edge_pos = contact_points_on_edge_[constraint_i];
|
||||
const float3 &edge_dir = edge_directions_[constraint_i];
|
||||
const float3 &edge_nor = edge_normals_[constraint_i];
|
||||
const float margin = edge_margins_[constraint_i];
|
||||
const float compliance_term = compliance_terms_[constraint_i];
|
||||
const float inv_m0 = params.inv_mass(geo_i_, point_pair[0]);
|
||||
const float inv_m1 = params.inv_mass(geo_i_, point_pair[1]);
|
||||
const float radius0 = point_radii_[constraint_i][0];
|
||||
const float radius1 = point_radii_[constraint_i][1];
|
||||
BLI_assert(math::is_unit(edge_dir));
|
||||
BLI_assert(math::is_unit(edge_nor));
|
||||
bool &is_active = active_states_[constraint_i];
|
||||
float &point_mix_factor = point_mix_factors_[constraint_i];
|
||||
|
||||
if (inv_m0 <= 0.0f && inv_m1 <= 0.0f) {
|
||||
/* Points with infinite mass are pinned and don't collide dynamically. */
|
||||
is_active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const SegmentClosestToRay closest = closest_on_segment_to_ray(
|
||||
pos0, pos1, edge_pos, edge_dir, true);
|
||||
const float segment_factor = closest.segment_lambda;
|
||||
point_mix_factor = segment_factor;
|
||||
|
||||
/* Effective weight/mass */
|
||||
const float weight0 = (1.0f - segment_factor) * inv_m0;
|
||||
const float weight1 = segment_factor * inv_m1;
|
||||
if (weight0 <= 0.0f && weight1 <= 0.0f) {
|
||||
/* Could happen if the segment_factor is exactly 0 or 1. */
|
||||
is_active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const float3 closest_on_segment = math::interpolate(pos0, pos1, segment_factor);
|
||||
const float3 closest_on_ray = edge_pos + closest.ray_lambda * edge_dir;
|
||||
/* Curve surface is ambiguous: The closest point on the center line isn't necessarily the
|
||||
* contact point of the implicit curve surface as defined by the curve radius. A prospective
|
||||
* surface point is the intersection of connecting line between the closest points on the
|
||||
* segment and the edge.
|
||||
* If the curve is "inside" the collider (intersects the half-plane under the edge) then use
|
||||
* the curve surface point on the opposite side is used as the contact. */
|
||||
float3 seg_nor = closest_on_ray - closest_on_segment;
|
||||
const std::optional<PlaneIntersection> intersection = intersect_plane(
|
||||
pos0, pos1, edge_pos, edge_dir, edge_nor);
|
||||
if (intersection && math::dot(intersection->position - edge_pos, edge_nor) < 0.0f) {
|
||||
/* Reflect on segment direction. This produces the correct normal also in case the closest
|
||||
* segment point is clamped and the normal isn't perpendicular to the edge direction. */
|
||||
seg_nor = 2.0f * edge_dir * math::dot(edge_dir, seg_nor) - seg_nor;
|
||||
}
|
||||
float seg_dist;
|
||||
seg_nor = math::normalize_and_get_length(seg_nor, seg_dist);
|
||||
|
||||
const float radius = math::interpolate(radius0, radius1, segment_factor);
|
||||
const float3 distance = (closest_on_segment + radius * seg_nor) -
|
||||
(closest_on_ray + margin * edge_nor);
|
||||
const float residual = -math::dot(distance, seg_nor);
|
||||
if (residual >= 0.0f) {
|
||||
is_active = false;
|
||||
return;
|
||||
}
|
||||
const float3 gradient = -seg_nor;
|
||||
|
||||
/* Positional correction for penetration. */
|
||||
float3 offset0 = float3(0.0f);
|
||||
float3 offset1 = float3(0.0f);
|
||||
float &lambda_normal = lambdas_normal_[constraint_i];
|
||||
const float error_squared = math::square(residual + compliance_term * lambda_normal);
|
||||
const float delta_lambda_normal = -residual / (weight0 + weight1 + compliance_term);
|
||||
offset0 += delta_lambda_normal * weight0 * gradient;
|
||||
offset1 += delta_lambda_normal * weight1 * gradient;
|
||||
lambda_normal += delta_lambda_normal;
|
||||
|
||||
/* Apply static friction as a direct positional update. */
|
||||
const float3 &prev_pos0 = params.prev_position(geo_i_, point_pair[0]);
|
||||
const float3 &prev_pos1 = params.prev_position(geo_i_, point_pair[1]);
|
||||
const float3 &collider_velocity = contact_points_motion_[constraint_i];
|
||||
const float3 velocity = math::interpolate(pos0 - prev_pos0, pos1 - prev_pos1, segment_factor) -
|
||||
collider_velocity;
|
||||
const float3 velocity_tangent = velocity - math::dot(velocity, gradient) * gradient;
|
||||
const float lambda_tangent_sq = math::length_squared(velocity_tangent /
|
||||
(weight0 + weight1 + compliance_term));
|
||||
const bool is_static = lambda_tangent_sq <
|
||||
math::square(static_frictions_[constraint_i] * lambda_normal);
|
||||
if (is_static) {
|
||||
offset0 -= velocity_tangent * weight0 / (weight0 + weight1 + compliance_term);
|
||||
offset1 -= velocity_tangent * weight1 / (weight0 + weight1 + compliance_term);
|
||||
}
|
||||
|
||||
is_active = true;
|
||||
updater.update_position(geo_i_, point_pair[0], offset0);
|
||||
updater.update_position(geo_i_, point_pair[1], offset1);
|
||||
updater.add_residual_error(geo_i_, error_squared * error_scales_[constraint_i]);
|
||||
}
|
||||
|
||||
ConstraintColoring color_constraints(LinearAllocator<> &memory) const override
|
||||
{
|
||||
return color_constraints__binary(point_pairs_, memory);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::xpbd
|
||||
@@ -0,0 +1,130 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GEO_xpbd_constraint_coloring_utils.hh"
|
||||
#include "GEO_xpbd_constraint_set_templated.hh"
|
||||
|
||||
namespace blender::xpbd {
|
||||
|
||||
/* Constraint implementation for static and dynamic friction is based on
|
||||
* "Detailed Rigid Body Simulation with Extended Position Based Dynamics",
|
||||
* Mueller, Macklin, et al., 2020 */
|
||||
class CollisionFaceConstraintSet : public TemplatedConstraintSet<CollisionFaceConstraintSet> {
|
||||
private:
|
||||
int geo_i_;
|
||||
Span<int> points_;
|
||||
Span<float> point_radii_;
|
||||
Span<float3> contact_points_on_face_;
|
||||
Span<float3> contact_points_motion_;
|
||||
Span<float3> face_normals_;
|
||||
Span<float> face_margins_;
|
||||
Span<float> compliance_terms_;
|
||||
Span<float> static_frictions_;
|
||||
Span<float> dynamic_frictions_;
|
||||
/* Scale factor for residual error. */
|
||||
Span<float> error_scales_;
|
||||
MutableSpan<bool> active_states_;
|
||||
MutableSpan<float> lambdas_normal_;
|
||||
|
||||
public:
|
||||
static constexpr StringRefNull debug_name = "Collision Plane";
|
||||
|
||||
CollisionFaceConstraintSet(const int geo_i,
|
||||
const Span<int> points,
|
||||
const Span<float> point_radii,
|
||||
const Span<float3> contact_points_on_face,
|
||||
const Span<float3> contact_points_motion,
|
||||
const Span<float3> face_normals,
|
||||
const Span<float> face_margins,
|
||||
const Span<float> compliance_terms,
|
||||
const Span<float> static_frictions,
|
||||
const Span<float> dynamic_frictions,
|
||||
const Span<float> error_scales,
|
||||
MutableSpan<bool> active_states,
|
||||
MutableSpan<float> lambdas_normal)
|
||||
: TemplatedConstraintSet<CollisionFaceConstraintSet>(points.size(), {geo_i}),
|
||||
geo_i_(geo_i),
|
||||
points_(points),
|
||||
point_radii_(point_radii),
|
||||
contact_points_on_face_(contact_points_on_face),
|
||||
contact_points_motion_(contact_points_motion),
|
||||
face_normals_(face_normals),
|
||||
face_margins_(face_margins),
|
||||
compliance_terms_(compliance_terms),
|
||||
static_frictions_(static_frictions),
|
||||
dynamic_frictions_(dynamic_frictions),
|
||||
error_scales_(error_scales),
|
||||
active_states_(active_states),
|
||||
lambdas_normal_(lambdas_normal)
|
||||
{
|
||||
}
|
||||
|
||||
void reset_force(const int constraint_i) const
|
||||
{
|
||||
active_states_[constraint_i] = false;
|
||||
lambdas_normal_[constraint_i] = 0.0f;
|
||||
}
|
||||
|
||||
template<typename UpdaterT>
|
||||
void solve_single(const ConstraintSetParams ¶ms,
|
||||
UpdaterT &updater,
|
||||
const int constraint_i) const
|
||||
{
|
||||
const int point_i = points_[constraint_i];
|
||||
const float radius = point_radii_[constraint_i];
|
||||
const float3 &pos = params.position(geo_i_, point_i);
|
||||
const float3 &face_pos = contact_points_on_face_[constraint_i];
|
||||
const float3 &face_nor = face_normals_[constraint_i];
|
||||
const float margin = face_margins_[constraint_i];
|
||||
const float compliance_term = compliance_terms_[constraint_i];
|
||||
const float inv_m = params.inv_mass(geo_i_, point_i);
|
||||
bool &is_active = active_states_[constraint_i];
|
||||
|
||||
if (inv_m <= 0.0f) {
|
||||
/* Points with infinite mass are pinned and don't collide dynamically. */
|
||||
is_active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const float3 diff = pos - face_pos;
|
||||
const float normal_distance = math::dot(diff, face_nor) - margin - radius;
|
||||
is_active = normal_distance < 0.0f;
|
||||
if (!is_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Positional correction for penetration. */
|
||||
float3 offset = float3(0.0f);
|
||||
float &lambda_normal = lambdas_normal_[constraint_i];
|
||||
const float error_squared = math::square(normal_distance + compliance_term * lambda_normal);
|
||||
const float delta_lambda_normal = -normal_distance / (inv_m + compliance_term);
|
||||
offset += delta_lambda_normal * inv_m * face_nor;
|
||||
lambda_normal += delta_lambda_normal;
|
||||
|
||||
/* Apply static friction as a direct positional update. */
|
||||
const float3 &prev_pos = params.prev_position(geo_i_, point_i);
|
||||
const float3 &collider_velocity = contact_points_motion_[constraint_i];
|
||||
const float3 velocity = (pos - prev_pos) - collider_velocity;
|
||||
const float3 velocity_tangent = velocity - math::dot(velocity, face_nor) * face_nor;
|
||||
const float lambda_tangent_sq = math::length_squared(velocity_tangent /
|
||||
(inv_m + compliance_term));
|
||||
const bool is_static = lambda_tangent_sq <
|
||||
math::square(static_frictions_[constraint_i] * lambda_normal);
|
||||
if (is_static) {
|
||||
offset -= velocity_tangent * inv_m / (inv_m + compliance_term);
|
||||
}
|
||||
|
||||
updater.update_position(geo_i_, point_i, offset);
|
||||
updater.add_residual_error(geo_i_, error_squared * error_scales_[constraint_i]);
|
||||
}
|
||||
|
||||
ConstraintColoring color_constraints(LinearAllocator<> &memory) const override
|
||||
{
|
||||
return color_constraints__unary(points_, memory);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::xpbd
|
||||
@@ -0,0 +1,15 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_index_mask.hh"
|
||||
namespace blender::xpbd {
|
||||
|
||||
struct ConstraintColoring {
|
||||
/** Indices within the same #IndexMask are independent and can be evaluated in parallel. */
|
||||
Vector<IndexMask> colors;
|
||||
};
|
||||
|
||||
} // namespace blender::xpbd
|
||||
@@ -0,0 +1,25 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_index_mask.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "GEO_xpbd_constraint_coloring.hh"
|
||||
|
||||
namespace blender::xpbd {
|
||||
|
||||
ConstraintColoring color_constraints__unary(const Span<int> affected_points,
|
||||
LinearAllocator<> &memory);
|
||||
|
||||
ConstraintColoring color_constraints__binary(const Span<int2> affected_points,
|
||||
LinearAllocator<> &memory);
|
||||
|
||||
ConstraintColoring color_constraints__n_ary(const GroupedSpan<int> affected_points,
|
||||
LinearAllocator<> &memory);
|
||||
|
||||
ConstraintColoring color_constraints__all_independent(const int constraints_num);
|
||||
|
||||
} // namespace blender::xpbd
|
||||
@@ -0,0 +1,58 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GEO_xpbd_constraint_set_templated.hh"
|
||||
|
||||
namespace blender::xpbd {
|
||||
|
||||
class AngularDampingConstraintSet
|
||||
: public TemplatedVelocityConstraintSet<AngularDampingConstraintSet> {
|
||||
private:
|
||||
int geo_i_;
|
||||
IndexRange points_;
|
||||
/** Indexed by constraint index. */
|
||||
Span<float> angular_dampings_;
|
||||
MutableSpan<float> lambdas_;
|
||||
|
||||
public:
|
||||
static constexpr StringRefNull debug_name = "Angular Damping";
|
||||
|
||||
AngularDampingConstraintSet(const int geo_i,
|
||||
const IndexRange points,
|
||||
const Span<float> angular_dampings,
|
||||
MutableSpan<float> lambdas)
|
||||
: TemplatedVelocityConstraintSet<AngularDampingConstraintSet>(points.size(), {geo_i}),
|
||||
geo_i_(geo_i),
|
||||
points_(points),
|
||||
angular_dampings_(angular_dampings),
|
||||
lambdas_(lambdas)
|
||||
{
|
||||
}
|
||||
|
||||
void reset_force(const int constraint_i) const
|
||||
{
|
||||
lambdas_[constraint_i] = 0.0f;
|
||||
}
|
||||
|
||||
template<typename UpdaterT>
|
||||
void solve_single(const ConstraintSetParams ¶ms,
|
||||
UpdaterT &updater,
|
||||
const int constraint_i) const
|
||||
{
|
||||
const int point_i = points_[constraint_i];
|
||||
const float3 &angular_velocity = params.angular_velocity(geo_i_, point_i);
|
||||
const float damping = angular_dampings_[constraint_i];
|
||||
const float damping_factor = damping * params.delta_time;
|
||||
float residual;
|
||||
const float3 gradient = math::normalize_and_get_length(angular_velocity, residual);
|
||||
const float delta_lambda = -residual * damping_factor - lambdas_[constraint_i];
|
||||
const float3 offset = gradient * delta_lambda;
|
||||
lambdas_[constraint_i] += delta_lambda;
|
||||
updater.update_angular_velocity(geo_i_, point_i, offset);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::xpbd
|
||||
@@ -0,0 +1,58 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GEO_xpbd_constraint_set_templated.hh"
|
||||
|
||||
namespace blender::xpbd {
|
||||
|
||||
class LinearDampingConstraintSet
|
||||
: public TemplatedVelocityConstraintSet<LinearDampingConstraintSet> {
|
||||
private:
|
||||
int geo_i_;
|
||||
IndexRange points_;
|
||||
/** Indexed by constraint index. */
|
||||
Span<float> linear_dampings_;
|
||||
MutableSpan<float> lambdas_;
|
||||
|
||||
public:
|
||||
static constexpr StringRefNull debug_name = "Linear Damping";
|
||||
|
||||
LinearDampingConstraintSet(const int geo_i,
|
||||
const IndexRange points,
|
||||
const Span<float> linear_dampings,
|
||||
MutableSpan<float> lambdas)
|
||||
: TemplatedVelocityConstraintSet<LinearDampingConstraintSet>(points.size(), {geo_i}),
|
||||
geo_i_(geo_i),
|
||||
points_(points),
|
||||
linear_dampings_(linear_dampings),
|
||||
lambdas_(lambdas)
|
||||
{
|
||||
}
|
||||
|
||||
void reset_force(const int constraint_i) const
|
||||
{
|
||||
lambdas_[constraint_i] = 0.0f;
|
||||
}
|
||||
|
||||
template<typename UpdaterT>
|
||||
void solve_single(const ConstraintSetParams ¶ms,
|
||||
UpdaterT &updater,
|
||||
const int constraint_i) const
|
||||
{
|
||||
const int point_i = points_[constraint_i];
|
||||
const float3 &velocity = params.velocity(geo_i_, point_i);
|
||||
const float damping = linear_dampings_[constraint_i];
|
||||
const float damping_factor = std::clamp(params.delta_time * damping, 0.0f, 1.0f);
|
||||
float residual;
|
||||
const float3 gradient = math::normalize_and_get_length(velocity, residual);
|
||||
const float delta_lambda = -residual * damping_factor - lambdas_[constraint_i];
|
||||
const float3 offset = gradient * delta_lambda;
|
||||
lambdas_[constraint_i] += delta_lambda;
|
||||
updater.update_velocity(geo_i_, point_i, offset);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::xpbd
|
||||
@@ -0,0 +1,107 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GEO_xpbd_constraint_coloring_utils.hh"
|
||||
#include "GEO_xpbd_constraint_set_templated.hh"
|
||||
|
||||
namespace blender::xpbd {
|
||||
|
||||
struct DistanceConstraintResult {
|
||||
float delta_lambda = 0.0f;
|
||||
float3 offset0 = float3(0.0f);
|
||||
float3 offset1 = float3(0.0f);
|
||||
float residual_error_squared = 0.0f;
|
||||
};
|
||||
|
||||
inline DistanceConstraintResult evaluate_distance_constraint(const float3 &p0,
|
||||
const float3 &p1,
|
||||
const float inv_m0,
|
||||
const float inv_m1,
|
||||
const float rest_distance,
|
||||
const float compliance_term,
|
||||
const float lambda_prev)
|
||||
{
|
||||
if (inv_m0 == 0.0f && inv_m1 == 0.0f) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const float3 p_diff = p1 - p0;
|
||||
float length;
|
||||
const float3 normalized_dir = math::normalize_and_get_length(p_diff, length);
|
||||
const float length_diff = length - rest_distance;
|
||||
const float error_squared = math::square(length_diff + compliance_term * lambda_prev);
|
||||
const float delta_lambda = (-length_diff - compliance_term * lambda_prev) /
|
||||
(inv_m0 + inv_m1 + compliance_term);
|
||||
|
||||
const float3 offset0 = -delta_lambda * inv_m0 * normalized_dir;
|
||||
const float3 offset1 = delta_lambda * inv_m1 * normalized_dir;
|
||||
|
||||
return {delta_lambda, offset0, offset1, error_squared};
|
||||
}
|
||||
|
||||
class DistanceConstraintSet : public TemplatedConstraintSet<DistanceConstraintSet> {
|
||||
private:
|
||||
/** Indexed by constraint index. */
|
||||
Span<int2> point_pairs_;
|
||||
Span<float> distances_;
|
||||
Span<float> compliances_;
|
||||
/* Scale factor for residual error. */
|
||||
float error_scale_;
|
||||
MutableSpan<float> lambdas_;
|
||||
|
||||
public:
|
||||
static constexpr StringRefNull debug_name = "Distance Constraint";
|
||||
|
||||
DistanceConstraintSet(const int geo_i,
|
||||
const Span<int2> point_pairs,
|
||||
const Span<float> distances,
|
||||
const Span<float> compliances,
|
||||
const float error_scale,
|
||||
MutableSpan<float> lambdas)
|
||||
: TemplatedConstraintSet<DistanceConstraintSet>(point_pairs.size(), {geo_i}),
|
||||
point_pairs_(point_pairs),
|
||||
distances_(distances),
|
||||
compliances_(compliances),
|
||||
error_scale_(error_scale),
|
||||
lambdas_(lambdas)
|
||||
{
|
||||
}
|
||||
|
||||
void reset_force(const int constraint_i) const
|
||||
{
|
||||
lambdas_[constraint_i] = 0.0f;
|
||||
}
|
||||
|
||||
template<typename UpdaterT>
|
||||
void solve_single(const ConstraintSetParams ¶ms,
|
||||
UpdaterT &updater,
|
||||
const int constraint_i) const
|
||||
{
|
||||
const int geo_i = affected_geo_indices_[0];
|
||||
const int2 &point_pair = point_pairs_[constraint_i];
|
||||
const int point_i0 = point_pair[0];
|
||||
const int point_i1 = point_pair[1];
|
||||
const DistanceConstraintResult result = evaluate_distance_constraint(
|
||||
params.position(geo_i, point_i0),
|
||||
params.position(geo_i, point_i1),
|
||||
params.inv_mass(geo_i, point_i0),
|
||||
params.inv_mass(geo_i, point_i1),
|
||||
distances_[constraint_i],
|
||||
compliances_[constraint_i] * params.compliance_term_factor,
|
||||
lambdas_[constraint_i]);
|
||||
lambdas_[constraint_i] += result.delta_lambda;
|
||||
updater.update_position(geo_i, point_i0, result.offset0);
|
||||
updater.update_position(geo_i, point_i1, result.offset1);
|
||||
updater.add_residual_error(geo_i, result.residual_error_squared * error_scale_);
|
||||
}
|
||||
|
||||
ConstraintColoring color_constraints(LinearAllocator<> &memory) const override
|
||||
{
|
||||
return color_constraints__binary(point_pairs_, memory);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::xpbd
|
||||
@@ -0,0 +1,91 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GEO_xpbd_constraint_math.hh"
|
||||
#include "GEO_xpbd_constraint_set_templated.hh"
|
||||
|
||||
namespace blender::xpbd {
|
||||
|
||||
class FrictionEdgeConstraintSet
|
||||
: public TemplatedVelocityConstraintSet<FrictionEdgeConstraintSet> {
|
||||
private:
|
||||
int geo_i_;
|
||||
/* Constraint index for each point. */
|
||||
Span<int2> point_pairs_;
|
||||
Span<float3> separating_axes_;
|
||||
Span<float3> contact_velocities_;
|
||||
/* Constraint multiplier lambda for the normal displacement divided by time step. */
|
||||
Span<float> dynamic_friction_terms_;
|
||||
Span<float> lambdas_normal_;
|
||||
Span<float> point_mix_factors_;
|
||||
MutableSpan<float> lambdas_;
|
||||
|
||||
public:
|
||||
static constexpr StringRefNull debug_name = "Friction";
|
||||
|
||||
FrictionEdgeConstraintSet(const int geo_i,
|
||||
const Span<int2> point_pairs,
|
||||
const Span<float3> separating_axes,
|
||||
const Span<float3> contact_velocities,
|
||||
const Span<float> dynamic_friction_terms,
|
||||
const Span<float> lambdas_normal,
|
||||
const Span<float> point_mix_factors,
|
||||
MutableSpan<float> lambdas)
|
||||
: TemplatedVelocityConstraintSet<FrictionEdgeConstraintSet>(point_pairs.size(), {geo_i}),
|
||||
geo_i_(geo_i),
|
||||
point_pairs_(point_pairs),
|
||||
separating_axes_(separating_axes),
|
||||
contact_velocities_(contact_velocities),
|
||||
dynamic_friction_terms_(dynamic_friction_terms),
|
||||
lambdas_normal_(lambdas_normal),
|
||||
point_mix_factors_(point_mix_factors),
|
||||
lambdas_(lambdas)
|
||||
{
|
||||
}
|
||||
|
||||
void reset_force(const int constraint_i) const
|
||||
{
|
||||
lambdas_[constraint_i] = 0.0f;
|
||||
}
|
||||
|
||||
template<typename UpdaterT>
|
||||
void solve_single(const ConstraintSetParams ¶ms,
|
||||
UpdaterT &updater,
|
||||
const int constraint_i) const
|
||||
{
|
||||
const int2 point_pair = point_pairs_[constraint_i];
|
||||
const float segment_factor = point_mix_factors_[constraint_i];
|
||||
|
||||
/* Effective weight/mass */
|
||||
const float inv_m0 = params.inv_mass(geo_i_, point_pair[0]);
|
||||
const float inv_m1 = params.inv_mass(geo_i_, point_pair[1]);
|
||||
const float weight0 = (1.0f - segment_factor) * inv_m0;
|
||||
const float weight1 = segment_factor * inv_m1;
|
||||
/* Should be inactive if both are zero. */
|
||||
BLI_assert(weight0 > 0.0f || weight1 > 0.0f);
|
||||
|
||||
const float dynamic_friction = dynamic_friction_terms_[constraint_i] *
|
||||
params.dynamic_friction_factor;
|
||||
const float lambda_normal = lambdas_normal_[constraint_i];
|
||||
const float3 &axis = separating_axes_[constraint_i];
|
||||
const float3 &contact_velocity = contact_velocities_[constraint_i];
|
||||
const float3 &velocity = math::interpolate(params.velocity(geo_i_, point_pair[0]),
|
||||
params.velocity(geo_i_, point_pair[1]),
|
||||
segment_factor) -
|
||||
contact_velocity;
|
||||
const float3 velocity_tangent = velocity - math::dot(velocity, axis) * axis;
|
||||
float residual;
|
||||
const float3 gradient = math::normalize_and_get_length(velocity_tangent, residual);
|
||||
const float delta_lambda = std::min(dynamic_friction * lambda_normal,
|
||||
residual / (weight0 + weight1));
|
||||
|
||||
lambdas_[constraint_i] += delta_lambda;
|
||||
updater.update_velocity(geo_i_, point_pair[0], -weight0 * gradient * delta_lambda);
|
||||
updater.update_velocity(geo_i_, point_pair[1], -weight1 * gradient * delta_lambda);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::xpbd
|
||||
@@ -0,0 +1,77 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GEO_xpbd_constraint_set_templated.hh"
|
||||
|
||||
namespace blender::xpbd {
|
||||
|
||||
class FrictionFaceConstraintSet
|
||||
: public TemplatedVelocityConstraintSet<FrictionFaceConstraintSet> {
|
||||
private:
|
||||
int geo_i_;
|
||||
/* Constraint index for each point. */
|
||||
Span<int> points_;
|
||||
Span<float3> separating_axes_;
|
||||
Span<float3> contact_velocities_;
|
||||
/* Constraint multiplier lambda for the normal displacement divided by time step. */
|
||||
Span<float> dynamic_friction_terms_;
|
||||
Span<float> lambdas_normal_;
|
||||
MutableSpan<float> lambdas_;
|
||||
|
||||
public:
|
||||
static constexpr StringRefNull debug_name = "Friction";
|
||||
|
||||
FrictionFaceConstraintSet(const int geo_i,
|
||||
const Span<int> points,
|
||||
const Span<float3> separating_axes,
|
||||
const Span<float3> contact_velocities,
|
||||
const Span<float> dynamic_friction_terms,
|
||||
const Span<float> lambdas_normal,
|
||||
MutableSpan<float> lambdas)
|
||||
: TemplatedVelocityConstraintSet<FrictionFaceConstraintSet>(points.size(), {geo_i}),
|
||||
geo_i_(geo_i),
|
||||
points_(points),
|
||||
separating_axes_(separating_axes),
|
||||
contact_velocities_(contact_velocities),
|
||||
dynamic_friction_terms_(dynamic_friction_terms),
|
||||
lambdas_normal_(lambdas_normal),
|
||||
lambdas_(lambdas)
|
||||
{
|
||||
}
|
||||
|
||||
void reset_force(const int constraint_i) const
|
||||
{
|
||||
lambdas_[constraint_i] = 0.0f;
|
||||
}
|
||||
|
||||
template<typename UpdaterT>
|
||||
void solve_single(const ConstraintSetParams ¶ms,
|
||||
UpdaterT &updater,
|
||||
const int constraint_i) const
|
||||
{
|
||||
const int point_i = points_[constraint_i];
|
||||
const float inv_m = params.inv_mass(geo_i_, point_i);
|
||||
/* Should be inactive if weight is zero. */
|
||||
BLI_assert(inv_m > 0.0f);
|
||||
|
||||
const float dynamic_friction = dynamic_friction_terms_[constraint_i] *
|
||||
params.dynamic_friction_factor;
|
||||
const float lambda_normal = lambdas_normal_[constraint_i];
|
||||
const float3 &axis = separating_axes_[constraint_i];
|
||||
const float3 &contact_velocity = contact_velocities_[constraint_i];
|
||||
const float3 &velocity = params.velocity(geo_i_, point_i) - contact_velocity;
|
||||
const float3 velocity_tangent = velocity - math::dot(velocity, axis) * axis;
|
||||
float residual;
|
||||
const float3 gradient = math::normalize_and_get_length(velocity_tangent, residual);
|
||||
/* Note: lambda_normal already includes the 1/inv_m weighting factor. */
|
||||
const float delta_lambda = std::min(dynamic_friction * lambda_normal, residual / inv_m);
|
||||
|
||||
lambdas_[constraint_i] += delta_lambda;
|
||||
updater.update_velocity(geo_i_, point_i, -gradient * inv_m * delta_lambda);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::xpbd
|
||||
@@ -0,0 +1,104 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_math_vector.hh"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace blender::xpbd {
|
||||
|
||||
struct PlaneIntersection {
|
||||
/* Intersection point. */
|
||||
float3 position;
|
||||
/* Position of the intersection relative to segment points. */
|
||||
float segment_lambda;
|
||||
};
|
||||
|
||||
/**
|
||||
* Test intersection of a line segment with a plane defined by two tangent vectors.
|
||||
* \param pos0: First point of the line segment.
|
||||
* \param pos1: Second point of the line segment.
|
||||
* \param origin: Point on the half-plane edge.
|
||||
* \param edge: Edge of the half-plane.
|
||||
* \param normal: Direction away from the half-plane.
|
||||
* \return Intersection if the line segment intersects the plane.
|
||||
*/
|
||||
inline std::optional<PlaneIntersection> intersect_plane(const float3 &pos0,
|
||||
const float3 &pos1,
|
||||
const float3 &origin,
|
||||
const float3 &edge,
|
||||
const float3 &normal)
|
||||
{
|
||||
BLI_assert(math::is_unit(edge));
|
||||
BLI_assert(math::is_unit(normal));
|
||||
|
||||
const float3 tangent = math::cross(edge, normal);
|
||||
BLI_assert(math::is_unit(tangent));
|
||||
|
||||
const float len = math::dot(tangent, pos0 - pos1);
|
||||
if (math::abs(len) < 1e-9) {
|
||||
/* Segment is parallel to the plane or too short. */
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const float dist0 = math::dot(tangent, pos0 - origin);
|
||||
const float lambda = dist0 / len;
|
||||
if (lambda < 0.0f || lambda > 1.0f) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const float3 position = math::interpolate(pos0, pos1, lambda);
|
||||
return PlaneIntersection{position, lambda};
|
||||
}
|
||||
|
||||
struct SegmentClosestToRay {
|
||||
/* Position relative to segment points. */
|
||||
float segment_lambda;
|
||||
/* Distance of the closest point along the ray, unclamped. */
|
||||
float ray_lambda;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find closest point of a segment to a ray.
|
||||
* \param pos0: First point of the line segment.
|
||||
* \param pos1: Second point of the line segment.
|
||||
* \param ray_pos: Origin of the ray.
|
||||
* \param ray_dir: Direction of the ray.
|
||||
* \return Closest point on the segment or null if the closest point is outside the segment.
|
||||
*/
|
||||
inline SegmentClosestToRay closest_on_segment_to_ray(const float3 &pos0,
|
||||
const float3 &pos1,
|
||||
const float3 &ray_pos,
|
||||
const float3 &ray_dir,
|
||||
const bool clamp)
|
||||
{
|
||||
BLI_assert(math::is_unit(ray_dir));
|
||||
|
||||
const float3 segment = pos1 - pos0;
|
||||
const float3 dist0 = pos0 - ray_pos;
|
||||
const float a = math::dot(segment, dist0);
|
||||
const float b = math::dot(ray_dir, dist0);
|
||||
const float c = math::dot(segment, ray_dir);
|
||||
|
||||
const float len_segment_sq = math::length_squared(segment);
|
||||
if (UNLIKELY(len_segment_sq <= 1e-9)) {
|
||||
return SegmentClosestToRay{0.0f, b};
|
||||
}
|
||||
|
||||
float segment_lambda;
|
||||
if (clamp) {
|
||||
segment_lambda = math::clamp(math::safe_divide(c * b - a, len_segment_sq - c * c), 0.0f, 1.0f);
|
||||
}
|
||||
else {
|
||||
segment_lambda = math::safe_divide(c * b - a, len_segment_sq - c * c);
|
||||
}
|
||||
|
||||
const float ray_lambda = c * segment_lambda + b;
|
||||
|
||||
return SegmentClosestToRay{segment_lambda, ray_lambda};
|
||||
}
|
||||
|
||||
} // namespace blender::xpbd
|
||||
@@ -0,0 +1,72 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GEO_xpbd_constraint_coloring_utils.hh"
|
||||
#include "GEO_xpbd_constraint_distance.hh"
|
||||
#include "GEO_xpbd_constraint_set_templated.hh"
|
||||
|
||||
namespace blender::xpbd {
|
||||
|
||||
class PinPositionConstraintSet : public TemplatedConstraintSet<PinPositionConstraintSet> {
|
||||
private:
|
||||
/** Indexed by constraint index. */
|
||||
Span<int> point_indices_;
|
||||
Span<float3> pin_positions_;
|
||||
Span<float> compliances_;
|
||||
/* Scale factor for residual error. */
|
||||
float error_scale_;
|
||||
MutableSpan<float> lambdas_;
|
||||
|
||||
public:
|
||||
static constexpr StringRefNull debug_name = "Pinned Position";
|
||||
|
||||
PinPositionConstraintSet(const int geo_i,
|
||||
const Span<int> point_indices,
|
||||
const Span<float3> pin_positions,
|
||||
const Span<float> compliances,
|
||||
const float error_scale,
|
||||
const MutableSpan<float> lambdas)
|
||||
: TemplatedConstraintSet<PinPositionConstraintSet>(point_indices.size(), {geo_i}),
|
||||
point_indices_(point_indices),
|
||||
pin_positions_(pin_positions),
|
||||
compliances_(compliances),
|
||||
error_scale_(error_scale),
|
||||
lambdas_(lambdas)
|
||||
{
|
||||
}
|
||||
|
||||
void reset_force(const int constraint_i) const
|
||||
{
|
||||
lambdas_[constraint_i] = 0.0f;
|
||||
}
|
||||
|
||||
template<typename UpdaterT>
|
||||
void solve_single(const ConstraintSetParams ¶ms,
|
||||
UpdaterT &updater,
|
||||
const int constraint_i) const
|
||||
{
|
||||
const int geo_i = affected_geo_indices_[0];
|
||||
const int point_i = point_indices_[constraint_i];
|
||||
const DistanceConstraintResult result = evaluate_distance_constraint(
|
||||
params.position(geo_i, point_i),
|
||||
pin_positions_[constraint_i],
|
||||
params.inv_mass(geo_i, point_i),
|
||||
0.0f,
|
||||
0.0f,
|
||||
compliances_[constraint_i] * params.compliance_term_factor,
|
||||
lambdas_[constraint_i]);
|
||||
lambdas_[constraint_i] += result.delta_lambda;
|
||||
updater.update_position(geo_i, point_i, result.offset0);
|
||||
updater.add_residual_error(geo_i, result.residual_error_squared * error_scale_);
|
||||
}
|
||||
|
||||
ConstraintColoring color_constraints(LinearAllocator<> &memory) const override
|
||||
{
|
||||
return color_constraints__unary(point_indices_, memory);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::xpbd
|
||||
@@ -0,0 +1,72 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GEO_xpbd_constraint_align_rotations.hh"
|
||||
#include "GEO_xpbd_constraint_coloring_utils.hh"
|
||||
#include "GEO_xpbd_constraint_set_templated.hh"
|
||||
|
||||
namespace blender::xpbd {
|
||||
|
||||
class PinRotationConstraintSet : public TemplatedConstraintSet<PinRotationConstraintSet> {
|
||||
private:
|
||||
/** Indexed by constraint index. */
|
||||
Span<float> compliances_;
|
||||
Span<int> point_indices_;
|
||||
Span<math::Quaternion> pin_rotations_;
|
||||
/* Scale factor for residual error. */
|
||||
float error_scale_;
|
||||
MutableSpan<float4> lambdas_;
|
||||
|
||||
public:
|
||||
static constexpr StringRefNull debug_name = "Pin Rotation";
|
||||
|
||||
PinRotationConstraintSet(const int geo_i,
|
||||
const Span<int> point_indices,
|
||||
const Span<math::Quaternion> pin_rotations,
|
||||
const Span<float> compliances,
|
||||
const float error_scale,
|
||||
MutableSpan<float4> lambdas)
|
||||
: TemplatedConstraintSet<PinRotationConstraintSet>(point_indices.size(), {geo_i}),
|
||||
compliances_(compliances),
|
||||
point_indices_(point_indices),
|
||||
pin_rotations_(pin_rotations),
|
||||
error_scale_(error_scale),
|
||||
lambdas_(lambdas)
|
||||
{
|
||||
}
|
||||
|
||||
void reset_force(const int constraint_i) const
|
||||
{
|
||||
lambdas_[constraint_i] = float4(0.0f);
|
||||
}
|
||||
|
||||
template<typename UpdaterT>
|
||||
void solve_single(const ConstraintSetParams ¶ms,
|
||||
UpdaterT &updater,
|
||||
const int constraint_i) const
|
||||
{
|
||||
const int geo_i = affected_geo_indices_[0];
|
||||
const int point_i = point_indices_[constraint_i];
|
||||
const AlignRotationsConstraintResult result = evaluate_align_rotations_constraint(
|
||||
params.rotation(geo_i, point_i),
|
||||
pin_rotations_[constraint_i],
|
||||
params.moment_of_inertia(geo_i, point_i),
|
||||
float3(std::numeric_limits<float>::infinity()),
|
||||
math::Quaternion::identity(),
|
||||
compliances_[constraint_i] * params.compliance_term_factor,
|
||||
lambdas_[constraint_i]);
|
||||
lambdas_[constraint_i] += result.delta_lambda;
|
||||
updater.update_rotation(geo_i, point_i, result.offset0);
|
||||
updater.add_residual_error(geo_i, result.residual_error_squared * error_scale_);
|
||||
}
|
||||
|
||||
ConstraintColoring color_constraints(LinearAllocator<> &memory) const override
|
||||
{
|
||||
return color_constraints__unary(point_indices_, memory);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::xpbd
|
||||
@@ -0,0 +1,95 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GEO_xpbd_constraint_align_rotations.hh"
|
||||
#include "GEO_xpbd_constraint_coloring_utils.hh"
|
||||
#include "GEO_xpbd_constraint_set_templated.hh"
|
||||
|
||||
namespace blender::xpbd {
|
||||
|
||||
/** Aligns rotations of two consecutive rods based on a rest rotation. */
|
||||
class RodBendAndTwistConstraintSet : public TemplatedConstraintSet<RodBendAndTwistConstraintSet> {
|
||||
private:
|
||||
/** Curves that are effected by this constraint set. Each curve is seen as one constraint. */
|
||||
IndexRange curves_range_;
|
||||
OffsetIndices<int> points_by_curve_;
|
||||
|
||||
/** Indexed by point index. */
|
||||
Span<math::Quaternion> rest_rotations_;
|
||||
|
||||
/** Indexed by `point_i - first_point_i_in_constraint_set`. */
|
||||
Span<float> compliances_;
|
||||
|
||||
/* Scale factor for residual error. */
|
||||
float error_scale_;
|
||||
|
||||
MutableSpan<float4> lambdas_;
|
||||
|
||||
public:
|
||||
static constexpr StringRefNull debug_name = "Rod Bend and Twist";
|
||||
|
||||
RodBendAndTwistConstraintSet(const int geo_i,
|
||||
const IndexRange curves_range,
|
||||
const OffsetIndices<int> points_by_curve,
|
||||
const Span<math::Quaternion> rest_rotations,
|
||||
const Span<float> compliances,
|
||||
const float error_scale,
|
||||
MutableSpan<float4> lambdas)
|
||||
: TemplatedConstraintSet<RodBendAndTwistConstraintSet>(curves_range.size(), {geo_i}),
|
||||
curves_range_(curves_range),
|
||||
points_by_curve_(points_by_curve),
|
||||
rest_rotations_(rest_rotations),
|
||||
compliances_(compliances),
|
||||
error_scale_(error_scale),
|
||||
lambdas_(lambdas)
|
||||
{
|
||||
}
|
||||
|
||||
void reset_force(const int constraint_i) const
|
||||
{
|
||||
const int curve_i = curves_range_[constraint_i];
|
||||
const IndexRange points = points_by_curve_[curve_i];
|
||||
lambdas_.slice(points).fill(float4(0.0f));
|
||||
}
|
||||
|
||||
template<typename UpdaterT>
|
||||
void solve_single(const ConstraintSetParams ¶ms,
|
||||
UpdaterT &updater,
|
||||
const int constraint_i) const
|
||||
{
|
||||
const int curve_i = curves_range_[constraint_i];
|
||||
const IndexRange points = points_by_curve_[curve_i];
|
||||
const int geo_i = affected_geo_indices_[0];
|
||||
const int first_point_i_in_constraint_set = points_by_curve_[curves_range_.first()].first();
|
||||
|
||||
/* Could implement bilateral interleaving ordering for better stability. */
|
||||
/* Note that the last segment does not have this constraint, because the rotation of the last
|
||||
* point in the rod is meaningless.*/
|
||||
for (const int point_i0 : points.drop_back(2)) {
|
||||
const int point_i1 = point_i0 + 1;
|
||||
const float compliance = compliances_[point_i0 - first_point_i_in_constraint_set];
|
||||
const AlignRotationsConstraintResult result = evaluate_align_rotations_constraint(
|
||||
params.rotation(geo_i, point_i0),
|
||||
params.rotation(geo_i, point_i1),
|
||||
params.moment_of_inertia(geo_i, point_i0),
|
||||
params.moment_of_inertia(geo_i, point_i1),
|
||||
rest_rotations_[point_i0],
|
||||
compliance * params.compliance_term_factor,
|
||||
lambdas_[point_i0]);
|
||||
lambdas_[point_i0] += result.delta_lambda;
|
||||
updater.update_rotation(geo_i, point_i0, result.offset0);
|
||||
updater.update_rotation(geo_i, point_i1, result.offset1);
|
||||
updater.add_residual_error(geo_i, result.residual_error_squared * error_scale_);
|
||||
}
|
||||
}
|
||||
|
||||
ConstraintColoring color_constraints(LinearAllocator<> & /*memory*/) const override
|
||||
{
|
||||
return color_constraints__all_independent(constraints_num_);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::xpbd
|
||||
@@ -0,0 +1,175 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_math_base.h"
|
||||
|
||||
#include "GEO_xpbd_constraint_coloring_utils.hh"
|
||||
#include "GEO_xpbd_constraint_set_templated.hh"
|
||||
|
||||
namespace blender::xpbd {
|
||||
|
||||
struct RodStretchAndShearConstraintResult {
|
||||
float3 delta_lambda_pos;
|
||||
float3 delta_lambda_rot;
|
||||
float3 offset0 = float3(0.0f);
|
||||
float3 offset1 = float3(0.0f);
|
||||
math::Quaternion offset_rot = math::Quaternion(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
float residual_error_squared = 0.0f;
|
||||
};
|
||||
|
||||
inline RodStretchAndShearConstraintResult evaluate_rod_stretch_and_shear_constraint(
|
||||
const float3 &p0,
|
||||
const float3 &p1,
|
||||
const math::Quaternion &rot,
|
||||
const float inv_m0,
|
||||
const float inv_m1,
|
||||
const float3 &inertia,
|
||||
const float rest_length,
|
||||
const float compliance_term,
|
||||
const float3 &lambda_pos_prev,
|
||||
const float3 &lambda_rot_prev)
|
||||
{
|
||||
/* Lumped weight for the rotation influence. The higher the inertia, the lower the change of
|
||||
* the rotation should be compared to the change in point positions. */
|
||||
const float inv_lumped_inertia = math::safe_rcp(0.5f * (inertia.x + inertia.y + inertia.z));
|
||||
|
||||
if (inv_m0 == 0.0f && inv_m1 == 0.0f && inv_lumped_inertia == 0.0f) {
|
||||
/* Everything is pinned, so the constraint can't do anything. */
|
||||
return {};
|
||||
}
|
||||
|
||||
/* TODO The positional and rotational parts use different residuals to avoid errors when the
|
||||
* current segment length deviates too much from the rest length. The rotational offset uses
|
||||
* the residual as the angle of rotation which becomes larger with stretching. To avoid
|
||||
* instabilities the rotation residual is computed relative to the current length.
|
||||
* This should be cleaned up and optimized if possible. */
|
||||
|
||||
/* Current non-normalized tangent of the rod. */
|
||||
const float3 p_diff = p1 - p0;
|
||||
const float p_len = math::length(p_diff);
|
||||
/* Expected non-normalized tangent of the rod based on the rotation. */
|
||||
const float3 forward_rest = math::transform_point(rot, float3(0.0f, 0.0f, rest_length));
|
||||
const float3 forward = math::transform_point(rot, float3(0.0f, 0.0f, p_len));
|
||||
/* How much the rod is stretched and sheared. */
|
||||
const float3 residual_pos = p_diff - forward_rest;
|
||||
const float3 residual_rot = p_diff - forward;
|
||||
|
||||
const float error_squared = math::length_squared(residual_pos +
|
||||
compliance_term * lambda_pos_prev);
|
||||
|
||||
/* Based on "Position and Orientation Based Cosserat Rods" (Kugelstadt, Schömer, 2016). */
|
||||
const float weight_sum = inv_m0 + inv_m1 + 4.0f * inv_lumped_inertia * pow2f(rest_length);
|
||||
const float weight_sum_rot = inv_m0 + inv_m1 + 4.0f * inv_lumped_inertia * pow2f(p_len);
|
||||
const float3 delta_lambda_pos = (-residual_pos - compliance_term * lambda_pos_prev) /
|
||||
(weight_sum + compliance_term);
|
||||
const float3 delta_lambda_rot = (-residual_rot - compliance_term * lambda_rot_prev) /
|
||||
(weight_sum_rot + compliance_term);
|
||||
|
||||
RodStretchAndShearConstraintResult result;
|
||||
result.delta_lambda_pos = delta_lambda_pos;
|
||||
result.delta_lambda_rot = delta_lambda_rot;
|
||||
result.offset0 = -delta_lambda_pos * inv_m0;
|
||||
result.offset1 = delta_lambda_pos * inv_m1;
|
||||
result.offset_rot = math::Quaternion(0.0f, -delta_lambda_rot * inv_lumped_inertia * p_len) *
|
||||
rot * math::Quaternion(0, 0, 0, -1);
|
||||
result.residual_error_squared = error_squared;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Considers a single rod at a time. Tries to enforce that the rotation of the rod is aligned with
|
||||
* the actual tangent of the rod. If it is misaligned, it moves the start position, end position
|
||||
* and rotation of the frame. At the same time, it enforces a certain length.
|
||||
*/
|
||||
class RodStretchAndShearConstraintSet
|
||||
: public TemplatedConstraintSet<RodStretchAndShearConstraintSet> {
|
||||
private:
|
||||
/** Curves that are effected by this constraint set. Each curve is seen as one constraint. */
|
||||
IndexRange curves_range_;
|
||||
OffsetIndices<int> points_by_curve_;
|
||||
|
||||
/** Indexed by segment-end point index. */
|
||||
Span<float> rest_lengths_;
|
||||
MutableSpan<float3> lambdas_pos_;
|
||||
MutableSpan<float3> lambdas_rot_;
|
||||
|
||||
/** Indexed by `point_i - first_point_i_in_constraint_set`. */
|
||||
Span<float> compliances_;
|
||||
|
||||
/* Scale factor for residual error. */
|
||||
float error_scale_;
|
||||
|
||||
public:
|
||||
static constexpr StringRefNull debug_name = "Rod Stretch and Shear";
|
||||
|
||||
RodStretchAndShearConstraintSet(const int geo_i,
|
||||
const IndexRange curves_range,
|
||||
const OffsetIndices<int> points_by_curve,
|
||||
const Span<float> rest_lengths,
|
||||
const Span<float> compliances,
|
||||
const float error_scale,
|
||||
MutableSpan<float3> lambdas_pos,
|
||||
MutableSpan<float3> lambdas_rot)
|
||||
: TemplatedConstraintSet<RodStretchAndShearConstraintSet>(curves_range.size(), {geo_i}),
|
||||
curves_range_(curves_range),
|
||||
points_by_curve_(points_by_curve),
|
||||
rest_lengths_(rest_lengths),
|
||||
lambdas_pos_(lambdas_pos),
|
||||
lambdas_rot_(lambdas_rot),
|
||||
compliances_(compliances),
|
||||
error_scale_(error_scale)
|
||||
{
|
||||
}
|
||||
|
||||
void reset_force(const int constraint_i) const
|
||||
{
|
||||
const int curve_i = curves_range_[constraint_i];
|
||||
const IndexRange points = points_by_curve_[curve_i];
|
||||
lambdas_pos_.slice(points).fill(float3(0.0f));
|
||||
lambdas_rot_.slice(points).fill(float3(0.0f));
|
||||
}
|
||||
|
||||
template<typename UpdaterT>
|
||||
void solve_single(const ConstraintSetParams ¶ms,
|
||||
UpdaterT &updater,
|
||||
const int constraint_i) const
|
||||
{
|
||||
const int curve_i = curves_range_[constraint_i];
|
||||
const IndexRange points = points_by_curve_[curve_i];
|
||||
const int geo_i = affected_geo_indices_[0];
|
||||
const int first_point_i_in_constraint_set = points_by_curve_[curves_range_.first()].first();
|
||||
|
||||
/* Could try implementing bilateral interleaving ordering for better stability. */
|
||||
for (const int point_i0 : points.drop_back(1)) {
|
||||
const int point_i1 = point_i0 + 1;
|
||||
const float compliance = compliances_[point_i0 - first_point_i_in_constraint_set];
|
||||
const RodStretchAndShearConstraintResult result = evaluate_rod_stretch_and_shear_constraint(
|
||||
params.position(geo_i, point_i0),
|
||||
params.position(geo_i, point_i1),
|
||||
params.rotation(geo_i, point_i0),
|
||||
params.inv_mass(geo_i, point_i0),
|
||||
params.inv_mass(geo_i, point_i1),
|
||||
params.moment_of_inertia(geo_i, point_i0),
|
||||
rest_lengths_[point_i1],
|
||||
compliance * params.compliance_term_factor,
|
||||
lambdas_pos_[point_i1],
|
||||
lambdas_rot_[point_i1]);
|
||||
lambdas_pos_[point_i1] += result.delta_lambda_pos;
|
||||
lambdas_rot_[point_i1] += result.delta_lambda_rot;
|
||||
updater.update_position(geo_i, point_i0, result.offset0);
|
||||
updater.update_position(geo_i, point_i1, result.offset1);
|
||||
updater.update_rotation(geo_i, point_i0, result.offset_rot);
|
||||
updater.add_residual_error(geo_i, result.residual_error_squared * error_scale_);
|
||||
}
|
||||
}
|
||||
|
||||
ConstraintColoring color_constraints(LinearAllocator<> & /*memory*/) const override
|
||||
{
|
||||
return color_constraints__all_independent(constraints_num_);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::xpbd
|
||||
@@ -0,0 +1,77 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_index_mask.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "GEO_xpbd_constraint_coloring.hh"
|
||||
#include "GEO_xpbd_constraint_set_params.hh"
|
||||
#include "GEO_xpbd_updater_gauss_seidel.hh"
|
||||
#include "GEO_xpbd_updater_velocity.hh"
|
||||
|
||||
namespace blender::xpbd {
|
||||
|
||||
/**
|
||||
* Base class for constraint evaluators. It evaluate a batch of constraints and writes back the
|
||||
* results using a passed in "updater".
|
||||
*
|
||||
* Use #TemplatedConstraintSet to instantiate the constraint evaluation for each updater
|
||||
* automatically. This avoids having to implement separate Jacobian and Gauss Seidel code paths for
|
||||
* such constraints.
|
||||
*/
|
||||
class ConstraintSet {
|
||||
protected:
|
||||
int constraints_num_;
|
||||
Vector<int> affected_geo_indices_;
|
||||
|
||||
public:
|
||||
ConstraintSet(int constraints_num, Vector<int> affected_geo_indices)
|
||||
: constraints_num_(constraints_num), affected_geo_indices_(std::move(affected_geo_indices))
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~ConstraintSet() = default;
|
||||
|
||||
virtual StringRefNull debug_name() const = 0;
|
||||
virtual void solve_sequential(const ConstraintSetParams ¶ms,
|
||||
GaussSeidelUpdater &updater,
|
||||
const IndexMask &mask) = 0;
|
||||
virtual void reset_forces() = 0;
|
||||
virtual ConstraintColoring color_constraints(LinearAllocator<> &memory) const = 0;
|
||||
|
||||
void solve_sequential_all(const ConstraintSetParams ¶ms, GaussSeidelUpdater &updater)
|
||||
{
|
||||
this->solve_sequential(params, updater, IndexMask(constraints_num_));
|
||||
}
|
||||
|
||||
Span<int> get_affected_geo_indices() const
|
||||
{
|
||||
return affected_geo_indices_;
|
||||
}
|
||||
};
|
||||
|
||||
class VelocityConstraintSet {
|
||||
protected:
|
||||
Vector<int> affected_geo_indices_;
|
||||
|
||||
public:
|
||||
VelocityConstraintSet(Vector<int> affected_geo_indices)
|
||||
: affected_geo_indices_(std::move(affected_geo_indices))
|
||||
{
|
||||
}
|
||||
virtual ~VelocityConstraintSet() = default;
|
||||
|
||||
virtual void reset_forces() = 0;
|
||||
virtual void solve_sequential(const ConstraintSetParams ¶ms, VelocityUpdater &updater) = 0;
|
||||
|
||||
Span<int> get_affected_geo_indices() const
|
||||
{
|
||||
return affected_geo_indices_;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::xpbd
|
||||
@@ -0,0 +1,166 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GEO_xpbd_geometry_ref.hh"
|
||||
|
||||
namespace blender::xpbd {
|
||||
|
||||
/** Provides access to the input data that should be considered by a constraint. */
|
||||
class ConstraintSetParams {
|
||||
private:
|
||||
Span<GeometryRef> geometry_refs_;
|
||||
|
||||
public:
|
||||
float delta_time;
|
||||
float compliance_term_factor;
|
||||
float dynamic_friction_factor;
|
||||
|
||||
ConstraintSetParams(Span<GeometryRef> geometry_refs, float delta_time);
|
||||
|
||||
Span<GeometryRef> geometry_refs() const;
|
||||
|
||||
Span<float3> positions(int geo_i) const;
|
||||
const float3 &position(int geo_i, int point_i) const;
|
||||
|
||||
Span<math::Quaternion> rotations(int geo_i) const;
|
||||
const math::Quaternion &rotation(int geo_i, int point_i) const;
|
||||
|
||||
Span<float3> prev_positions(int geo_i) const;
|
||||
const float3 &prev_position(int geo_i, int point_i) const;
|
||||
|
||||
Span<math::Quaternion> prev_rotations(int geo_i) const;
|
||||
const math::Quaternion &prev_rotation(int geo_i, int point_i) const;
|
||||
|
||||
Span<float3> velocities(int geo_i) const;
|
||||
const float3 &velocity(int geo_i, int point_i) const;
|
||||
|
||||
Span<float3> angular_velocities(int geo_i) const;
|
||||
const float3 &angular_velocity(int geo_i, int point_i) const;
|
||||
|
||||
Span<float> inv_masses(int geo_i) const;
|
||||
float inv_mass(int geo_i, int point_i) const;
|
||||
|
||||
Span<float3> moments_of_inertia(int geo_i) const;
|
||||
float3 moment_of_inertia(int geo_i, int point_i) const;
|
||||
|
||||
Span<float3> inv_moments_of_inertia(int geo_i) const;
|
||||
float3 inv_moment_of_inertia(int geo_i, int point_i) const;
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Inline Functions
|
||||
* \{ */
|
||||
|
||||
inline ConstraintSetParams::ConstraintSetParams(Span<GeometryRef> geometry_refs,
|
||||
const float delta_time)
|
||||
: geometry_refs_(geometry_refs),
|
||||
delta_time(delta_time),
|
||||
compliance_term_factor(math::safe_rcp(delta_time * delta_time)),
|
||||
dynamic_friction_factor(math::safe_rcp(delta_time))
|
||||
{
|
||||
}
|
||||
|
||||
inline Span<GeometryRef> ConstraintSetParams::geometry_refs() const
|
||||
{
|
||||
return geometry_refs_;
|
||||
}
|
||||
|
||||
inline const float3 &ConstraintSetParams::position(const int geo_i, const int point_i) const
|
||||
{
|
||||
return geometry_refs_[geo_i].positions[point_i];
|
||||
}
|
||||
|
||||
inline const math::Quaternion &ConstraintSetParams::rotation(const int geo_i,
|
||||
const int point_i) const
|
||||
{
|
||||
return geometry_refs_[geo_i].rotations[point_i];
|
||||
}
|
||||
|
||||
inline const float3 &ConstraintSetParams::prev_position(const int geo_i, const int point_i) const
|
||||
{
|
||||
return geometry_refs_[geo_i].prev_positions[point_i];
|
||||
}
|
||||
|
||||
inline const math::Quaternion &ConstraintSetParams::prev_rotation(const int geo_i,
|
||||
const int point_i) const
|
||||
{
|
||||
return geometry_refs_[geo_i].prev_rotations[point_i];
|
||||
}
|
||||
|
||||
inline const float3 &ConstraintSetParams::velocity(const int geo_i, const int point_i) const
|
||||
{
|
||||
return geometry_refs_[geo_i].velocities[point_i];
|
||||
}
|
||||
|
||||
inline const float3 &ConstraintSetParams::angular_velocity(const int geo_i,
|
||||
const int point_i) const
|
||||
{
|
||||
return geometry_refs_[geo_i].angular_velocities[point_i];
|
||||
}
|
||||
|
||||
inline Span<float3> ConstraintSetParams::positions(const int geo_i) const
|
||||
{
|
||||
return geometry_refs_[geo_i].positions;
|
||||
}
|
||||
|
||||
inline Span<math::Quaternion> ConstraintSetParams::rotations(const int geo_i) const
|
||||
{
|
||||
return geometry_refs_[geo_i].rotations;
|
||||
}
|
||||
|
||||
inline Span<float3> ConstraintSetParams::prev_positions(const int geo_i) const
|
||||
{
|
||||
return geometry_refs_[geo_i].prev_positions;
|
||||
}
|
||||
|
||||
inline Span<math::Quaternion> ConstraintSetParams::prev_rotations(const int geo_i) const
|
||||
{
|
||||
return geometry_refs_[geo_i].prev_rotations;
|
||||
}
|
||||
|
||||
inline Span<float3> ConstraintSetParams::velocities(const int geo_i) const
|
||||
{
|
||||
return geometry_refs_[geo_i].velocities;
|
||||
}
|
||||
|
||||
inline Span<float3> ConstraintSetParams::angular_velocities(const int geo_i) const
|
||||
{
|
||||
return geometry_refs_[geo_i].angular_velocities;
|
||||
}
|
||||
|
||||
inline float ConstraintSetParams::inv_mass(const int geo_i, const int point_i) const
|
||||
{
|
||||
return geometry_refs_[geo_i].inv_masses[point_i];
|
||||
}
|
||||
|
||||
inline Span<float> ConstraintSetParams::inv_masses(const int geo_i) const
|
||||
{
|
||||
return geometry_refs_[geo_i].inv_masses;
|
||||
}
|
||||
|
||||
inline float3 ConstraintSetParams::moment_of_inertia(const int geo_i, const int point_i) const
|
||||
{
|
||||
return geometry_refs_[geo_i].moments_of_inertia[point_i];
|
||||
}
|
||||
|
||||
inline Span<float3> ConstraintSetParams::moments_of_inertia(const int geo_i) const
|
||||
{
|
||||
return geometry_refs_[geo_i].moments_of_inertia;
|
||||
}
|
||||
|
||||
inline float3 ConstraintSetParams::inv_moment_of_inertia(const int geo_i, const int point_i) const
|
||||
{
|
||||
return geometry_refs_[geo_i].inv_moments_of_inertia[point_i];
|
||||
}
|
||||
|
||||
inline Span<float3> ConstraintSetParams::inv_moments_of_inertia(const int geo_i) const
|
||||
{
|
||||
return geometry_refs_[geo_i].inv_moments_of_inertia;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::xpbd
|
||||
@@ -0,0 +1,79 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GEO_xpbd_constraint_set.hh"
|
||||
|
||||
namespace blender::xpbd {
|
||||
|
||||
/**
|
||||
* Utility to implement a constraint evaluator that automatically works with multiple updaters like
|
||||
* #GaussSeidelUpdater.
|
||||
*
|
||||
* Child classes have to implement the templated #solve_single method.
|
||||
*/
|
||||
template<typename Child> class TemplatedConstraintSet : public ConstraintSet {
|
||||
public:
|
||||
TemplatedConstraintSet(const int constraints_num, Vector<int> affected_geo_indices)
|
||||
: ConstraintSet(constraints_num, std::move(affected_geo_indices))
|
||||
{
|
||||
}
|
||||
|
||||
void reset_forces() override
|
||||
{
|
||||
const Child &self = static_cast<const Child &>(*this);
|
||||
for (const int constraint_i : IndexRange(constraints_num_)) {
|
||||
self.reset_force(constraint_i);
|
||||
}
|
||||
}
|
||||
|
||||
void solve_sequential(const ConstraintSetParams ¶ms,
|
||||
GaussSeidelUpdater &updater,
|
||||
const IndexMask &mask) override
|
||||
{
|
||||
const Child &self = static_cast<const Child &>(*this);
|
||||
mask.foreach_index(
|
||||
[&](const int64_t constraint_i) { self.solve_single(params, updater, constraint_i); });
|
||||
}
|
||||
|
||||
StringRefNull debug_name() const final
|
||||
{
|
||||
return Child::debug_name;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Child> class TemplatedVelocityConstraintSet : public VelocityConstraintSet {
|
||||
protected:
|
||||
const int constraint_num_;
|
||||
|
||||
public:
|
||||
TemplatedVelocityConstraintSet(int constraints_num, Vector<int> affected_geo_indices)
|
||||
: VelocityConstraintSet(std::move(affected_geo_indices)), constraint_num_(constraints_num)
|
||||
{
|
||||
}
|
||||
|
||||
void reset_forces() override
|
||||
{
|
||||
Child &self = static_cast<Child &>(*this);
|
||||
for (const int constraint_i : IndexRange(constraint_num_)) {
|
||||
self.reset_force(constraint_i);
|
||||
}
|
||||
}
|
||||
|
||||
void solve_sequential(const ConstraintSetParams ¶ms, VelocityUpdater &updater) override
|
||||
{
|
||||
Child &self = static_cast<Child &>(*this);
|
||||
for (const int constraint_i : IndexRange(constraint_num_)) {
|
||||
self.solve_single(params, updater, constraint_i);
|
||||
}
|
||||
}
|
||||
|
||||
StringRef debug_name() const
|
||||
{
|
||||
return Child::debug_name;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::xpbd
|
||||
@@ -0,0 +1,43 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_math_quaternion_types.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_span.hh"
|
||||
|
||||
namespace blender::xpbd {
|
||||
|
||||
/**
|
||||
* References to the data of a geometry that is being simulated.
|
||||
*/
|
||||
struct GeometryRef {
|
||||
/** The position of each point. */
|
||||
MutableSpan<float3> positions;
|
||||
/** The linear velocity of each point. */
|
||||
MutableSpan<float3> velocities;
|
||||
/** Positions before time integration, at the beginning of the current substep. */
|
||||
Span<float3> prev_positions;
|
||||
/** Inverse mass of each point. */
|
||||
Span<float> inv_masses;
|
||||
|
||||
/** Optional rotation data. */
|
||||
MutableSpan<math::Quaternion> rotations;
|
||||
/** Optional angular_velocity data. */
|
||||
MutableSpan<float3> angular_velocities;
|
||||
/** Rotations before time integration. */
|
||||
Span<math::Quaternion> prev_rotations;
|
||||
/** Optional moment of inertia of each point. */
|
||||
Span<float3> moments_of_inertia;
|
||||
/** Inverse of the above. */
|
||||
Span<float3> inv_moments_of_inertia;
|
||||
|
||||
uint64_t size() const
|
||||
{
|
||||
return this->positions.size();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::xpbd
|
||||
@@ -0,0 +1,60 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_math_quaternion.hh"
|
||||
|
||||
#include "GEO_xpbd_geometry_ref.hh"
|
||||
|
||||
namespace blender::xpbd {
|
||||
|
||||
inline math::Quaternion apply_rotation_offset(math::Quaternion rotation, float4 offset)
|
||||
{
|
||||
return math::normalize(math::Quaternion(float4(rotation) + offset));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updater that writes the changes directly to the simulated points.
|
||||
*/
|
||||
class GaussSeidelUpdater {
|
||||
private:
|
||||
Span<GeometryRef> geometry_refs_;
|
||||
float total_error_squared_;
|
||||
int total_error_count_;
|
||||
|
||||
public:
|
||||
GaussSeidelUpdater(Span<GeometryRef> geometry_refs)
|
||||
: geometry_refs_(geometry_refs), total_error_squared_(0.0f), total_error_count_(0.0f)
|
||||
{
|
||||
}
|
||||
|
||||
float total_error_squared() const
|
||||
{
|
||||
return total_error_squared_;
|
||||
}
|
||||
int total_error_count() const
|
||||
{
|
||||
return total_error_count_;
|
||||
}
|
||||
|
||||
void update_position(const int geo_i, const int point_i, const float3 &offset)
|
||||
{
|
||||
geometry_refs_[geo_i].positions[point_i] += offset;
|
||||
}
|
||||
void update_rotation(const int geo_i, const int point_i, const math::Quaternion &offset)
|
||||
{
|
||||
math::Quaternion &rotation = geometry_refs_[geo_i].rotations[point_i];
|
||||
rotation = apply_rotation_offset(rotation, float4(offset));
|
||||
}
|
||||
void add_residual_error(const int /*geo_i*/, const float error_squared)
|
||||
{
|
||||
/* Geometry index is ignored for now for simplicity. We could record a separate error for each
|
||||
* geometry. */
|
||||
total_error_squared_ += error_squared;
|
||||
++total_error_count_;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::xpbd
|
||||
@@ -0,0 +1,32 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GEO_xpbd_geometry_ref.hh"
|
||||
|
||||
namespace blender::xpbd {
|
||||
|
||||
/**
|
||||
* Updater that writes the changes directly to the simulated points.
|
||||
*/
|
||||
class VelocityUpdater {
|
||||
private:
|
||||
Span<GeometryRef> geometry_refs_;
|
||||
|
||||
public:
|
||||
VelocityUpdater(const Span<GeometryRef> geometry_refs) : geometry_refs_(geometry_refs) {}
|
||||
|
||||
void update_velocity(const int geo_i, const int point_i, const float3 &offset)
|
||||
{
|
||||
geometry_refs_[geo_i].velocities[point_i] += offset;
|
||||
}
|
||||
|
||||
void update_angular_velocity(const int geo_i, const int point_i, const float3 &offset)
|
||||
{
|
||||
geometry_refs_[geo_i].angular_velocities[point_i] += offset;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::xpbd
|
||||
@@ -0,0 +1,98 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_multi_value_map.hh"
|
||||
|
||||
#include "GEO_xpbd_constraint_coloring_utils.hh"
|
||||
|
||||
namespace blender::xpbd {
|
||||
|
||||
template<typename PointID, typename GetConstraintPointIdsFn>
|
||||
inline int color_constraints(GetConstraintPointIdsFn &&get_constraint_point_ids_fn,
|
||||
MutableSpan<int> r_colors)
|
||||
{
|
||||
const int constraints_num = r_colors.size();
|
||||
MultiValueMap<PointID, int> constraints_by_point;
|
||||
for (const int constraint_i : IndexRange(constraints_num)) {
|
||||
for (const PointID &point_id : get_constraint_point_ids_fn(constraint_i)) {
|
||||
constraints_by_point.add(point_id, constraint_i);
|
||||
}
|
||||
}
|
||||
int colors_num = 0;
|
||||
for (const int constraint_i : IndexRange(constraints_num)) {
|
||||
Vector<int> used_colors;
|
||||
for (const PointID &point_id : get_constraint_point_ids_fn(constraint_i)) {
|
||||
for (const int other_constraint_i : constraints_by_point.lookup(point_id)) {
|
||||
if (other_constraint_i >= constraint_i) {
|
||||
continue;
|
||||
}
|
||||
used_colors.append_non_duplicates(r_colors[other_constraint_i]);
|
||||
}
|
||||
}
|
||||
int best_color = 0;
|
||||
while (used_colors.contains(best_color)) {
|
||||
best_color++;
|
||||
}
|
||||
r_colors[constraint_i] = best_color;
|
||||
colors_num = std::max(colors_num, best_color + 1);
|
||||
}
|
||||
return colors_num;
|
||||
}
|
||||
|
||||
template<typename PointID, typename GetConstraintPointIdsFn>
|
||||
inline ConstraintColoring generic_constraint_coloring(
|
||||
GetConstraintPointIdsFn &&get_constraint_points_fn,
|
||||
const int constraints_num,
|
||||
LinearAllocator<> &memory)
|
||||
{
|
||||
if (constraints_num == 0) {
|
||||
return {};
|
||||
}
|
||||
Array<int> colors(constraints_num);
|
||||
const int colors_num = color_constraints<PointID>(get_constraint_points_fn, colors);
|
||||
Array<Vector<int>> color_indices(colors_num);
|
||||
for (const int constraint_i : IndexRange(constraints_num)) {
|
||||
color_indices[colors[constraint_i]].append(constraint_i);
|
||||
}
|
||||
ConstraintColoring coloring;
|
||||
for (const int color_i : IndexRange(colors_num)) {
|
||||
const IndexMask mask = IndexMask::from_indices<int>(color_indices[color_i], memory);
|
||||
coloring.colors.append(mask);
|
||||
}
|
||||
return coloring;
|
||||
}
|
||||
|
||||
ConstraintColoring color_constraints__unary(const Span<int> affected_points,
|
||||
LinearAllocator<> &memory)
|
||||
{
|
||||
return generic_constraint_coloring<int>(
|
||||
[&](const int constraint_i) { return Span<int>(&affected_points[constraint_i], 1); },
|
||||
affected_points.size(),
|
||||
memory);
|
||||
}
|
||||
|
||||
ConstraintColoring color_constraints__binary(const Span<int2> affected_points,
|
||||
LinearAllocator<> &memory)
|
||||
{
|
||||
return generic_constraint_coloring<int>(
|
||||
[&](const int constraint_i) { return Span<int>(&affected_points[constraint_i][0], 2); },
|
||||
affected_points.size(),
|
||||
memory);
|
||||
}
|
||||
|
||||
ConstraintColoring color_constraints__n_ary(const GroupedSpan<int> affected_points,
|
||||
LinearAllocator<> &memory)
|
||||
{
|
||||
return generic_constraint_coloring<int>(
|
||||
[&](const int constraint_i) { return affected_points[constraint_i]; },
|
||||
affected_points.size(),
|
||||
memory);
|
||||
}
|
||||
|
||||
ConstraintColoring color_constraints__all_independent(const int constraints_num)
|
||||
{
|
||||
return ConstraintColoring{{IndexMask(constraints_num)}};
|
||||
}
|
||||
|
||||
} // namespace blender::xpbd
|
||||
Reference in New Issue
Block a user