@playcanvas/splat-transform
Version:
Library and CLI tool for 3D Gaussian splat format conversion and transformation
1,154 lines (1,143 loc) • 4.22 MB
JavaScript
'use strict';
var playcanvas = require('playcanvas');
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
const toBase64 = (bytes) => {
// Node.js environment
if (typeof Buffer !== 'undefined') {
return Buffer.from(bytes).toString('base64');
}
// Browser environment - chunk to avoid call stack limits
let binary = '';
const chunkSize = 0x8000; // 32KB chunks
for (let i = 0; i < bytes.length; i += chunkSize) {
const chunk = bytes.subarray(i, i + chunkSize);
binary += String.fromCharCode(...chunk);
}
return btoa(binary);
};
/**
* Format a duration in milliseconds as a human-readable string.
*
* - Sub-minute durations render in seconds (e.g. `1.234s`).
* - Sub-hour durations render as `MmS.SSSs`.
* - Otherwise as `HhMmS.SSSs`.
*
* @param ms - The duration in milliseconds.
* @returns The formatted string.
*/
const fmtTime = (ms) => {
if (!Number.isFinite(ms) || ms < 0)
return `${ms}ms`;
if (ms < 60_000)
return `${(ms / 1000).toFixed(3)}s`;
const h = Math.floor(ms / 3_600_000);
const m = Math.floor((ms % 3_600_000) / 60_000);
const s = ((ms % 60_000) / 1000).toFixed(3);
return h > 0 ? `${h}h${m}m${s}s` : `${m}m${s}s`;
};
/**
* Format a byte count using binary (1024-based) units.
*
* @param n - The number of bytes.
* @returns The formatted string (e.g. `1.5MB`).
*/
const fmtBytes = (n) => {
if (!Number.isFinite(n) || n < 0)
return `${n}B`;
if (n < 1024)
return `${n}B`;
if (n < 1024 * 1024)
return `${(n / 1024).toFixed(1)}KB`;
if (n < 1024 * 1024 * 1024)
return `${(n / (1024 * 1024)).toFixed(1)}MB`;
return `${(n / (1024 * 1024 * 1024)).toFixed(2)}GB`;
};
/**
* Format a distance in metres as a human-readable string, picking the most
* appropriate unit (mm/cm/m/km).
*
* @param m - The distance in metres.
* @returns The formatted string.
*/
const fmtDistance = (m) => {
if (!Number.isFinite(m))
return `${m}m`;
const abs = Math.abs(m);
if (abs === 0)
return '0m';
if (abs < 0.01)
return `${+(m * 1000).toPrecision(3)}mm`;
if (abs < 1)
return `${+(m * 100).toPrecision(3)}cm`;
if (abs < 1000)
return `${+m.toPrecision(3)}m`;
return `${+(m / 1000).toPrecision(3)}km`;
};
/**
* Format a count using SI suffixes (K/M/B/T) above 1000.
*
* @param n - The count to format.
* @returns The formatted string.
*/
const fmtCount = (n) => {
if (!Number.isFinite(n))
return `${n}`;
const abs = Math.abs(n);
if (abs < 1000)
return `${n}`;
if (abs < 1e6)
return `${+(n / 1e3).toPrecision(3)}K`;
if (abs < 1e9)
return `${+(n / 1e6).toPrecision(3)}M`;
if (abs < 1e12)
return `${+(n / 1e9).toPrecision(3)}B`;
return `${+(n / 1e12).toPrecision(3)}T`;
};
const now = () => {
if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
return performance.now();
}
return Date.now();
};
const fmtArgs = (args) => {
return args.map((a) => {
if (a instanceof Error)
return a.stack ?? a.message;
if (typeof a === 'string')
return a;
if (typeof a === 'number' || typeof a === 'boolean' || a == null)
return String(a);
try {
return JSON.stringify(a);
}
catch {
return String(a);
}
}).join(' ');
};
/**
* Default no-op renderer. Used when no other renderer is installed (e.g. in
* library/embedded contexts where the host wants to consume `LogEvent`s
* directly via {@link Logger.setRenderer} but hasn't done so yet). Drops
* every event silently.
*/
class NullRenderer {
handle(_event) { }
}
const verbosityRank = {
quiet: 0,
normal: 1,
verbose: 2
};
const messageMinVerbosity = {
error: 'quiet',
warn: 'quiet',
info: 'normal',
debug: 'verbose'
};
/**
* Active-scope manager and message router. The single shared instance lives
* inside this module; the public `logger` surface is a thin façade over it.
*/
class LoggerCore {
/** Stack of currently-open scopes (innermost last). */
stack = [];
renderer = new NullRenderer();
verbosity = 'normal';
setRenderer(r) {
this.renderer = r;
}
setVerbosity(v) {
this.verbosity = v;
}
getVerbosity() {
return this.verbosity;
}
/**
* Whether a message at `level` would be emitted at the current
* verbosity. Primary use: the `logger` façade calls this before
* formatting arguments so filtered `info`/`warn`/`debug` calls don't
* allocate the joined string that {@link emit} would only throw away.
*
* @param level - The message level to test.
* @returns `true` if a message at `level` would reach the renderer.
*/
isLevelVisible(level) {
return verbosityRank[this.verbosity] >= verbosityRank[messageMinVerbosity[level]];
}
/**
* Hand the event to the renderer. Lifecycle events (`scopeStart`,
* `scopeEnd`, `barStart`, `barTick`, `barEnd`) and `output` are always
* forwarded; presentation policy (e.g. hiding successful `scopeEnd`
* footers at non-`verbose` verbosity) lives in the renderer so
* embedders consuming the event stream see a complete, faithful
* record of scope and bar lifecycles. `message` is assumed already
* gated at the façade via {@link LoggerCore.isLevelVisible} (so
* callers can skip formatting args for filtered levels); anything
* that reaches here is passed through.
*
* @param event - The event to deliver.
*/
emit(event) {
this.renderer.handle(event);
}
/**
* Pop the scope at the top of the stack (no-op if empty) and emit the
* matching `*End` event.
*
* @param failed - When true, mark the closed scope as having failed.
*/
popScope(failed = false) {
if (this.stack.length === 0)
return;
const top = this.stack[this.stack.length - 1];
this.stack.pop();
const durationMs = now() - top.start;
if (top.kind === 'bar') {
this.emit({
kind: 'barEnd',
depth: top.depth,
name: top.name,
durationMs,
current: top.current,
total: top.total,
failed
});
return;
}
const numbering = top.index !== undefined && top.total !== undefined ?
{ index: top.index, total: top.total } :
{};
this.emit({ kind: 'scopeEnd', depth: top.depth, name: top.name, durationMs, failed, ...numbering });
}
/**
* Open a named, timed group at the current depth. Pass
* `{ index, total }` to render the group as part of a numbered series
* (e.g. `[2/5] name`); both must be present together.
*
* @param name - The group name.
* @param options - Optional configuration.
* @param options.index - 1-based position in the numbered series.
* @param options.total - Total length of the numbered series.
* @returns A handle for closing the group and writing nested log entries.
*/
pushGroup(name, options = {}) {
const { index, total } = options;
if ((index === undefined) !== (total === undefined)) {
throw new Error('logger.group: { index, total } must be passed together');
}
const depth = this.stack.length;
const numbering = index !== undefined && total !== undefined ? { index, total } : undefined;
const scope = numbering ?
{ kind: 'group', name, depth, start: now(), index: numbering.index, total: numbering.total } :
{ kind: 'group', name, depth, start: now() };
this.stack.push(scope);
this.emit({ kind: 'scopeStart', depth, name, ...(numbering ?? {}) });
return this.makeGroup(scope);
}
/**
* Open a labelled progress bar at the current stack depth (i.e. nested
* directly under whatever scope is currently on top of the stack). This
* is a pure-push operation: it does not pop or auto-close anything.
* Callers control nesting purely by the order in which they open and
* close scopes.
*
* @param name - The bar's label, displayed alongside the progress indicator.
* @param total - Total number of ticks the bar will report before completing.
* A `total` of 0 is allowed (e.g. processing an empty payload); both
* `LoggerCore` and `TextRenderer` already handle non-positive totals.
* @returns A handle for advancing and closing the bar.
*/
pushBar(name, total) {
const scope = {
kind: 'bar',
name,
depth: this.stack.length,
start: now(),
total: Math.max(0, total),
current: 0
};
this.stack.push(scope);
this.emit({ kind: 'barStart', depth: scope.depth, name: scope.name, total: scope.total });
return this.makeBar(scope);
}
makeBar(scope) {
let closed = false;
// Bars are strictly LIFO from a renderer's perspective: a `TextRenderer`
// (or any other line-based renderer) only tracks one active bar line,
// so ticking a bar that isn't currently on top of the stack would
// corrupt whatever inner bar is. We still update `scope.current`
// internally so the recap line at `barEnd` is accurate, but we
// suppress `barTick` emission unless this bar is actually on top.
const isTopOfStack = () => this.stack[this.stack.length - 1] === scope;
const handle = {
tick: (n = 1) => {
if (closed)
return;
if (this.stack.indexOf(scope) === -1) {
// scope was popped from underneath us (e.g. a sibling
// bar opened inside a function call). Silently retire
// the handle so further ticks are no-ops.
closed = true;
return;
}
const next = Math.min(scope.total, scope.current + Math.max(0, n));
if (next === scope.current)
return;
scope.current = next;
if (!isTopOfStack())
return;
this.emit({ kind: 'barTick', depth: scope.depth, name: scope.name, current: scope.current, total: scope.total });
},
update: (current) => {
if (closed)
return;
if (this.stack.indexOf(scope) === -1) {
closed = true;
return;
}
const next = Math.min(scope.total, Math.max(0, current));
if (next === scope.current)
return;
scope.current = next;
if (!isTopOfStack())
return;
this.emit({ kind: 'barTick', depth: scope.depth, name: scope.name, current: scope.current, total: scope.total });
},
end: () => {
if (closed)
return;
closed = true;
const idx = this.stack.indexOf(scope);
if (idx === -1)
return;
while (this.stack.length > idx + 1)
this.popScope(true);
this.popScope();
},
[Symbol.dispose]: () => handle.end()
};
return handle;
}
makeGroup(scope) {
let closed = false;
const handle = {
end: () => {
if (closed)
return;
closed = true;
const idx = this.stack.indexOf(scope);
if (idx === -1)
return;
while (this.stack.length > idx + 1)
this.popScope(true);
this.popScope();
},
[Symbol.dispose]: () => handle.end()
};
return handle;
}
message(level, text) {
this.emit({ kind: 'message', depth: this.stack.length, level, text });
if (level === 'error')
this.unwindAll(true);
}
/**
* Pop every open scope, emitting end-events with optional `failed` flag.
* Called automatically on `logger.error(...)` so that aborted work renders
* a clean trail of `(failed)` markers without callers needing try/finally.
*
* @param failed - When true, mark every closed scope as having failed.
*/
unwindAll(failed = false) {
while (this.stack.length > 0)
this.popScope(failed);
}
output(text) {
this.emit({ kind: 'output', text });
}
}
const core = new LoggerCore();
/**
* Public logger surface.
*
* Open named, timed scopes with {@link Logger.group}. Pass `{ index, total }`
* to render the group as part of a numbered series. Indeterminate progress is
* reported with {@link Logger.bar}. Free-form messages route through `info` /
* `warn` / `error` / `debug`, indented under whatever is on top of the
* active-scope stack.
*
* Both `group` and `bar` are pure-push operations: opening a new scope simply
* places it on top of the stack without auto-closing siblings, so call order
* directly determines nesting. Close scopes with `handle.end()` after the
* body. Callers that route failures through {@link Logger.error} get scope
* cleanup for free; embedders that swallow exceptions should call
* {@link Logger.unwindAll} from their catch to close every still-open scope.
*/
const logger = {
/**
* Open a named, timed scope. Returns a {@link Group} handle. Call `end()`
* to close it. Group children indent automatically based on call depth.
*
* Pass `{ index, total }` to render the group as part of a numbered
* series (e.g. `[2/5] name`). Both fields must be supplied together.
*
* @param name - The group name.
* @param options - Optional configuration.
* @param options.index - 1-based position in the numbered series.
* @param options.total - Total length of the numbered series.
* @returns A handle for closing the group and writing nested log entries.
*/
group(name, options) {
return core.pushGroup(name, options);
},
/**
* Open a labelled progress bar nested directly under whatever scope is
* currently on top of the active-scope stack. Renders as a single line
* at child indent.
*
* Like {@link Logger.group}, this is a pure-push operation: it does not
* close any sibling already on the stack. Close with `bar.end()`, or let
* an enclosing group's `end()` / {@link Logger.unwindAll} pop it.
*
* @param name - The bar's label.
* @param total - Expected number of ticks (or absolute total when using
* {@link Bar.update}).
* @returns A handle for advancing and closing the bar.
*/
bar(name, total) {
return core.pushBar(name, total);
},
/**
* Emit an info message indented under the innermost active scope.
* @param args - Message parts (joined with a space).
*/
info(...args) {
if (!core.isLevelVisible('info'))
return;
core.message('info', fmtArgs(args));
},
/**
* Emit a warning indented under the innermost active scope.
* @param args - Message parts.
*/
warn(...args) {
if (!core.isLevelVisible('warn'))
return;
core.message('warn', fmtArgs(args));
},
/**
* Emit an error message. Always shown, regardless of verbosity. Triggers
* an automatic unwind of all open scopes, marking each as failed.
* @param args - Message parts.
*/
error(...args) {
core.message('error', fmtArgs(args));
},
/**
* Emit a debug message. Shown only at `verbose` verbosity.
* @param args - Message parts.
*/
debug(...args) {
if (!core.isLevelVisible('debug'))
return;
core.message('debug', fmtArgs(args));
},
/**
* Emit a logical unit of pipeable output (typically one line, or a
* multi-line block treated as a single unit). The renderer terminates
* each unit with a newline, so callers should not include a trailing
* `\n`. Always shown, regardless of verbosity.
* @param text - The text to emit (without a trailing newline).
*/
output(text) {
core.output(text);
},
/**
* Replace the active renderer. Embedders install their own renderer here
* to consume `LogEvent`s; the default renderer is a no-op. Renderers
* receive every scope/bar lifecycle event regardless of verbosity, so
* progress UIs can rely on `scopeStart`/`scopeEnd` and `barStart`/`barEnd`
* to manage their state.
* @param r - The renderer to install.
*/
setRenderer(r) {
core.setRenderer(r);
},
/**
* Set verbosity: `quiet` (errors and warnings), `normal` (default),
* `verbose` (includes debug).
* @param v - The verbosity level.
*/
setVerbosity(v) {
core.setVerbosity(v);
},
/**
* Close every open scope and bar, optionally marking them as failed.
* Use this from an embedder's catch when an exception is being swallowed
* (rather than rethrown into a `logger.error()` call), to prevent
* dangling scopes from corrupting subsequent output.
* @param failed - When true, mark every closed scope as having failed.
*/
unwindAll(failed = false) {
core.unwindAll(failed);
},
/**
* Get the current verbosity level.
* @returns The active verbosity level.
*/
getVerbosity() {
return core.getVerbosity();
}
};
const sigmoid$1 = (v) => 1 / (1 + Math.exp(-v));
const _tv = new playcanvas.Vec3();
const _sv = new playcanvas.Vec3();
/**
* A source-to-engine coordinate transform comprising translation, rotation
* and uniform scale. Lives alongside a DataTable to describe how raw
* column data maps to PlayCanvas engine coordinates.
*
* @example
* ```ts
* const t = new Transform().fromEulers(0, 0, 180);
* console.log(t.isIdentity()); // false
*
* const inv = t.clone().invert();
* console.log(t.mul(inv).isIdentity()); // true
* ```
*/
class Transform {
translation;
rotation;
scale;
constructor(translation, rotation, scale) {
this.translation = translation ? translation.clone() : new playcanvas.Vec3();
this.rotation = rotation ? rotation.clone() : new playcanvas.Quat();
this.scale = scale ?? 1;
}
/**
* Sets this transform to a rotation-only transform from Euler angles in degrees.
*
* @param x - Rotation around X axis in degrees.
* @param y - Rotation around Y axis in degrees.
* @param z - Rotation around Z axis in degrees.
* @returns This transform (for chaining).
*/
fromEulers(x, y, z) {
this.translation.set(0, 0, 0);
this.rotation.setFromEulerAngles(x, y, z);
this.scale = 1;
return this;
}
/**
* Creates a deep copy of this transform.
*
* @returns A new Transform with the same values.
*/
clone() {
return new Transform(this.translation, this.rotation, this.scale);
}
/**
* Tests whether this transform equals another within the given tolerance.
* Quaternion comparison accounts for double-cover (q and -q represent
* the same rotation).
*
* @param other - The transform to compare against.
* @param epsilon - Floating-point tolerance. Defaults to 1e-6.
* @returns True if the transforms are equal within the tolerance.
*/
equals(other, epsilon = 1e-6) {
const ta = this.translation;
const tb = other.translation;
if (Math.abs(ta.x - tb.x) > epsilon || Math.abs(ta.y - tb.y) > epsilon || Math.abs(ta.z - tb.z) > epsilon) {
return false;
}
const ra = this.rotation;
const rb = other.rotation;
const dot = ra.x * rb.x + ra.y * rb.y + ra.z * rb.z + ra.w * rb.w;
if (Math.abs(dot) < 1 - epsilon) {
return false;
}
if (Math.abs(this.scale - other.scale) > epsilon) {
return false;
}
return true;
}
/**
* Tests whether this transform is effectively identity within the given tolerance.
*
* @param epsilon - Floating-point tolerance. Defaults to 1e-6.
* @returns True if identity within the tolerance.
*/
isIdentity(epsilon = 1e-6) {
return this.equals(Transform.IDENTITY, epsilon);
}
/**
* Inverts this transform in-place.
*
* @returns This transform (for chaining).
*/
invert() {
if (this.scale === 0) {
throw new Error('Cannot invert a Transform with scale 0');
}
this.scale = 1 / this.scale;
this.rotation.invert();
this.translation.mulScalar(-this.scale);
this.rotation.transformVector(this.translation, this.translation);
return this;
}
/**
* Sets this transform to the composition of a * b. Handles aliasing
* (either a or b may be this).
*
* @param a - The first (left) transform.
* @param b - The second (right) transform.
* @returns This transform (for chaining).
*/
mul2(a, b) {
// Translation must be computed first using original a.rotation
a.rotation.transformVector(b.translation, _tv);
_tv.mulScalar(a.scale).add(a.translation);
this.rotation.mul2(a.rotation, b.rotation);
this.scale = a.scale * b.scale;
this.translation.copy(_tv);
return this;
}
/**
* Sets this transform to this * other.
*
* @param other - The transform to multiply with.
* @returns This transform (for chaining).
*/
mul(other) {
return this.mul2(this, other);
}
/**
* Transforms a point by this TRS transform: result = translation + rotation * (scale * point).
*
* @param point - The input point.
* @param result - The Vec3 to write the result into (may alias point).
* @returns The transformed point.
*/
transformPoint(point, result) {
result.copy(point).mulScalar(this.scale);
this.rotation.transformVector(result, result);
result.add(this.translation);
return result;
}
/**
* Fills the provided Mat4 with the TRS matrix for this transform.
*
* @param result - The Mat4 to fill.
* @returns The filled Mat4.
*/
getMatrix(result) {
_sv.set(this.scale, this.scale, this.scale);
return result.setTRS(this.translation, this.rotation, _sv);
}
static freeze(t) {
Object.freeze(t.translation);
Object.freeze(t.rotation);
return Object.freeze(t);
}
static IDENTITY = Transform.freeze(new Transform());
/**
* PLY coordinate convention: 180-degree rotation around Z.
* Used by formats that store Gaussian data in PLY-style coordinates:
* PLY, splat, KSplat, SPZ, and SOG.
*/
static PLY = Transform.freeze(new Transform().fromEulers(0, 0, 180));
}
/**
* Partition indices around the k-th smallest element using quickselect
* (median-of-three pivot selection).
*
* After this call, `idx[k]` holds the index of the k-th smallest value
* in `data`, and all indices before k map to smaller-or-equal values.
*
* @param data - The data array to use for comparison values.
* @param idx - The index array to partition (mutated in place).
* @param k - The target partition index.
* @returns The index value at position k after partitioning.
*/
const quickselect = (data, idx, k) => {
const valAt = (p) => data[idx[p]];
const swap = (i, j) => {
const t = idx[i];
idx[i] = idx[j];
idx[j] = t;
};
const n = idx.length;
let l = 0;
let r = n - 1;
while (true) {
if (r <= l + 1) {
if (r === l + 1 && valAt(r) < valAt(l))
swap(l, r);
return idx[k];
}
const mid = (l + r) >>> 1;
swap(mid, l + 1);
if (valAt(l) > valAt(r))
swap(l, r);
if (valAt(l + 1) > valAt(r))
swap(l + 1, r);
if (valAt(l) > valAt(l + 1))
swap(l, l + 1);
let i = l + 1;
let j = r;
const pivotIdxVal = valAt(l + 1);
const pivotIdx = idx[l + 1];
while (true) {
do {
i++;
} while (i <= r && valAt(i) < pivotIdxVal);
do {
j--;
} while (j >= l && valAt(j) > pivotIdxVal);
if (j < i)
break;
swap(i, j);
}
idx[l + 1] = idx[j];
idx[j] = pivotIdx;
if (j >= k)
r = j - 1;
if (j <= k)
l = i;
}
};
/* eslint-disable indent */
const kSqrt03_02 = Math.sqrt(3.0 / 2.0);
const kSqrt01_03 = Math.sqrt(1.0 / 3.0);
const kSqrt02_03 = Math.sqrt(2.0 / 3.0);
const kSqrt04_03 = Math.sqrt(4.0 / 3.0);
const kSqrt01_04 = Math.sqrt(1.0 / 4.0);
const kSqrt03_04 = Math.sqrt(3.0 / 4.0);
const kSqrt01_05 = Math.sqrt(1.0 / 5.0);
const kSqrt03_05 = Math.sqrt(3.0 / 5.0);
const kSqrt06_05 = Math.sqrt(6.0 / 5.0);
const kSqrt08_05 = Math.sqrt(8.0 / 5.0);
const kSqrt09_05 = Math.sqrt(9.0 / 5.0);
const kSqrt01_06 = Math.sqrt(1.0 / 6.0);
const kSqrt05_06 = Math.sqrt(5.0 / 6.0);
const kSqrt03_08 = Math.sqrt(3.0 / 8.0);
const kSqrt05_08 = Math.sqrt(5.0 / 8.0);
const kSqrt09_08 = Math.sqrt(9.0 / 8.0);
const kSqrt05_09 = Math.sqrt(5.0 / 9.0);
const kSqrt08_09 = Math.sqrt(8.0 / 9.0);
const kSqrt01_10 = Math.sqrt(1.0 / 10.0);
const kSqrt03_10 = Math.sqrt(3.0 / 10.0);
const kSqrt01_12 = Math.sqrt(1.0 / 12.0);
const kSqrt04_15 = Math.sqrt(4.0 / 15.0);
const kSqrt01_16 = Math.sqrt(1.0 / 16.0);
const kSqrt15_16 = Math.sqrt(15.0 / 16.0);
const kSqrt01_18 = Math.sqrt(1.0 / 18.0);
const kSqrt01_60 = Math.sqrt(1.0 / 60.0);
const dp = (n, start, a, b) => {
let sum = 0;
for (let i = 0; i < n; i++) {
sum += a[start + i] * b[i];
}
return sum;
};
const coeffsIn = new Float32Array(15);
// Build a sparse representation of the SH rotation matrices. For axis-aligned
// rotations the matrices are highly sparse, so iterating only over non-zero
// entries is significantly faster than the full dot-product approach.
const buildSparse = (sh1, sh2, sh3) => {
const counts = [];
const indices = [];
const values = [];
const addBand = (matrix, size, base) => {
for (let i = 0; i < size; i++) {
let count = 0;
for (let j = 0; j < size; j++) {
if (Math.abs(matrix[i][j]) > 1e-10) {
indices.push(base + j);
values.push(matrix[i][j]);
count++;
}
}
counts.push(count);
}
};
addBand(sh1, 3, 0);
addBand(sh2, 5, 3);
addBand(sh3, 7, 8);
return { counts, indices, values };
};
// Returns true if the rotation matrix is a signed permutation (every entry is 0 or ±1),
// i.e. the rotation maps each axis to ±another axis (multiples of 90°).
const isAxisAligned = (rot) => {
for (let i = 0; i < 9; i++) {
const a = Math.abs(rot[i]);
if (a > 0.01 && Math.abs(a - 1) > 0.01)
return false;
}
return true;
};
// Rotate spherical harmonics up to band 3 based on https://github.com/andrewwillmott/sh-lib
//
// This implementation calculates the rotation factors during construction which can then
// be used to rotate multiple spherical harmonics cheaply.
class RotateSH {
apply;
constructor(mat) {
const rot = mat.data;
// band 1
const sh1 = [
[rot[4], -rot[7], rot[1]],
[-rot[5], rot[8], -rot[2]],
[rot[3], -rot[6], rot[0]]
];
// band 2
const sh2 = [[
kSqrt01_04 * ((sh1[2][2] * sh1[0][0] + sh1[2][0] * sh1[0][2]) + (sh1[0][2] * sh1[2][0] + sh1[0][0] * sh1[2][2])),
(sh1[2][1] * sh1[0][0] + sh1[0][1] * sh1[2][0]),
kSqrt03_04 * (sh1[2][1] * sh1[0][1] + sh1[0][1] * sh1[2][1]),
(sh1[2][1] * sh1[0][2] + sh1[0][1] * sh1[2][2]),
kSqrt01_04 * ((sh1[2][2] * sh1[0][2] - sh1[2][0] * sh1[0][0]) + (sh1[0][2] * sh1[2][2] - sh1[0][0] * sh1[2][0]))
], [
kSqrt01_04 * ((sh1[1][2] * sh1[0][0] + sh1[1][0] * sh1[0][2]) + (sh1[0][2] * sh1[1][0] + sh1[0][0] * sh1[1][2])),
sh1[1][1] * sh1[0][0] + sh1[0][1] * sh1[1][0],
kSqrt03_04 * (sh1[1][1] * sh1[0][1] + sh1[0][1] * sh1[1][1]),
sh1[1][1] * sh1[0][2] + sh1[0][1] * sh1[1][2],
kSqrt01_04 * ((sh1[1][2] * sh1[0][2] - sh1[1][0] * sh1[0][0]) + (sh1[0][2] * sh1[1][2] - sh1[0][0] * sh1[1][0]))
], [
kSqrt01_03 * (sh1[1][2] * sh1[1][0] + sh1[1][0] * sh1[1][2]) - kSqrt01_12 * ((sh1[2][2] * sh1[2][0] + sh1[2][0] * sh1[2][2]) + (sh1[0][2] * sh1[0][0] + sh1[0][0] * sh1[0][2])),
kSqrt04_03 * sh1[1][1] * sh1[1][0] - kSqrt01_03 * (sh1[2][1] * sh1[2][0] + sh1[0][1] * sh1[0][0]),
sh1[1][1] * sh1[1][1] - kSqrt01_04 * (sh1[2][1] * sh1[2][1] + sh1[0][1] * sh1[0][1]),
kSqrt04_03 * sh1[1][1] * sh1[1][2] - kSqrt01_03 * (sh1[2][1] * sh1[2][2] + sh1[0][1] * sh1[0][2]),
kSqrt01_03 * (sh1[1][2] * sh1[1][2] - sh1[1][0] * sh1[1][0]) - kSqrt01_12 * ((sh1[2][2] * sh1[2][2] - sh1[2][0] * sh1[2][0]) + (sh1[0][2] * sh1[0][2] - sh1[0][0] * sh1[0][0]))
], [
kSqrt01_04 * ((sh1[1][2] * sh1[2][0] + sh1[1][0] * sh1[2][2]) + (sh1[2][2] * sh1[1][0] + sh1[2][0] * sh1[1][2])),
sh1[1][1] * sh1[2][0] + sh1[2][1] * sh1[1][0],
kSqrt03_04 * (sh1[1][1] * sh1[2][1] + sh1[2][1] * sh1[1][1]),
sh1[1][1] * sh1[2][2] + sh1[2][1] * sh1[1][2],
kSqrt01_04 * ((sh1[1][2] * sh1[2][2] - sh1[1][0] * sh1[2][0]) + (sh1[2][2] * sh1[1][2] - sh1[2][0] * sh1[1][0]))
], [
kSqrt01_04 * ((sh1[2][2] * sh1[2][0] + sh1[2][0] * sh1[2][2]) - (sh1[0][2] * sh1[0][0] + sh1[0][0] * sh1[0][2])),
(sh1[2][1] * sh1[2][0] - sh1[0][1] * sh1[0][0]),
kSqrt03_04 * (sh1[2][1] * sh1[2][1] - sh1[0][1] * sh1[0][1]),
(sh1[2][1] * sh1[2][2] - sh1[0][1] * sh1[0][2]),
kSqrt01_04 * ((sh1[2][2] * sh1[2][2] - sh1[2][0] * sh1[2][0]) - (sh1[0][2] * sh1[0][2] - sh1[0][0] * sh1[0][0]))
]];
// band 3
const sh3 = [[
kSqrt01_04 * ((sh1[2][2] * sh2[0][0] + sh1[2][0] * sh2[0][4]) + (sh1[0][2] * sh2[4][0] + sh1[0][0] * sh2[4][4])),
kSqrt03_02 * (sh1[2][1] * sh2[0][0] + sh1[0][1] * sh2[4][0]),
kSqrt15_16 * (sh1[2][1] * sh2[0][1] + sh1[0][1] * sh2[4][1]),
kSqrt05_06 * (sh1[2][1] * sh2[0][2] + sh1[0][1] * sh2[4][2]),
kSqrt15_16 * (sh1[2][1] * sh2[0][3] + sh1[0][1] * sh2[4][3]),
kSqrt03_02 * (sh1[2][1] * sh2[0][4] + sh1[0][1] * sh2[4][4]),
kSqrt01_04 * ((sh1[2][2] * sh2[0][4] - sh1[2][0] * sh2[0][0]) + (sh1[0][2] * sh2[4][4] - sh1[0][0] * sh2[4][0]))
], [
kSqrt01_06 * (sh1[1][2] * sh2[0][0] + sh1[1][0] * sh2[0][4]) + kSqrt01_06 * ((sh1[2][2] * sh2[1][0] + sh1[2][0] * sh2[1][4]) + (sh1[0][2] * sh2[3][0] + sh1[0][0] * sh2[3][4])),
sh1[1][1] * sh2[0][0] + (sh1[2][1] * sh2[1][0] + sh1[0][1] * sh2[3][0]),
kSqrt05_08 * sh1[1][1] * sh2[0][1] + kSqrt05_08 * (sh1[2][1] * sh2[1][1] + sh1[0][1] * sh2[3][1]),
kSqrt05_09 * sh1[1][1] * sh2[0][2] + kSqrt05_09 * (sh1[2][1] * sh2[1][2] + sh1[0][1] * sh2[3][2]),
kSqrt05_08 * sh1[1][1] * sh2[0][3] + kSqrt05_08 * (sh1[2][1] * sh2[1][3] + sh1[0][1] * sh2[3][3]),
sh1[1][1] * sh2[0][4] + (sh1[2][1] * sh2[1][4] + sh1[0][1] * sh2[3][4]),
kSqrt01_06 * (sh1[1][2] * sh2[0][4] - sh1[1][0] * sh2[0][0]) + kSqrt01_06 * ((sh1[2][2] * sh2[1][4] - sh1[2][0] * sh2[1][0]) + (sh1[0][2] * sh2[3][4] - sh1[0][0] * sh2[3][0]))
], [
kSqrt04_15 * (sh1[1][2] * sh2[1][0] + sh1[1][0] * sh2[1][4]) + kSqrt01_05 * (sh1[0][2] * sh2[2][0] + sh1[0][0] * sh2[2][4]) - kSqrt01_60 * ((sh1[2][2] * sh2[0][0] + sh1[2][0] * sh2[0][4]) - (sh1[0][2] * sh2[4][0] + sh1[0][0] * sh2[4][4])),
kSqrt08_05 * sh1[1][1] * sh2[1][0] + kSqrt06_05 * sh1[0][1] * sh2[2][0] - kSqrt01_10 * (sh1[2][1] * sh2[0][0] - sh1[0][1] * sh2[4][0]),
sh1[1][1] * sh2[1][1] + kSqrt03_04 * sh1[0][1] * sh2[2][1] - kSqrt01_16 * (sh1[2][1] * sh2[0][1] - sh1[0][1] * sh2[4][1]),
kSqrt08_09 * sh1[1][1] * sh2[1][2] + kSqrt02_03 * sh1[0][1] * sh2[2][2] - kSqrt01_18 * (sh1[2][1] * sh2[0][2] - sh1[0][1] * sh2[4][2]),
sh1[1][1] * sh2[1][3] + kSqrt03_04 * sh1[0][1] * sh2[2][3] - kSqrt01_16 * (sh1[2][1] * sh2[0][3] - sh1[0][1] * sh2[4][3]),
kSqrt08_05 * sh1[1][1] * sh2[1][4] + kSqrt06_05 * sh1[0][1] * sh2[2][4] - kSqrt01_10 * (sh1[2][1] * sh2[0][4] - sh1[0][1] * sh2[4][4]),
kSqrt04_15 * (sh1[1][2] * sh2[1][4] - sh1[1][0] * sh2[1][0]) + kSqrt01_05 * (sh1[0][2] * sh2[2][4] - sh1[0][0] * sh2[2][0]) - kSqrt01_60 * ((sh1[2][2] * sh2[0][4] - sh1[2][0] * sh2[0][0]) - (sh1[0][2] * sh2[4][4] - sh1[0][0] * sh2[4][0]))
], [
kSqrt03_10 * (sh1[1][2] * sh2[2][0] + sh1[1][0] * sh2[2][4]) - kSqrt01_10 * ((sh1[2][2] * sh2[3][0] + sh1[2][0] * sh2[3][4]) + (sh1[0][2] * sh2[1][0] + sh1[0][0] * sh2[1][4])),
kSqrt09_05 * sh1[1][1] * sh2[2][0] - kSqrt03_05 * (sh1[2][1] * sh2[3][0] + sh1[0][1] * sh2[1][0]),
kSqrt09_08 * sh1[1][1] * sh2[2][1] - kSqrt03_08 * (sh1[2][1] * sh2[3][1] + sh1[0][1] * sh2[1][1]),
sh1[1][1] * sh2[2][2] - kSqrt01_03 * (sh1[2][1] * sh2[3][2] + sh1[0][1] * sh2[1][2]),
kSqrt09_08 * sh1[1][1] * sh2[2][3] - kSqrt03_08 * (sh1[2][1] * sh2[3][3] + sh1[0][1] * sh2[1][3]),
kSqrt09_05 * sh1[1][1] * sh2[2][4] - kSqrt03_05 * (sh1[2][1] * sh2[3][4] + sh1[0][1] * sh2[1][4]),
kSqrt03_10 * (sh1[1][2] * sh2[2][4] - sh1[1][0] * sh2[2][0]) - kSqrt01_10 * ((sh1[2][2] * sh2[3][4] - sh1[2][0] * sh2[3][0]) + (sh1[0][2] * sh2[1][4] - sh1[0][0] * sh2[1][0]))
], [
kSqrt04_15 * (sh1[1][2] * sh2[3][0] + sh1[1][0] * sh2[3][4]) + kSqrt01_05 * (sh1[2][2] * sh2[2][0] + sh1[2][0] * sh2[2][4]) - kSqrt01_60 * ((sh1[2][2] * sh2[4][0] + sh1[2][0] * sh2[4][4]) + (sh1[0][2] * sh2[0][0] + sh1[0][0] * sh2[0][4])),
kSqrt08_05 * sh1[1][1] * sh2[3][0] + kSqrt06_05 * sh1[2][1] * sh2[2][0] - kSqrt01_10 * (sh1[2][1] * sh2[4][0] + sh1[0][1] * sh2[0][0]),
sh1[1][1] * sh2[3][1] + kSqrt03_04 * sh1[2][1] * sh2[2][1] - kSqrt01_16 * (sh1[2][1] * sh2[4][1] + sh1[0][1] * sh2[0][1]),
kSqrt08_09 * sh1[1][1] * sh2[3][2] + kSqrt02_03 * sh1[2][1] * sh2[2][2] - kSqrt01_18 * (sh1[2][1] * sh2[4][2] + sh1[0][1] * sh2[0][2]),
sh1[1][1] * sh2[3][3] + kSqrt03_04 * sh1[2][1] * sh2[2][3] - kSqrt01_16 * (sh1[2][1] * sh2[4][3] + sh1[0][1] * sh2[0][3]),
kSqrt08_05 * sh1[1][1] * sh2[3][4] + kSqrt06_05 * sh1[2][1] * sh2[2][4] - kSqrt01_10 * (sh1[2][1] * sh2[4][4] + sh1[0][1] * sh2[0][4]),
kSqrt04_15 * (sh1[1][2] * sh2[3][4] - sh1[1][0] * sh2[3][0]) + kSqrt01_05 * (sh1[2][2] * sh2[2][4] - sh1[2][0] * sh2[2][0]) - kSqrt01_60 * ((sh1[2][2] * sh2[4][4] - sh1[2][0] * sh2[4][0]) + (sh1[0][2] * sh2[0][4] - sh1[0][0] * sh2[0][0]))
], [
kSqrt01_06 * (sh1[1][2] * sh2[4][0] + sh1[1][0] * sh2[4][4]) + kSqrt01_06 * ((sh1[2][2] * sh2[3][0] + sh1[2][0] * sh2[3][4]) - (sh1[0][2] * sh2[1][0] + sh1[0][0] * sh2[1][4])),
sh1[1][1] * sh2[4][0] + (sh1[2][1] * sh2[3][0] - sh1[0][1] * sh2[1][0]),
kSqrt05_08 * sh1[1][1] * sh2[4][1] + kSqrt05_08 * (sh1[2][1] * sh2[3][1] - sh1[0][1] * sh2[1][1]),
kSqrt05_09 * sh1[1][1] * sh2[4][2] + kSqrt05_09 * (sh1[2][1] * sh2[3][2] - sh1[0][1] * sh2[1][2]),
kSqrt05_08 * sh1[1][1] * sh2[4][3] + kSqrt05_08 * (sh1[2][1] * sh2[3][3] - sh1[0][1] * sh2[1][3]),
sh1[1][1] * sh2[4][4] + (sh1[2][1] * sh2[3][4] - sh1[0][1] * sh2[1][4]),
kSqrt01_06 * (sh1[1][2] * sh2[4][4] - sh1[1][0] * sh2[4][0]) + kSqrt01_06 * ((sh1[2][2] * sh2[3][4] - sh1[2][0] * sh2[3][0]) - (sh1[0][2] * sh2[1][4] - sh1[0][0] * sh2[1][0]))
], [
kSqrt01_04 * ((sh1[2][2] * sh2[4][0] + sh1[2][0] * sh2[4][4]) - (sh1[0][2] * sh2[0][0] + sh1[0][0] * sh2[0][4])),
kSqrt03_02 * (sh1[2][1] * sh2[4][0] - sh1[0][1] * sh2[0][0]),
kSqrt15_16 * (sh1[2][1] * sh2[4][1] - sh1[0][1] * sh2[0][1]),
kSqrt05_06 * (sh1[2][1] * sh2[4][2] - sh1[0][1] * sh2[0][2]),
kSqrt15_16 * (sh1[2][1] * sh2[4][3] - sh1[0][1] * sh2[0][3]),
kSqrt03_02 * (sh1[2][1] * sh2[4][4] - sh1[0][1] * sh2[0][4]),
kSqrt01_04 * ((sh1[2][2] * sh2[4][4] - sh1[2][0] * sh2[4][0]) - (sh1[0][2] * sh2[0][4] - sh1[0][0] * sh2[0][0]))
]];
if (isAxisAligned(rot)) {
const { counts, indices, values } = buildSparse(sh1, sh2, sh3);
this.apply = (result, src) => {
if (!src || src === result) {
coeffsIn.set(result);
src = coeffsIn;
}
let vp = 0;
if (result.length < 3)
return;
for (let i = 0; i < 3; i++) {
let sum = 0;
for (let k = 0; k < counts[i]; k++) {
sum += values[vp] * src[indices[vp]];
vp++;
}
result[i] = sum;
}
if (result.length < 8)
return;
for (let i = 0; i < 5; i++) {
let sum = 0;
for (let k = 0; k < counts[3 + i]; k++) {
sum += values[vp] * src[indices[vp]];
vp++;
}
result[3 + i] = sum;
}
if (result.length < 15)
return;
for (let i = 0; i < 7; i++) {
let sum = 0;
for (let k = 0; k < counts[8 + i]; k++) {
sum += values[vp] * src[indices[vp]];
vp++;
}
result[8 + i] = sum;
}
};
}
else {
this.apply = (result, src) => {
if (!src || src === result) {
coeffsIn.set(result);
src = coeffsIn;
}
// band 1
if (result.length < 3) {
return;
}
result[0] = dp(3, 0, src, sh1[0]);
result[1] = dp(3, 0, src, sh1[1]);
result[2] = dp(3, 0, src, sh1[2]);
// band 2
if (result.length < 8) {
return;
}
result[3] = dp(5, 3, src, sh2[0]);
result[4] = dp(5, 3, src, sh2[1]);
result[5] = dp(5, 3, src, sh2[2]);
result[6] = dp(5, 3, src, sh2[3]);
result[7] = dp(5, 3, src, sh2[4]);
// band 3
if (result.length < 15) {
return;
}
result[8] = dp(7, 8, src, sh3[0]);
result[9] = dp(7, 8, src, sh3[1]);
result[10] = dp(7, 8, src, sh3[2]);
result[11] = dp(7, 8, src, sh3[3]);
result[12] = dp(7, 8, src, sh3[4]);
result[13] = dp(7, 8, src, sh3[5]);
result[14] = dp(7, 8, src, sh3[6]);
};
}
}
}
const indent = (depth) => ' '.repeat(Math.max(0, depth));
const BAR_WIDTH = 20;
/**
* Default human-readable text renderer. Emits one event per line - no
* carriage-return rewriting, no TTY detection, no buffering. Bars render
* as `[#### ...... ] duration`, with `#` appended incrementally on each
* `barTick` and the remainder padded with `.` on `barEnd`. `output`
* events are treated as line-oriented: their text is written to the
* pipeable sink with a trailing `\n` appended (callers should not include
* one themselves).
*
* Verbosity is consulted directly from the shared {@link logger} on each
* event, so this renderer alone decides what to display - the core
* delivers every scope/bar lifecycle event so embedders consuming the
* event stream see a faithful record. The display rules are:
*
* - `quiet` - suppresses every scope/bar lifecycle line (start, tick,
* end - including failed ends). Errors, warnings and `output` still
* show.
* - `normal` (default) - shows scope/bar headers and bar progress;
* shows failed `scopeEnd` / `barEnd` footers (the "failed in ..."
* cascade from `logger.error` / `unwindAll(true)`); hides successful
* `scopeEnd` footers.
* - `verbose` - shows everything, including successful `scopeEnd`
* footers ("done in ...").
*
* Sinks are injected (no `process` reference here) so the renderer works in
* both Node CLI and browser/bundle contexts: the CLI passes
* `process.stderr.write` for status and `process.stdout.write` for raw
* output; library/browser consumers can pass a `console.log` line buffer.
*/
class TextRenderer {
write;
output;
getPeakMemory;
getLiveMemory;
/**
* When true, scope-end and bar-end lines gain a `[peak X]` suffix
* (or `[peak X | live Y]` when {@link TextRendererOptions.getLiveMemory}
* is also supplied) sourced from
* {@link TextRendererOptions.getPeakMemory}. No effect when the probe
* is omitted. Defaults to `true` when `getPeakMemory` is provided so
* embedders that supply a probe see the overlay automatically. Mutable
* so the host can toggle the overlay without re-installing the renderer.
*/
mem;
/** True while a bar header has been written without its closing `\n`. */
lineDirty = false;
/**
* Hash count already written for the active bar. Bars are strictly LIFO
* (the active-scope stack guarantees it), so a single counter suffices.
*/
barFilled = 0;
constructor(options) {
this.write = options.write;
this.output = options.output ?? options.write;
this.getPeakMemory = options.getPeakMemory;
this.getLiveMemory = options.getLiveMemory;
this.mem = options.getPeakMemory !== undefined;
}
rank() {
return verbosityRank[logger.getVerbosity()];
}
handle(event) {
switch (event.kind) {
case 'scopeStart': {
if (this.rank() < verbosityRank.normal)
return;
this.commitDirty();
const numbered = event.index !== undefined && event.total !== undefined ?
`[${event.index}/${event.total}] ` : '';
this.write(`${indent(event.depth)}\u25b8 ${numbered}${event.name}\n`);
return;
}
case 'scopeEnd': {
const rank = this.rank();
if (event.failed) {
if (rank < verbosityRank.normal)
return;
}
else if (rank < verbosityRank.verbose) {
return;
}
this.commitDirty();
const verb = event.failed ? 'failed in' : 'done in';
this.write(`${indent(event.depth + 1)}${verb} ${fmtTime(event.durationMs)}${this.memSuffix()}\n`);
return;
}
case 'barStart': {
if (this.rank() < verbosityRank.normal)
return;
this.commitDirty();
this.write(`${indent(event.depth)}\u25b8 ${event.name} [`);
this.lineDirty = true;
this.barFilled = 0;
return;
}
case 'barTick': {
if (!this.lineDirty)
return;
if (this.rank() < verbosityRank.normal)
return;
const target = event.total <= 0 ? 0 :
Math.min(BAR_WIDTH, Math.floor((event.current / event.total) * BAR_WIDTH));
if (target > this.barFilled) {
this.write('#'.repeat(target - this.barFilled));
this.barFilled = target;
}
return;
}
case 'barEnd': {
if (this.rank() < verbosityRank.normal)
return;
const suffix = event.failed ?
`] (failed) ${fmtTime(event.durationMs)}` :
`] ${fmtTime(event.durationMs)}`;
if (this.lineDirty) {
const remaining = Math.max(0, BAR_WIDTH - this.barFilled);
this.write(`${'.'.repeat(remaining)}${suffix}${this.memSuffix()}\n`);
this.lineDirty = false;
this.barFilled = 0;
}
else {
// bar's inline line was committed early by a nested
// event (e.g. a child group/message). Emit a recap
// line whose fill reflects actual progress, so bars
// that ended early or failed don't read as complete.
const filled = event.total <= 0 ? 0 :
Math.min(BAR_WIDTH, Math.floor((event.current / event.total) * BAR_WIDTH));
const bar = `${'#'.repeat(filled)}${'.'.repeat(BAR_WIDTH - filled)}`;
this.write(`${indent(event.depth)}\u25b8 ${event.name} [${bar}${suffix}${this.memSuffix()}\n`);
}
return;
}
case 'message': {
this.commitDirty();
// info/debug get a `\u00b7` glyph only when nested under a
// scope - at depth 0 they're framing lines (banners,
// summaries) and read cleaner without decoration. warn/error
// always carry their severity glyph regardless of depth.
let prefix = '';
if (event.level === 'error')
prefix = '\u2717 ';
else if (event.level === 'warn')
prefix = '! ';
else if (event.depth > 0)
prefix = '\u00b7 ';
this.write(`${indent(event.depth)}${prefix}${event.text}\n`);
return;
}
case 'output': {
this.commitDirty();
this.output(`${event.text}\n`);
}
}
}
/**
* Terminate any in-progress bar line so subsequent output starts on a
* fresh line. The bar's `#` count is preserved on its own line; the
* eventual `barEnd` will produce its own footer line if it fires later.
*/
commitDirty() {
if (this.lineDirty) {
this.write('\n');
this.lineDirty = false;
}
}
memSuffix() {
if (!this.mem || !this.getPeakMemory)
return '';
const peak = fmtBytes(this.getPeakMemory());
if (!this.getLiveMemory)
return ` [peak ${peak}]`;
return ` [peak ${peak} | live ${fmtBytes(this.getLiveMemory())}]`;
}
}
var Module = (() => {
return (
async function(moduleArg = {}) {
var moduleRtn;
var Module=moduleArg;var readyPromiseResolve,readyPromiseReject;var readyPromise=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject;});var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof WorkerGlobalScope!="undefined";var ENVIRONMENT_IS_NODE=typeof process=="object"&&process.versions?.node&&process.type!="renderer";if(ENVIRONMENT_IS_NODE){const{createRequire}=await import('module');var require$1=createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));}var _scriptName=(typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href));var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require$1("fs");var nodePath=require$1("path");if(_scriptName.startsWith("file:")){scriptDirectory=nodePath.dirname(require$1("url").fileURLToPath(_scriptName))+"/";}readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(process.argv.length>1){process.argv[1].replace(/\\/g,"/");}process.argv.slice(2);}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_s