fix(street): 0.1.1 collision patch — flush sidewalk edge, graded crossing ramps (#21)
Merged PR #21: 0.1.1 collision patch (flush sidewalk edge, graded ramps) Co-authored-by: Zwitschi <zwitschi82@gmail.com>
This commit was merged in pull request #21.
This commit is contained in:
1 parent
0dd4a55c87
commit
e75e5500a7
38 files changed
+162
-83
No files matched your search
+25
-6
@@ -80,7 +80,10 @@ LD_LIBRARY_PATH="$B/blender/lib" "$B/blender/blender" -b --factory-startup \
|
||||
```
|
||||
|
||||
- `Road_Hollywood`: 8 120 verts, 7 812 faces, carriageway −10.5…+10.5 m, DEM elevations plus a 2 cm lift so it does not z-fight the terrain.
|
||||
- `Sidewalk_South` 4.0 m wide, `Sidewalk_North` 5.3 m wide, both raised by the kerb height.
|
||||
- `Sidewalk_South` 4.0 m wide, `Sidewalk_North` 5.3 m wide. The kerb upstand is
|
||||
**full at the road-side edge and tapers to zero at the building-side edge**, so
|
||||
the walk surface meets the terrain flush (before `0.1.1` the outer lip stood the
|
||||
full kerb height above the DEM — a 15.8 cm ledge).
|
||||
- `Kerb_South`, `Kerb_North`: vertical faces, mostly **0.13 m** — the 0.15 m assumption minus nothing on the road side — and flushed to 0 within ±2.5 m of each signalised crossing with a 1.5 m blend. Measured from the built mesh: 256 steps at 0.13 m, 15 flush, a few in transition.
|
||||
|
||||
Crown check on the built mesh at mid-block: gutter −10.5 m → +0.99, −5 m → +1.38, centre → +1.52, +5 m → +1.58, +10.5 m → +1.52. The crown sits just north of the centreline, as the cross-sections said.
|
||||
@@ -105,14 +108,30 @@ A downward raycast on a 1 m grid along both sidewalks and the designated crossin
|
||||
|
||||
| Route | Samples | Holes | Z range | Worst step |
|
||||
| ----------------- | ------: | ----: | ------------- | ---------: |
|
||||
| South sidewalk | 279 | 0 | −0.45 … +3.13 | 20.5 cm |
|
||||
| North sidewalk | 279 | 0 | +0.07 … +3.83 | 15.1 cm |
|
||||
| Crossing at 130 m | 26 | 0 | +1.16 … +1.80 | 8.6 cm |
|
||||
| South sidewalk | 279 | 0 | −0.48 … +3.06 | 13.4 cm (s=198) |
|
||||
| North sidewalk | 279 | 0 | +0.06 … +3.76 | 6.9 cm (s=33) |
|
||||
| Crossing at 130 m | 26 | 0 | +1.14 … +1.80 | 9.0 cm (off −8.5) |
|
||||
|
||||
At `--sample 0.25` the same routes measure 11.6 cm / 8.0 cm / 3.4 cm. The
|
||||
pre-check now prints the grade and the **location** of the worst step, and its
|
||||
coverage no longer shrinks with `--sample`.
|
||||
|
||||
No holes anywhere, so nothing drops the player on either side. The two flagged steps:
|
||||
|
||||
- **15.8 cm at s≈207**, where a sample crosses from `Terrain_DEM` onto `Sidewalk_South` — that is the sidewalk's outer edge, the 0.15 m kerb height minus the terrain. Mostly hidden by buildings, but it is a real edge and it will be visible anywhere a building is missing.
|
||||
- **10–13 cm steps at s≈33, 39 and 126**, inside the sidewalk mesh: the kerb-flush transition at the crossings. That is a ~10 % ramp over 1.5 m, steeper than a real crossing ramp (8 % max). Worth grading properly when the crossings are detailed.
|
||||
- ~~15.8 cm at s≈207, the sidewalk's outer edge~~ — **fixed in `0.1.1`**: the
|
||||
sidewalk upstand now tapers to zero at the building side, so the outer edge is
|
||||
flush with the terrain (north route 15.1 → 6.9 cm).
|
||||
- ~~10–13 cm steps at the crossings, ~10 % ramp over 1.5 m~~ — **fixed in `0.1.1`**:
|
||||
the ramp run is now derived from a target grade (`--max-ramp-grade`, default 8 %),
|
||||
giving 1.63 m instead of the fixed 1.5 m (8.7 %). Measured at 0.25 m sampling the
|
||||
crossing now steps 3.4 cm (~1.4 % of the route) at the kerb line.
|
||||
|
||||
**Remaining finding (not a crossing, not the outer edge):** the south route still
|
||||
measures a **13.4 cm step at s≈198 m** (11.6 cm at 0.25 m sampling) and the north
|
||||
route 8.0 cm at s≈251 m. Neither is at a crossing and neither is the outer lip, so
|
||||
this is a local discontinuity in the sidewalk strip — most likely the DEM sampling
|
||||
or a centreline vertex near the block ends. Tracked under the `0.1.1` "audit any
|
||||
collision gaps" item rather than left undocumented.
|
||||
|
||||
This is a pre-check, not the `0.1.0` release check: the release check is traversal in the engine.
|
||||
|
||||
|
||||
Binary file not shown.
@@ -165,10 +165,16 @@ def main():
|
||||
def lat_lon(s, offset):
|
||||
return oc.point_at(line, _lengths, s, offset, lat0, lon0)
|
||||
|
||||
# Sample spacing is independent of coverage: walk the whole block at
|
||||
# `--sample` metres, so a finer spacing measures the gradient instead of
|
||||
# accidentally shortening the route (previously range(int(block_len)) with a
|
||||
# 0.25 m spacing only covered a quarter of the block).
|
||||
along = int(block_len / args.sample)
|
||||
across = int(26.0 / args.sample)
|
||||
route_specs = {
|
||||
"south sidewalk": [(s, -12.5) for s in [i * args.sample for i in range(int(block_len))]],
|
||||
"north sidewalk": [(s, 13.0) for s in [i * args.sample for i in range(int(block_len))]],
|
||||
"crossing at 130 m": [(130.0, o) for o in [i * args.sample - 12.5 for i in range(26)]],
|
||||
"south sidewalk": [(i * args.sample, -12.5) for i in range(along)],
|
||||
"north sidewalk": [(i * args.sample, 13.0) for i in range(along)],
|
||||
"crossing at 130 m": [(130.0, i * args.sample - 12.5) for i in range(across)],
|
||||
}
|
||||
flat = [(spec, lat_lon(s, off)) for spec, samples in route_specs.items() for s, off in samples]
|
||||
local = project_to_local([ll for _spec, ll in flat], find_proj())
|
||||
@@ -185,12 +191,20 @@ def main():
|
||||
heights.append(None)
|
||||
else:
|
||||
heights.append(z)
|
||||
deltas = [abs(b - a) for a, b in zip(heights, heights[1:])
|
||||
deltas = [(i, abs(b - a)) for i, (a, b) in enumerate(zip(heights, heights[1:]))
|
||||
if a is not None and b is not None]
|
||||
max_step = max(deltas) if deltas else float("nan")
|
||||
worst_i, max_step = (max(deltas, key=lambda t: t[1]) if deltas else (0, float("nan")))
|
||||
known = [h for h in heights if h is not None]
|
||||
grade = (max_step / args.sample) * 100 if deltas else float("nan")
|
||||
# Position of the worst step, as arc length / cross offset, so the cause
|
||||
# can be located instead of guessed.
|
||||
if label == "crossing at 130 m":
|
||||
where = f"s=130 off={(worst_i * args.sample - 12.5):+.2f} m"
|
||||
else:
|
||||
where = f"s={worst_i * args.sample:.1f} m"
|
||||
print(f" {label:18} samples {len(heights):3} holes {holes} "
|
||||
f"Z {min(known):+.2f}..{max(known):+.2f} max step {max_step*100:.1f} cm")
|
||||
f"Z {min(known):+.2f}..{max(known):+.2f} max step {max_step*100:.1f} cm "
|
||||
f"({grade:.1f} % grade) at {where}")
|
||||
|
||||
out = os.path.abspath(args.out)
|
||||
os.makedirs(os.path.dirname(out), exist_ok=True)
|
||||
|
||||
Binary file not shown.
@@ -68,8 +68,13 @@ def parse_args():
|
||||
ap.add_argument("--cross", type=float, default=0.75)
|
||||
ap.add_argument("--kerb", type=float, default=0.15)
|
||||
ap.add_argument("--ramp-half-width", type=float, default=2.5)
|
||||
ap.add_argument("--ramp-blend", type=float, default=1.5)
|
||||
return ap.parse_args(argv)
|
||||
ap.add_argument("--ramp-blend", type=float, default=None,
|
||||
help="override the ramp run; default derives it from --max-ramp-grade")
|
||||
ap.add_argument("--max-ramp-grade", type=float, default=0.08,
|
||||
help="maximum crossing-ramp slope (0.08 = 8%%)")
|
||||
args = ap.parse_args(argv)
|
||||
args.ramp_blend = ramp_blend(args.kerb, args.max_ramp_grade, args.ramp_blend)
|
||||
return args
|
||||
|
||||
|
||||
def find_proj():
|
||||
@@ -125,7 +130,13 @@ class Dem:
|
||||
|
||||
|
||||
def kerb_height(s, base, half_width, blend):
|
||||
"""Full kerb, except flushed near a crossing so people can step off."""
|
||||
"""Full kerb, except flushed near a crossing so people can step off.
|
||||
|
||||
`blend` is the horizontal run over which the kerb drops from `base` to the
|
||||
0.02 m flush height. It is derived from a target maximum grade (see
|
||||
`ramp_blend`) so the crossing ramps stay within a real ramp's slope instead
|
||||
of the ~8.7 % the old fixed 1.5 m run produced.
|
||||
"""
|
||||
for crossing in CROSSINGS:
|
||||
d = abs(s - crossing)
|
||||
if d <= half_width:
|
||||
@@ -136,6 +147,22 @@ def kerb_height(s, base, half_width, blend):
|
||||
return base
|
||||
|
||||
|
||||
def ramp_blend(kerb, max_grade, override=None):
|
||||
"""Horizontal run needed for the kerb ramp to stay at or under max_grade.
|
||||
|
||||
0.15 m kerb at an 8 % grade needs 1.63 m of run; the previous fixed 1.5 m
|
||||
gave 8.7 %, and the walk pre-check measured the transition as a 10-13 cm
|
||||
step over 1.5 m. When `override` is given it wins (kept for reproducing old
|
||||
meshes), otherwise the run is computed from the live kerb height so it stays
|
||||
correct if the kerb height assumption changes.
|
||||
"""
|
||||
if override is not None:
|
||||
return max(0.0, override)
|
||||
if max_grade <= 0.0:
|
||||
return 0.0
|
||||
return max(0.0, (kerb - 0.02)) / max_grade
|
||||
|
||||
|
||||
def strip(rows, offsets, z_at, name, material):
|
||||
"""Build a quad grid from per-row sampled vertices."""
|
||||
verts, faces = [], []
|
||||
@@ -218,13 +245,31 @@ def main():
|
||||
z = dem_z(x, y)
|
||||
return (z if z is not None else 0.0) + 0.02
|
||||
|
||||
def south_z(r, off, x, y):
|
||||
def sidewalk_z(r, off, inner, outer, x, y):
|
||||
"""Sidewalk surface: full kerb upstand at the road-side (inner) edge,
|
||||
tapering to zero at the building-side (outer) edge so the walk surface
|
||||
meets the terrain flush.
|
||||
|
||||
Before this taper the outer lip stood the full kerb height above the DEM,
|
||||
which the walkability pre-check measured as a 15.8 cm step where a sample
|
||||
crosses from Terrain_DEM onto Sidewalk_South (with a matching lip on the
|
||||
north side). A 0.15 m lip over a 4.0 m / 5.3 m strip is a ~3-4 % ramp:
|
||||
walkable and visually negligible, and it removes the ledge entirely.
|
||||
"""
|
||||
z = dem_z(x, y)
|
||||
base = z if z is not None else 0.0
|
||||
return base + kerb_height(rows[r][0], args.kerb, args.ramp_half_width, args.ramp_blend)
|
||||
h = kerb_height(rows[r][0], args.kerb, args.ramp_half_width, args.ramp_blend)
|
||||
span = inner - outer
|
||||
if abs(span) < 1e-9:
|
||||
return base + h
|
||||
t = min(1.0, max(0.0, (off - outer) / span))
|
||||
return base + h * t
|
||||
|
||||
def south_z(r, off, x, y):
|
||||
return sidewalk_z(r, off, SOUTH_GUTTER, SOUTH_SIDEWALK, x, y)
|
||||
|
||||
def north_z(r, off, x, y):
|
||||
return south_z(r, off, x, y)
|
||||
return sidewalk_z(r, off, NORTH_GUTTER, NORTH_SIDEWALK, x, y)
|
||||
|
||||
road = strip(rows, road_offsets, road_z, "Road_Hollywood", asphalt)
|
||||
sw_s = strip(rows, south_offsets, south_z, "Sidewalk_South", concrete)
|
||||
|
||||
Reference in new issue
Block a user