@ortho-earth/voxel 0.0.0-stage → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,289 @@
1
+ import { prepareGrid, transferables, checkVoxelData, checkClip, buildColormap, thrOf, frameAt } from "./grid.js";
2
+ export { prepare, checkVoxelData, checkClip, thrOf, buildColormap, colorStops, parseColor, quantize, toRgba8, valueRange, placeBox, placeRegion, unitAt, hprMat, enuBasis, ecefToLngLat, ecefEnuRows, inv3, mul3, rayBox, marchBlocks, COLORMAPS, COLORMAP_NAMES, SHELL_N, prepareGrid, maxOf, needsBricks, brickSpans, extract, parentIndex, frameValues, frameAt, BRICK, BRICK_LIMIT } from "./grid.js";
3
+ // 描き手(worker)の URL=入口(voxel-draw.js が兄弟を import)。字面は .js のまま=npm の dist では実体・repo の中では Vite が .ts に読み替える
4
+ const VOXEL_URL = new URL("./voxel-draw.js", import.meta.url).href;
5
+ export const VERSION = "0.4.0";
6
+ /** 楕円体表示の b/a(region の殻を本体と同じ形で作る) */
7
+ const R_AX_ELL = 1 - 1 / 298.257223563;
8
+ const rAxOf = (map) => { try {
9
+ return map.ellipsoidOn?.() ? R_AX_ELL : 1;
10
+ }
11
+ catch {
12
+ return 1;
13
+ } };
14
+ const shared = new WeakMap();
15
+ /** 手綱が全部外れてからオーバーレイを片付けるまでの猶予(ms)=場面の切り替え(remove → すぐ addData)で同じ名のオーバーレイを壊して作り直さない(render worker の片付けと新しい登録が競う) */
16
+ export const OVERLAY_LINGER_MS = 250;
17
+ function acquire(map, above, id, backend) {
18
+ let m = shared.get(map);
19
+ if (!m)
20
+ shared.set(map, (m = { ids: new Set() }));
21
+ if (m.ids.has(id))
22
+ throw new Error(`voxel: a layer with id "${id}" already exists`);
23
+ const k = above ? "above" : "below";
24
+ if (m[k]?.idle) {
25
+ clearTimeout(m[k].idle);
26
+ m[k].idle = null;
27
+ } // 片付けの猶予の中=そのまま使う
28
+ if (!m[k]) {
29
+ const ov = map.overlay(VOXEL_URL, { name: above ? "voxel:above" : "voxel", above, opts: { depth: true, backend } });
30
+ const slot = { ov, refs: 0, backend: null, errors: new Map(), idle: null };
31
+ ov.onmessage = (d) => {
32
+ const e = d;
33
+ if (e?.type === "backend") {
34
+ slot.backend = e.backend ?? null;
35
+ if (e.error)
36
+ console.error("[voxel] overlay failed:", e.error);
37
+ }
38
+ else if (e?.type === "error" && e.q != null) {
39
+ console.warn("[voxel]", e.q, e.error);
40
+ slot.errors.get(e.q)?.(e.error || "error");
41
+ }
42
+ };
43
+ m[k] = slot;
44
+ }
45
+ m[k].refs++;
46
+ m.ids.add(id);
47
+ return { ov: m[k].ov, slot: m[k], release() {
48
+ m.ids.delete(id);
49
+ const s = m[k];
50
+ if (!s || --s.refs > 0)
51
+ return;
52
+ s.idle = setTimeout(() => { if (s.refs > 0 || m[k] !== s)
53
+ return; s.ov.remove(); delete m[k]; }, OVERLAY_LINGER_MS); // 猶予の後に片付ける(その間に acquire が来れば続投)
54
+ } };
55
+ }
56
+ let reader = null, readerBroken = false, liveHandles = 0, readerIdle = null;
57
+ function spawnReader() {
58
+ if (reader)
59
+ return reader;
60
+ if (readerBroken || typeof Worker === "undefined")
61
+ return null;
62
+ try {
63
+ const w = new Worker(new URL("./voxel-reader.js", import.meta.url), { type: "module", name: "voxel-reader" });
64
+ const r = { w, pending: new Map(), seq: 0 };
65
+ w.onmessage = ({ data: m }) => { const p = r.pending.get(m.seq); if (!p)
66
+ return; r.pending.delete(m.seq); if (m.ok) {
67
+ const { seq: _s, ok: _o, ...rest } = m;
68
+ p.res(rest);
69
+ }
70
+ else
71
+ p.rej(new Error(m.error)); };
72
+ w.onerror = (e) => {
73
+ readerBroken = true;
74
+ reader = null;
75
+ try {
76
+ w.terminate();
77
+ }
78
+ catch { /* 既に死んでいる */ }
79
+ const err = new Error(`voxel: reader worker failed (${e?.message || "error"})`);
80
+ err.fallback = true;
81
+ for (const p of r.pending.values())
82
+ p.rej(err);
83
+ r.pending.clear();
84
+ console.warn("[voxel] reader worker unavailable, preparing on the main thread:", e?.message || e);
85
+ };
86
+ return (reader = r);
87
+ }
88
+ catch (e) {
89
+ readerBroken = true;
90
+ console.warn("[voxel] reader worker unavailable, preparing on the main thread:", e?.message || e);
91
+ return null;
92
+ }
93
+ }
94
+ const readerUsed = () => { liveHandles++; if (readerIdle) {
95
+ clearTimeout(readerIdle);
96
+ readerIdle = null;
97
+ } };
98
+ const readerFree = () => { if (--liveHandles > 0 || !reader)
99
+ return; readerIdle = setTimeout(() => { readerIdle = null; if (liveHandles || !reader)
100
+ return; reader.w.terminate(); reader = null; }, 5000); };
101
+ const isFileSource = (src) => typeof src === "string" || src instanceof URL || src instanceof ArrayBuffer || src instanceof Uint8Array;
102
+ async function load(src, o, rAx) {
103
+ if (isFileSource(src))
104
+ throw new Error("voxel: file sources (.nc / .zarr / .vox / .npy) are not read yet (stage 5); pass VoxelData");
105
+ const d = checkVoxelData(src);
106
+ const popts = { colormap: o.colormap, range: o.range ?? null, alphaRamp: o.alphaRamp ?? null, threshold: o.threshold ?? null, rAx };
107
+ const r = spawnReader();
108
+ if (r) {
109
+ try {
110
+ const seq = ++r.seq, req = { seq, data: d, opts: popts };
111
+ const p = await new Promise((res, rej) => { r.pending.set(seq, { res, rej }); r.w.postMessage(req); }); // data は写し=渡した人の配列は手放さない
112
+ return { ...p, reader: "worker" };
113
+ }
114
+ catch (e) {
115
+ if (!e?.fallback)
116
+ throw e;
117
+ }
118
+ }
119
+ return { ...prepareGrid(d, popts), reader: "main" };
120
+ }
121
+ const isVoxelHit = (id) => (t) => t.layer === id;
122
+ const checkEnum = (v, allowed, name) => { if (!allowed.includes(v))
123
+ throw new RangeError(`voxel: ${name} must be ${allowed.map(a => JSON.stringify(a)).join(" | ")}`); };
124
+ const MODE = { blocks: 0, volume: 1, iso: 2 };
125
+ /** 格子を上げ直さずに変えられる opts(setProps はこれだけなら "style" を送る) */
126
+ export const STYLE_KEYS = Object.freeze(["render", "opacity", "step", "threshold", "clip", "depthTest", "properties", "colormap", "alphaRamp", "time"]);
127
+ function checkOpts(opts) {
128
+ if (opts.render != null)
129
+ checkEnum(opts.render, ["blocks", "volume", "iso"], "render");
130
+ if (opts.altitude != null)
131
+ checkEnum(opts.altitude, ["relative", "absolute"], "altitude");
132
+ if (opts.step != null && !(typeof opts.step === "number" && opts.step > 0))
133
+ throw new RangeError("voxel: step must be a positive number (cells)");
134
+ if (opts.opacity != null && !(typeof opts.opacity === "number" && opts.opacity >= 0 && opts.opacity <= 1))
135
+ throw new RangeError("voxel: opacity must be 0..1");
136
+ if (opts.threshold != null && !Number.isFinite(opts.threshold))
137
+ throw new TypeError("voxel: threshold must be a finite number (data units)");
138
+ checkClip(opts.clip);
139
+ if (opts.time != null && opts.time !== "clock" && !(typeof opts.time === "number" && Number.isFinite(opts.time)))
140
+ throw new TypeError("voxel: time must be \"clock\" or a time in ms");
141
+ }
142
+ let anonSeq = 0;
143
+ /** map.addData(data, { kind: "voxel", … }) の実体 */
144
+ export function createVoxel(map, data, opts = {}, { backend = "auto" } = {}) {
145
+ checkOpts(opts);
146
+ if (!isFileSource(data))
147
+ checkVoxelData(data); // 形の間違いは今・同期に投げる(worker へ送ってからでは遅い)
148
+ const id = opts.id ?? `voxel${++anonSeq}`;
149
+ const { ov, slot, release } = acquire(map, !!opts.above, id, backend);
150
+ try {
151
+ ov.addLayerId?.(id);
152
+ }
153
+ catch (e) {
154
+ release();
155
+ throw e;
156
+ }
157
+ readerUsed();
158
+ let cur = data, props = { ...opts }, removed = false, seq = 0, visible = true, values = null, comps = 1, baked = false;
159
+ let qs = []; // 描き手に送った層の名前(一枚=id・ブリック=id#0, id#1, …)
160
+ // 時間の次元=見せるこま(opts.time="clock"(既定)は map.clock に付いて行く・数は固定)。こまの切り替えは "style" の frame だけ(上げ直さない)
161
+ let times = null, following = false;
162
+ const frameNow = () => { if (!times)
163
+ return 0; const t = props.time ?? "clock"; return frameAt(times, t === "clock" ? (map.clock?.time ?? Date.now()) : t); };
164
+ const showFrame = () => { const f = frameNow(); if (f === h.frame || removed)
165
+ return; h.frame = f; for (const q of qs)
166
+ ov.post({ type: "style", q, frame: f }); };
167
+ const onClock = () => showFrame();
168
+ const follow = (on) => { if (on === following || !map.clock)
169
+ return; following = on; if (on)
170
+ map.clock.on("tick", onClock).on("change", onClock);
171
+ else
172
+ map.clock.off("tick", onClock).off("change", onClock); };
173
+ const run = async (src, o) => {
174
+ const my = ++seq;
175
+ const p = await load(src, o, rAxOf(map));
176
+ if (removed || my !== seq)
177
+ return h;
178
+ h.dims = p.dims;
179
+ h.n = p.n;
180
+ h.anchor = p.anchor;
181
+ h.range = p.range;
182
+ h.reader = p.reader;
183
+ h.error = null;
184
+ h.shape = p.shape;
185
+ h.bricks = p.bricks?.length ?? 0;
186
+ times = h.times = p.times;
187
+ h.frame = frameNow();
188
+ follow(!!times && (o.time ?? "clock") === "clock");
189
+ cur = src;
190
+ values = src.values;
191
+ comps = p.comps;
192
+ baked = true;
193
+ const style = { mode: MODE[o.render ?? "volume"], thr: p.thr, step: o.step ?? 0.5, opacity: o.opacity ?? 1, clip: checkClip(o.clip), depthTest: o.depthTest !== false, properties: o.properties ?? null, range: p.range, on: visible, frame: h.frame, alt: o.altitude ?? (p.placed ? "absolute" : "relative") };
194
+ const next = p.bricks ? p.bricks.map((_, b) => `${id}#${b}`) : [id];
195
+ for (const q of qs)
196
+ if (!next.includes(q)) {
197
+ ov.post({ type: "remove", q });
198
+ slot.errors.delete(q);
199
+ } // 前の形の層(一枚 ↔ ブリック・ブリックの数)を片付ける
200
+ qs = next;
201
+ for (const q of qs)
202
+ slot.errors.set(q, e => { h.error = e; }); // 描き手の build の失敗(3D テクスチャの上限など)は非同期に届く=warn と手綱の error(ready は解決する)
203
+ if (!p.bricks)
204
+ ov.post({ type: "layer", q: id, shape: p.shape, dims: p.dims, comps: p.comps, tex: p.tex, frames: p.frames, cmap: p.cmap, anchor: p.anchor, A: p.A, radius: p.radius, geo: p.geo, vmax: p.vmax, ...style }, transferables(p));
205
+ else
206
+ p.bricks.forEach((b, i) => ov.post({ type: "layer", q: qs[i], layer: id, shape: p.shape, dims: b.dims, comps: p.comps, tex: b.tex, frames: b.frames, cmap: i ? p.cmap.slice() : p.cmap, anchor: b.anchor, A: b.A, radius: b.radius, geo: b.geo, vmax: b.vmax, off: b.off, own: b.own, full: p.dims, ...style }, [b.tex.buffer, b.A.buffer, ...(b.geo ? [b.geo.shell.buffer] : []), ...(b.frames ?? []).map(f => f.buffer)].filter((x, k, a) => a.indexOf(x) === k)));
207
+ return h;
208
+ };
209
+ // 見た目だけの変更=格子を上げ直さない(render・opacity・step・threshold・clip・depthTest・properties・colormap・alphaRamp)
210
+ const restyle = (p) => {
211
+ const s = {};
212
+ if ("render" in p)
213
+ s.mode = MODE[props.render ?? "volume"];
214
+ if ("opacity" in p)
215
+ s.opacity = props.opacity ?? 1;
216
+ if ("step" in p)
217
+ s.step = props.step ?? 0.5;
218
+ if ("threshold" in p)
219
+ s.thr = comps === 4 || !h.range ? 0 : thrOf(props.threshold, h.range);
220
+ if ("clip" in p)
221
+ s.clip = checkClip(props.clip);
222
+ if ("depthTest" in p)
223
+ s.depthTest = props.depthTest !== false;
224
+ if ("properties" in p)
225
+ s.properties = props.properties ?? null;
226
+ if ("time" in p && times) {
227
+ follow((props.time ?? "clock") === "clock");
228
+ const f = frameNow();
229
+ if (f !== h.frame) {
230
+ h.frame = f;
231
+ s.frame = f;
232
+ }
233
+ }
234
+ const cmap = "colormap" in p || "alphaRamp" in p ? buildColormap(props.colormap, props.alphaRamp ?? null) : null;
235
+ if (!Object.keys(s).length && !cmap)
236
+ return;
237
+ qs.forEach((q, i) => { const c = cmap && i < qs.length - 1 ? cmap.slice() : cmap; ov.post({ type: "style", q, ...s, ...(c ? { cmap: c } : {}) }, c ? [c.buffer] : []); });
238
+ };
239
+ // 当たりの value=main が持つ元の値(worker の答えは番号だけ)
240
+ const withValue = (t) => { const k = t.index + (h.frame || 0) * (h.n || 0); if (values && comps === 1 && t.index >= 0 && k < values.length) {
241
+ const v = values[k];
242
+ t.value = v;
243
+ t.properties = { ...t.properties, value: v };
244
+ } return t; };
245
+ const h = {
246
+ id, kind: "voxel", overlay: ov, dims: null, n: 0, anchor: null, range: null, reader: null, error: null, shape: null, bricks: 0, times: null, frame: 0,
247
+ get backend() { return slot.backend; },
248
+ ready: undefined,
249
+ setData: (d) => { if (!isFileSource(d))
250
+ checkVoxelData(d); return (h.ready = run(d, props)); },
251
+ setProps: (p) => {
252
+ checkOpts(p);
253
+ props = { ...props, ...p };
254
+ if (baked && !removed && Object.keys(p).every(k => STYLE_KEYS.includes(k))) {
255
+ restyle(p);
256
+ return h.ready;
257
+ }
258
+ return (h.ready = run(cur, props));
259
+ },
260
+ setVisible: (on) => { visible = !!on; if (!removed)
261
+ for (const q of qs)
262
+ ov.post({ type: "vis", q, on: visible }); return h; },
263
+ pick: async (x, y, r = 4) => (await map.pickOverlays(x, y, { radius: r, names: [ov.name] })).filter(isVoxelHit(id)).map(withValue),
264
+ remove: () => { if (removed)
265
+ return; removed = true; follow(false); for (const q of qs) {
266
+ ov.post({ type: "remove", q });
267
+ slot.errors.delete(q);
268
+ } ov.removeLayerId?.(id); release(); readerFree(); },
269
+ };
270
+ h.ready = run(data, props);
271
+ return h;
272
+ }
273
+ /** map.use(voxelPlugin()) → map.addData(data, { kind: "voxel", render?, colormap?, range?, threshold?, alphaRamp?, step?, opacity?, altitude?, depthTest?, above?, properties? }) */
274
+ export function voxelPlugin({ backend = "auto" } = {}) {
275
+ checkEnum(backend, ["auto", "webgpu", "webgl2"], "backend");
276
+ return {
277
+ name: "voxel",
278
+ version: VERSION,
279
+ provides: ["data.voxel"],
280
+ install(map) {
281
+ map.registry.dataKinds.add("voxel", { create: (m, data, o) => createVoxel(m, data, o, { backend }) });
282
+ return () => { try {
283
+ map.registry.dataKinds.remove("voxel");
284
+ }
285
+ catch { /* 既に外れた */ } };
286
+ },
287
+ };
288
+ }
289
+ export default voxelPlugin;
@@ -0,0 +1,16 @@
1
+ import type { VoxelMessage, VoxelPickResult, CamState, VoxelApi, PickContext, OverlayHost } from "./voxel-gl.js";
2
+ export type VoxelBackend = "webgpu" | "webgl2";
3
+ export interface VoxelDrawOpts {
4
+ depth?: boolean;
5
+ backend?: VoxelBackend | "auto";
6
+ }
7
+ export declare function init(canvas: OffscreenCanvas, opts?: VoxelDrawOpts, h?: OverlayHost | null): {
8
+ depth: true;
9
+ } | undefined;
10
+ export declare function message(d: VoxelMessage): void;
11
+ export declare function frame(cam: unknown, s: CamState, size: {
12
+ w: number;
13
+ h: number;
14
+ }, api: VoxelApi): boolean;
15
+ export declare function pick(x: number, y: number, r: number, ctx: PickContext | null | undefined): VoxelPickResult | null | Promise<VoxelPickResult | null>;
16
+ export declare function destroy(): void;
@@ -0,0 +1,77 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ // render worker はオーバーレイの名前ごとに一つのモジュール実体(URL の ?ov=名前)=全部外して同じ名前で立て直すと、この実体がそのまま使い回される。
10
+ // =init で状態を戻し、古い init の非同期は世代(gen)で捨てる(以前は dead が true のまま残り、立て直したオーバーレイが何も描かず probe も答えなかった)
11
+ let impl = null, queue = [], gen = 0;
12
+ const withQuery = (u) => { u.search = new URL(import.meta.url).search; return u.href; };
13
+ const GPU_URL = () => withQuery(new URL("./voxel-gpu.js", import.meta.url)), GL_URL = () => withQuery(new URL("./voxel-gl.js", import.meta.url));
14
+ const hasGpu = () => !!globalThis.navigator?.gpu;
15
+ export function init(canvas, opts = {}, h = null) {
16
+ const my = ++gen, dead = () => my !== gen;
17
+ impl?.m.destroy();
18
+ impl = null;
19
+ queue = [];
20
+ const pref = opts.backend ?? "auto", want = opts.depth !== false, t0 = performance.now();
21
+ (async () => {
22
+ try {
23
+ const tryGpu = pref === "webgpu" ? hasGpu() : pref === "auto" && hasGpu() && !!h?.gpuDevice?.(); // auto=本体の device がある時だけ
24
+ if (tryGpu) {
25
+ try {
26
+ const mod = await import(__rewriteRelativeImportExtension(/* @vite-ignore */ GPU_URL()));
27
+ if (dead())
28
+ return;
29
+ const m = mod.create();
30
+ await m.init(canvas, { depth: want }, h);
31
+ if (dead()) {
32
+ m.destroy();
33
+ return;
34
+ }
35
+ impl = { kind: "webgpu", m };
36
+ }
37
+ catch (e) {
38
+ if (pref === "webgpu")
39
+ throw e;
40
+ console.warn("[voxel] WebGPU unavailable, drawing with WebGL2:", e?.message || e);
41
+ }
42
+ }
43
+ else if (pref === "webgpu")
44
+ throw new Error("WebGPU is not available (navigator.gpu)");
45
+ if (!impl) {
46
+ const m = await import(__rewriteRelativeImportExtension(/* @vite-ignore */ GL_URL()));
47
+ if (dead())
48
+ return;
49
+ m.init(canvas, { depth: want }, h);
50
+ impl = { kind: "webgl2", m };
51
+ }
52
+ for (const d of queue)
53
+ impl.m.message(d);
54
+ queue = [];
55
+ console.info(`[voxel] backend=${impl.kind}${impl.kind === "webgpu" && impl.m.shared ? " (map's device)" : ""} in ${Math.round(performance.now() - t0)}ms`);
56
+ h?.post?.({ type: "backend", backend: impl.kind, shared: impl.kind === "webgpu" && impl.m.shared });
57
+ h?.requestDraw?.();
58
+ }
59
+ catch (e) {
60
+ const error = String(e?.message || e);
61
+ console.error("[voxel] overlay failed", e);
62
+ h?.post?.({ type: "backend", backend: null, error });
63
+ for (const d of queue)
64
+ if (d.type === "probe")
65
+ h?.post?.({ type: "pixels", id: d.id, error }); // 検定の probe を宙吊りにしない
66
+ queue = [];
67
+ }
68
+ })();
69
+ return want ? { depth: true } : undefined;
70
+ }
71
+ export function message(d) { if (impl)
72
+ impl.m.message(d);
73
+ else
74
+ queue.push(d); }
75
+ export function frame(cam, s, size, api) { return impl ? impl.m.frame(cam, s, size, api) : false; }
76
+ export function pick(x, y, r, ctx) { return impl ? impl.m.pick(x, y, r, ctx) : null; }
77
+ export function destroy() { gen++; impl?.m.destroy(); impl = null; queue = []; }
@@ -0,0 +1,184 @@
1
+ import type { OverlayFrameApi } from "@ortho-earth/globe";
2
+ import type { GeoGrid, Clip } from "./grid.js";
3
+ export interface OverlayHost {
4
+ depthGLSL?: string | null;
5
+ requestDraw?(): void;
6
+ post?(d: unknown): void;
7
+ gpuDevice?(): GPUDevice | null;
8
+ }
9
+ export interface CamState {
10
+ mvp: ArrayLike<number>;
11
+ eye?: ArrayLike<number> | null;
12
+ }
13
+ export type VoxelApi = Pick<OverlayFrameApi, "rAx" | "elevM" | "liftScale"> & {
14
+ depth?: OverlayFrameApi["depth"];
15
+ };
16
+ export interface PickContext {
17
+ s: CamState;
18
+ api: VoxelApi;
19
+ size: {
20
+ w: number;
21
+ h: number;
22
+ };
23
+ }
24
+ export type VoxelAltitude = "relative" | "absolute";
25
+ export interface VoxelStyle {
26
+ frame?: number;
27
+ mode?: 0 | 1 | 2;
28
+ thr?: number;
29
+ step?: number;
30
+ opacity?: number;
31
+ clip?: Clip | null;
32
+ depthTest?: boolean;
33
+ cmap?: Uint8Array;
34
+ properties?: Record<string, unknown> | null;
35
+ }
36
+ export interface VoxelLayerBody extends VoxelStyle {
37
+ layer?: string; /** ブリック=テクスチャの 0 番のセルの親での位置・描く範囲・親の dims */
38
+ off?: [number, number, number];
39
+ own?: [number, number, number, number, number, number];
40
+ full?: [number, number, number]; /** 最大値(R8・RGBA は α)=閾より下なら描かない */
41
+ vmax?: number; /** 時間の次元=こまごとのテクスチャ(frames[0]=tex)。style の frame で選ぶ(上げ直さない) */
42
+ frames?: Uint8Array[] | null;
43
+ shape: "box" | "region";
44
+ dims: [number, number, number];
45
+ comps: 1 | 4;
46
+ tex: Uint8Array;
47
+ cmap: Uint8Array;
48
+ anchor: [number, number, number];
49
+ alt?: VoxelAltitude;
50
+ A: Float64Array | ArrayLike<number>;
51
+ radius: number;
52
+ geo?: GeoGrid | null;
53
+ mode: 0 | 1 | 2;
54
+ thr: number;
55
+ step: number;
56
+ opacity: number;
57
+ range: [number, number];
58
+ on?: boolean;
59
+ }
60
+ export type VoxelMessage = ({
61
+ type: "layer";
62
+ q: string;
63
+ } & VoxelLayerBody) | ({
64
+ type: "style";
65
+ q: string;
66
+ } & VoxelStyle) | {
67
+ type: "remove";
68
+ q: string;
69
+ } | {
70
+ type: "vis";
71
+ q: string;
72
+ on: boolean;
73
+ } | {
74
+ type: "clear";
75
+ } | {
76
+ type: "probe";
77
+ id: unknown;
78
+ };
79
+ export interface VoxelPickResult {
80
+ id: number;
81
+ q: string;
82
+ layer: string;
83
+ index: number;
84
+ cell: [number, number, number];
85
+ value: number | null;
86
+ distance: number;
87
+ lngLat: [number, number];
88
+ properties: Record<string, unknown> & {
89
+ index: number;
90
+ cell: [number, number, number];
91
+ value: number | null;
92
+ };
93
+ }
94
+ export interface EnuBasis {
95
+ E: number[];
96
+ N: number[];
97
+ U: number[];
98
+ u: number[];
99
+ }
100
+ export declare const BOX: Float32Array<ArrayBufferLike>;
101
+ export declare function init(canvas: OffscreenCanvas, opts?: {
102
+ depth?: boolean;
103
+ }, h?: OverlayHost | null): {
104
+ depth: true;
105
+ } | undefined;
106
+ /** 錨の東北上 m → 単位球(列=東・北・上×1/R) */
107
+ export declare function enuBasis(lon: number, lat: number, rAx?: number): EnuBasis;
108
+ /** 法線の行列(セルの軸 → 東北上の単位ベクトル・列優先)と勾配の行列(A⁻ᵀ・列優先) */
109
+ export declare function normalMats(A: Float64Array, Ainv: Float64Array): {
110
+ nrm: Float32Array;
111
+ gradM: Float32Array;
112
+ };
113
+ export declare function message(d: VoxelMessage): void;
114
+ /** 刻み(box=セル・region=m)と最大の歩数(光線の空間で) */
115
+ export declare function stepsOf(L: {
116
+ shape: "box" | "region";
117
+ dims: [number, number, number];
118
+ mode: number;
119
+ step: number;
120
+ geo: GeoGrid | null;
121
+ }): {
122
+ step: number;
123
+ maxSteps: number;
124
+ };
125
+ /** 断面 → セル空間の範囲 */
126
+ export declare const clipBounds: (dims: [number, number, number], clip: Clip | null) => {
127
+ lo: number[];
128
+ hi: number[];
129
+ };
130
+ type BrickLike = {
131
+ dims: [number, number, number];
132
+ clip: Clip | null;
133
+ off: [number, number, number] | null;
134
+ own: [number, number, number, number, number, number] | null;
135
+ full: [number, number, number] | null;
136
+ };
137
+ /** 描く範囲(テクスチャのセル座標)=断面(親の割合)と持ち分の交わり。空なら null(そのブリックは描かない) */
138
+ export declare function drawBounds(L: BrickLike): {
139
+ lo: number[];
140
+ hi: number[];
141
+ } | null;
142
+ /** 閾より下しか無い層(ブリック)=描かない(vmax=R8 の 0〜255。RGBA は α 0 の時だけ) */
143
+ export declare const isEmpty: (L: {
144
+ vmax: number;
145
+ thr: number;
146
+ comps: 1 | 4;
147
+ }) => boolean;
148
+ /** 目(東北上 m)から層の中心まで=奥から描く順(同じ大きさの箱が並ぶブリックは中心の遠い順で正しく重なる) */
149
+ export declare function centerDist(L: {
150
+ dims: [number, number, number];
151
+ A: ArrayLike<number>;
152
+ off: [number, number, number] | null;
153
+ geo: GeoGrid | null;
154
+ }, enu: number[]): number;
155
+ /** 当たり(層の中の番号)→ 親の層の id・番号・セル(ブリックは off と full で戻す) */
156
+ export declare function pickedCell(q: string, L: {
157
+ layer: string | null;
158
+ dims: [number, number, number];
159
+ off: [number, number, number] | null;
160
+ full: [number, number, number] | null;
161
+ }, index: number): {
162
+ layer: string;
163
+ index: number;
164
+ cell: [number, number, number];
165
+ };
166
+ export declare function frame(cam: unknown, s: CamState, { w, h }: {
167
+ w: number;
168
+ h: number;
169
+ }, api: VoxelApi): boolean;
170
+ /** 当たりの番号 → セル・経緯度(セルの中心=box は A で・region は経緯度の枠で) */
171
+ export declare function hitOf(L: {
172
+ dims: [number, number, number];
173
+ A: Float64Array;
174
+ anchor: [number, number, number];
175
+ geo: GeoGrid | null;
176
+ properties: Record<string, unknown> | null;
177
+ off?: [number, number, number] | null;
178
+ }, index: number): {
179
+ cell: [number, number, number];
180
+ lngLat: [number, number];
181
+ };
182
+ export declare function pick(x: number, y: number, r: number, ctx: PickContext | null | undefined): VoxelPickResult | null;
183
+ export declare function destroy(): void;
184
+ export {};