diff --git a/src/layers/streetLevel/groundCast.js b/src/layers/streetLevel/groundCast.js index 89dec4131..64a21280e 100644 --- a/src/layers/streetLevel/groundCast.js +++ b/src/layers/streetLevel/groundCast.js @@ -206,10 +206,18 @@ export function createGroundCaster({ return run; } - /** `prepare` for lines, including the points densifying adds. */ + /** + * `prepare` for lines, including the points densifying adds. `castLine` + * densifies finer when mesh heights refine it, and those points can fall in + * grid cells the coarse points skip, so both sets are prepared: otherwise + * the cast comes back null and the line stays draped over roofs and trees. + */ function prepareLines(lines, options) { return prepare( - lines.flatMap((coords) => densifyLine(coords)), + lines.flatMap((coords) => [ + ...densifyLine(coords), + ...densifyLine(coords, MESH_DENSIFY_DEG), + ]), options, ); } diff --git a/src/layers/streetLevel/groundCast.test.mjs b/src/layers/streetLevel/groundCast.test.mjs index 8dd831781..e53307d74 100644 --- a/src/layers/streetLevel/groundCast.test.mjs +++ b/src/layers/streetLevel/groundCast.test.mjs @@ -254,3 +254,31 @@ test('cancelling a prepare cancels its terrain request', async () => { assert.equal(seen.length, 1); assert.equal(seen[0], controller.signal, 'the tile signal reaches the proxy'); }); + +test('prepared lines always cast with the mesh, however their segments cross the grid (review IC8 P1)', async () => { + // Seeded walks with 50–300 m segments: the finer mesh densifying used to + // land in grid cells the coarse points skipped, so most of them came back + // null and stayed draped over roofs. + let seed = 7; + const random = () => { + seed = (seed * 16807) % 2147483647; + return seed / 2147483647; + }; + let failures = 0; + for (let n = 0; n < 300; n++) { + const caster = createGroundCaster({ terrain: fakeTerrain() }); + const line = [[10 + random() * 0.01, 50 + random() * 0.01]]; + for (let k = 0; k < 10; k++) { + const [lon, lat] = line[line.length - 1]; + const length = (50 + random() * 250) / 111_000; + const angle = random() * 2 * Math.PI; + line.push([ + lon + length * Math.cos(angle), + lat + length * Math.sin(angle), + ]); + } + assert.equal(await caster.prepareLines([line]), true); + if (caster.castLine(line, { meshAt: () => undefined }) === null) failures++; + } + assert.equal(failures, 0); +});