mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
feat(street-level): refine ground casting with the Google 3D surface
The raycast challenge on #768 showed bare-earth casting misplacing roads that are not at grade: lines floated over freeways in trenches and steep streets (rendered line pixels floating: Austin 8.5%, SF 27.1%, LA 43.8%), because a ~111 m terrain grid cannot see a 30 m-wide cut or a slope between its corners. meshSampler.js samples the rendered Google 3D surface (scene.sampleHeight, overlays excluded) once per ~11 m cell within 900 m of the camera, nearest first, in idle time within a 6 ms budget; misses are retried, not latched. groundCast.refineHeights blends it in, per point along a line: - mesh up to 3 m above bare earth is the road: follow it, 1 m above; - higher mesh (canopy, deck, roof): carry the road's offset from bare earth across from the road samples on both sides (up to 8 points); - otherwise bare earth + 2 m, as before. Lines, cones and the marker use it; tiles touched by new samples are redrawn one per idle slice with the no-blink swap. Rendered line pixels on the surface / floating, before -> after: Austin 90.0 -> 95.3% / 8.5 -> 0.7%; SF 69.0 -> 86.4% / 27.1 -> 9.1%; LA 52.3 -> 91.0% / 43.8 -> 4.0%; Innsbruck 95.0 -> 95.7% / 0.2 -> 0.2%; Sacramento 80.7 -> 79.3% / 3.8 -> 2.7%; buried lines stay under 1%. Cones floating in SF 44 -> 5%, buried at LA 59 -> 28%. Main-thread long tasks added during refinement: at most 134 ms (Austin, densest tile). Roads on elevated decks are still drawn at ground level under the deck. Unit tests cover the blend (trench, slope, canopy bridge, deck, bad probe), the finer cast with meshAt, and the sampler (once per cell, nearest first, overlays excluded, far cells skipped, misses not latched, idle when off). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
abbf69a512
commit
514788303d
13 files changed
+592
-30
No files matched your search
@@ -2,6 +2,8 @@ import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
GROUND_CAST_LIFT_M,
|
||||
MESH_LIFT_M,
|
||||
refineHeights,
|
||||
SURFACE_TERRAIN_ENTER_M,
|
||||
SURFACE_TERRAIN_EXIT_M,
|
||||
createGroundCaster,
|
||||
@@ -158,3 +160,62 @@ test('prepares run one at a time so neighbours share corners', async () => {
|
||||
test('the caster needs a terrain service', () => {
|
||||
assert.throws(() => createGroundCaster({ terrain: null }), TypeError);
|
||||
});
|
||||
|
||||
test('refineHeights follows the mesh where it is the road', () => {
|
||||
// A freeway trench 6 m below the bare-earth grid, and a steep street 2 m above it.
|
||||
const heights = refineHeights([
|
||||
{ dem: 100, mesh: 94 },
|
||||
{ dem: 100, mesh: 102 },
|
||||
{ dem: 100, mesh: null },
|
||||
]);
|
||||
assert.deepEqual(heights, [
|
||||
94 + MESH_LIFT_M,
|
||||
102 + MESH_LIFT_M,
|
||||
100 + GROUND_CAST_LIFT_M,
|
||||
]);
|
||||
});
|
||||
|
||||
test('refineHeights carries the road under a canopy from both sides', () => {
|
||||
// Road 1 m and 3 m above bare earth either side of a tree 12 m tall.
|
||||
const heights = refineHeights([
|
||||
{ dem: 50, mesh: 51 },
|
||||
{ dem: 50, mesh: 62 },
|
||||
{ dem: 50, mesh: 62 },
|
||||
{ dem: 50, mesh: 53 },
|
||||
]);
|
||||
assert.equal(heights[0], 51 + MESH_LIFT_M);
|
||||
assert.ok(Math.abs(heights[1] - (50 + 1 + 2 / 3 + MESH_LIFT_M)) < 1e-9);
|
||||
assert.ok(Math.abs(heights[2] - (50 + 1 + 4 / 3 + MESH_LIFT_M)) < 1e-9);
|
||||
assert.equal(heights[3], 53 + MESH_LIFT_M);
|
||||
});
|
||||
|
||||
test('refineHeights keeps bare earth under a deck with no road beside it, and rejects bad probes', () => {
|
||||
assert.deepEqual(
|
||||
refineHeights([
|
||||
{ dem: 20, mesh: 35 },
|
||||
{ dem: 20, mesh: 35 },
|
||||
]),
|
||||
[20 + GROUND_CAST_LIFT_M, 20 + GROUND_CAST_LIFT_M],
|
||||
);
|
||||
// A probe kilometres under the ground is not a road.
|
||||
assert.deepEqual(refineHeights([{ dem: 20, mesh: -14000 }]), [
|
||||
20 + GROUND_CAST_LIFT_M,
|
||||
]);
|
||||
});
|
||||
|
||||
test('castLine refines heights with sampled mesh and densifies finer', async () => {
|
||||
const caster = createGroundCaster({ terrain: fakeTerrain(), step: 0.001 });
|
||||
const line = [
|
||||
[10.0001, 50.0001],
|
||||
[10.0009, 50.0001],
|
||||
];
|
||||
await caster.prepareLines([line]);
|
||||
const plain = caster.castLine(line);
|
||||
const meshed = caster.castLine(line, { meshAt: () => 98 });
|
||||
assert.ok(meshed.length > plain.length, 'finer spacing with the mesh');
|
||||
for (let i = 2; i < meshed.length; i += 3)
|
||||
assert.equal(meshed[i], 98 + MESH_LIFT_M);
|
||||
// Unsampled points keep the bare-earth height.
|
||||
const unsampled = caster.castLine(line, { meshAt: () => undefined });
|
||||
assert.ok(unsampled[2] > 100 && unsampled[2] < 110);
|
||||
});
|
||||
Reference in new issue
Block a user