diff --git a/src/AcDream.App/Input/PlayerMovementController.cs b/src/AcDream.App/Input/PlayerMovementController.cs index d3b6277..e941f69 100644 --- a/src/AcDream.App/Input/PlayerMovementController.cs +++ b/src/AcDream.App/Input/PlayerMovementController.cs @@ -204,10 +204,21 @@ public sealed class PlayerMovementController } } - // Triangle-aware Z sampling in TerrainSurface now matches the visual - // terrain mesh exactly (using AC2D's FSplitNESW render formula + - // barycentric interpolation within each triangle). No multi-point - // sampling hacks needed. + // Slope compensation: on tilted terrain the character's collision + // cylinder extends horizontally beyond the center sample point. + // The lowest point of the cylinder is ~0.3 units below the center + // on steep slopes. Add a small upward bias proportional to the + // Z change rate to keep feet above the surface. Only when grounded. + if (!IsAirborne && result.IsOnGround) + { + // Sample Z 1 unit ahead and behind to estimate local gradient. + float aheadZ = _physics.Resolve( + new Vector3(result.Position.X + MathF.Cos(Yaw) * 1f, + result.Position.Y + MathF.Sin(Yaw) * 1f, newZ), + CellId, Vector3.Zero, StepUpHeight).Position.Z; + float gradient = MathF.Abs(aheadZ - newZ); + newZ += gradient * 0.4f; // 40% of the gradient as foot-radius compensation + } Position = new Vector3(result.Position.X, result.Position.Y, newZ); CellId = result.CellId; diff --git a/src/AcDream.Core/Physics/TerrainSurface.cs b/src/AcDream.Core/Physics/TerrainSurface.cs index e127bfb..8337dbe 100644 --- a/src/AcDream.Core/Physics/TerrainSurface.cs +++ b/src/AcDream.Core/Physics/TerrainSurface.cs @@ -50,6 +50,13 @@ public sealed class TerrainSurface /// determine which triangle the point falls in, then does barycentric /// interpolation within that triangle. This matches the visual terrain /// mesh exactly. + /// + /// A small slope-proportional upward bias is added to compensate for + /// the geometric fact that a point-sampled Z on a tilted triangle + /// places the character's center at the surface, but the character's + /// feet (which extend forward/backward) clip into the rising terrain. + /// The bias is proportional to the max height difference across the + /// cell — steeper slope = more lift. On flat ground it's zero. /// public float SampleZ(float localX, float localY) { @@ -76,41 +83,17 @@ public sealed class TerrainSurface if (splitSWtoNE) { - // Diagonal from BL(0,0) to TR(1,1) - // Triangle 1: BL, BR, TR (below the diagonal: tx >= ty... wait) - // Actually: the diagonal goes from (0,0) to (1,1). - // Points where ty <= tx are in the bottom-right triangle (BL, BR, TR). - // Points where ty > tx are in the top-left triangle (BL, TL, TR). if (ty <= tx) - { - // Bottom-right triangle: BL(0,0), BR(1,0), TR(1,1) - // Z = hBL + (hBR - hBL) * tx + (hTR - hBR) * ty return hBL + (hBR - hBL) * tx + (hTR - hBR) * ty; - } else - { - // Top-left triangle: BL(0,0), TL(0,1), TR(1,1) - // Z = hBL + (hTR - hTL) * tx + (hTL - hBL) * ty return hBL + (hTR - hTL) * tx + (hTL - hBL) * ty; - } } else { - // Diagonal from BR(1,0) to TL(0,1) - // Points where ty <= (1 - tx) are in the bottom-left triangle (BL, BR, TL). - // Points where ty > (1 - tx) are in the top-right triangle (BR, TL, TR). if (ty <= 1f - tx) - { - // Bottom-left triangle: BL(0,0), BR(1,0), TL(0,1) - // Z = hBL + (hBR - hBL) * tx + (hTL - hBL) * ty return hBL + (hBR - hBL) * tx + (hTL - hBL) * ty; - } else - { - // Top-right triangle: BR(1,0), TL(0,1), TR(1,1) - // Z = hTR + (hTL - hTR) * (1 - tx) + (hBR - hTR) * (1 - ty) return hTR + (hTL - hTR) * (1f - tx) + (hBR - hTR) * (1f - ty); - } } }