Skip to content

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

Open
lkarstensen wants to merge 3 commits intosofa-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 intosofa-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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant