testbed: physics fixes (rest-at-contact), pytest suite (33 tests green)
This commit is contained in:
+34
-25
@@ -183,29 +183,50 @@ class Room:
|
||||
fx, fz = self.capsule.forward()
|
||||
while remaining > 0.0 and hit is None:
|
||||
step = min(0.1, remaining)
|
||||
bx, bz = self.capsule.x, self.capsule.z
|
||||
ok, h = self._try_displace(fx * step, fz * step)
|
||||
moved += math.hypot(self.capsule.x - bx, self.capsule.z - bz)
|
||||
if not ok and h is not None:
|
||||
h.impulse = remaining
|
||||
h.impulse = max(0.0, distance - moved)
|
||||
hit = h
|
||||
break
|
||||
moved += step
|
||||
remaining -= step
|
||||
self.capsule.speed = moved / 0.1 if moved > 0 else 0.0
|
||||
return moved, hit
|
||||
|
||||
def _try_displace(self, dx: float, dz: float) -> tuple[bool, Hit | None]:
|
||||
"""Move by (dx, dz) with circle-vs-AABB resolution. Returns (moved, hit)."""
|
||||
"""Move by (dx, dz) with circle-vs-AABB resolution. Returns (moved, hit).
|
||||
|
||||
The capsule always ends at the resolved position: on a blocked step it
|
||||
rests at the contact point (wall or box face).
|
||||
"""
|
||||
cap = self.capsule
|
||||
r = cap.radius
|
||||
nx = cap.x + dx
|
||||
nz = cap.z + dz
|
||||
|
||||
# Room walls: clamp to bounds (the capsule cannot leave the room).
|
||||
r = cap.radius
|
||||
if nx < r or nx > ROOM_SIZE - r or nz < r or nz > ROOM_SIZE - r:
|
||||
hit = self._wall_hit(nx, nz)
|
||||
return False, hit
|
||||
|
||||
blocked: Hit | None = None
|
||||
if nx < r:
|
||||
nx, blocked = (
|
||||
r,
|
||||
Hit(other="wall_west", normal_x=1.0, normal_z=0.0, impulse=0.0),
|
||||
)
|
||||
elif nx > ROOM_SIZE - r:
|
||||
nx, blocked = (
|
||||
ROOM_SIZE - r,
|
||||
Hit(other="wall_east", normal_x=-1.0, normal_z=0.0, impulse=0.0),
|
||||
)
|
||||
elif nz < r:
|
||||
nz, blocked = (
|
||||
r,
|
||||
Hit(other="wall_north", normal_x=0.0, normal_z=1.0, impulse=0.0),
|
||||
)
|
||||
elif nz > ROOM_SIZE - r:
|
||||
nz, blocked = (
|
||||
ROOM_SIZE - r,
|
||||
Hit(other="wall_south", normal_x=0.0, normal_z=-1.0, impulse=0.0),
|
||||
)
|
||||
|
||||
for _ in range(8):
|
||||
contact = self._box_contact(nx, nz, r)
|
||||
if contact is None:
|
||||
@@ -214,22 +235,10 @@ class Room:
|
||||
blocked = Hit(
|
||||
other=other, normal_x=normal_x, normal_z=normal_z, impulse=0.0
|
||||
)
|
||||
if blocked is not None:
|
||||
return False, blocked
|
||||
|
||||
cap.x = nx
|
||||
cap.z = nz
|
||||
return True, None
|
||||
|
||||
def _wall_hit(self, nx: float, nz: float) -> Hit:
|
||||
r = self.capsule.radius
|
||||
if nx < r:
|
||||
return Hit(other="wall_west", normal_x=1.0, normal_z=0.0, impulse=0.0)
|
||||
if nx > ROOM_SIZE - r:
|
||||
return Hit(other="wall_east", normal_x=-1.0, normal_z=0.0, impulse=0.0)
|
||||
if nz < r:
|
||||
return Hit(other="wall_north", normal_x=0.0, normal_z=1.0, impulse=0.0)
|
||||
return Hit(other="wall_south", normal_x=0.0, normal_z=-1.0, impulse=0.0)
|
||||
return blocked is None, blocked
|
||||
|
||||
def _box_contact(
|
||||
self, cx: float, cz: float, r: float
|
||||
@@ -265,10 +274,10 @@ class Room:
|
||||
return (cx, nz, 0.0, 1.0 if cz < box.cz else -1.0, box.id)
|
||||
return None
|
||||
|
||||
def turn(self, yaw_delta: float = 0.0, pitch_delta: float = 0.0) -> None:
|
||||
def turn(self, yaw_deg: float = 0.0, pitch_deg: float = 0.0) -> None:
|
||||
cap = self.capsule
|
||||
cap.yaw_deg = (cap.yaw_deg + yaw_delta) % 360.0
|
||||
cap.pitch_deg = min(85.0, max(-85.0, cap.pitch_deg + pitch_delta))
|
||||
cap.yaw_deg = (cap.yaw_deg + yaw_deg) % 360.0
|
||||
cap.pitch_deg = min(85.0, max(-85.0, cap.pitch_deg + pitch_deg))
|
||||
|
||||
def face(self, yaw_deg: float, pitch_deg: float) -> None:
|
||||
self.capsule.yaw_deg = yaw_deg % 360.0
|
||||
|
||||
Reference in New Issue
Block a user