feat(ui): debug overlay + refined input controls
Adds the first on-screen HUD for the dev client plus today's mouse-control refinements. Also lands yesterday's scenery-alignment changes that were left uncommitted in the working tree. Overlay: - BitmapFont rasterizes a system TTF via StbTrueTypeSharp into a 512x512 R8 atlas at startup (Consolas on Windows, DejaVu/Menlo fallbacks) - TextRenderer batches 2D quads in screen-space with ortho projection; one shader + two draw calls (rect then text) for panel backgrounds under glyphs - DebugOverlay composes info / stats / compass / help panels on top of the 3D scene; toggles via F1/F4/F5/F6; transient toasts for key events - DebugLineRenderer and its shaders (carried over from the scenery work) are properly committed in this commit Controls: - Per-mode mouse sensitivity (Chase 0.15, Fly 1.0, Orbit 1.0); F8/F9 to adjust the active mode multiplicatively (x1.2) - Hold RMB to free-orbit the chase camera around the player; release stays at the new angle (no snap-back) - Mouse-wheel zooms chase distance between 2m and 40m - Chase pitch widened to [-0.7, 1.4] so mouse-Y tilts both ways from the default neutral angle Scenery alignment (carried from yesterday's session): - ShadowObjectRegistry AllEntriesForDebug + Scale field - SceneryGenerator uses ACViewer's OnRoad polygon test + baseLoc + set_heading rotation - BSPQuery dispatchers accept localToWorld so normals/offsets transform correctly per part - TransitionTypes.CylinderCollision rewritten with wall-slide + push-out - PhysicsDataCache caches visual-mesh AABB for scenery that lacks physics Setup bounds
This commit is contained in:
parent
6b4e7569a3
commit
ff325abd7b
20 changed files with 2734 additions and 268 deletions
|
|
@ -1453,19 +1453,46 @@ public static class BSPQuery
|
|||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Path 5: Contact — sphere_intersects_poly + step_sphere_up / slide
|
||||
// ACE transforms collision normal from local→global before step_up/slide
|
||||
// Path 5: Contact — sphere_intersects_poly + wall-slide
|
||||
// ACE retail uses StepSphereUp here, deferring to a retry loop that
|
||||
// executes the step-up motion. We haven't ported that execution, so
|
||||
// we apply the same wall-slide response as Path 6 — this at least
|
||||
// gives correct blocking + sliding behavior for walls, buildings,
|
||||
// and tree trunks while the player is on the ground.
|
||||
// ----------------------------------------------------------------
|
||||
if (obj.State.HasFlag(ObjectInfoState.Contact))
|
||||
{
|
||||
ResolvedPolygon? hitPoly0 = null;
|
||||
Vector3 contact0 = Vector3.Zero;
|
||||
|
||||
if (SphereIntersectsPolyInternal(root, resolved, sphere0, movement,
|
||||
ref hitPoly0, ref contact0))
|
||||
bool hit0 = SphereIntersectsPolyInternal(root, resolved, sphere0, movement,
|
||||
ref hitPoly0, ref contact0);
|
||||
|
||||
if (hit0 || hitPoly0 is not null)
|
||||
{
|
||||
var worldNormal = L2W(hitPoly0!.Plane.Normal);
|
||||
return StepSphereUp(transition, worldNormal);
|
||||
// Wall-slide response (same as Path 6 below).
|
||||
var localNormal = hitPoly0!.Plane.Normal;
|
||||
var localMovement = sphere0.Center - localCurrCenter;
|
||||
|
||||
float movementIntoWall = Vector3.Dot(localMovement, localNormal);
|
||||
Vector3 projectedMovement = localMovement - localNormal * movementIntoWall;
|
||||
|
||||
Vector3 slidPos = localCurrCenter + projectedMovement;
|
||||
float slidDist = Vector3.Dot(slidPos, localNormal) + hitPoly0.Plane.D;
|
||||
float minDist = sphere0.Radius + 0.01f;
|
||||
if (slidDist < minDist)
|
||||
{
|
||||
slidPos += localNormal * (minDist - slidDist);
|
||||
}
|
||||
|
||||
Vector3 localDelta = slidPos - sphere0.Center;
|
||||
Vector3 worldDelta = Vector3.Transform(localDelta, localToWorld) * scale;
|
||||
path.AddOffsetToCheckPos(worldDelta);
|
||||
|
||||
var worldNormal = L2W(localNormal);
|
||||
collisions.SetCollisionNormal(worldNormal);
|
||||
collisions.SetSlidingNormal(worldNormal);
|
||||
return TransitionState.Slid;
|
||||
}
|
||||
|
||||
if (sphere1 is not null)
|
||||
|
|
@ -1473,17 +1500,34 @@ public static class BSPQuery
|
|||
ResolvedPolygon? hitPoly1 = null;
|
||||
Vector3 contact1 = Vector3.Zero;
|
||||
|
||||
if (SphereIntersectsPolyInternal(root, resolved, sphere1, movement,
|
||||
ref hitPoly1, ref contact1))
|
||||
{
|
||||
var worldNormal = L2W(hitPoly1!.Plane.Normal);
|
||||
return SlideSphere(transition, worldNormal);
|
||||
}
|
||||
bool hit1 = SphereIntersectsPolyInternal(root, resolved, sphere1, movement,
|
||||
ref hitPoly1, ref contact1);
|
||||
|
||||
if (hitPoly1 is not null)
|
||||
return NegPolyHitDispatch(path, hitPoly1, false, localToWorld);
|
||||
if (hitPoly0 is not null)
|
||||
return NegPolyHitDispatch(path, hitPoly0, true, localToWorld);
|
||||
if (hit1 || hitPoly1 is not null)
|
||||
{
|
||||
var localNormal = hitPoly1!.Plane.Normal;
|
||||
var localMovement = sphere1.Center - localCurrCenter;
|
||||
|
||||
float movementIntoWall = Vector3.Dot(localMovement, localNormal);
|
||||
Vector3 projectedMovement = localMovement - localNormal * movementIntoWall;
|
||||
|
||||
Vector3 slidPos = localCurrCenter + projectedMovement;
|
||||
float slidDist = Vector3.Dot(slidPos, localNormal) + hitPoly1.Plane.D;
|
||||
float minDist = sphere1.Radius + 0.01f;
|
||||
if (slidDist < minDist)
|
||||
{
|
||||
slidPos += localNormal * (minDist - slidDist);
|
||||
}
|
||||
|
||||
Vector3 localDelta = slidPos - sphere1.Center;
|
||||
Vector3 worldDelta = Vector3.Transform(localDelta, localToWorld) * scale;
|
||||
path.AddOffsetToCheckPos(worldDelta);
|
||||
|
||||
var worldNormal = L2W(localNormal);
|
||||
collisions.SetCollisionNormal(worldNormal);
|
||||
collisions.SetSlidingNormal(worldNormal);
|
||||
return TransitionState.Slid;
|
||||
}
|
||||
}
|
||||
|
||||
return TransitionState.OK;
|
||||
|
|
@ -1509,11 +1553,50 @@ public static class BSPQuery
|
|||
hitPoly0!, contact0, scale, localToWorld);
|
||||
}
|
||||
|
||||
var worldNormal = L2W(hitPoly0!.Plane.Normal);
|
||||
// ─── Wall-slide response ─────────────────────────────────
|
||||
// Instead of just pushing the sphere out of penetration
|
||||
// (which undoes the whole step), compute the wall-slide
|
||||
// position: where the sphere WOULD be if the movement had
|
||||
// been projected along the wall tangent.
|
||||
//
|
||||
// In local space:
|
||||
// curr = localCurrCenter
|
||||
// target = sphere0.Center
|
||||
// movement = target - curr
|
||||
// normal = polygon plane normal (outward)
|
||||
// projectedMovement = movement - (movement · normal) * normal
|
||||
// slidPos = curr + projectedMovement
|
||||
//
|
||||
// Then ensure slidPos is outside the plane by at least radius+eps.
|
||||
var localNormal = hitPoly0!.Plane.Normal;
|
||||
var localMovement = sphere0.Center - localCurrCenter;
|
||||
|
||||
// Project movement along wall tangent
|
||||
float movementIntoWall = Vector3.Dot(localMovement, localNormal);
|
||||
Vector3 projectedMovement = localMovement - localNormal * movementIntoWall;
|
||||
|
||||
// Slid position in local space
|
||||
Vector3 slidPos = localCurrCenter + projectedMovement;
|
||||
|
||||
// Ensure slid position is OUTSIDE the plane by radius + epsilon
|
||||
float slidDist = Vector3.Dot(slidPos, localNormal) + hitPoly0.Plane.D;
|
||||
float minDist = sphere0.Radius + 0.01f;
|
||||
if (slidDist < minDist)
|
||||
{
|
||||
slidPos += localNormal * (minDist - slidDist);
|
||||
}
|
||||
|
||||
// Delta from current CheckPos sphere center to slid position (local)
|
||||
Vector3 localDelta = slidPos - sphere0.Center;
|
||||
// Transform to world and apply
|
||||
Vector3 worldDelta = Vector3.Transform(localDelta, localToWorld) * scale;
|
||||
path.AddOffsetToCheckPos(worldDelta);
|
||||
|
||||
var worldNormal = L2W(localNormal);
|
||||
path.WalkableAllowance = PhysicsGlobals.LandingZ;
|
||||
path.Collide = true;
|
||||
collisions.SetCollisionNormal(worldNormal);
|
||||
return TransitionState.Adjusted;
|
||||
collisions.SetSlidingNormal(worldNormal);
|
||||
return TransitionState.Slid;
|
||||
}
|
||||
|
||||
if (sphere1 is not null)
|
||||
|
|
@ -1526,9 +1609,29 @@ public static class BSPQuery
|
|||
|
||||
if (hit1 || hitPoly1 is not null)
|
||||
{
|
||||
var worldNormal = L2W(hitPoly1!.Plane.Normal);
|
||||
// Head sphere hit: apply the same wall-slide as above.
|
||||
var localNormal = hitPoly1!.Plane.Normal;
|
||||
var localMovement = sphere1.Center - localCurrCenter;
|
||||
|
||||
float movementIntoWall = Vector3.Dot(localMovement, localNormal);
|
||||
Vector3 projectedMovement = localMovement - localNormal * movementIntoWall;
|
||||
|
||||
Vector3 slidPos = localCurrCenter + projectedMovement;
|
||||
float slidDist = Vector3.Dot(slidPos, localNormal) + hitPoly1.Plane.D;
|
||||
float minDist = sphere1.Radius + 0.01f;
|
||||
if (slidDist < minDist)
|
||||
{
|
||||
slidPos += localNormal * (minDist - slidDist);
|
||||
}
|
||||
|
||||
Vector3 localDelta = slidPos - sphere1.Center;
|
||||
Vector3 worldDelta = Vector3.Transform(localDelta, localToWorld) * scale;
|
||||
path.AddOffsetToCheckPos(worldDelta);
|
||||
|
||||
var worldNormal = L2W(localNormal);
|
||||
collisions.SetCollisionNormal(worldNormal);
|
||||
return TransitionState.Collided;
|
||||
collisions.SetSlidingNormal(worldNormal);
|
||||
return TransitionState.Slid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue