Skip to content

Fix wires with 3+ sections: initTopology accumulation + chained-section rest frame#228

Open
lkarstensen wants to merge 3 commits into
sofa-framework:masterfrom
lkarstensen:fix/multi-section-rest-shape-predecessor-frame
Open

Fix wires with 3+ sections: initTopology accumulation + chained-section rest frame#228
lkarstensen wants to merge 3 commits into
sofa-framework:masterfrom
lkarstensen:fix/multi-section-rest-shape-predecessor-frame

Conversation

@lkarstensen
Copy link
Copy Markdown

Fixes #227 (combined issue covering both bugs).

Two related bugs make any wire with three or more sections (or two chained non-straight sections) unusable. Both are fixed here.

Bug 1 — WireRestShape::initTopology accumulation

prev_length = length and prev_edges = nbrVisuEdges discard the cumulative offset/edge count after section 0. Only N=2 wires happen to produce identical output. From iteration 2 onward, points are placed at non-monotonic abscissas, edges become degenerate, and WireBeamInterpolation / MultiAdaptiveBeamMapping break.

Fix:

prev_length += length;
prev_edges += nbrVisuEdges;

(Originally tackled in #226, closed when further testing exposed Bug 2.)

Bug 2 — Rod section rest shape ignores predecessor section frame

BaseRodSectionMaterial::getRestTransformOnX(global_H_local, x_used, x_start) carries no predecessor-tip transform. RodSpireSection / RodMeshSection reconstruct world position with + Vec3(x_start, 0, 0), which is correct only when the wire so far ran straight along world +X. As soon as a non-straight section follows another non-straight section, the rest pose sits in empty space, AdaptiveBeamForceFieldAndMass injects unbalanced elastic forces, and the terminal section becomes "floppy".

Fix:

  • Virtual signature gains const Transform& predecessor_H_sectionStart.
  • RodStraightSection / RodSpireSection / RodMeshSection compute their local rest geometry in the section's own frame (origin at section start, +X = predecessor tip tangent) and compose with the predecessor transform instead of adding Vec3(x_start, 0, 0).
  • WireRestShape::getRestTransformOnX walks preceding sections, queries each tip at keyPts[i] with its accumulated predecessor, then dispatches to the owning section with the composed frame.

Backward-compatible:

  • One-section wire: predecessor = identity, x_start = 0 → unchanged.
  • Straight + curved tip: straight tip = (keyPts[1], 0, 0) with identity → matches old hardcoded form.

Files changed

  • src/BeamAdapter/component/engine/WireRestShape.inl
  • src/BeamAdapter/component/model/BaseRodSectionMaterial.h
  • src/BeamAdapter/component/model/RodStraightSection.{h,inl}
  • src/BeamAdapter/component/model/RodSpireSection.{h,inl}
  • src/BeamAdapter/component/model/RodMeshSection.{h,inl}

Test plan

Verified manually:

  • Existing WireRestShape_test (Straight + Spire) still passes — backward-compat case: predecessor stays identity for the straight, then (95, 0, 0) identity for the spire dispatch, matching the old Vec3(x_start, 0, 0) form.
  • 3-section straight wire (100 + 50 + 25): visual EdgeSetTopologyContainer has monotonic point coordinates and the expected total edge count, no overlap.
  • Straight + Spire(+90°) + Spire(+90°): rest pose of the second spire continues tangent to the first spire's tip instead of being placed along world +X at keyPts[2]; tip no longer drifts under no input.
  • Build green: cmake --build . --target BeamAdapter.

Lennart Karstensen added 3 commits April 29, 2026 16:34
…ons (sofa-framework#223)

x_used is the global curvilinear abscissa; adding x_start again placed
the rest node at 2*x_start + local_offset instead of x_used. For wires
with two or more RodStraightSections this produced enormous elastic
restoring forces at every timestep, causing simulation explosion.

Fix: use x_used directly, consistent with RodSpireSection and RodMeshSection.
…logy

Fixes sofa-framework#225. Both accumulators were reset via assignment instead of
accumulation, corrupting point coordinates and edge indices for any
wire with three or more sections.
BaseRodSectionMaterial::getRestTransformOnX previously received only
(x_used, x_start) and reconstructed world rest positions by adding
Vec3(x_start, 0, 0). That assumes every preceding section ran straight
along world +X -- true for one Straight base + one curved tip, but wrong
as soon as a non-straight section is chained after another non-straight
section: the rest shape was placed along world +X at keyPts[i] instead
of continuing tangent-continuous from the curved predecessor tip,
producing persistent unbalanced elastic forces and a "floppy" terminal
section.

- BaseRodSectionMaterial::getRestTransformOnX gains a
  const Transform& predecessor_H_sectionStart parameter.
- RodStraightSection / RodSpireSection / RodMeshSection compute their
  local rest geometry in the section's own frame (origin at section
  start, +X = predecessor tip tangent) and compose with the predecessor
  transform instead of adding Vec3(x_start, 0, 0).
- WireRestShape::getRestTransformOnX walks preceding sections,
  querying each tip at keyPts[i] with its accumulated predecessor,
  then dispatches to the owning section with the composed frame.

Backward-compatible for one-section wires (predecessor = identity,
x_start = 0) and for Straight + curved-tip pairs (straight tip
produces (keyPts[1], 0, 0) with identity, matching the old hardcoded
form). Fixes chained non-straight sections.
@fredroy fredroy added the pr: status to review To notify reviewers to review this pull-request label May 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr: status to review To notify reviewers to review this pull-request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Wires with 3+ sections fail: initTopology accumulation + chained-section rest frame

2 participants