@kalyptos/narcissus 0.1.1 → 0.1.2

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.
@@ -1,9 +1,10 @@
1
1
  import { Controls } from '../controls/index.ts';
2
2
  import { type Engine } from '../engine/index.ts';
3
3
  /**
4
- * attributes: src, the url of a world.json; budget, the splats a frame may draw; view-distance in
5
- * metres; camera, the pose to open on as E,N,H,yaw,pitch,dist; sky as r,g,b from 0 to 1. camera
6
- * and sky are read when a world opens and when the element starts.
4
+ * attributes: src, the url of a world.json; budget, the splats a frame may draw, which otherwise
5
+ * adapts to the frame rate; view-distance in metres; camera, the pose to open on as
6
+ * E,N,H,yaw,pitch,dist, its focus on the ground there when H is left empty, as the opening view's;
7
+ * sky as r,g,b from 0 to 1. camera and sky are read when a world opens and when the element starts.
7
8
  * events: ready, with the World as its detail, and error, with a message.
8
9
  * after an error, such as a browser without webgpu, its own content shows in place of the canvas,
9
10
  * or the message when it has none.
@@ -1,16 +1,16 @@
1
- import { b as e, h as t, p as n, s as r, t as i } from "../controls-BCrDCO3z.js";
1
+ import { b as e, h as t, p as n, s as r, t as i } from "../controls-mxyJLzqv.js";
2
2
  //#region src/element/index.ts
3
- var a = 4e3, o = [
3
+ var a = 4e3, o = 4e6, s = [2e6, 6e6], c = 2e6, l = [
4
4
  .62,
5
5
  .72,
6
6
  .8
7
- ], s = document.createElement("template");
8
- s.innerHTML = "<style>\n:host { display: block; position: relative; box-sizing: border-box; width: 100%; aspect-ratio: 16 / 9; }\ncanvas { position: absolute; inset: 0; width: 100%; height: 100%; touch-action: none; }\n[hidden] { display: none; }\n</style><canvas></canvas><slot hidden></slot><p part=\"message\" hidden></p>";
9
- function c() {
7
+ ], u = document.createElement("template");
8
+ u.innerHTML = "<style>\n:host { display: block; position: relative; box-sizing: border-box; width: 100%; aspect-ratio: 16 / 9; }\ncanvas { position: absolute; inset: 0; width: 100%; height: 100%; touch-action: none; }\n[hidden] { display: none; }\n</style><canvas></canvas><slot hidden></slot><p part=\"message\" hidden></p>";
9
+ function d() {
10
10
  let e = navigator;
11
11
  return e.userAgentData?.mobile ?? /Mobi|Android/i.test(e.userAgent);
12
12
  }
13
- var l = class extends HTMLElement {
13
+ var f = class extends HTMLElement {
14
14
  static observedAttributes = [
15
15
  "src",
16
16
  "budget",
@@ -26,7 +26,7 @@ var l = class extends HTMLElement {
26
26
  constructor() {
27
27
  super();
28
28
  let e = this.attachShadow({ mode: "open" });
29
- e.append(s.content.cloneNode(!0)), this.#e = e.querySelector("canvas"), this.#t = e.querySelector("slot"), this.#n = e.querySelector("p");
29
+ e.append(u.content.cloneNode(!0)), this.#e = e.querySelector("canvas"), this.#t = e.querySelector("slot"), this.#n = e.querySelector("p");
30
30
  }
31
31
  get engine() {
32
32
  return this.#r;
@@ -44,13 +44,20 @@ var l = class extends HTMLElement {
44
44
  this.#r && (e === "src" ? this.#l(this.#r) : this.#r.configure(this.#s()));
45
45
  }
46
46
  #s() {
47
- let e = (e, t) => {
48
- let n = Number(this.getAttribute(e));
49
- return n > 0 && Number.isFinite(n) ? n : t;
50
- };
51
- return {
52
- budget: e("budget", c() ? 2e6 : 4e6),
53
- viewDistance: e("view-distance", a)
47
+ let e = (e) => {
48
+ let t = Number(this.getAttribute(e));
49
+ return t > 0 && Number.isFinite(t) ? t : null;
50
+ }, t = e("budget"), n = e("view-distance") ?? a;
51
+ return t === null ? d() ? {
52
+ budget: c,
53
+ viewDistance: n
54
+ } : {
55
+ budget: o,
56
+ budgetRange: s,
57
+ viewDistance: n
58
+ } : {
59
+ budget: t,
60
+ viewDistance: n
54
61
  };
55
62
  }
56
63
  async #c() {
@@ -64,7 +71,7 @@ var l = class extends HTMLElement {
64
71
  n[0],
65
72
  n[1],
66
73
  n[2]
67
- ] : o
74
+ ] : l
68
75
  });
69
76
  if (t !== this.#o) return r.destroy();
70
77
  this.#r = r, r.onLost((e) => this.#f(`The graphics device stopped (${e}).`)), this.#a.observe(this), this.#u(), await this.#l(r);
@@ -84,11 +91,16 @@ var l = class extends HTMLElement {
84
91
  return this.#f(`Could not load the world (${t}).`);
85
92
  }
86
93
  if (o !== this.#o) return;
87
- let c = (t, n) => e.ground(t, n) ?? s.extent.h[0], l = t(this.getAttribute("camera") ?? "") ?? {
94
+ let c = (t, n) => e.ground(t, n) ?? s.extent.h[0], l = (this.getAttribute("camera") ?? "").split(","), u = l.length === 6 && l[2].trim() === "", d = t((u ? l.with(2, "0") : l).join(",")), f = d ?? {
88
95
  ...n(s),
89
96
  ...r
90
97
  };
91
- this.#i?.destroy(), this.#i = new i(this.#e, e, c, l), this.dispatchEvent(new CustomEvent("ready", { detail: s }));
98
+ if (u || !d) {
99
+ let t = await e.groundAt(f.e, f.n).catch(() => void 0);
100
+ if (o !== this.#o) return;
101
+ f.h = t ?? s.extent.h[0];
102
+ }
103
+ this.#i?.destroy(), this.#i = new i(this.#e, e, c, f), this.dispatchEvent(new CustomEvent("ready", { detail: s }));
92
104
  }
93
105
  #u() {
94
106
  this.#r?.resize(this.clientWidth, this.clientHeight, devicePixelRatio);
@@ -102,6 +114,6 @@ var l = class extends HTMLElement {
102
114
  this.#t.hidden = !t, this.#n.hidden = t, this.#n.textContent = e, this.dispatchEvent(new CustomEvent("error", { detail: e }));
103
115
  }
104
116
  };
105
- customElements.get("narcissus-viewer") || customElements.define("narcissus-viewer", l);
117
+ customElements.get("narcissus-viewer") || customElements.define("narcissus-viewer", f);
106
118
  //#endregion
107
- export { l as NarcissusViewer };
119
+ export { f as NarcissusViewer };
@@ -32,6 +32,8 @@ export interface Engine {
32
32
  /** world point under canvas css pixel (x, y), or null over nothing */
33
33
  pick(x: number, y: number): Promise<[number, number, number] | null>;
34
34
  ground(e: number, n: number): number | undefined;
35
+ /** ground(e, n) once every .idx over (e, n) is read */
36
+ groundAt(e: number, n: number): Promise<number | undefined>;
35
37
  /**
36
38
  * tops, as world heights, of the solid splats the next frame draws over cells x cells of 1 m from the
37
39
  * south-west corner (west, south), rows south to north, NaN where none; null if no frame comes
@@ -3,17 +3,17 @@ import type { Drawn } from '../types.ts';
3
3
  export declare class Pool {
4
4
  #private;
5
5
  /** GEOMETRY_WORDS per record */
6
- readonly geometry: GPUBuffer;
6
+ geometry: GPUBuffer;
7
7
  /** COLOUR_WORDS per record */
8
- readonly colour: GPUBuffer;
8
+ colour: GPUBuffer;
9
9
  /** Page per table slot */
10
- readonly pages: GPUBuffer;
10
+ pages: GPUBuffer;
11
11
  /** Cluster per slot */
12
- readonly clusters: GPUBuffer;
13
- readonly list: GPUBuffer;
12
+ clusters: GPUBuffer;
13
+ list: GPUBuffer;
14
14
  listed: number;
15
15
  /** splats */
16
- readonly capacity: number;
16
+ capacity: number;
17
17
  constructor(device: GPUDevice, capacity: number);
18
18
  /** clusters the pool can hold */
19
19
  get slots(): number;
@@ -39,5 +39,7 @@ export declare class Pool {
39
39
  draw(drawn: Drawn[]): void;
40
40
  /** world position of the floating origin; rewrites every page origin */
41
41
  setOrigin(origin: readonly [number, number, number]): void;
42
+ /** room for capacity splats, the pages held staying in place; new buffers, so new bind groups */
43
+ grow(capacity: number): void;
42
44
  destroy(): void;
43
45
  }
@@ -1,4 +1,4 @@
1
- import type { PageNode, Tree, Wanted } from '../world/tree.ts';
1
+ import { type PageNode, type Tree, type Wanted } from '../world/tree.ts';
2
2
  /** a page refined in the last plan stays refined until its count reaches the target times this */
3
3
  export declare const MARGIN: number;
4
4
  /** ms a refinement seen on screen stays while off screen */
@@ -34,7 +34,7 @@ export interface Plan {
34
34
  idx: Wanted[];
35
35
  /** ids the pool must keep: the cut, its ancestors and held pages */
36
36
  keep: Set<number>;
37
- /** splats of the cut inside the frustum */
37
+ /** splats of the cut's quadrants inside the frustum */
38
38
  planned: number;
39
39
  /** of planned, the splats not resident yet */
40
40
  missing: number;
@@ -1,3 +1,4 @@
1
+ import { type PageRow } from '../../format/index.ts';
1
2
  import type { Drawn } from '../types.ts';
2
3
  import type { PageNode } from '../world/tree.ts';
3
4
  export declare const FADE_MS = 250;
@@ -14,3 +15,4 @@ export declare class Fader {
14
15
  fading: boolean;
15
16
  };
16
17
  }
18
+ export declare function quadrantSplats({ count, quadrants }: PageRow, q: number): number;
@@ -10,3 +10,8 @@ export interface Footprint {
10
10
  export declare function centreWeight(left: number, right: number, bottom: number, top: number): number;
11
11
  /** box shifted by origin into view m; tan is tan(fov / 2), margin widens the viewport */
12
12
  export declare function footprint(box: ArrayLike<number>, m: ArrayLike<number>, width: number, height: number, tan: number, near: number, far: number, margin?: number, out?: Footprint, origin?: readonly [number, number, number]): Footprint;
13
+ /**
14
+ * bit q set where quadrant q of a page overlaps the viewport: quarter q of its square (x, y, side),
15
+ * within the box; arguments as footprint's
16
+ */
17
+ export declare function shownQuadrants(box: ArrayLike<number>, [x, y, side]: readonly [number, number, number], m: ArrayLike<number>, width: number, height: number, tan: number, near: number, far: number, origin?: readonly [number, number, number]): number;
@@ -2,9 +2,15 @@ export declare const MIN_BUDGET = 500000;
2
2
  export declare const MAX_PIXELS: number;
3
3
  export declare function pixelRatio(width: number, height: number, ratio: number, scale: number): number;
4
4
  export declare function percentile(values: readonly number[], fraction: number): number;
5
+ /** holds SLOW_FPS with the budget first, within range, then the render scale */
5
6
  export declare class Quality {
6
7
  #private;
8
+ /** budget alone stays fixed */
9
+ constructor(budget: number, [min, max]?: readonly [number, number]);
7
10
  get scale(): number;
11
+ /** splats a frame may draw */
12
+ get budget(): number;
8
13
  full(): void;
9
- sample(ms: number, now: number, consecutive: boolean): boolean;
14
+ /** ms since the last frame; planned, the splats the plan draws; true when the scale or budget changed */
15
+ sample(ms: number, now: number, consecutive: boolean, planned: number): boolean;
10
16
  }
@@ -15,6 +15,8 @@ export interface Camera {
15
15
  export interface Settings {
16
16
  /** splats the engine may draw per frame */
17
17
  budget: number;
18
+ /** the budget adapts within it to hold 30 fps, starting from budget; without it the budget stays */
19
+ budgetRange?: readonly [number, number];
18
20
  /** metres; the far clip and the planner's reach */
19
21
  viewDistance: number;
20
22
  }
@@ -46,6 +48,8 @@ export interface Stats {
46
48
  };
47
49
  /** splats the pool holds at most */
48
50
  capacity: number;
51
+ /** splats a frame may draw now */
52
+ budget: number;
49
53
  scale: number;
50
54
  loading: boolean;
51
55
  }
@@ -43,6 +43,8 @@ export declare class Tree {
43
43
  wanted(): Wanted[];
44
44
  /** the lowest extent minimum h of the finest page over (e, n), or undefined outside the world */
45
45
  ground(e: number, n: number): number | undefined;
46
+ /** adds every unit over (e, n) that the tree knows of and lacks, each .idx read by fetch */
47
+ load(e: number, n: number, fetch: (path: string) => Promise<Idx>): Promise<void>;
46
48
  /**
47
49
  * removes every non-root unit with no page in keep and no added unit below it, deepest first;
48
50
  * returns the ids of the pages it removed
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { S as e, _ as t, a as n, b as r, c as i, d as a, f as o, g as s, h as c, i as l, l as u, m as d, n as f, o as p, p as m, r as h, s as g, t as _, u as v, v as y, x as b, y as x } from "./controls-BCrDCO3z.js";
1
+ import { S as e, _ as t, a as n, b as r, c as i, d as a, f as o, g as s, h as c, i as l, l as u, m as d, n as f, o as p, p as m, r as h, s as g, t as _, u as v, v as y, x as b, y as x } from "./controls-mxyJLzqv.js";
2
2
  import { t as S } from "./ports-DZRKjdYD.js";
3
3
  import { t as C } from "./http-DL7R39Nc.js";
4
4
  //#region src/engine/plugins/grade.wgsl?raw
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kalyptos/narcissus",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "A WebGPU engine and tools for Guldmund Trees, streamed gaussian-splat scenes",
5
5
  "license": "BSD-3-Clause",
6
6
  "repository": {