Skip to content

Elongated Footprint Support#2829

Draft
KyleAnthonyShepherd wants to merge 15 commits intobeyond-all-reason:masterfrom
KyleAnthonyShepherd:footprint-separators
Draft

Elongated Footprint Support#2829
KyleAnthonyShepherd wants to merge 15 commits intobeyond-all-reason:masterfrom
KyleAnthonyShepherd:footprint-separators

Conversation

@KyleAnthonyShepherd
Copy link
Copy Markdown
Collaborator

Executive Summary:

PR to allow mobile units with a non-square footprint to collide and separate like a rectangle, and not as a square dependent on movedef. This new feature is explicitly gated behind modInfo.allowSepAxisCollisionTest = true. And collision and separation logic for standard square movedef units should be untouched.

This PR was inspired by:

  1. Some drama on the BAR discord, where naval ship design is unnecessarily constrained by the square movedef requirement.
  2. Stress testing Claude Opus 4.6. Vibe coded up to a working state, then a manual review to cut out the cruft and fix edge cases.

See videos for tests on 3x10 long units (using a 3x3 movedef), and 9x3 wide units (using a 9x9 movedef):

long_ship.mp4
wide_ship3.mp4

One note, the grey model sphere must be large enough to allow units (elongated or not) to push other units out of the way. Related to CGroundMoveType::CheckCollisionSkid() I think.
This is a separate bug? that should be addressed in another PR.

claude and others added 15 commits February 26, 2026 16:00
Change the collision detection and "keep separated" push logic in
HandleObjectCollisions/HandleUnitCollisions to use the unit's UnitDef
footprint (CSolidObject::xsize/zsize) instead of the MoveDef footprint.

This allows elongated units like battleships to have a small square
MoveDef for pathfinding (e.g. 2x2) while using a larger rectangular
footprint (e.g. 10x2) for collision avoidance. The existing SAT
(Separating Axis Theorem) collision test already accounts for unit
orientation via frontdir/rightdir, so the asymmetric footprint
correctly rotates with the unit.

Changes:
- HandleObjectCollisions: collider radius and stretch factor now come
  from collider->CalcFootPrintMaxInteriorRadius/AxisStretchFactor
- HandleUnitCollisions: collidee radius and stretch factor now come
  from collidee->CalcFootPrintMaxInteriorRadius/AxisStretchFactor
- SAT::HaveSeparatingAxis: footprint dimensions now come from
  collider/collidee->GetFootPrint instead of MoveDef->GetFootPrint

Pathfinding, TestMoveSquare checks, and ownerRadius (waypoint/arrival)
remain based on MoveDef and are unchanged.

https://claude.ai/code/session_01Bq9KX9MfLKC5PmwM5YS4fH
The previous change (collider->CalcFootPrintMaxInteriorRadius) had no
effect because GroundMoveType initialization overrides the unit's
xsize/zsize with MoveDef values (line 530-531). So both the MoveDef
and SolidObject methods returned the same values.

Fix by reading directly from CSolidObject::footprint, which is set
from the UnitDef's footprintX/footprintZ at Unit.cpp:263 and is never
overwritten by MoveDef.

Specifically:
- HandleObjectCollisions: compute collider radius and stretch factor
  from collider->footprint.x/y
- HandleUnitCollisions: compute mobile collidee radius and stretch
  factor from collidee->footprint.x/y; immobile collidees keep using
  xsize/zsize (not overridden, so correct)
- SAT::HaveSeparatingAxis: use footprint for mobile units (collideeMD
  != nullptr), keep xsize/zsize for features/immobile (not overridden)

https://claude.ai/code/session_01Bq9KX9MfLKC5PmwM5YS4fH
@lhog lhog requested a review from marcushutchings March 1, 2026 22:19
Copy link
Copy Markdown
Collaborator

@lostsquirrel1 lostsquirrel1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel this would be better handled by an explicit footprintX and footprintZ on the unitDef, which the ground move type can use instead for some arbitrary size calculation (which is what it does at the moment.) In fact, it already has it: they are xsize zsize - so you just need a variable to act as an override and then update groundmove to use it for mobile unit <-> unit collisions.

Comment on lines +530 to +540
// true if the unit's UnitDef footprint is significantly non-square (stretch factor > 0.1);
// gates elongated-footprint collision logic (OBB scan expansion, projected radii, etc.)
bool hasElongatedFootprint = false;

// Pre-computed collision geometry derived from the UnitDef footprint (int2).
// Mobile units have xsize/zsize overridden by MoveDef, so these cache the true
// physical shape for use in collision detection / OBB tests.
// Half-extents in world units: ((footprint - 1) * 0.5 * SQUARE_SIZE) per axis.
float2 footprintHalfExtents;
// Max half-extent (bounding circle radius): max(footprint.x, footprint.y) - 1) * 0.5 * SQUARE_SIZE.
float footprintMaxRadius = 0.0f;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The footprint changes should be applied to the unit def, not the individual unit. Logically all units of the same type will have the same footprint.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Units of the same unitdef type can have a different effective footprint due to differing moveDef via Spring.MoveCtrl.SetMoveDef though

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a good point; though you probably still need to be able to calibrate this at unitDef level. Then override at unit level in code if you need.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

footprintX/Z already exist on the unitdef level. But I think the functionality has been basically reduced to the dimensions needed to place the nanoframe on the ground.

In current master, almost all other unit dimensions are derived from the MoveDef.

So, this PR uses the unitdef footprintX/Z to control collisions and separation IF they are not square.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification. That's fine for mobile unit <-> mobile unit collisions.

There seems to be similar changes for collisions with static units. We can't use elongated collision against buildings because that can interfere with the unit's ability to follow a path given to it by the path finder. We need absolute parity and agreement between the movement and the pathing systems otherwise units can get stuck or behave badly.

mobile units <-> mobile units is fine because the pathing system cannot see mobile units, and mobile units are expected to move around and push each other about.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the torture test in the video above, where a 3x10 long unit with a 3x3 pathfinding movedef is forced to turn a corner in a 3-wide hallway, shows that some degree of "phasing/intangability" is needed to prevent stuck situations.

I suspect there would be a possible construction of a 3x3 pathable passage that a 3x10 would be unable to traverse due to pushout forces.

So some failsafe mechanism to collapse the "collision" volume of a 3x10 unit into its square 3x3 movedef would be needed.

@lostsquirrel1
Copy link
Copy Markdown
Collaborator

With respects to the grey sphere issue: how are you changing that? I believe that should only refer to a unit's collision for projectiles.

@KyleAnthonyShepherd
Copy link
Copy Markdown
Collaborator Author

With respects to the grey sphere issue: how are you changing that? I believe that should only refer to a unit's collision for projectiles.

I have done no changes to that subsystem in this PR.
I only noticed it when armbats (with a model radius about equal to the short length), was unable to push through a crowd of other armbats.

But then when I increased the model radius to the long dimension, it was able to push through a crowd of armbats.

[Also did tests with armthor. Armthors with a small model radius were not pushing other armthors away, but armthors wirh a large model radius easily pushed away other armthors]

@lostsquirrel1
Copy link
Copy Markdown
Collaborator

lostsquirrel1 commented Mar 2, 2026

That's right, model radius, is correct for determining unit <-> unit collision.

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.

4 participants