Complete portable planetary presentation contracts
This commit is contained in:
parent
42fef7cdd7
commit
102403efac
74 changed files with 1166 additions and 31 deletions
21
web/planetary-tile.cross-runtime.test.mjs
Normal file
21
web/planetary-tile.cross-runtime.test.mjs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { decodePlanetarySurfaceTile } from "./planetary-tile.ts";
|
||||
|
||||
if (process.argv.length !== 3) throw new Error("Expected a generated CMPT fixture path");
|
||||
const bytes = await readFile(process.argv[2]);
|
||||
const arrayBuffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
||||
const tile = decodePlanetarySurfaceTile(arrayBuffer);
|
||||
assert.equal(tile.header.projectionVersion, 3);
|
||||
assert.equal(tile.header.layerId, 29);
|
||||
assert.equal(tile.header.projectionKind, 2);
|
||||
assert.equal(tile.header.interiorSize, 5);
|
||||
assert.equal(tile.header.borderSize, 1);
|
||||
assert.equal(tile.storageSize, 7);
|
||||
assert.equal(tile.samples.length, 49);
|
||||
assert.ok(tile.samples.some(sample => sample !== null));
|
||||
for (const sample of tile.samples) {
|
||||
if (sample === null) continue;
|
||||
assert.ok(Number.isFinite(sample.radialDisplacement));
|
||||
assert.ok(Number.isFinite(sample.unresolvedHeightBound));
|
||||
}
|
||||
7
web/planetary-tile.cross-runtime.test.mjs.meta
Normal file
7
web/planetary-tile.cross-runtime.test.mjs.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7bc397e5660d2b6448919c41983d35e8
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
39
web/planetary-tile.test.mjs
Normal file
39
web/planetary-tile.test.mjs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { decodePlanetarySurfaceTile } from "./planetary-tile.ts";
|
||||
|
||||
const bytes = new ArrayBuffer(152);
|
||||
const view = new DataView(bytes);
|
||||
let offset = 0;
|
||||
const u8 = value => view.setUint8(offset++, value);
|
||||
const u32 = value => { view.setUint32(offset, value, true); offset += 4; };
|
||||
const i32 = value => { view.setInt32(offset, value, true); offset += 4; };
|
||||
const u64 = value => { view.setBigUint64(offset, value, true); offset += 8; };
|
||||
const f32 = value => { view.setFloat32(offset, value, true); offset += 4; };
|
||||
const f64 = value => { view.setFloat64(offset, value, true); offset += 8; };
|
||||
|
||||
for (const character of "CMPT") u8(character.charCodeAt(0));
|
||||
u32(1); u64(0x1234n); u32(2); u32(17); i32(0);
|
||||
f64(0); f64(0); f64(1);
|
||||
i32(0); i32(0); i32(0); i32(2); i32(0);
|
||||
f32(10); f32(0.5); i32(4);
|
||||
u8(1);
|
||||
for (const value of [1, 0, 0]) f32(value);
|
||||
f32(8.4); f32(0.25);
|
||||
for (const value of [0, 0.1, 0]) f32(value);
|
||||
for (const value of [0.99, -0.1, 0]) f32(value);
|
||||
for (const value of [0.1, 0.8, 0.2, 2.5, 0.05]) f32(value);
|
||||
u8(0); u8(0); u8(0);
|
||||
assert.equal(offset, bytes.byteLength);
|
||||
|
||||
const tile = decodePlanetarySurfaceTile(bytes);
|
||||
assert.equal(tile.header.fieldVersion, 0x1234n);
|
||||
assert.equal(tile.header.layerId, 17);
|
||||
assert.equal(tile.storageSize, 2);
|
||||
assert.equal(tile.samples.length, 4);
|
||||
assert.equal(tile.samples[0]?.radialDisplacement, 0.25);
|
||||
assert.equal(tile.samples[1], null);
|
||||
|
||||
assert.throws(() => decodePlanetarySurfaceTile(bytes.slice(0, bytes.byteLength - 1)), /Truncated/);
|
||||
const trailing = new Uint8Array(bytes.byteLength + 1);
|
||||
trailing.set(new Uint8Array(bytes));
|
||||
assert.throws(() => decodePlanetarySurfaceTile(trailing.buffer), /trailing/);
|
||||
7
web/planetary-tile.test.mjs.meta
Normal file
7
web/planetary-tile.test.mjs.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 04c93ecab90d465499aa3d60722e6fc3
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
89
web/planetary-tile.ts
Normal file
89
web/planetary-tile.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
export const CULTMATH_PLANETARY_TILE_VERSION = 1;
|
||||
|
||||
export interface PlanetaryTileHeader {
|
||||
fieldVersion: bigint;
|
||||
projectionVersion: number;
|
||||
layerId: number;
|
||||
projectionKind: number;
|
||||
centerLongitude: number;
|
||||
centerLatitude: number;
|
||||
projectionScale: number;
|
||||
level: number;
|
||||
x: number;
|
||||
y: number;
|
||||
interiorSize: number;
|
||||
borderSize: number;
|
||||
footprintMeters: number;
|
||||
maximumUnresolvedHeight: number;
|
||||
}
|
||||
|
||||
export interface PlanetaryTileSample {
|
||||
direction: readonly [number, number, number];
|
||||
radius: number;
|
||||
radialDisplacement: number;
|
||||
tangentGradient: readonly [number, number, number];
|
||||
surfaceNormal: readonly [number, number, number];
|
||||
slope: number;
|
||||
ridge: number;
|
||||
gully: number;
|
||||
finestResolvedWavelength: number;
|
||||
unresolvedHeightBound: number;
|
||||
}
|
||||
|
||||
export interface PlanetarySurfaceTile {
|
||||
header: PlanetaryTileHeader;
|
||||
storageSize: number;
|
||||
samples: readonly (PlanetaryTileSample | null)[];
|
||||
}
|
||||
|
||||
export function decodePlanetarySurfaceTile(bytes: ArrayBuffer): PlanetarySurfaceTile {
|
||||
const view = new DataView(bytes);
|
||||
let offset = 0;
|
||||
const u8 = () => view.getUint8(offset++);
|
||||
const u32 = () => { const value = view.getUint32(offset, true); offset += 4; return value; };
|
||||
const i32 = () => { const value = view.getInt32(offset, true); offset += 4; return value; };
|
||||
const f32 = () => { const value = view.getFloat32(offset, true); offset += 4; return value; };
|
||||
const f64 = () => { const value = view.getFloat64(offset, true); offset += 8; return value; };
|
||||
const u64 = () => { const value = view.getBigUint64(offset, true); offset += 8; return value; };
|
||||
const vector3 = (): [number, number, number] => [f32(), f32(), f32()];
|
||||
const requireRemaining = (count: number) => {
|
||||
if (offset + count > view.byteLength) throw new Error("Truncated CultMath planetary tile");
|
||||
};
|
||||
|
||||
requireRemaining(8);
|
||||
if (String.fromCharCode(u8(), u8(), u8(), u8()) !== "CMPT") throw new Error("Not a CultMath planetary tile");
|
||||
const version = u32();
|
||||
if (version !== CULTMATH_PLANETARY_TILE_VERSION) throw new Error(`Unsupported CultMath planetary tile version ${version}`);
|
||||
requireRemaining(76);
|
||||
const header: PlanetaryTileHeader = {
|
||||
fieldVersion: u64(),
|
||||
projectionVersion: u32(),
|
||||
layerId: u32(),
|
||||
projectionKind: i32(),
|
||||
centerLongitude: f64(),
|
||||
centerLatitude: f64(),
|
||||
projectionScale: f64(),
|
||||
level: i32(), x: i32(), y: i32(), interiorSize: i32(), borderSize: i32(),
|
||||
footprintMeters: f32(), maximumUnresolvedHeight: f32(),
|
||||
};
|
||||
const count = i32();
|
||||
const storageSize = header.interiorSize + header.borderSize * 2;
|
||||
if (count !== storageSize * storageSize) throw new Error("CultMath planetary tile sample count does not match layout");
|
||||
const samples: (PlanetaryTileSample | null)[] = new Array(count);
|
||||
for (let index = 0; index < count; index++) {
|
||||
requireRemaining(1);
|
||||
if (u8() === 0) { samples[index] = null; continue; }
|
||||
requireRemaining(64);
|
||||
samples[index] = {
|
||||
direction: vector3(),
|
||||
radius: f32(),
|
||||
radialDisplacement: f32(),
|
||||
tangentGradient: vector3(),
|
||||
surfaceNormal: vector3(),
|
||||
slope: f32(), ridge: f32(), gully: f32(),
|
||||
finestResolvedWavelength: f32(), unresolvedHeightBound: f32(),
|
||||
};
|
||||
}
|
||||
if (offset !== view.byteLength) throw new Error("CultMath planetary tile has trailing bytes");
|
||||
return { header, storageSize, samples };
|
||||
}
|
||||
7
web/planetary-tile.ts.meta
Normal file
7
web/planetary-tile.ts.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f284e64628320844196b7aaea76475a4
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in a new issue