@displayxr/inline3d 1.0.0 → 1.1.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/model.d.ts ADDED
@@ -0,0 +1,64 @@
1
+ // Type definitions for @displayxr/inline3d/model.
2
+ // EXPERIMENTAL — not covered by the 1.x semver promise. See docs/sdk-stability.md.
3
+
4
+ import type { SceneViewer, SubjectBounds, OrbitPose } from './viewer.js';
5
+
6
+ export interface ModelOptions {
7
+ /** Metres of world the tile's height spans (default 0.24). */
8
+ virtualDisplayHeight?: number;
9
+ /** Precomputed subject bounds. Rarely needed for a mesh — Box3 is exact. */
10
+ frame?: SubjectBounds;
11
+ /** Degrees/second of turntable once idle (default 8). */
12
+ idleSpin?: number;
13
+ orbit?: boolean;
14
+ fit?: 'contain' | 'height' | 'cover' | 'none';
15
+ /** Fraction of the tile the subject may occupy (default 0.8) — width AND height. */
16
+ margin?: number;
17
+ /** Backstop on total depth, in display heights (default 4.0). Rarely binds. */
18
+ depthLimit?: number;
19
+ /**
20
+ * Fit the horizontal against the box's DIAGONAL (width and depth) rather than width alone,
21
+ * so a long subject still fits once the turntable turns it (default true).
22
+ */
23
+ fitSweep?: boolean;
24
+ /** Per-eye buffer scale; 0.5–0.7 is usually free (default 1). */
25
+ renderScale?: number;
26
+ feather?: number;
27
+ /** Built-in three-point lighting. Meshes arrive unlit; splats do not need this. */
28
+ environment?: 'studio' | 'none';
29
+ /** A PMREM-processed environment texture. Better than `environment` for metal; overrides it. */
30
+ envMap?: object;
31
+ /** Hand in the GLTFLoader class instead of resolving it from `three/addons/`. */
32
+ GLTFLoader?: unknown;
33
+ /** Element whose visibility gates the lazy create/close lifecycle. */
34
+ observe?: Element;
35
+ }
36
+
37
+ /** What {@link addModel} returns — the same shape as addSplat's handle. */
38
+ export interface ModelHandle {
39
+ readonly viewer: SceneViewer;
40
+ /** The loaded glTF scene root; null until `ready` resolves. */
41
+ model: object | null;
42
+ /** Bounds used for framing; null until `ready` resolves. */
43
+ frame: SubjectBounds | null;
44
+ /** Resolves once the model has loaded and been framed; rejects if the load failed. */
45
+ readonly ready: Promise<ModelHandle>;
46
+
47
+ setPose(pose?: OrbitPose): void;
48
+ resetPose(): void;
49
+
50
+ remove(): void;
51
+ exclude(el: Element): void;
52
+ unexclude(el: Element): void;
53
+ }
54
+
55
+ /**
56
+ * Load a glTF/GLB into an inline-3D window. Safe to call with an unsupported wall — it renders a
57
+ * flat, orbitable view instead, so pages need no branch.
58
+ */
59
+ export function addModel(
60
+ wall: object | null | undefined,
61
+ canvas: HTMLCanvasElement,
62
+ src: string,
63
+ opts?: ModelOptions,
64
+ ): ModelHandle;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@displayxr/inline3d",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Turn any HTML <canvas> into a glasses-free-3D window on a DisplayXR display, inside an ordinary web page. Dependency-free; progressive enhancement (falls back to plain 2D everywhere else).",
5
5
  "type": "module",
6
6
  "types": "./index.d.ts",
@@ -14,14 +14,33 @@
14
14
  "./three": {
15
15
  "types": "./three.d.ts",
16
16
  "import": "./js/inline3d-three.js"
17
+ },
18
+ "./viewer": {
19
+ "types": "./viewer.d.ts",
20
+ "import": "./js/inline3d-viewer.js"
21
+ },
22
+ "./splat": {
23
+ "types": "./splat.d.ts",
24
+ "import": "./js/inline3d-splat.js"
25
+ },
26
+ "./model": {
27
+ "types": "./model.d.ts",
28
+ "import": "./js/inline3d-model.js"
17
29
  }
18
30
  },
19
31
  "sideEffects": false,
20
32
  "files": [
21
33
  "js/inline3d.js",
22
34
  "js/inline3d-three.js",
35
+ "js/inline3d-viewer.js",
36
+ "js/inline3d-splat.js",
37
+ "js/inline3d-model.js",
23
38
  "index.d.ts",
24
39
  "three.d.ts",
40
+ "viewer.d.ts",
41
+ "splat.d.ts",
42
+ "model.d.ts",
43
+ "CHANGELOG.md",
25
44
  "README.md",
26
45
  "LICENSE"
27
46
  ],
@@ -45,11 +64,15 @@
45
64
  },
46
65
  "license": "Apache-2.0",
47
66
  "peerDependencies": {
48
- "three": ">=0.150.0"
67
+ "three": ">=0.150.0",
68
+ "@sparkjsdev/spark": ">=2.0.0"
49
69
  },
50
70
  "peerDependenciesMeta": {
51
71
  "three": {
52
72
  "optional": true
73
+ },
74
+ "@sparkjsdev/spark": {
75
+ "optional": true
53
76
  }
54
77
  },
55
78
  "publishConfig": {
package/splat.d.ts ADDED
@@ -0,0 +1,91 @@
1
+ // Type definitions for @displayxr/inline3d/splat.
2
+ // EXPERIMENTAL — not covered by the 1.x semver promise. See docs/sdk-stability.md.
3
+
4
+ import type { SceneViewer, SubjectBounds, OrbitPose } from './viewer.js';
5
+
6
+ export interface SplatOptions {
7
+ /** Metres of world the tile's height spans (default 0.24). */
8
+ virtualDisplayHeight?: number;
9
+ /**
10
+ * Precomputed subject bounds. Strongly preferred — bake these at conversion time, where the
11
+ * full opacity-weighted subject detection is cheap, instead of paying for a weaker
12
+ * approximation in the page.
13
+ */
14
+ frame?: SubjectBounds;
15
+ /** Apply the 180° X flip most splat exports need (default true). */
16
+ flipY?: boolean;
17
+ /** Degrees/second of turntable once idle (default 8). */
18
+ idleSpin?: number;
19
+ orbit?: boolean;
20
+ fit?: 'contain' | 'height' | 'cover' | 'none';
21
+ /** Fraction of the tile the subject may occupy (default 0.8) — width AND height. */
22
+ margin?: number;
23
+ /** Backstop on total depth, in display heights (default 4.0). Rarely binds. */
24
+ depthLimit?: number;
25
+ /**
26
+ * Fit the horizontal against the box's DIAGONAL (width and depth) rather than width alone,
27
+ * so a long subject still fits once the turntable turns it (default true).
28
+ */
29
+ fitSweep?: boolean;
30
+ /** Per-eye buffer scale; 0.5–0.7 is usually free (default 1). */
31
+ renderScale?: number;
32
+ feather?: number;
33
+ /** Minimum ms between splat sorts. Defaults to 16 so both eyes share one sort per frame. */
34
+ sortIntervalMs?: number;
35
+ /**
36
+ * Disambiguates .splat from .ksplat when passing BYTES — content-sniffing cannot separate
37
+ * those two. Unnecessary for .sog/.ply/.spz, which are identifiable by magic number.
38
+ */
39
+ fileName?: string;
40
+ /**
41
+ * Container format, when passing bytes. Usually unnecessary — the magic number is sniffed —
42
+ * but note Spark's names are not the file extensions: a `.sog` is `pcsogszip`.
43
+ */
44
+ fileType?: 'ply' | 'spz' | 'splat' | 'ksplat' | 'pcsogs' | 'pcsogszip' | 'rad';
45
+ /** Element whose visibility gates the lazy create/close lifecycle. */
46
+ observe?: Element;
47
+ }
48
+
49
+ /** What {@link addSplat} returns: a TileHandle plus the objects behind it. */
50
+ export interface SplatHandle {
51
+ readonly viewer: SceneViewer;
52
+ /** Spark's SplatMesh. */
53
+ readonly mesh: object;
54
+ /** Spark's SparkRenderer. */
55
+ readonly spark: object;
56
+ /** Bounds actually used for framing; null until `ready` resolves. */
57
+ frame: SubjectBounds | null;
58
+ /** Resolves once the asset has loaded and been framed; rejects if the load failed. */
59
+ readonly ready: Promise<SplatHandle>;
60
+
61
+ setPose(pose?: OrbitPose): void;
62
+ resetPose(): void;
63
+
64
+ /** Close this window and release its GPU resources. */
65
+ remove(): void;
66
+ /** Mark a 2D element painted over this window so the weave leaves it crisp. */
67
+ exclude(el: Element): void;
68
+ unexclude(el: Element): void;
69
+ }
70
+
71
+ /**
72
+ * Load a splat into an inline-3D window. Safe to call with an unsupported wall — it renders a
73
+ * flat, orbitable view instead, so pages need no branch.
74
+ */
75
+ export function addSplat(
76
+ wall: object | null | undefined,
77
+ canvas: HTMLCanvasElement,
78
+ /**
79
+ * A URL, or the bytes themselves.
80
+ *
81
+ * Prefer BYTES for anything generated rather than fetched. Spark reads a splat's format from
82
+ * the URL path, so an object URL from URL.createObjectURL() — which has no extension — fails
83
+ * with "Unknown file type" before it fetches anything, and that reads like a corrupt asset
84
+ * rather than a missing hint. Given bytes, Spark sniffs the magic number instead.
85
+ */
86
+ src: string | Blob | ArrayBuffer,
87
+ opts?: SplatOptions,
88
+ ): SplatHandle;
89
+
90
+ /** Robust model-space bounds of a loaded SplatMesh, lifted through its own matrix. */
91
+ export function measureSplatBounds(mesh: object, three?: object): SubjectBounds | null;
package/viewer.d.ts ADDED
@@ -0,0 +1,94 @@
1
+ // Type definitions for @displayxr/inline3d/viewer.
2
+ // EXPERIMENTAL — not covered by the 1.x semver promise. See docs/sdk-stability.md.
3
+
4
+ /** Model-space bounds of the subject a viewer frames on. */
5
+ export interface SubjectBounds {
6
+ center: number[];
7
+ extent: number[];
8
+ }
9
+
10
+ export interface SceneViewerOptions {
11
+ /** Metres of world the tile's HEIGHT spans. Pass the same value to addScene. */
12
+ virtualDisplayHeight?: number;
13
+ /**
14
+ * How `fitTo` sizes the subject. `contain` caps BOTH width and height at `margin` of the tile.
15
+ * `height` pins height to `margin` and only guards against running off the sides, giving a
16
+ * consistent apparent size across a catalogue.
17
+ */
18
+ fit?: 'contain' | 'height' | 'cover' | 'none';
19
+ /** Fraction of the tile the subject may occupy (default 0.8). */
20
+ margin?: number;
21
+ /** Backstop on total subject depth, in display heights (default 4.0). Rarely binds. */
22
+ depthLimit?: number;
23
+ /**
24
+ * Fit the horizontal against the box's DIAGONAL (width and depth) rather than width alone,
25
+ * so a long subject still fits once the turntable turns it (default true).
26
+ */
27
+ fitSweep?: boolean;
28
+ /** Drag to spin, wheel/pinch to zoom (default true). */
29
+ orbit?: boolean;
30
+ /** Degrees/second of turntable once idle. Ignored under prefers-reduced-motion. */
31
+ idleSpin?: number;
32
+ /** Per-eye buffer scale; 0.5–0.7 is usually free after the interlace (default 1). */
33
+ renderScale?: number;
34
+ /** Edge fade in buffer px (needs EdgeFeather from ./three). */
35
+ feather?: number;
36
+ /** Pitch clamp in degrees (default [-60, 60]). */
37
+ pitchLimit?: number[];
38
+ }
39
+
40
+ export interface OrbitPose {
41
+ yaw?: number;
42
+ pitch?: number;
43
+ zoom?: number;
44
+ }
45
+
46
+ /**
47
+ * Robust model-space bounds from a flat [x,y,z,…] array. The cheap path; prefer bounds computed
48
+ * at conversion time where you have them.
49
+ *
50
+ * Percentiles REJECT outliers, they do not measure extent: `lo`/`hi` bound a rejection window
51
+ * `expand` core-extents wide, and the returned extent is the true min/max inside it. Returning
52
+ * the trimmed box itself under-reports a dense subject by 10-15% — a uniform cube measures 0.899
53
+ * of its real size — which a fit then turns into a subject overflowing its tile. `expand: 0`
54
+ * restores the old percentile-only box.
55
+ */
56
+ export function boundsFromPositions(
57
+ xyz: ArrayLike<number>,
58
+ opts?: { lo?: number; hi?: number; expand?: number },
59
+ ): SubjectBounds | null;
60
+
61
+ /** A single framed object in an inline-3D window: SBS loop, framing, orbit, mono fallback. */
62
+ export declare class SceneViewer {
63
+ constructor(THREE: object, canvas: HTMLCanvasElement, opts?: SceneViewerOptions);
64
+
65
+ readonly canvas: HTMLCanvasElement;
66
+ /** Put your three.js object in here. */
67
+ readonly content: object;
68
+ readonly scene: object;
69
+ readonly renderer: object;
70
+ readonly monoCamera: object;
71
+ /** True while the side-by-side backing store is in use. */
72
+ readonly is3D: boolean;
73
+
74
+ /** Centre the subject on the zero-disparity plane and scale it to the tile. */
75
+ fitTo(
76
+ center: number[] | { x: number; y: number; z: number },
77
+ extent: number[] | { x: number; y: number; z: number },
78
+ ): void;
79
+
80
+ setPose(pose?: OrbitPose): void;
81
+ resetPose(): void;
82
+
83
+ /** Pass straight to `wall.addScene(canvas, viewer.onFrame, …)`. Pre-bound. */
84
+ onFrame(views: readonly XRView[], layer: object): void;
85
+
86
+ /** Supply the ./three glue so the 3D path can build its eye camera. Returns `this`. */
87
+ useEyeCamera(EyeCameraClass: unknown, EdgeFeatherClass?: unknown): this;
88
+
89
+ /** Flat single-camera loop for browsers without inline-3D. */
90
+ startMono(): void;
91
+ stopMono(): void;
92
+
93
+ dispose(): void;
94
+ }