fix(import): correct clamping and bound local-file rendering

Normalize GeoJSON ground footprints, preserve explicit KML track heights,
and tessellate ground paths before native loading. Validate coordinates,
geometry types and decoded image budgets before allocating native resources;
strip loader-sensitive metadata and settle deferred failures and cancellation.

Render static pins in owned billboard collections instead of walking tens
of thousands of point entities each frame. Refine nearby ground heights in
batches, isolate imported pins from height picks, and release owned resources
and point selections on removal. Use safe ray origins above a hidden coarse
globe and retain valid mesh heights outside the current footprint.

Frame complete geometry with viewport-aware nadir arrivals, including walls
and rotated overlays; respect camera gestures and accept document file drops.
Strengthen real-loader, native 3D Tiles, paint, clock and lifecycle coverage.
Addresses both P1 and all 25 P2 findings in the follow-up review of #1122.
This commit is contained in:
Mazeyar Moeini Feizabadi committed 2026-10-15 04:10:41 +02:00
1 parent 4092c2e9a2
commit 5ea6d81310
19 files changed
+2023 -332

No files matched your search

+74
View File
@@ -0,0 +1,74 @@
// Inspect encoded headers before allowing the browser to decode local images.
export const MAX_IMPORT_IMAGE_PIXELS = 16 * 1024 * 1024;
const MAX_SIDE = 8192;
/** Width and height from a supported raster header; no image is decoded. */
export function importImageSize(bytes) {
const v = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
const ascii = (at, n) => String.fromCharCode(...bytes.subarray(at, at + n));
const u24 = (at) => bytes[at] | (bytes[at + 1] << 8) | (bytes[at + 2] << 16);
if (
bytes.length >= 24 &&
v.getUint32(0) === 0x89504e47 &&
ascii(12, 4) === 'IHDR'
)
return [v.getUint32(16), v.getUint32(20)];
if (bytes.length >= 10 && /^GIF8[79]a$/.test(ascii(0, 6)))
return [v.getUint16(6, true), v.getUint16(8, true)];
if (bytes.length >= 26 && ascii(0, 2) === 'BM') {
if (v.getUint32(14, true) === 12)
return [v.getUint16(18, true), v.getUint16(20, true)];
return [Math.abs(v.getInt32(18, true)), Math.abs(v.getInt32(22, true))];
}
if (bytes.length >= 30 && ascii(0, 4) === 'RIFF' && ascii(8, 4) === 'WEBP') {
const kind = ascii(12, 4);
if (kind === 'VP8X') return [1 + u24(24), 1 + u24(27)];
if (kind === 'VP8 ' && u24(23) === 0x2a019d)
return [v.getUint16(26, true) & 0x3fff, v.getUint16(28, true) & 0x3fff];
if (kind === 'VP8L' && bytes[20] === 0x2f) {
const bits = v.getUint32(21, true);
return [(bits & 0x3fff) + 1, ((bits >>> 14) & 0x3fff) + 1];
}
}
if (bytes.length >= 4 && v.getUint16(0) === 0xffd8) {
for (let at = 2; at + 4 <= bytes.length;) {
if (bytes[at++] !== 0xff) break;
while (bytes[at] === 0xff) at++;
const marker = bytes[at++];
if (marker === 0xd9 || marker === 0xda) break;
if (marker === 1 || (marker >= 0xd0 && marker <= 0xd7)) continue;
const length = v.getUint16(at);
if (length < 2 || at + length > bytes.length) break;
if (
[
0xc0, 0xc1, 0xc2, 0xc3, 0xc5, 0xc6, 0xc7, 0xc9, 0xca, 0xcb, 0xcd,
0xce, 0xcf,
].includes(marker) &&
length >= 8
)
return [v.getUint16(at + 5), v.getUint16(at + 3)];
at += length;
}
}
return null;
}
/** Charge each distinct encoded image once against the file's decoded budget. */
export function createImportImageBudget() {
const seen = new Set();
let pixels = 0;
return (key, bytes) => {
if (seen.has(key)) return;
const size = importImageSize(bytes);
if (
!size ||
size.some((n) => !Number.isInteger(n) || n <= 0 || n > MAX_SIDE) ||
pixels + size[0] * size[1] > MAX_IMPORT_IMAGE_PIXELS
)
throw new Error(
'Local images exceed the decoded image limit or have an unreadable header.',
);
pixels += size[0] * size[1];
seen.add(key);
};
}