@harpern/edu-modelhub-viewer-core 0.1.0-beta.1 → 0.1.0-beta.3
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/CHANGELOG.md +37 -1
- package/README.md +3 -3
- package/dist/backgrounds.d.ts +7 -7
- package/dist/camera.d.ts +3 -3
- package/dist/framing.d.ts +2 -2
- package/dist/index.cjs +7 -7
- package/dist/index.d.ts +2 -2
- package/dist/index.js +139 -123
- package/dist/lighting.d.ts +1 -1
- package/dist/markers.d.ts +4 -4
- package/dist/normalize.d.ts +9 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,35 @@ here.
|
|
|
6
6
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
7
7
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
8
8
|
|
|
9
|
+
## [Unreleased]
|
|
10
|
+
|
|
11
|
+
## [0.1.0-beta.3] - 2026-09-13
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- American spellings throughout the source, tests and README, matching the
|
|
16
|
+
rest of the project. The only user-visible effect is the background swatch
|
|
17
|
+
labels, which now read "Light gray", "Medium gray" and "Dark gray". The
|
|
18
|
+
swatch ids are deliberately unchanged, still `light-grey`, `medium-grey`
|
|
19
|
+
and `dark-grey`, because both viewers and any saved collection or model
|
|
20
|
+
settings refer to them by id.
|
|
21
|
+
|
|
22
|
+
## [0.1.0-beta.2] - 2026-09-13
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- `readableRegions` and `isAuthoredRegion`, for prose views such as a reading
|
|
27
|
+
mode. The workspace addon invents one region per object when
|
|
28
|
+
nobody authored any, described as "Auto-created from object …"; these
|
|
29
|
+
helpers drop those while keeping named regions that simply have no
|
|
30
|
+
description. Interactive region lists should keep using every region,
|
|
31
|
+
because an invented region still highlights real geometry.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- The spoken model summary says "labeled regions" instead of "labelled
|
|
36
|
+
regions".
|
|
37
|
+
|
|
9
38
|
## [0.1.0-beta.1] - 2026-09-13
|
|
10
39
|
|
|
11
40
|
### Fixed
|
|
@@ -21,7 +50,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
21
50
|
which checks the error name: with the parser bundled in, a class identity
|
|
22
51
|
check against a separate copy would silently fail.
|
|
23
52
|
|
|
24
|
-
## [
|
|
53
|
+
## [0.1.0-beta.0] - 2026-09-13
|
|
54
|
+
|
|
55
|
+
First publish. Deprecated: it declared a runtime dependency on an unpublished
|
|
56
|
+
package and cannot be installed. Use `0.1.0-beta.1` or later.
|
|
25
57
|
|
|
26
58
|
### Added
|
|
27
59
|
|
|
@@ -43,4 +75,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
43
75
|
so rigged models no longer frame several times too small.
|
|
44
76
|
- Camera placement preferring an authored camera, with an eased flight that
|
|
45
77
|
becomes a jump cut under reduced motion.
|
|
78
|
+
- Lighting presets, including rigs measured from Sketchfab's viewer, applied
|
|
79
|
+
and removed as a unit.
|
|
80
|
+
- Background swatches, with custom colors resolved and their lightness
|
|
81
|
+
measured so overlays stay readable.
|
|
46
82
|
- A spoken model summary, a throttled announcer, and a Markdown transcript.
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The parts of an EDU_modelhub viewer that should not be written twice.
|
|
4
4
|
|
|
5
|
-
This package holds the
|
|
5
|
+
This package holds the behavior two viewers of the same model must agree on:
|
|
6
6
|
how metadata is found, what happens when it is missing, how annotations are
|
|
7
7
|
numbered, where markers sit, which geometry a region owns, and where the
|
|
8
8
|
camera goes when someone selects an annotation.
|
|
@@ -72,7 +72,7 @@ used to mean the opposite, so this rule is enforced in one function.
|
|
|
72
72
|
|
|
73
73
|
**Regions bound to geometry.** Built from `primitive.extras.modelhubRegionId`,
|
|
74
74
|
which three.js copies onto the geometry's `userData` rather than the mesh's.
|
|
75
|
-
`applyVisibilityDefaults`
|
|
75
|
+
`applyVisibilityDefaults` honors a region authored as hidden.
|
|
76
76
|
|
|
77
77
|
**Markers.** Numbered from reading order, so the visual order and the
|
|
78
78
|
announced order cannot disagree. Use sprites in the scene, or project anchors
|
|
@@ -86,6 +86,6 @@ viewer prefers reduced motion.
|
|
|
86
86
|
**Text.** A spoken model summary, a throttled announcer so held keys do not
|
|
87
87
|
flood a live region, and a Markdown transcript of everything the model says.
|
|
88
88
|
|
|
89
|
-
##
|
|
89
|
+
## License
|
|
90
90
|
|
|
91
91
|
Apache-2.0.
|
package/dist/backgrounds.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Viewer background
|
|
2
|
+
* Viewer background colors.
|
|
3
3
|
*
|
|
4
4
|
* Which background a model sits on changes how readable it is: a pale bone
|
|
5
|
-
* specimen disappears on white and reads clearly on dark
|
|
5
|
+
* specimen disappears on white and reads clearly on dark gray. Offering the
|
|
6
6
|
* same choices in every viewer means an instructor who picked one for a
|
|
7
7
|
* collection gets it wherever the model is shown.
|
|
8
8
|
*/
|
|
@@ -12,11 +12,11 @@ export interface BackgroundOption {
|
|
|
12
12
|
label: string;
|
|
13
13
|
/** Canonical `#rrggbb`. */
|
|
14
14
|
hex: string;
|
|
15
|
-
/** True when the
|
|
15
|
+
/** True when the color is light enough that dark overlays read better. */
|
|
16
16
|
light: boolean;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
-
* The standard swatches. Dark
|
|
19
|
+
* The standard swatches. Dark gray is the reference background, and gallery
|
|
20
20
|
* thumbnails are captured against it.
|
|
21
21
|
*/
|
|
22
22
|
export declare const BACKGROUND_OPTIONS: readonly BackgroundOption[];
|
|
@@ -25,7 +25,7 @@ export declare const DEFAULT_BACKGROUND = "dark-grey";
|
|
|
25
25
|
/**
|
|
26
26
|
* Accept `#rgb` or `#rrggbb`, with or without the hash, and return canonical
|
|
27
27
|
* `#rrggbb`. Anything else returns an empty string, so a viewer falls back to
|
|
28
|
-
* a known swatch rather than handing an invalid
|
|
28
|
+
* a known swatch rather than handing an invalid color to the renderer.
|
|
29
29
|
*/
|
|
30
30
|
export declare function normalizeHexColor(value: unknown): string;
|
|
31
31
|
/** Perceived brightness, 0 to 1, using the usual luma weights. */
|
|
@@ -33,8 +33,8 @@ export declare function luminance(hex: string): number;
|
|
|
33
33
|
/**
|
|
34
34
|
* The background actually in force.
|
|
35
35
|
*
|
|
36
|
-
* A custom
|
|
36
|
+
* A custom color, typically chosen per collection or per model, wins over
|
|
37
37
|
* the named swatch, and its `light` flag is measured rather than declared so
|
|
38
|
-
* overlays stay readable on a
|
|
38
|
+
* overlays stay readable on a color nobody anticipated.
|
|
39
39
|
*/
|
|
40
40
|
export declare function resolveBackground(backgroundId?: string, custom?: string): BackgroundOption;
|
package/dist/camera.d.ts
CHANGED
|
@@ -24,9 +24,9 @@ export interface CameraPlacement {
|
|
|
24
24
|
/** Tuning for the computed fallback placement. */
|
|
25
25
|
export interface TourPlacementOptions {
|
|
26
26
|
/**
|
|
27
|
-
* `true` puts the camera between the model
|
|
27
|
+
* `true` puts the camera between the model center and the annotation,
|
|
28
28
|
* looking outward, which suits interior walkthrough scenes. `false` puts it
|
|
29
|
-
* beyond the annotation looking back at the
|
|
29
|
+
* beyond the annotation looking back at the center, which suits a single
|
|
30
30
|
* specimen and needs a real standoff or the camera lands inside the mesh.
|
|
31
31
|
*/
|
|
32
32
|
outward?: boolean;
|
|
@@ -60,7 +60,7 @@ export interface FlyOptions {
|
|
|
60
60
|
durationMs?: number;
|
|
61
61
|
/** Called after each step so the host can request a render. */
|
|
62
62
|
onFrame?: () => void;
|
|
63
|
-
/** Called once the move finishes or is
|
|
63
|
+
/** Called once the move finishes or is canceled. */
|
|
64
64
|
onDone?: () => void;
|
|
65
65
|
}
|
|
66
66
|
/** Handle for an in-flight camera move. */
|
package/dist/framing.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Fitting a model into view.
|
|
3
3
|
*
|
|
4
4
|
* Models arrive at wildly different scales, so every viewer normalizes them
|
|
5
|
-
* to a known size and
|
|
5
|
+
* to a known size and centers them at the origin. Doing it the same way in
|
|
6
6
|
* both apps means an authored camera means the same thing in each.
|
|
7
7
|
*/
|
|
8
8
|
import { Box3 } from "three";
|
|
@@ -19,7 +19,7 @@ export declare const NORMALIZED_SIZE = 2;
|
|
|
19
19
|
*/
|
|
20
20
|
export declare function computeDisplayBox(root: Object3D): Box3;
|
|
21
21
|
/**
|
|
22
|
-
* Scale a model so its longest axis is `size`, then
|
|
22
|
+
* Scale a model so its longest axis is `size`, then center it at the origin.
|
|
23
23
|
* Mutates the model's transform and returns the scale factor applied.
|
|
24
24
|
*/
|
|
25
25
|
export declare function frameModel(root: Object3D, size?: number): number;
|
package/dist/index.cjs
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const p=require("three"),_=new Set(["__proto__","constructor","prototype"]);function U(e,t){const n=new WeakSet,o=[{value:e,path:""}],r=(i,s)=>{o.push({value:i,path:s})};for(;o.length>0;){const{value:i,path:s}=o.pop();if(!(typeof i!="object"||i===null||n.has(i)))if(n.add(i),Array.isArray(i))for(let c=0;c<i.length;c++)r(i[c],`${s}[${c}]`);else t(i,s,r)}}function re(e){const t=[];return U(e,(n,o,r)=>{for(const i of Object.keys(n)){const s=o===""?i:`${o}.${i}`;_.has(i)?t.push({path:s,key:i}):r(n[i],s)}}),t}function ie(e){return U(e,(t,n,o)=>{for(const r of Object.keys(t))_.has(r)?delete t[r]:o(t[r],n)}),e}const L=1179937895,E=2,se=1313821514,ae=21;class y extends Error{constructor(t){super(t),this.name="GlbParseError"}}function ce(e){return e instanceof ArrayBuffer?new DataView(e):new DataView(e.buffer,e.byteOffset,e.byteLength)}function ue(e){return e===null?"null":Array.isArray(e)?"an array":typeof e}function le(e){const t=ce(e);if(t.byteLength<ae)throw new y(`Input too short to be a GLB file (${t.byteLength} bytes).`);const n=t.getUint32(0,!0);if(n!==L)throw new y(`Invalid GLB magic: expected 0x${L.toString(16)}, got 0x${n.toString(16)}.`);const o=t.getUint32(4,!0);if(o!==E)throw new y(`Unsupported GLB version: expected ${E}, got ${o}.`);const r=t.getUint32(8,!0);if(r>t.byteLength)throw new y(`GLB header reports ${r} bytes but only ${t.byteLength} bytes were supplied.`);if(t.byteLength<20)throw new y("GLB file truncated before first chunk header.");const i=t.getUint32(12,!0),s=t.getUint32(16,!0);if(s!==se)throw new y(`First GLB chunk is not JSON (type 0x${s.toString(16)}).`);if(20+i>r)throw new y(`JSON chunk claims ${i} bytes but the declared GLB length is only ${r} bytes.`);if(20+i>t.byteLength)throw new y(`JSON chunk claims ${i} bytes but only ${t.byteLength-20} bytes remain in the buffer.`);const c=new Uint8Array(t.buffer,t.byteOffset+20,i),l=new TextDecoder("utf-8").decode(c);let a;try{a=JSON.parse(l)}catch(u){throw new y(`Failed to parse GLB JSON chunk: ${u.message}`)}if(a===null||typeof a!="object"||Array.isArray(a))throw new y(`GLB JSON chunk root must be an object, got ${ue(a)}.`);return a}function de(e){const t=le(e).extensions;if(t===null||typeof t!="object"||Array.isArray(t))return null;const n=t.EDU_modelhub;return n===void 0?null:ie(n)}const fe=/^[a-z][a-z0-9_]*$/,me=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/,he=/^[a-z]{2}$/,pe=/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;function d(e,t){return{field:e,message:t,severity:"error"}}function $(e,t){return{field:e,message:t,severity:"warning"}}function x(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function w(e){return typeof e=="string"&&e.trim()!==""}function F(e){return typeof e=="number"&&Number.isInteger(e)&&e>=0}function h(e){if(e===void 0)return"undefined";let t;if(typeof e=="string")t=`"${e}"`;else try{const n=JSON.stringify(e);if(n===void 0)return typeof e;t=n}catch{return Array.isArray(e)?"an array":typeof e}return t.length>60?`${t.slice(0,57)}...`:t}function V(e){const t=[],n=e;if(!x(n))return[d("",`Payload must be a JSON object, got ${h(n)}.`)];ge(n,t),ye(n.specVersion,t),be(n.coordinateSpace,t),$e(n.model,t);const o=Se(n.regions,t);return Ie(n.annotations,o,t),t}function ge(e,t){for(const n of re(e))t.push(d(n.path,`Disallowed key "${n.key}" at ${n.path} (prototype-pollution vector).`))}function ye(e,t){(typeof e!="string"||!pe.test(e))&&t.push(d("specVersion",`specVersion must be a semver string (e.g. "0.1.0"), got ${h(e)}.`))}function be(e,t){e!==void 0&&e!=="gltf"&&e!=="blender"&&t.push(d("coordinateSpace",`coordinateSpace must be "gltf" or "blender" when present, got ${h(e)}.`))}function $e(e,t){if(!x(e)){t.push(d("model",`model is required and must be an object, got ${h(e)}.`));return}const n=e,o=n.title;w(o)||t.push(d("model.title",`model.title is required and must be a non-empty string, got ${h(o)}.`));const r=n.altText;w(r)?r.length>300&&t.push(d("model.altText",`model.altText exceeds 300 characters (${r.length} chars).`)):t.push(d("model.altText",`model.altText is required and must be a non-empty string, got ${h(r)}.`));const i=n.language;i!==void 0&&(typeof i!="string"||!he.test(i))&&t.push($("model.language",`model.language should be an ISO 639-1 two-letter code (e.g. "en"), got ${h(i)}.`)),we(n,t),xe(n.source,t),Ae(n,t),w(n.longDescription)||t.push($("model.longDescription","model.longDescription is recommended."))}function we(e,t){const n=["CC0","CC BY","CC BY-SA","All Rights Reserved"],o=e.license;o!==void 0&&(typeof o!="string"||!n.includes(o))&&t.push(d("model.license",`model.license must be one of ${n.map(r=>`"${r}"`).join(", ")}, got ${h(o)}.`)),(o==="CC BY"||o==="CC BY-SA")&&!w(e.attribution)&&t.push($("model.attribution",'model.attribution is recommended when license is "CC BY" or "CC BY-SA".'))}function xe(e,t){if(e===void 0)return;const n=["scan","photogrammetry","manual","other"];if(typeof e!="string"||!n.includes(e)){const o=n.map(r=>`"${r}"`).join(", ");t.push(d("model.source",`model.source must be one of ${o}, got ${h(e)}.`))}}function Ae(e,t){for(const n of["created","modified"]){const o=e[n];o!==void 0&&(typeof o!="string"||!me.test(o))&&t.push($(`model.${n}`,`model.${n} should be ISO 8601 UTC (YYYY-MM-DDTHH:MM:SSZ), got ${h(o)}.`))}}function Se(e,t){const n=new Set;if(!Array.isArray(e)||e.length===0)return t.push(d("regions","At least one region is required.")),n;for(let o=0;o<e.length;o++){const r=e[o],i=`regions[${o}]`;if(!x(r)){t.push(d(i,`${i} must be an object, got ${h(r)}.`));continue}ve(r,i,n,t)}return n}function ve(e,t,n,o){const r=e.id;typeof r!="string"||!fe.test(r)?o.push(d(`${t}.id`,`${t}.id must be a non-empty snake_case string (lowercase letters, digits, underscores; must start with a letter), got ${h(r)}.`)):n.has(r)?o.push(d(`${t}.id`,`Duplicate region id "${r}" at ${t}.`)):n.add(r);const i=e.visibilityDefault;i!==void 0&&i!=="shown"&&i!=="hidden"&&o.push(d(`${t}.visibilityDefault`,`${t}.visibilityDefault must be "shown" or "hidden", got ${h(i)}.`));const s=e.order;s!==void 0&&!F(s)&&o.push($(`${t}.order`,`${t}.order should be a non-negative integer, got ${h(s)}.`))}function Ie(e,t,n){if(!Array.isArray(e)){n.push(d("annotations","annotations must be an array."));return}e.length<3&&n.push($("annotations",`Only ${e.length} annotation(s) defined (3–8 recommended).`)),e.length>8&&n.push($("annotations",`${e.length} annotations defined (3–8 recommended).`));const o={seenRegionIds:t,seenAnnotationIds:new Set,seenReadingOrders:new Set};for(let r=0;r<e.length;r++){const i=e[r],s=`annotations[${r}]`;if(!x(i)){n.push(d(s,`${s} must be an object, got ${h(i)}.`));continue}Me(i,s,o,n)}Be(o.seenReadingOrders,n)}function Me(e,t,n,o){const r=e.id;w(r)?n.seenAnnotationIds.has(r)?o.push(d(`${t}.id`,`Duplicate annotation id "${r}" at ${t}.`)):n.seenAnnotationIds.add(r):o.push(d(`${t}.id`,`${t}.id is required and must be a non-empty string, got ${h(r)}.`));const i=e.regionId;i!==void 0&&i!==""&&(typeof i!="string"||!n.seenRegionIds.has(i))&&o.push(d(`${t}.regionId`,`${t}.regionId ${h(i)} does not match any region id.`)),Oe(e.readingOrder,t,n,o),Te(e.anchor,t,o),Ne(e.camera,t,o)}function Oe(e,t,n,o){e!==void 0&&(F(e)?n.seenReadingOrders.has(e)?o.push(d(`${t}.readingOrder`,`Duplicate readingOrder value ${e} at ${t}.`)):n.seenReadingOrders.add(e):o.push(d(`${t}.readingOrder`,`${t}.readingOrder must be a non-negative integer, got ${h(e)}.`)))}function Te(e,t,n){if(!x(e)){n.push(d(`${t}.anchor`,`${t}.anchor is required and must be an object, got ${h(e)}.`));return}const o=e,r=o.type;if(r==="marker"){const i=o.position;(!Array.isArray(i)||i.length!==3||!i.every(s=>typeof s=="number"))&&n.push(d(`${t}.anchor.position`,`${t}.anchor.position must be an array of exactly 3 numbers for type "marker".`))}else r==="node"||r==="primitive"||r==="aabb"?w(o.ref)||n.push(d(`${t}.anchor.ref`,`${t}.anchor.ref is required for anchor type "${r}".`)):n.push(d(`${t}.anchor.type`,`${t}.anchor.type must be "marker", "node", "primitive", or "aabb", got ${h(r)}.`))}function Ne(e,t,n){if(e===void 0)return;if(!x(e)){n.push(d(`${t}.camera`,`${t}.camera must be an object when present, got ${h(e)}.`));return}for(const r of["eye","target"]){const i=e[r];(!Array.isArray(i)||i.length!==3||!i.every(s=>typeof s=="number"&&Number.isFinite(s)))&&n.push(d(`${t}.camera.${r}`,`${t}.camera.${r} must be an array of exactly 3 finite numbers.`))}const o=e.fov;o!==void 0&&(typeof o!="number"||!(o>0))&&n.push(d(`${t}.camera.fov`,`${t}.camera.fov must be a number greater than 0 when present, got ${h(o)}.`))}function Be(e,t){if(e.size===0)return;const n=[...e].sort((o,r)=>o-r);for(let o=0;o<n.length;o++)if(n[o]!==o){t.push($("annotations[*].readingOrder",`readingOrder values are not sequential from 0. Expected 0–${n.length-1} but found gaps or wrong start.`));break}}class k extends Error{constructor(t){super(t),this.name="AdaptError"}}class Ce{adapt(t){var n,o;const r=t.model,i=((n=r==null?void 0:r.title)==null?void 0:n.trim())??"",s=((o=r==null?void 0:r.altText)==null?void 0:o.trim())??"";if(!i)throw new k("Sidecar is missing required field: model.title");if(!s)throw new k("Sidecar is missing required field: model.altText");return t}}const Re=new Ce;function Le(e){return Re.adapt(e)}function G(e){return e.filter(t=>t.severity==="error")}function P(e){const[t,n=""]=e.split("?",2);if(!/\.glb$/i.test(t))return null;const o=t.replace(/\.glb$/i,".modelhub.json");return n?`${o}?${n}`:o}function Ee(e,t){var i;if(!e||e.startsWith("blob:")||e.startsWith("data:"))return"";const n=t??((i=globalThis.location)==null?void 0:i.href),o=n?new URL(e,n).href:e,r=o.lastIndexOf("/");return r===-1?"":o.slice(0,r+1)}async function z(e,t=fetch){const n=await t(e);if(!n.ok)throw new Error(`Failed to fetch ${e} (HTTP ${n.status}).`);return n.arrayBuffer()}function j(e){const t=de(e);if(!t)return{payload:null,source:"none",findings:[],problem:"This model carries no EDU_modelhub metadata."};const n=V(t);return{payload:t,source:"embedded",findings:n,problem:null}}async function ke(e,t={}){const n=t.fetchImpl??fetch,o=await z(e,n),r=j(o);if(r.payload)return{...r,bytes:o};if(t.sidecar===!1)return{...r,bytes:o};const s=(t.sidecarUrl??P)(e);if(!s)return{...r,bytes:o};try{const c=await n(s);if(c.ok){const l=Le(await c.json());return{payload:l,source:"sidecar",findings:V(l),problem:null,bytes:o}}}catch{}return{payload:null,source:"none",findings:[],problem:"This model has no EDU_modelhub metadata, either embedded in the file or in a sidecar beside it.",bytes:o}}function De(e,t="EDU_modelhub",n=console){if(!(e!=null&&e.length))return;const o=G(e),r=e.filter(i=>i.severity!=="error");o.length&&n.warn(`${t}: ${o.length} validation error(s)`,o),r.length&&n.info(`${t}: ${r.length} validation warning(s)`,r)}function _e(e){return(e==null?void 0:e.name)==="GlbParseError"}function T(e){return Array.isArray(e)&&e.length===3&&e.every(t=>typeof t=="number"&&Number.isFinite(t))}function N(e){return e==="blender"}function A(e,t){if(!T(e))return null;const[n,o,r]=e;return N(t)?[n,r,-o]:[n,o,r]}function Ue(e,t){if(!T(e))return null;const[n,o,r]=e;return N(t)?[n,-r,o]:[n,o,r]}function W(e){if(!Array.isArray(e))return[];const t=new Set;return e.map((n,o)=>{const i=(typeof(n==null?void 0:n.id)=="string"?n.id.trim():"")||`annotation-${o+1}`;let s=i,c=2;for(;t.has(s);)s=`${i}-${c++}`;return t.add(s),{...n??{},id:s}})}function I(e){const t=W(e==null?void 0:e.annotations),n=o=>Number.isFinite(o.readingOrder)?o.readingOrder:Number.MAX_SAFE_INTEGER;return t.map((o,r)=>({annotation:o,index:r})).sort((o,r)=>n(o.annotation)-n(r.annotation)||o.index-r.index).map(o=>o.annotation)}function B(e){return(Array.isArray(e==null?void 0:e.regions)?e.regions:[]).map((n,o)=>({region:n,index:o})).sort((n,o)=>(n.region.order??Number.MAX_SAFE_INTEGER)-(o.region.order??Number.MAX_SAFE_INTEGER)||n.index-o.index).map(n=>n.region)}function M(e){var t;return e?((t=e.label)==null?void 0:t.trim())||e.id||"Unnamed region":"Unknown region"}function Fe(e){if(!e)return[];const t=[],n=/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g;let o=0,r;for(;(r=n.exec(e))!==null;)r.index>o&&t.push({text:e.slice(o,r.index)}),t.push({text:r[1],url:r[2]}),o=r.index+r[0].length;return o<e.length&&t.push({text:e.slice(o)}),t.filter(i=>i.text.trim()||i.url)}function Ve(e){const t=e==null?void 0:e.coordinateSpace,n=B(e),o=I(e),r=new Map(n.map(a=>[a.id,a])),i=new Map(o.map(a=>[a.id,a])),s=new Map;for(const a of o){const u=a.regionId;if(!u)continue;const f=s.get(u);f?f.push(a):s.set(u,[a])}const c=[];for(const a of o){const u=a.anchor;if(!u||u.type!=="marker")continue;const f=A(u.position,t);if(!f)continue;const m=a.camera,g=m?A(m.eye,t):null,b=m?A(m.target,t):null;c.push({annotation:a,position:f,...g&&b?{camera:{eye:g,target:b,...m!=null&&m.fov?{fov:m.fov}:{}}}:{}})}const l=new Set(n.filter(a=>a.visibilityDefault==="hidden").map(a=>a.id));return{payload:e,regions:n,regionsById:r,annotations:o,annotationsById:i,annotationsByRegion:s,markerAnchors:c,hiddenRegionIds:l}}function Ge(e){return e.isMesh===!0}function Pe(e,t,n=[]){var l;const o=new Map,r=new Map,i=new Map,s=new Map;for(const[a,u]of((t==null?void 0:t.meshes)??[]).entries())for(const f of u.primitives??[]){const m=(l=f.extras)==null?void 0:l.modelhubRegionId;typeof m=="string"&&m&&(s.set(a,m),u.name&&r.set(u.name,m))}for(const a of(t==null?void 0:t.nodes)??[]){if(a.mesh===void 0||!a.name)continue;const u=s.get(a.mesh);u&&i.set(a.name,u)}const c=new Set(n.map(a=>a.id));return e.traverse(a=>{var g,b;if(!Ge(a))return;const u=(b=(g=a.geometry)==null?void 0:g.userData)==null?void 0:b.modelhubRegionId,f=typeof u=="string"&&u||i.get(a.name)||r.get(a.name)||(c.has(a.name)?a.name:void 0);if(!f)return;const m=o.get(f);m?m.push(a):o.set(f,[a]),a.userData.modelhubOriginalMaterial===void 0&&(a.userData.modelhubOriginalMaterial=a.material)}),o}function ze(e){const t={};for(const[n,o]of e)t[n]=o.length;return t}function q(e,t,n){for(const o of e.get(t)??[])o.visible=n}function je(e,t){for(const n of e.keys())q(e,n,!t.has(n))}function We(e,t,n){for(const o of e.get(t)??[])o.material=n}function qe(e,t){for(const n of e.get(t)??[]){const o=n.userData.modelhubOriginalMaterial;o&&(n.material=o)}}const H="#2196f3",He="#ff5722";function C(e,t={}){const{fill:n=H,text:o="#ffffff",textStroke:r="rgba(0, 0, 0, 0.45)"}=t,i=256,s=document.createElement("canvas");s.width=i,s.height=i;const c=s.getContext("2d");return c&&(c.beginPath(),c.arc(i/2,i/2,i/2-4,0,Math.PI*2),c.fillStyle=n,c.fill(),c.strokeStyle="#ffffff",c.lineWidth=8,c.stroke(),c.fillStyle=o,c.strokeStyle=r,c.lineWidth=10,c.font="bold 132px Arial, sans-serif",c.textAlign="center",c.textBaseline="middle",c.strokeText(String(e),i/2,i/2),c.fillText(String(e),i/2,i/2)),s}function Y(e,t=.2,n){const o=new p.SpriteMaterial({map:new p.CanvasTexture(C(e,n)),transparent:!0,depthTest:!1}),r=new p.Sprite(o);return r.scale.set(t,t,1),r.renderOrder=999,r}function Ye(e,t,n){var r;const o=e.material;(r=o.map)==null||r.dispose(),o.map=new p.CanvasTexture(C(t,{fill:n})),o.needsUpdate=!0}function Ke(e,t,n=.2,o){return t.map((r,i)=>{const s=Y(i+1,n,o);return s.position.set(...r.position),s.name=`modelhub_marker_${r.annotation.id}`,s.userData.annotationId=r.annotation.id,s.userData.displayNumber=i+1,e.add(s),s})}function Ze(e){const{camera:t,model:n,anchors:o,width:r,height:i,isOccluded:s}=e,c=e.referenceDistance??1,l=new p.Vector3,a={};n.updateMatrixWorld();for(const u of o){l.set(...u.position).applyMatrix4(n.matrixWorld);const f=l.distanceTo(t.position),m=s?s(l,f):!1,g=l.clone().project(t);a[u.annotation.id]={x:(g.x*.5+.5)*r,y:(-g.y*.5+.5)*i,scale:Math.min(1.6,Math.max(.55,c/Math.max(f,.001))),zIndex:Math.max(1,Math.round((1-g.z)*1e5))+(m?0:2e5),inView:g.z<1&&g.x>-1.05&&g.x<1.05&&g.y>-1.05&&g.y<1.05,occluded:m}}return a}const K=.03,Xe={enabled:!1,isOccluded:()=>!1,dispose:()=>{}};async function Qe(e){var i;let t=[],n=[];if(e.traverse(s=>{if(!s.isMesh)return;const c=s.geometry;c!=null&&c.computeBoundsTree&&(t.push(c),n.push(s))}),!t.length)return Xe;for(const s of t)s.boundsTree||(i=s.computeBoundsTree)==null||i.call(s,{maxLeafTris:8});const o=new p.Raycaster;o.firstHitOnly=!0;const r=new p.Vector3;return{enabled:!0,isOccluded(s,c,l){return r.subVectors(c,s.position).normalize(),o.set(s.position,r),o.near=0,o.far=Math.max(0,l-K),o.camera=s,o.intersectObjects(n,!1).length>0},dispose(){var s;for(const c of t)(s=c.disposeBoundsTree)==null||s.call(c);t=[],n=[]}}}const v={headlight:{ambient:.7,lights:[{color:[1,1,1],intensity:.9,position:[4,8,6]},{color:[1,1,1],intensity:2.4,position:[0,0,0],attach:!0,spot:!0}]},studio:{ambient:.85,lights:[{color:[.78,.8,1],intensity:1.45,position:[1.35,6.75,-7.26]},{color:[1,.88,.71],intensity:1.5,position:[-5.71,3.5,7.43]},{color:[.42,.74,1],intensity:.25,position:[7.23,6.78,1.29]}]},"dual-cam":{ambient:.6,lights:[{color:[.6,.48,.3],intensity:1.6,position:[-1,0,1],attach:!0},{color:[.42,.42,.6],intensity:1.6,position:[1,0,1],attach:!0}]},"studio-cam":{ambient:.7,lights:[{color:[.78,.8,1],intensity:1.35,position:[-1,.3,1],attach:!0},{color:[1,.88,.71],intensity:1.4,position:[1,.3,1],attach:!0},{color:[.42,.74,1],intensity:.25,position:[7.23,6.78,1.29]}]}},Z="headlight";function Je(){return Object.keys(v)}function et(e){var t;(t=e.parent)==null||t.remove(e)}function tt(e){var c;const{scene:t,camera:n,exposure:o=1}=e;(c=e.previous)==null||c.remove();const r=v[e.preset??""]??v[Z],i=[],s=new p.AmbientLight(16777215,r.ambient*o);t.add(s),i.push(s);for(const l of r.lights){let a;if(l.spot){const f=new p.SpotLight(new p.Color(...l.color));f.angle=.5,f.penumbra=.9,f.decay=0,a=f}else a=new p.DirectionalLight(new p.Color(...l.color));a.intensity=l.intensity,a.position.set(...l.position);const u=a.target;l.attach?(u==null||u.position.set(0,0,l.spot?-1:0),n.add(a),u&&n.add(u)):(t.add(a),u&&t.add(u)),i.push(a),u&&i.push(u)}return{lights:i,remove(){for(const l of i)et(l);i.length=0}}}const S=[{id:"white",label:"White",hex:"#ffffff",light:!0},{id:"light-grey",label:"Light grey",hex:"#d4d4d4",light:!0},{id:"medium-grey",label:"Medium grey",hex:"#808080",light:!1},{id:"dark-grey",label:"Dark grey",hex:"#252525",light:!1},{id:"black",label:"Black",hex:"#000000",light:!1}],X="dark-grey";function Q(e){if(typeof e!="string")return"";const t=e.trim().replace(/^#/,"");return/^[0-9a-f]{6}$/i.test(t)?`#${t.toLowerCase()}`:/^[0-9a-f]{3}$/i.test(t)?`#${[...t].map(n=>n+n).join("").toLowerCase()}`:""}function J(e){const t=Number.parseInt(e.replace("#",""),16);return Number.isFinite(t)?(.299*(t>>16&255)+.587*(t>>8&255)+.114*(t&255))/255:0}function nt(e,t){const n=Q(t);return n?{id:"custom",label:"Custom",hex:n,light:J(n)>.5}:S.find(o=>o.id===e)??S.find(o=>o.id===X)??S[0]}const ee=2;function O(e){e.updateMatrixWorld(!0);const t=new p.Box3,n=new p.Box3;return e.traverse(o=>{const r=o,i=o;r.isSkinnedMesh?(r.computeBoundingBox(),r.boundingBox&&(n.copy(r.boundingBox).applyMatrix4(r.matrixWorld),t.union(n))):i.isMesh&&i.geometry&&(i.geometry.boundingBox||i.geometry.computeBoundingBox(),i.geometry.boundingBox&&(n.copy(i.geometry.boundingBox).applyMatrix4(i.matrixWorld),t.union(n)))}),t.isEmpty()?new p.Box3().setFromObject(e):t}function ot(e,t=ee){const o=O(e).getSize(new p.Vector3),r=Math.max(o.x,o.y,o.z)||1,i=t/r;e.scale.multiplyScalar(i);const s=O(e);return e.position.sub(s.getCenter(new p.Vector3)),i}function rt(e,t=1.3){return t/Math.tan(e*Math.PI/360)}const te=.5,ne=1.5;function it(e,t,n={}){t.updateMatrixWorld();const o=new p.Vector3(...e.position).applyMatrix4(t.matrixWorld);if(e.camera){const l=new p.Vector3(...e.camera.eye).applyMatrix4(t.matrixWorld),u=new p.Vector3(...e.camera.target).applyMatrix4(t.matrixWorld).clone().sub(l);u.lengthSq()<1e-10&&u.copy(o).sub(l),u.normalize();const f=Math.max(o.distanceTo(l),.2);return{position:l,target:l.clone().addScaledVector(u,f),...e.camera.fov?{fov:e.camera.fov}:{}}}const r=n.outward??!0,i=new p.Vector3(o.x,0,o.z);i.lengthSq()<1e-8&&i.set(0,0,1),i.normalize();const s=r?-(n.outwardDistance??te):n.inwardDistance??ne,c=o.clone().addScaledVector(i,s);return c.y=o.y,{position:c,target:o}}const st={cancel:()=>{}};function at(e){const{camera:t,controls:n,to:o,onFrame:r,onDone:i}=e,s=()=>{r==null||r(),i==null||i()};if(e.reducedMotion)return t.position.copy(o.position),n.target.copy(o.target),n.update(),s(),st;const c=e.durationMs??900,l=t.position.clone(),a=n.target.clone(),u=performance.now();let f=null;const m=g=>{const b=Math.min((g-u)/c,1),R=b*(2-b);t.position.lerpVectors(l,o.position,R),n.target.lerpVectors(a,o.target,R),n.update(),r==null||r(),b<1?f=requestAnimationFrame(m):(f=null,i==null||i())};return f=requestAnimationFrame(m),{cancel(){f!==null&&cancelAnimationFrame(f),f=null}}}function ct(e,t,n){const o=t||n;e.fov!==o&&(e.fov=o,e.updateProjectionMatrix())}const oe=500;function ut(e){var r,i,s;if(!e)return"Model loaded, but it carries no educational metadata.";const t=[];(r=e.model)!=null&&r.title&&t.push(`${e.model.title} loaded`),(i=e.model)!=null&&i.altText&&t.push(e.model.altText);const n=((s=e.regions)==null?void 0:s.length)??0,o=I(e).length;return n&&t.push(`${n} labelled ${n===1?"region":"regions"}`),o&&t.push(`${o} ${o===1?"annotation":"annotations"}`),t.map(c=>c.replace(/\.$/,"")).join(". ")+"."}function lt(e,t,n){return`Annotation ${t+1} of ${n}: ${e||"Untitled"}.`}function dt(e,t=oe,n=()=>Date.now()){let o=-1/0,r=null,i=null;const s=a=>{o=n(),r=null,e(a)};return{announce:a=>{const u=n()-o;if(u>=t){s(a);return}r=a,i||(i=setTimeout(()=>{i=null,r!==null&&s(r)},t-u))},flush:()=>{i&&(clearTimeout(i),i=null),r!==null&&s(r)}}}function D(e,t){return`## ${e}
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const p=require("three"),_=new Set(["__proto__","constructor","prototype"]);function U(e,t){const n=new WeakSet,r=[{value:e,path:""}],o=(i,s)=>{r.push({value:i,path:s})};for(;r.length>0;){const{value:i,path:s}=r.pop();if(!(typeof i!="object"||i===null||n.has(i)))if(n.add(i),Array.isArray(i))for(let c=0;c<i.length;c++)o(i[c],`${s}[${c}]`);else t(i,s,o)}}function ie(e){const t=[];return U(e,(n,r,o)=>{for(const i of Object.keys(n)){const s=r===""?i:`${r}.${i}`;_.has(i)?t.push({path:s,key:i}):o(n[i],s)}}),t}function se(e){return U(e,(t,n,r)=>{for(const o of Object.keys(t))_.has(o)?delete t[o]:r(t[o],n)}),e}const E=1179937895,L=2,ae=1313821514,ce=21;class y extends Error{constructor(t){super(t),this.name="GlbParseError"}}function ue(e){return e instanceof ArrayBuffer?new DataView(e):new DataView(e.buffer,e.byteOffset,e.byteLength)}function le(e){return e===null?"null":Array.isArray(e)?"an array":typeof e}function de(e){const t=ue(e);if(t.byteLength<ce)throw new y(`Input too short to be a GLB file (${t.byteLength} bytes).`);const n=t.getUint32(0,!0);if(n!==E)throw new y(`Invalid GLB magic: expected 0x${E.toString(16)}, got 0x${n.toString(16)}.`);const r=t.getUint32(4,!0);if(r!==L)throw new y(`Unsupported GLB version: expected ${L}, got ${r}.`);const o=t.getUint32(8,!0);if(o>t.byteLength)throw new y(`GLB header reports ${o} bytes but only ${t.byteLength} bytes were supplied.`);if(t.byteLength<20)throw new y("GLB file truncated before first chunk header.");const i=t.getUint32(12,!0),s=t.getUint32(16,!0);if(s!==ae)throw new y(`First GLB chunk is not JSON (type 0x${s.toString(16)}).`);if(20+i>o)throw new y(`JSON chunk claims ${i} bytes but the declared GLB length is only ${o} bytes.`);if(20+i>t.byteLength)throw new y(`JSON chunk claims ${i} bytes but only ${t.byteLength-20} bytes remain in the buffer.`);const c=new Uint8Array(t.buffer,t.byteOffset+20,i),l=new TextDecoder("utf-8").decode(c);let a;try{a=JSON.parse(l)}catch(u){throw new y(`Failed to parse GLB JSON chunk: ${u.message}`)}if(a===null||typeof a!="object"||Array.isArray(a))throw new y(`GLB JSON chunk root must be an object, got ${le(a)}.`);return a}function fe(e){const t=de(e).extensions;if(t===null||typeof t!="object"||Array.isArray(t))return null;const n=t.EDU_modelhub;return n===void 0?null:se(n)}const me=/^[a-z][a-z0-9_]*$/,he=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/,pe=/^[a-z]{2}$/,ge=/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;function d(e,t){return{field:e,message:t,severity:"error"}}function $(e,t){return{field:e,message:t,severity:"warning"}}function x(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function w(e){return typeof e=="string"&&e.trim()!==""}function F(e){return typeof e=="number"&&Number.isInteger(e)&&e>=0}function h(e){if(e===void 0)return"undefined";let t;if(typeof e=="string")t=`"${e}"`;else try{const n=JSON.stringify(e);if(n===void 0)return typeof e;t=n}catch{return Array.isArray(e)?"an array":typeof e}return t.length>60?`${t.slice(0,57)}...`:t}function G(e){const t=[],n=e;if(!x(n))return[d("",`Payload must be a JSON object, got ${h(n)}.`)];ye(n,t),be(n.specVersion,t),$e(n.coordinateSpace,t),we(n.model,t);const r=ve(n.regions,t);return Me(n.annotations,r,t),t}function ye(e,t){for(const n of ie(e))t.push(d(n.path,`Disallowed key "${n.key}" at ${n.path} (prototype-pollution vector).`))}function be(e,t){(typeof e!="string"||!ge.test(e))&&t.push(d("specVersion",`specVersion must be a semver string (e.g. "0.1.0"), got ${h(e)}.`))}function $e(e,t){e!==void 0&&e!=="gltf"&&e!=="blender"&&t.push(d("coordinateSpace",`coordinateSpace must be "gltf" or "blender" when present, got ${h(e)}.`))}function we(e,t){if(!x(e)){t.push(d("model",`model is required and must be an object, got ${h(e)}.`));return}const n=e,r=n.title;w(r)||t.push(d("model.title",`model.title is required and must be a non-empty string, got ${h(r)}.`));const o=n.altText;w(o)?o.length>300&&t.push(d("model.altText",`model.altText exceeds 300 characters (${o.length} chars).`)):t.push(d("model.altText",`model.altText is required and must be a non-empty string, got ${h(o)}.`));const i=n.language;i!==void 0&&(typeof i!="string"||!pe.test(i))&&t.push($("model.language",`model.language should be an ISO 639-1 two-letter code (e.g. "en"), got ${h(i)}.`)),xe(n,t),Ae(n.source,t),Se(n,t),w(n.longDescription)||t.push($("model.longDescription","model.longDescription is recommended."))}function xe(e,t){const n=["CC0","CC BY","CC BY-SA","All Rights Reserved"],r=e.license;r!==void 0&&(typeof r!="string"||!n.includes(r))&&t.push(d("model.license",`model.license must be one of ${n.map(o=>`"${o}"`).join(", ")}, got ${h(r)}.`)),(r==="CC BY"||r==="CC BY-SA")&&!w(e.attribution)&&t.push($("model.attribution",'model.attribution is recommended when license is "CC BY" or "CC BY-SA".'))}function Ae(e,t){if(e===void 0)return;const n=["scan","photogrammetry","manual","other"];if(typeof e!="string"||!n.includes(e)){const r=n.map(o=>`"${o}"`).join(", ");t.push(d("model.source",`model.source must be one of ${r}, got ${h(e)}.`))}}function Se(e,t){for(const n of["created","modified"]){const r=e[n];r!==void 0&&(typeof r!="string"||!he.test(r))&&t.push($(`model.${n}`,`model.${n} should be ISO 8601 UTC (YYYY-MM-DDTHH:MM:SSZ), got ${h(r)}.`))}}function ve(e,t){const n=new Set;if(!Array.isArray(e)||e.length===0)return t.push(d("regions","At least one region is required.")),n;for(let r=0;r<e.length;r++){const o=e[r],i=`regions[${r}]`;if(!x(o)){t.push(d(i,`${i} must be an object, got ${h(o)}.`));continue}Ie(o,i,n,t)}return n}function Ie(e,t,n,r){const o=e.id;typeof o!="string"||!me.test(o)?r.push(d(`${t}.id`,`${t}.id must be a non-empty snake_case string (lowercase letters, digits, underscores; must start with a letter), got ${h(o)}.`)):n.has(o)?r.push(d(`${t}.id`,`Duplicate region id "${o}" at ${t}.`)):n.add(o);const i=e.visibilityDefault;i!==void 0&&i!=="shown"&&i!=="hidden"&&r.push(d(`${t}.visibilityDefault`,`${t}.visibilityDefault must be "shown" or "hidden", got ${h(i)}.`));const s=e.order;s!==void 0&&!F(s)&&r.push($(`${t}.order`,`${t}.order should be a non-negative integer, got ${h(s)}.`))}function Me(e,t,n){if(!Array.isArray(e)){n.push(d("annotations","annotations must be an array."));return}e.length<3&&n.push($("annotations",`Only ${e.length} annotation(s) defined (3–8 recommended).`)),e.length>8&&n.push($("annotations",`${e.length} annotations defined (3–8 recommended).`));const r={seenRegionIds:t,seenAnnotationIds:new Set,seenReadingOrders:new Set};for(let o=0;o<e.length;o++){const i=e[o],s=`annotations[${o}]`;if(!x(i)){n.push(d(s,`${s} must be an object, got ${h(i)}.`));continue}Oe(i,s,r,n)}Be(r.seenReadingOrders,n)}function Oe(e,t,n,r){const o=e.id;w(o)?n.seenAnnotationIds.has(o)?r.push(d(`${t}.id`,`Duplicate annotation id "${o}" at ${t}.`)):n.seenAnnotationIds.add(o):r.push(d(`${t}.id`,`${t}.id is required and must be a non-empty string, got ${h(o)}.`));const i=e.regionId;i!==void 0&&i!==""&&(typeof i!="string"||!n.seenRegionIds.has(i))&&r.push(d(`${t}.regionId`,`${t}.regionId ${h(i)} does not match any region id.`)),Te(e.readingOrder,t,n,r),Ne(e.anchor,t,r),Re(e.camera,t,r)}function Te(e,t,n,r){e!==void 0&&(F(e)?n.seenReadingOrders.has(e)?r.push(d(`${t}.readingOrder`,`Duplicate readingOrder value ${e} at ${t}.`)):n.seenReadingOrders.add(e):r.push(d(`${t}.readingOrder`,`${t}.readingOrder must be a non-negative integer, got ${h(e)}.`)))}function Ne(e,t,n){if(!x(e)){n.push(d(`${t}.anchor`,`${t}.anchor is required and must be an object, got ${h(e)}.`));return}const r=e,o=r.type;if(o==="marker"){const i=r.position;(!Array.isArray(i)||i.length!==3||!i.every(s=>typeof s=="number"))&&n.push(d(`${t}.anchor.position`,`${t}.anchor.position must be an array of exactly 3 numbers for type "marker".`))}else o==="node"||o==="primitive"||o==="aabb"?w(r.ref)||n.push(d(`${t}.anchor.ref`,`${t}.anchor.ref is required for anchor type "${o}".`)):n.push(d(`${t}.anchor.type`,`${t}.anchor.type must be "marker", "node", "primitive", or "aabb", got ${h(o)}.`))}function Re(e,t,n){if(e===void 0)return;if(!x(e)){n.push(d(`${t}.camera`,`${t}.camera must be an object when present, got ${h(e)}.`));return}for(const o of["eye","target"]){const i=e[o];(!Array.isArray(i)||i.length!==3||!i.every(s=>typeof s=="number"&&Number.isFinite(s)))&&n.push(d(`${t}.camera.${o}`,`${t}.camera.${o} must be an array of exactly 3 finite numbers.`))}const r=e.fov;r!==void 0&&(typeof r!="number"||!(r>0))&&n.push(d(`${t}.camera.fov`,`${t}.camera.fov must be a number greater than 0 when present, got ${h(r)}.`))}function Be(e,t){if(e.size===0)return;const n=[...e].sort((r,o)=>r-o);for(let r=0;r<n.length;r++)if(n[r]!==r){t.push($("annotations[*].readingOrder",`readingOrder values are not sequential from 0. Expected 0–${n.length-1} but found gaps or wrong start.`));break}}class k extends Error{constructor(t){super(t),this.name="AdaptError"}}class Ce{adapt(t){var n,r;const o=t.model,i=((n=o==null?void 0:o.title)==null?void 0:n.trim())??"",s=((r=o==null?void 0:o.altText)==null?void 0:r.trim())??"";if(!i)throw new k("Sidecar is missing required field: model.title");if(!s)throw new k("Sidecar is missing required field: model.altText");return t}}const Ee=new Ce;function Le(e){return Ee.adapt(e)}function V(e){return e.filter(t=>t.severity==="error")}function P(e){const[t,n=""]=e.split("?",2);if(!/\.glb$/i.test(t))return null;const r=t.replace(/\.glb$/i,".modelhub.json");return n?`${r}?${n}`:r}function ke(e,t){var i;if(!e||e.startsWith("blob:")||e.startsWith("data:"))return"";const n=t??((i=globalThis.location)==null?void 0:i.href),r=n?new URL(e,n).href:e,o=r.lastIndexOf("/");return o===-1?"":r.slice(0,o+1)}async function z(e,t=fetch){const n=await t(e);if(!n.ok)throw new Error(`Failed to fetch ${e} (HTTP ${n.status}).`);return n.arrayBuffer()}function j(e){const t=fe(e);if(!t)return{payload:null,source:"none",findings:[],problem:"This model carries no EDU_modelhub metadata."};const n=G(t);return{payload:t,source:"embedded",findings:n,problem:null}}async function De(e,t={}){const n=t.fetchImpl??fetch,r=await z(e,n),o=j(r);if(o.payload)return{...o,bytes:r};if(t.sidecar===!1)return{...o,bytes:r};const s=(t.sidecarUrl??P)(e);if(!s)return{...o,bytes:r};try{const c=await n(s);if(c.ok){const l=Le(await c.json());return{payload:l,source:"sidecar",findings:G(l),problem:null,bytes:r}}}catch{}return{payload:null,source:"none",findings:[],problem:"This model has no EDU_modelhub metadata, either embedded in the file or in a sidecar beside it.",bytes:r}}function _e(e,t="EDU_modelhub",n=console){if(!(e!=null&&e.length))return;const r=V(e),o=e.filter(i=>i.severity!=="error");r.length&&n.warn(`${t}: ${r.length} validation error(s)`,r),o.length&&n.info(`${t}: ${o.length} validation warning(s)`,o)}function Ue(e){return(e==null?void 0:e.name)==="GlbParseError"}function T(e){return Array.isArray(e)&&e.length===3&&e.every(t=>typeof t=="number"&&Number.isFinite(t))}function N(e){return e==="blender"}function A(e,t){if(!T(e))return null;const[n,r,o]=e;return N(t)?[n,o,-r]:[n,r,o]}function Fe(e,t){if(!T(e))return null;const[n,r,o]=e;return N(t)?[n,-o,r]:[n,r,o]}function W(e){if(!Array.isArray(e))return[];const t=new Set;return e.map((n,r)=>{const i=(typeof(n==null?void 0:n.id)=="string"?n.id.trim():"")||`annotation-${r+1}`;let s=i,c=2;for(;t.has(s);)s=`${i}-${c++}`;return t.add(s),{...n??{},id:s}})}function I(e){const t=W(e==null?void 0:e.annotations),n=r=>Number.isFinite(r.readingOrder)?r.readingOrder:Number.MAX_SAFE_INTEGER;return t.map((r,o)=>({annotation:r,index:o})).sort((r,o)=>n(r.annotation)-n(o.annotation)||r.index-o.index).map(r=>r.annotation)}function R(e){return(Array.isArray(e==null?void 0:e.regions)?e.regions:[]).map((n,r)=>({region:n,index:r})).sort((n,r)=>(n.region.order??Number.MAX_SAFE_INTEGER)-(r.region.order??Number.MAX_SAFE_INTEGER)||n.index-r.index).map(n=>n.region)}const Ge="Auto-created from object ";function q(e){var n;const t=(n=e==null?void 0:e.description)==null?void 0:n.trim();return t?!t.startsWith(Ge):!1}function Ve(e){return Array.isArray(e)?e.filter(t=>{var n;return q(t)||!((n=t.description)!=null&&n.trim())}):[]}function M(e){var t;return e?((t=e.label)==null?void 0:t.trim())||e.id||"Unnamed region":"Unknown region"}function Pe(e){if(!e)return[];const t=[],n=/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g;let r=0,o;for(;(o=n.exec(e))!==null;)o.index>r&&t.push({text:e.slice(r,o.index)}),t.push({text:o[1],url:o[2]}),r=o.index+o[0].length;return r<e.length&&t.push({text:e.slice(r)}),t.filter(i=>i.text.trim()||i.url)}function ze(e){const t=e==null?void 0:e.coordinateSpace,n=R(e),r=I(e),o=new Map(n.map(a=>[a.id,a])),i=new Map(r.map(a=>[a.id,a])),s=new Map;for(const a of r){const u=a.regionId;if(!u)continue;const f=s.get(u);f?f.push(a):s.set(u,[a])}const c=[];for(const a of r){const u=a.anchor;if(!u||u.type!=="marker")continue;const f=A(u.position,t);if(!f)continue;const m=a.camera,g=m?A(m.eye,t):null,b=m?A(m.target,t):null;c.push({annotation:a,position:f,...g&&b?{camera:{eye:g,target:b,...m!=null&&m.fov?{fov:m.fov}:{}}}:{}})}const l=new Set(n.filter(a=>a.visibilityDefault==="hidden").map(a=>a.id));return{payload:e,regions:n,regionsById:o,annotations:r,annotationsById:i,annotationsByRegion:s,markerAnchors:c,hiddenRegionIds:l}}function je(e){return e.isMesh===!0}function We(e,t,n=[]){var l;const r=new Map,o=new Map,i=new Map,s=new Map;for(const[a,u]of((t==null?void 0:t.meshes)??[]).entries())for(const f of u.primitives??[]){const m=(l=f.extras)==null?void 0:l.modelhubRegionId;typeof m=="string"&&m&&(s.set(a,m),u.name&&o.set(u.name,m))}for(const a of(t==null?void 0:t.nodes)??[]){if(a.mesh===void 0||!a.name)continue;const u=s.get(a.mesh);u&&i.set(a.name,u)}const c=new Set(n.map(a=>a.id));return e.traverse(a=>{var g,b;if(!je(a))return;const u=(b=(g=a.geometry)==null?void 0:g.userData)==null?void 0:b.modelhubRegionId,f=typeof u=="string"&&u||i.get(a.name)||o.get(a.name)||(c.has(a.name)?a.name:void 0);if(!f)return;const m=r.get(f);m?m.push(a):r.set(f,[a]),a.userData.modelhubOriginalMaterial===void 0&&(a.userData.modelhubOriginalMaterial=a.material)}),r}function qe(e){const t={};for(const[n,r]of e)t[n]=r.length;return t}function H(e,t,n){for(const r of e.get(t)??[])r.visible=n}function He(e,t){for(const n of e.keys())H(e,n,!t.has(n))}function Ye(e,t,n){for(const r of e.get(t)??[])r.material=n}function Ke(e,t){for(const n of e.get(t)??[]){const r=n.userData.modelhubOriginalMaterial;r&&(n.material=r)}}const Y="#2196f3",Ze="#ff5722";function B(e,t={}){const{fill:n=Y,text:r="#ffffff",textStroke:o="rgba(0, 0, 0, 0.45)"}=t,i=256,s=document.createElement("canvas");s.width=i,s.height=i;const c=s.getContext("2d");return c&&(c.beginPath(),c.arc(i/2,i/2,i/2-4,0,Math.PI*2),c.fillStyle=n,c.fill(),c.strokeStyle="#ffffff",c.lineWidth=8,c.stroke(),c.fillStyle=r,c.strokeStyle=o,c.lineWidth=10,c.font="bold 132px Arial, sans-serif",c.textAlign="center",c.textBaseline="middle",c.strokeText(String(e),i/2,i/2),c.fillText(String(e),i/2,i/2)),s}function K(e,t=.2,n){const r=new p.SpriteMaterial({map:new p.CanvasTexture(B(e,n)),transparent:!0,depthTest:!1}),o=new p.Sprite(r);return o.scale.set(t,t,1),o.renderOrder=999,o}function Xe(e,t,n){var o;const r=e.material;(o=r.map)==null||o.dispose(),r.map=new p.CanvasTexture(B(t,{fill:n})),r.needsUpdate=!0}function Qe(e,t,n=.2,r){return t.map((o,i)=>{const s=K(i+1,n,r);return s.position.set(...o.position),s.name=`modelhub_marker_${o.annotation.id}`,s.userData.annotationId=o.annotation.id,s.userData.displayNumber=i+1,e.add(s),s})}function Je(e){const{camera:t,model:n,anchors:r,width:o,height:i,isOccluded:s}=e,c=e.referenceDistance??1,l=new p.Vector3,a={};n.updateMatrixWorld();for(const u of r){l.set(...u.position).applyMatrix4(n.matrixWorld);const f=l.distanceTo(t.position),m=s?s(l,f):!1,g=l.clone().project(t);a[u.annotation.id]={x:(g.x*.5+.5)*o,y:(-g.y*.5+.5)*i,scale:Math.min(1.6,Math.max(.55,c/Math.max(f,.001))),zIndex:Math.max(1,Math.round((1-g.z)*1e5))+(m?0:2e5),inView:g.z<1&&g.x>-1.05&&g.x<1.05&&g.y>-1.05&&g.y<1.05,occluded:m}}return a}const Z=.03,et={enabled:!1,isOccluded:()=>!1,dispose:()=>{}};async function tt(e){var i;let t=[],n=[];if(e.traverse(s=>{if(!s.isMesh)return;const c=s.geometry;c!=null&&c.computeBoundsTree&&(t.push(c),n.push(s))}),!t.length)return et;for(const s of t)s.boundsTree||(i=s.computeBoundsTree)==null||i.call(s,{maxLeafTris:8});const r=new p.Raycaster;r.firstHitOnly=!0;const o=new p.Vector3;return{enabled:!0,isOccluded(s,c,l){return o.subVectors(c,s.position).normalize(),r.set(s.position,o),r.near=0,r.far=Math.max(0,l-Z),r.camera=s,r.intersectObjects(n,!1).length>0},dispose(){var s;for(const c of t)(s=c.disposeBoundsTree)==null||s.call(c);t=[],n=[]}}}const v={headlight:{ambient:.7,lights:[{color:[1,1,1],intensity:.9,position:[4,8,6]},{color:[1,1,1],intensity:2.4,position:[0,0,0],attach:!0,spot:!0}]},studio:{ambient:.85,lights:[{color:[.78,.8,1],intensity:1.45,position:[1.35,6.75,-7.26]},{color:[1,.88,.71],intensity:1.5,position:[-5.71,3.5,7.43]},{color:[.42,.74,1],intensity:.25,position:[7.23,6.78,1.29]}]},"dual-cam":{ambient:.6,lights:[{color:[.6,.48,.3],intensity:1.6,position:[-1,0,1],attach:!0},{color:[.42,.42,.6],intensity:1.6,position:[1,0,1],attach:!0}]},"studio-cam":{ambient:.7,lights:[{color:[.78,.8,1],intensity:1.35,position:[-1,.3,1],attach:!0},{color:[1,.88,.71],intensity:1.4,position:[1,.3,1],attach:!0},{color:[.42,.74,1],intensity:.25,position:[7.23,6.78,1.29]}]}},X="headlight";function nt(){return Object.keys(v)}function rt(e){var t;(t=e.parent)==null||t.remove(e)}function ot(e){var c;const{scene:t,camera:n,exposure:r=1}=e;(c=e.previous)==null||c.remove();const o=v[e.preset??""]??v[X],i=[],s=new p.AmbientLight(16777215,o.ambient*r);t.add(s),i.push(s);for(const l of o.lights){let a;if(l.spot){const f=new p.SpotLight(new p.Color(...l.color));f.angle=.5,f.penumbra=.9,f.decay=0,a=f}else a=new p.DirectionalLight(new p.Color(...l.color));a.intensity=l.intensity,a.position.set(...l.position);const u=a.target;l.attach?(u==null||u.position.set(0,0,l.spot?-1:0),n.add(a),u&&n.add(u)):(t.add(a),u&&t.add(u)),i.push(a),u&&i.push(u)}return{lights:i,remove(){for(const l of i)rt(l);i.length=0}}}const S=[{id:"white",label:"White",hex:"#ffffff",light:!0},{id:"light-grey",label:"Light gray",hex:"#d4d4d4",light:!0},{id:"medium-grey",label:"Medium gray",hex:"#808080",light:!1},{id:"dark-grey",label:"Dark gray",hex:"#252525",light:!1},{id:"black",label:"Black",hex:"#000000",light:!1}],Q="dark-grey";function J(e){if(typeof e!="string")return"";const t=e.trim().replace(/^#/,"");return/^[0-9a-f]{6}$/i.test(t)?`#${t.toLowerCase()}`:/^[0-9a-f]{3}$/i.test(t)?`#${[...t].map(n=>n+n).join("").toLowerCase()}`:""}function ee(e){const t=Number.parseInt(e.replace("#",""),16);return Number.isFinite(t)?(.299*(t>>16&255)+.587*(t>>8&255)+.114*(t&255))/255:0}function it(e,t){const n=J(t);return n?{id:"custom",label:"Custom",hex:n,light:ee(n)>.5}:S.find(r=>r.id===e)??S.find(r=>r.id===Q)??S[0]}const te=2;function O(e){e.updateMatrixWorld(!0);const t=new p.Box3,n=new p.Box3;return e.traverse(r=>{const o=r,i=r;o.isSkinnedMesh?(o.computeBoundingBox(),o.boundingBox&&(n.copy(o.boundingBox).applyMatrix4(o.matrixWorld),t.union(n))):i.isMesh&&i.geometry&&(i.geometry.boundingBox||i.geometry.computeBoundingBox(),i.geometry.boundingBox&&(n.copy(i.geometry.boundingBox).applyMatrix4(i.matrixWorld),t.union(n)))}),t.isEmpty()?new p.Box3().setFromObject(e):t}function st(e,t=te){const r=O(e).getSize(new p.Vector3),o=Math.max(r.x,r.y,r.z)||1,i=t/o;e.scale.multiplyScalar(i);const s=O(e);return e.position.sub(s.getCenter(new p.Vector3)),i}function at(e,t=1.3){return t/Math.tan(e*Math.PI/360)}const ne=.5,re=1.5;function ct(e,t,n={}){t.updateMatrixWorld();const r=new p.Vector3(...e.position).applyMatrix4(t.matrixWorld);if(e.camera){const l=new p.Vector3(...e.camera.eye).applyMatrix4(t.matrixWorld),u=new p.Vector3(...e.camera.target).applyMatrix4(t.matrixWorld).clone().sub(l);u.lengthSq()<1e-10&&u.copy(r).sub(l),u.normalize();const f=Math.max(r.distanceTo(l),.2);return{position:l,target:l.clone().addScaledVector(u,f),...e.camera.fov?{fov:e.camera.fov}:{}}}const o=n.outward??!0,i=new p.Vector3(r.x,0,r.z);i.lengthSq()<1e-8&&i.set(0,0,1),i.normalize();const s=o?-(n.outwardDistance??ne):n.inwardDistance??re,c=r.clone().addScaledVector(i,s);return c.y=r.y,{position:c,target:r}}const ut={cancel:()=>{}};function lt(e){const{camera:t,controls:n,to:r,onFrame:o,onDone:i}=e,s=()=>{o==null||o(),i==null||i()};if(e.reducedMotion)return t.position.copy(r.position),n.target.copy(r.target),n.update(),s(),ut;const c=e.durationMs??900,l=t.position.clone(),a=n.target.clone(),u=performance.now();let f=null;const m=g=>{const b=Math.min((g-u)/c,1),C=b*(2-b);t.position.lerpVectors(l,r.position,C),n.target.lerpVectors(a,r.target,C),n.update(),o==null||o(),b<1?f=requestAnimationFrame(m):(f=null,i==null||i())};return f=requestAnimationFrame(m),{cancel(){f!==null&&cancelAnimationFrame(f),f=null}}}function dt(e,t,n){const r=t||n;e.fov!==r&&(e.fov=r,e.updateProjectionMatrix())}const oe=500;function ft(e){var o,i,s;if(!e)return"Model loaded, but it carries no educational metadata.";const t=[];(o=e.model)!=null&&o.title&&t.push(`${e.model.title} loaded`),(i=e.model)!=null&&i.altText&&t.push(e.model.altText);const n=((s=e.regions)==null?void 0:s.length)??0,r=I(e).length;return n&&t.push(`${n} labeled ${n===1?"region":"regions"}`),r&&t.push(`${r} ${r===1?"annotation":"annotations"}`),t.map(c=>c.replace(/\.$/,"")).join(". ")+"."}function mt(e,t,n){return`Annotation ${t+1} of ${n}: ${e||"Untitled"}.`}function ht(e,t=oe,n=()=>Date.now()){let r=-1/0,o=null,i=null;const s=a=>{r=n(),o=null,e(a)};return{announce:a=>{const u=n()-r;if(u>=t){s(a);return}o=a,i||(i=setTimeout(()=>{i=null,o!==null&&s(o)},t-u))},flush:()=>{i&&(clearTimeout(i),i=null),o!==null&&s(o)}}}function D(e,t){return`## ${e}
|
|
2
2
|
${t}
|
|
3
3
|
|
|
4
|
-
`}function
|
|
4
|
+
`}function pt(e,t={}){const n=e.model??{},r=R(e),o=I(e);let i=`# ${n.title||"Untitled model"}
|
|
5
5
|
|
|
6
|
-
`;if(n.altText&&(i+=D(t.summaryHeading??"Summary",n.altText)),n.longDescription&&(i+=D("Full description",n.longDescription)),
|
|
6
|
+
`;if(n.altText&&(i+=D(t.summaryHeading??"Summary",n.altText)),n.longDescription&&(i+=D("Full description",n.longDescription)),r.length){i+=`## Regions (${r.length})
|
|
7
7
|
|
|
8
|
-
`;for(const s of
|
|
8
|
+
`;for(const s of r)i+=`### ${M(s)}
|
|
9
9
|
${s.description||"No description."}
|
|
10
10
|
|
|
11
|
-
`}return
|
|
11
|
+
`}return o.length&&(i+=`## Annotations (${o.length})
|
|
12
12
|
|
|
13
|
-
`,
|
|
13
|
+
`,o.forEach((s,c)=>{const l=s.regionId?` (${M(r.find(u=>u.id===s.regionId))})`:"",a=`${c+1}. ${s.title||"Untitled"}${l}`;i+=`### ${a}
|
|
14
14
|
${s.body||"No description."}
|
|
15
15
|
|
|
16
16
|
`})),i+=`## Licensing
|
|
@@ -18,4 +18,4 @@ ${s.body||"No description."}
|
|
|
18
18
|
`,i+=`- **Attribution:** ${n.attribution||"Not specified"}
|
|
19
19
|
`,i+=`- **Creator:** ${n.creator||"Not specified"}
|
|
20
20
|
`,i+=`- **Source:** ${n.source||"Not specified"}
|
|
21
|
-
`,i}function
|
|
21
|
+
`,i}function gt(e){var n;return`${(((n=e.model)==null?void 0:n.title)||"model").trim().replace(/[^\w\-. ]+/g,"").replace(/\s+/g,"_")||"model"}_transcript.md`}function yt(e){e&&e.traverse(t=>{var i,s;const n=t,r=n.geometry;r&&(r.boundsTree&&r.disposeBoundsTree&&r.disposeBoundsTree(),(i=r.dispose)==null||i.call(r));const o=[].concat(n.material??[]);for(const c of o)if(c){for(const l of Object.values(c)){const a=l;a&&a.isTexture&&a.dispose()}(s=c.dispose)==null||s.call(c)}})}exports.BACKGROUND_OPTIONS=S;exports.CAMERA_ANNOUNCE_INTERVAL=oe;exports.DEFAULT_BACKGROUND=Q;exports.DEFAULT_LIGHTING=X;exports.LIGHTING_PRESETS=v;exports.MARKER_FILL=Y;exports.MARKER_FILL_ACTIVE=Ze;exports.NORMALIZED_SIZE=te;exports.OCCLUSION_BIAS=Z;exports.TOUR_DISTANCE_INWARD=re;exports.TOUR_DISTANCE_OUTWARD=ne;exports.annotationSummary=mt;exports.annotationsInReadingOrder=I;exports.applyFov=dt;exports.applyLighting=ot;exports.applyVisibilityDefaults=He;exports.buildRegionMeshMap=We;exports.buildTranscript=pt;exports.clearRegionHighlight=Ke;exports.computeDisplayBox=O;exports.computeTourPlacement=ct;exports.createAnnouncer=ht;exports.createMarkerSprite=K;exports.createMarkerSprites=Qe;exports.createOcclusionIndex=tt;exports.disposeSceneGraph=yt;exports.distanceForFov=at;exports.drawMarkerCanvas=B;exports.errorsOf=V;exports.fetchBytes=z;exports.flyCamera=lt;exports.frameModel=st;exports.fromViewerPosition=Fe;exports.highlightRegion=Ye;exports.isAuthoredRegion=q;exports.isGlbParseError=Ue;exports.isVec3=T;exports.lightingPresetNames=nt;exports.loadModel=De;exports.logFindings=_e;exports.luminance=ee;exports.modelSummary=ft;exports.needsConversion=N;exports.normalizeAnnotationIds=W;exports.normalizeHexColor=J;exports.normalizeModel=ze;exports.parseBodySegments=Pe;exports.projectMarkers=Je;exports.readEmbedded=j;exports.readableRegions=Ve;exports.regionBindingCounts=qe;exports.regionLabel=M;exports.regionsInOrder=R;exports.resolveBackground=it;exports.resourcePathFor=ke;exports.setMarkerSpriteColor=Xe;exports.setRegionVisible=H;exports.sidecarUrlFor=P;exports.toViewerPosition=A;exports.transcriptFileName=gt;
|
package/dist/index.d.ts
CHANGED
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
* placing annotation markers, and moving the camera to them.
|
|
8
8
|
*
|
|
9
9
|
* Nothing here renders or owns a scene. A host brings its own three.js
|
|
10
|
-
* renderer and its own UI; this package supplies the
|
|
10
|
+
* renderer and its own UI; this package supplies the behavior underneath so
|
|
11
11
|
* two viewers of the same model agree about what it says.
|
|
12
12
|
*/
|
|
13
13
|
export { errorsOf, fetchBytes, loadModel, logFindings, isGlbParseError, readEmbedded, resourcePathFor, sidecarUrlFor, type LoadOptions, type LoadedModel, type MetadataResult, type MetadataSource } from "./metadata.js";
|
|
14
14
|
export type { Annotation, AnnotationCamera, EduModelHub, ModelLicense, ModelMetadata, ModelSource, Region, ValidationResult, ValidationSeverity, VisibilityDefault } from "./types.js";
|
|
15
15
|
export { fromViewerPosition, isVec3, needsConversion, toViewerPosition, type Vec3 } from "./coordinates.js";
|
|
16
|
-
export { annotationsInReadingOrder, normalizeAnnotationIds, normalizeModel, parseBodySegments, regionLabel, regionsInOrder, type BodySegment, type IdentifiedAnnotation, type MarkerAnchor, type NormalizedModel } from "./normalize.js";
|
|
16
|
+
export { annotationsInReadingOrder, isAuthoredRegion, normalizeAnnotationIds, normalizeModel, parseBodySegments, readableRegions, regionLabel, regionsInOrder, type BodySegment, type IdentifiedAnnotation, type MarkerAnchor, type NormalizedModel } from "./normalize.js";
|
|
17
17
|
export { applyVisibilityDefaults, buildRegionMeshMap, clearRegionHighlight, highlightRegion, regionBindingCounts, setRegionVisible, type GltfJsonLike, type RegionMeshMap } from "./regions.js";
|
|
18
18
|
export { MARKER_FILL, MARKER_FILL_ACTIVE, createMarkerSprite, createMarkerSprites, drawMarkerCanvas, projectMarkers, setMarkerSpriteColor, type MarkerColors, type MarkerScreenPosition, type ProjectMarkersOptions } from "./markers.js";
|
|
19
19
|
export { OCCLUSION_BIAS, createOcclusionIndex, type OcclusionIndex } from "./occlusion.js";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { SpriteMaterial as
|
|
2
|
-
const
|
|
1
|
+
import { SpriteMaterial as V, CanvasTexture as E, Sprite as W, Vector3 as b, Raycaster as q, AmbientLight as Y, SpotLight as H, Color as T, DirectionalLight as X, Box3 as v } from "three";
|
|
2
|
+
const C = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
3
3
|
function _(e, t) {
|
|
4
4
|
const n = /* @__PURE__ */ new WeakSet(), o = [
|
|
5
5
|
{ value: e, path: "" }
|
|
@@ -16,19 +16,19 @@ function _(e, t) {
|
|
|
16
16
|
t(i, s, r);
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
-
function
|
|
19
|
+
function K(e) {
|
|
20
20
|
const t = [];
|
|
21
21
|
return _(e, (n, o, r) => {
|
|
22
22
|
for (const i of Object.keys(n)) {
|
|
23
23
|
const s = o === "" ? i : `${o}.${i}`;
|
|
24
|
-
|
|
24
|
+
C.has(i) ? t.push({ path: s, key: i }) : r(n[i], s);
|
|
25
25
|
}
|
|
26
26
|
}), t;
|
|
27
27
|
}
|
|
28
28
|
function Z(e) {
|
|
29
29
|
return _(e, (t, n, o) => {
|
|
30
30
|
for (const r of Object.keys(t))
|
|
31
|
-
|
|
31
|
+
C.has(r) ? delete t[r] : o(t[r], n);
|
|
32
32
|
}), e;
|
|
33
33
|
}
|
|
34
34
|
const N = 1179937895, k = 2, Q = 1313821514, J = 21;
|
|
@@ -149,7 +149,7 @@ function F(e) {
|
|
|
149
149
|
return ye(n.annotations, o, t), t;
|
|
150
150
|
}
|
|
151
151
|
function ce(e, t) {
|
|
152
|
-
for (const n of
|
|
152
|
+
for (const n of K(e))
|
|
153
153
|
t.push(
|
|
154
154
|
d(
|
|
155
155
|
n.path,
|
|
@@ -457,7 +457,7 @@ function Oe(e) {
|
|
|
457
457
|
const o = t.replace(/\.glb$/i, ".modelhub.json");
|
|
458
458
|
return n ? `${o}?${n}` : o;
|
|
459
459
|
}
|
|
460
|
-
function
|
|
460
|
+
function Ke(e, t) {
|
|
461
461
|
var i;
|
|
462
462
|
if (!e || e.startsWith("blob:") || e.startsWith("data:")) return "";
|
|
463
463
|
const n = t ?? ((i = globalThis.location) == null ? void 0 : i.href), o = n ? new URL(e, n).href : e, r = o.lastIndexOf("/");
|
|
@@ -481,7 +481,7 @@ function Ne(e) {
|
|
|
481
481
|
const n = F(t);
|
|
482
482
|
return { payload: t, source: "embedded", findings: n, problem: null };
|
|
483
483
|
}
|
|
484
|
-
async function
|
|
484
|
+
async function Ze(e, t = {}) {
|
|
485
485
|
const n = t.fetchImpl ?? fetch, o = await Te(e, n), r = Ne(o);
|
|
486
486
|
if (r.payload) return { ...r, bytes: o };
|
|
487
487
|
if (t.sidecar === !1) return { ...r, bytes: o };
|
|
@@ -509,7 +509,7 @@ async function Ke(e, t = {}) {
|
|
|
509
509
|
bytes: o
|
|
510
510
|
};
|
|
511
511
|
}
|
|
512
|
-
function
|
|
512
|
+
function Qe(e, t = "EDU_modelhub", n = console) {
|
|
513
513
|
if (!(e != null && e.length)) return;
|
|
514
514
|
const o = Me(e), r = e.filter((i) => i.severity !== "error");
|
|
515
515
|
o.length && n.warn(`${t}: ${o.length} validation error(s)`, o), r.length && n.info(
|
|
@@ -517,24 +517,24 @@ function Xe(e, t = "EDU_modelhub", n = console) {
|
|
|
517
517
|
r
|
|
518
518
|
);
|
|
519
519
|
}
|
|
520
|
-
function
|
|
520
|
+
function Je(e) {
|
|
521
521
|
return (e == null ? void 0 : e.name) === "GlbParseError";
|
|
522
522
|
}
|
|
523
523
|
function G(e) {
|
|
524
524
|
return Array.isArray(e) && e.length === 3 && e.every((t) => typeof t == "number" && Number.isFinite(t));
|
|
525
525
|
}
|
|
526
|
-
function
|
|
526
|
+
function P(e) {
|
|
527
527
|
return e === "blender";
|
|
528
528
|
}
|
|
529
529
|
function A(e, t) {
|
|
530
530
|
if (!G(e)) return null;
|
|
531
531
|
const [n, o, r] = e;
|
|
532
|
-
return
|
|
532
|
+
return P(t) ? [n, r, -o] : [n, o, r];
|
|
533
533
|
}
|
|
534
|
-
function
|
|
534
|
+
function et(e, t) {
|
|
535
535
|
if (!G(e)) return null;
|
|
536
536
|
const [n, o, r] = e;
|
|
537
|
-
return
|
|
537
|
+
return P(t) ? [n, -r, o] : [n, o, r];
|
|
538
538
|
}
|
|
539
539
|
function ke(e) {
|
|
540
540
|
if (!Array.isArray(e)) return [];
|
|
@@ -552,16 +552,30 @@ function M(e) {
|
|
|
552
552
|
(o, r) => n(o.annotation) - n(r.annotation) || o.index - r.index
|
|
553
553
|
).map((o) => o.annotation);
|
|
554
554
|
}
|
|
555
|
-
function
|
|
555
|
+
function z(e) {
|
|
556
556
|
return (Array.isArray(e == null ? void 0 : e.regions) ? e.regions : []).map((n, o) => ({ region: n, index: o })).sort(
|
|
557
557
|
(n, o) => (n.region.order ?? Number.MAX_SAFE_INTEGER) - (o.region.order ?? Number.MAX_SAFE_INTEGER) || n.index - o.index
|
|
558
558
|
).map((n) => n.region);
|
|
559
559
|
}
|
|
560
|
+
const Be = "Auto-created from object ";
|
|
561
|
+
function Le(e) {
|
|
562
|
+
var n;
|
|
563
|
+
const t = (n = e == null ? void 0 : e.description) == null ? void 0 : n.trim();
|
|
564
|
+
return t ? !t.startsWith(Be) : !1;
|
|
565
|
+
}
|
|
566
|
+
function tt(e) {
|
|
567
|
+
return Array.isArray(e) ? e.filter(
|
|
568
|
+
(t) => {
|
|
569
|
+
var n;
|
|
570
|
+
return Le(t) || !((n = t.description) != null && n.trim());
|
|
571
|
+
}
|
|
572
|
+
) : [];
|
|
573
|
+
}
|
|
560
574
|
function L(e) {
|
|
561
575
|
var t;
|
|
562
576
|
return e ? ((t = e.label) == null ? void 0 : t.trim()) || e.id || "Unnamed region" : "Unknown region";
|
|
563
577
|
}
|
|
564
|
-
function
|
|
578
|
+
function nt(e) {
|
|
565
579
|
if (!e) return [];
|
|
566
580
|
const t = [], n = /\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g;
|
|
567
581
|
let o = 0, r;
|
|
@@ -569,8 +583,8 @@ function Je(e) {
|
|
|
569
583
|
r.index > o && t.push({ text: e.slice(o, r.index) }), t.push({ text: r[1], url: r[2] }), o = r.index + r[0].length;
|
|
570
584
|
return o < e.length && t.push({ text: e.slice(o) }), t.filter((i) => i.text.trim() || i.url);
|
|
571
585
|
}
|
|
572
|
-
function
|
|
573
|
-
const t = e == null ? void 0 : e.coordinateSpace, n =
|
|
586
|
+
function ot(e) {
|
|
587
|
+
const t = e == null ? void 0 : e.coordinateSpace, n = z(e), o = M(e), r = new Map(n.map((a) => [a.id, a])), i = new Map(
|
|
574
588
|
o.map((a) => [a.id, a])
|
|
575
589
|
), s = /* @__PURE__ */ new Map();
|
|
576
590
|
for (const a of o) {
|
|
@@ -612,10 +626,10 @@ function et(e) {
|
|
|
612
626
|
hiddenRegionIds: l
|
|
613
627
|
};
|
|
614
628
|
}
|
|
615
|
-
function
|
|
629
|
+
function De(e) {
|
|
616
630
|
return e.isMesh === !0;
|
|
617
631
|
}
|
|
618
|
-
function
|
|
632
|
+
function rt(e, t, n = []) {
|
|
619
633
|
var l;
|
|
620
634
|
const o = /* @__PURE__ */ new Map(), r = /* @__PURE__ */ new Map(), i = /* @__PURE__ */ new Map(), s = /* @__PURE__ */ new Map();
|
|
621
635
|
for (const [a, u] of ((t == null ? void 0 : t.meshes) ?? []).entries())
|
|
@@ -631,38 +645,38 @@ function tt(e, t, n = []) {
|
|
|
631
645
|
const c = new Set(n.map((a) => a.id));
|
|
632
646
|
return e.traverse((a) => {
|
|
633
647
|
var p, y;
|
|
634
|
-
if (!
|
|
648
|
+
if (!De(a)) return;
|
|
635
649
|
const u = (y = (p = a.geometry) == null ? void 0 : p.userData) == null ? void 0 : y.modelhubRegionId, f = typeof u == "string" && u || i.get(a.name) || r.get(a.name) || (c.has(a.name) ? a.name : void 0);
|
|
636
650
|
if (!f) return;
|
|
637
651
|
const m = o.get(f);
|
|
638
652
|
m ? m.push(a) : o.set(f, [a]), a.userData.modelhubOriginalMaterial === void 0 && (a.userData.modelhubOriginalMaterial = a.material);
|
|
639
653
|
}), o;
|
|
640
654
|
}
|
|
641
|
-
function
|
|
655
|
+
function it(e) {
|
|
642
656
|
const t = {};
|
|
643
657
|
for (const [n, o] of e) t[n] = o.length;
|
|
644
658
|
return t;
|
|
645
659
|
}
|
|
646
|
-
function
|
|
660
|
+
function Re(e, t, n) {
|
|
647
661
|
for (const o of e.get(t) ?? []) o.visible = n;
|
|
648
662
|
}
|
|
649
|
-
function
|
|
663
|
+
function st(e, t) {
|
|
650
664
|
for (const n of e.keys())
|
|
651
|
-
|
|
665
|
+
Re(e, n, !t.has(n));
|
|
652
666
|
}
|
|
653
|
-
function
|
|
667
|
+
function at(e, t, n) {
|
|
654
668
|
for (const o of e.get(t) ?? []) o.material = n;
|
|
655
669
|
}
|
|
656
|
-
function
|
|
670
|
+
function ct(e, t) {
|
|
657
671
|
for (const n of e.get(t) ?? []) {
|
|
658
672
|
const o = n.userData.modelhubOriginalMaterial;
|
|
659
673
|
o && (n.material = o);
|
|
660
674
|
}
|
|
661
675
|
}
|
|
662
|
-
const
|
|
663
|
-
function
|
|
676
|
+
const Ee = "#2196f3", ut = "#ff5722";
|
|
677
|
+
function j(e, t = {}) {
|
|
664
678
|
const {
|
|
665
|
-
fill: n =
|
|
679
|
+
fill: n = Ee,
|
|
666
680
|
text: o = "#ffffff",
|
|
667
681
|
textStroke: r = "rgba(0, 0, 0, 0.45)"
|
|
668
682
|
} = t, i = 256, s = document.createElement("canvas");
|
|
@@ -671,25 +685,25 @@ function V(e, t = {}) {
|
|
|
671
685
|
return c && (c.beginPath(), c.arc(i / 2, i / 2, i / 2 - 4, 0, Math.PI * 2), c.fillStyle = n, c.fill(), c.strokeStyle = "#ffffff", c.lineWidth = 8, c.stroke(), c.fillStyle = o, c.strokeStyle = r, c.lineWidth = 10, c.font = "bold 132px Arial, sans-serif", c.textAlign = "center", c.textBaseline = "middle", c.strokeText(String(e), i / 2, i / 2), c.fillText(String(e), i / 2, i / 2)), s;
|
|
672
686
|
}
|
|
673
687
|
function Ce(e, t = 0.2, n) {
|
|
674
|
-
const o = new
|
|
675
|
-
map: new E(
|
|
688
|
+
const o = new V({
|
|
689
|
+
map: new E(j(e, n)),
|
|
676
690
|
transparent: !0,
|
|
677
691
|
depthTest: !1
|
|
678
692
|
}), r = new W(o);
|
|
679
693
|
return r.scale.set(t, t, 1), r.renderOrder = 999, r;
|
|
680
694
|
}
|
|
681
|
-
function
|
|
695
|
+
function lt(e, t, n) {
|
|
682
696
|
var r;
|
|
683
697
|
const o = e.material;
|
|
684
|
-
(r = o.map) == null || r.dispose(), o.map = new E(
|
|
698
|
+
(r = o.map) == null || r.dispose(), o.map = new E(j(t, { fill: n })), o.needsUpdate = !0;
|
|
685
699
|
}
|
|
686
|
-
function
|
|
700
|
+
function dt(e, t, n = 0.2, o) {
|
|
687
701
|
return t.map((r, i) => {
|
|
688
702
|
const s = Ce(i + 1, n, o);
|
|
689
703
|
return s.position.set(...r.position), s.name = `modelhub_marker_${r.annotation.id}`, s.userData.annotationId = r.annotation.id, s.userData.displayNumber = i + 1, e.add(s), s;
|
|
690
704
|
});
|
|
691
705
|
}
|
|
692
|
-
function
|
|
706
|
+
function ft(e) {
|
|
693
707
|
const { camera: t, model: n, anchors: o, width: r, height: i, isOccluded: s } = e, c = e.referenceDistance ?? 1, l = new b(), a = {};
|
|
694
708
|
n.updateMatrixWorld();
|
|
695
709
|
for (const u of o) {
|
|
@@ -709,20 +723,20 @@ function ut(e) {
|
|
|
709
723
|
}
|
|
710
724
|
return a;
|
|
711
725
|
}
|
|
712
|
-
const
|
|
726
|
+
const _e = 0.03, Ue = {
|
|
713
727
|
enabled: !1,
|
|
714
728
|
isOccluded: () => !1,
|
|
715
729
|
dispose: () => {
|
|
716
730
|
}
|
|
717
731
|
};
|
|
718
|
-
async function
|
|
732
|
+
async function mt(e) {
|
|
719
733
|
var i;
|
|
720
734
|
let t = [], n = [];
|
|
721
735
|
if (e.traverse((s) => {
|
|
722
736
|
if (!s.isMesh) return;
|
|
723
737
|
const c = s.geometry;
|
|
724
738
|
c != null && c.computeBoundsTree && (t.push(c), n.push(s));
|
|
725
|
-
}), !t.length) return
|
|
739
|
+
}), !t.length) return Ue;
|
|
726
740
|
for (const s of t)
|
|
727
741
|
s.boundsTree || (i = s.computeBoundsTree) == null || i.call(s, { maxLeafTris: 8 });
|
|
728
742
|
const o = new q();
|
|
@@ -731,7 +745,7 @@ async function lt(e) {
|
|
|
731
745
|
return {
|
|
732
746
|
enabled: !0,
|
|
733
747
|
isOccluded(s, c, l) {
|
|
734
|
-
return r.subVectors(c, s.position).normalize(), o.set(s.position, r), o.near = 0, o.far = Math.max(0, l -
|
|
748
|
+
return r.subVectors(c, s.position).normalize(), o.set(s.position, r), o.near = 0, o.far = Math.max(0, l - _e), o.camera = s, o.intersectObjects(n, !1).length > 0;
|
|
735
749
|
},
|
|
736
750
|
dispose() {
|
|
737
751
|
var s;
|
|
@@ -797,19 +811,19 @@ const I = {
|
|
|
797
811
|
{ color: [0.42, 0.74, 1], intensity: 0.25, position: [7.23, 6.78, 1.29] }
|
|
798
812
|
]
|
|
799
813
|
}
|
|
800
|
-
},
|
|
801
|
-
function
|
|
814
|
+
}, Fe = "headlight";
|
|
815
|
+
function ht() {
|
|
802
816
|
return Object.keys(I);
|
|
803
817
|
}
|
|
804
|
-
function
|
|
818
|
+
function Ge(e) {
|
|
805
819
|
var t;
|
|
806
820
|
(t = e.parent) == null || t.remove(e);
|
|
807
821
|
}
|
|
808
|
-
function
|
|
822
|
+
function pt(e) {
|
|
809
823
|
var c;
|
|
810
824
|
const { scene: t, camera: n, exposure: o = 1 } = e;
|
|
811
825
|
(c = e.previous) == null || c.remove();
|
|
812
|
-
const r = I[e.preset ?? ""] ?? I[
|
|
826
|
+
const r = I[e.preset ?? ""] ?? I[Fe], i = [], s = new Y(16777215, r.ambient * o);
|
|
813
827
|
t.add(s), i.push(s);
|
|
814
828
|
for (const l of r.lights) {
|
|
815
829
|
let a;
|
|
@@ -817,7 +831,7 @@ function ft(e) {
|
|
|
817
831
|
const f = new H(new T(...l.color));
|
|
818
832
|
f.angle = 0.5, f.penumbra = 0.9, f.decay = 0, a = f;
|
|
819
833
|
} else
|
|
820
|
-
a = new
|
|
834
|
+
a = new X(new T(...l.color));
|
|
821
835
|
a.intensity = l.intensity, a.position.set(...l.position);
|
|
822
836
|
const u = a.target;
|
|
823
837
|
l.attach ? (u == null || u.position.set(0, 0, l.spot ? -1 : 0), n.add(a), u && n.add(u)) : (t.add(a), u && t.add(u)), i.push(a), u && i.push(u);
|
|
@@ -825,37 +839,37 @@ function ft(e) {
|
|
|
825
839
|
return {
|
|
826
840
|
lights: i,
|
|
827
841
|
remove() {
|
|
828
|
-
for (const l of i)
|
|
842
|
+
for (const l of i) Ge(l);
|
|
829
843
|
i.length = 0;
|
|
830
844
|
}
|
|
831
845
|
};
|
|
832
846
|
}
|
|
833
847
|
const S = [
|
|
834
848
|
{ id: "white", label: "White", hex: "#ffffff", light: !0 },
|
|
835
|
-
{ id: "light-grey", label: "Light
|
|
836
|
-
{ id: "medium-grey", label: "Medium
|
|
837
|
-
{ id: "dark-grey", label: "Dark
|
|
849
|
+
{ id: "light-grey", label: "Light gray", hex: "#d4d4d4", light: !0 },
|
|
850
|
+
{ id: "medium-grey", label: "Medium gray", hex: "#808080", light: !1 },
|
|
851
|
+
{ id: "dark-grey", label: "Dark gray", hex: "#252525", light: !1 },
|
|
838
852
|
{ id: "black", label: "Black", hex: "#000000", light: !1 }
|
|
839
|
-
],
|
|
840
|
-
function
|
|
853
|
+
], Pe = "dark-grey";
|
|
854
|
+
function ze(e) {
|
|
841
855
|
if (typeof e != "string") return "";
|
|
842
856
|
const t = e.trim().replace(/^#/, "");
|
|
843
857
|
return /^[0-9a-f]{6}$/i.test(t) ? `#${t.toLowerCase()}` : /^[0-9a-f]{3}$/i.test(t) ? `#${[...t].map((n) => n + n).join("").toLowerCase()}` : "";
|
|
844
858
|
}
|
|
845
|
-
function
|
|
859
|
+
function je(e) {
|
|
846
860
|
const t = Number.parseInt(e.replace("#", ""), 16);
|
|
847
861
|
return Number.isFinite(t) ? (0.299 * (t >> 16 & 255) + 0.587 * (t >> 8 & 255) + 0.114 * (t & 255)) / 255 : 0;
|
|
848
862
|
}
|
|
849
|
-
function
|
|
850
|
-
const n =
|
|
863
|
+
function gt(e, t) {
|
|
864
|
+
const n = ze(t);
|
|
851
865
|
return n ? {
|
|
852
866
|
id: "custom",
|
|
853
867
|
label: "Custom",
|
|
854
868
|
hex: n,
|
|
855
|
-
light:
|
|
856
|
-
} : S.find((o) => o.id === e) ?? S.find((o) => o.id ===
|
|
869
|
+
light: je(n) > 0.5
|
|
870
|
+
} : S.find((o) => o.id === e) ?? S.find((o) => o.id === Pe) ?? S[0];
|
|
857
871
|
}
|
|
858
|
-
const
|
|
872
|
+
const Ve = 2;
|
|
859
873
|
function D(e) {
|
|
860
874
|
e.updateMatrixWorld(!0);
|
|
861
875
|
const t = new v(), n = new v();
|
|
@@ -864,17 +878,17 @@ function D(e) {
|
|
|
864
878
|
r.isSkinnedMesh ? (r.computeBoundingBox(), r.boundingBox && (n.copy(r.boundingBox).applyMatrix4(r.matrixWorld), t.union(n))) : i.isMesh && i.geometry && (i.geometry.boundingBox || i.geometry.computeBoundingBox(), i.geometry.boundingBox && (n.copy(i.geometry.boundingBox).applyMatrix4(i.matrixWorld), t.union(n)));
|
|
865
879
|
}), t.isEmpty() ? new v().setFromObject(e) : t;
|
|
866
880
|
}
|
|
867
|
-
function
|
|
881
|
+
function yt(e, t = Ve) {
|
|
868
882
|
const o = D(e).getSize(new b()), r = Math.max(o.x, o.y, o.z) || 1, i = t / r;
|
|
869
883
|
e.scale.multiplyScalar(i);
|
|
870
884
|
const s = D(e);
|
|
871
885
|
return e.position.sub(s.getCenter(new b())), i;
|
|
872
886
|
}
|
|
873
|
-
function
|
|
887
|
+
function bt(e, t = 1.3) {
|
|
874
888
|
return t / Math.tan(e * Math.PI / 360);
|
|
875
889
|
}
|
|
876
|
-
const
|
|
877
|
-
function
|
|
890
|
+
const We = 0.5, qe = 1.5;
|
|
891
|
+
function $t(e, t, n = {}) {
|
|
878
892
|
t.updateMatrixWorld();
|
|
879
893
|
const o = new b(...e.position).applyMatrix4(
|
|
880
894
|
t.matrixWorld
|
|
@@ -895,17 +909,17 @@ function gt(e, t, n = {}) {
|
|
|
895
909
|
}
|
|
896
910
|
const r = n.outward ?? !0, i = new b(o.x, 0, o.z);
|
|
897
911
|
i.lengthSq() < 1e-8 && i.set(0, 0, 1), i.normalize();
|
|
898
|
-
const s = r ? -(n.outwardDistance ??
|
|
912
|
+
const s = r ? -(n.outwardDistance ?? We) : n.inwardDistance ?? qe, c = o.clone().addScaledVector(i, s);
|
|
899
913
|
return c.y = o.y, { position: c, target: o };
|
|
900
914
|
}
|
|
901
|
-
const
|
|
915
|
+
const Ye = { cancel: () => {
|
|
902
916
|
} };
|
|
903
|
-
function
|
|
917
|
+
function wt(e) {
|
|
904
918
|
const { camera: t, controls: n, to: o, onFrame: r, onDone: i } = e, s = () => {
|
|
905
919
|
r == null || r(), i == null || i();
|
|
906
920
|
};
|
|
907
921
|
if (e.reducedMotion)
|
|
908
|
-
return t.position.copy(o.position), n.target.copy(o.target), n.update(), s(),
|
|
922
|
+
return t.position.copy(o.position), n.target.copy(o.target), n.update(), s(), Ye;
|
|
909
923
|
const c = e.durationMs ?? 900, l = t.position.clone(), a = n.target.clone(), u = performance.now();
|
|
910
924
|
let f = null;
|
|
911
925
|
const m = (p) => {
|
|
@@ -918,26 +932,26 @@ function yt(e) {
|
|
|
918
932
|
}
|
|
919
933
|
};
|
|
920
934
|
}
|
|
921
|
-
function
|
|
935
|
+
function xt(e, t, n) {
|
|
922
936
|
const o = t || n;
|
|
923
937
|
e.fov !== o && (e.fov = o, e.updateProjectionMatrix());
|
|
924
938
|
}
|
|
925
|
-
const
|
|
926
|
-
function
|
|
939
|
+
const He = 500;
|
|
940
|
+
function vt(e) {
|
|
927
941
|
var r, i, s;
|
|
928
942
|
if (!e)
|
|
929
943
|
return "Model loaded, but it carries no educational metadata.";
|
|
930
944
|
const t = [];
|
|
931
945
|
(r = e.model) != null && r.title && t.push(`${e.model.title} loaded`), (i = e.model) != null && i.altText && t.push(e.model.altText);
|
|
932
946
|
const n = ((s = e.regions) == null ? void 0 : s.length) ?? 0, o = M(e).length;
|
|
933
|
-
return n && t.push(`${n}
|
|
947
|
+
return n && t.push(`${n} labeled ${n === 1 ? "region" : "regions"}`), o && t.push(
|
|
934
948
|
`${o} ${o === 1 ? "annotation" : "annotations"}`
|
|
935
949
|
), t.map((c) => c.replace(/\.$/, "")).join(". ") + ".";
|
|
936
950
|
}
|
|
937
|
-
function
|
|
951
|
+
function At(e, t, n) {
|
|
938
952
|
return `Annotation ${t + 1} of ${n}: ${e || "Untitled"}.`;
|
|
939
953
|
}
|
|
940
|
-
function
|
|
954
|
+
function St(e, t = He, n = () => Date.now()) {
|
|
941
955
|
let o = -1 / 0, r = null, i = null;
|
|
942
956
|
const s = (a) => {
|
|
943
957
|
o = n(), r = null, e(a);
|
|
@@ -955,18 +969,18 @@ function xt(e, t = qe, n = () => Date.now()) {
|
|
|
955
969
|
i && (clearTimeout(i), i = null), r !== null && s(r);
|
|
956
970
|
} };
|
|
957
971
|
}
|
|
958
|
-
function
|
|
972
|
+
function R(e, t) {
|
|
959
973
|
return `## ${e}
|
|
960
974
|
${t}
|
|
961
975
|
|
|
962
976
|
`;
|
|
963
977
|
}
|
|
964
|
-
function
|
|
965
|
-
const n = e.model ?? {}, o =
|
|
978
|
+
function It(e, t = {}) {
|
|
979
|
+
const n = e.model ?? {}, o = z(e), r = M(e);
|
|
966
980
|
let i = `# ${n.title || "Untitled model"}
|
|
967
981
|
|
|
968
982
|
`;
|
|
969
|
-
if (n.altText && (i +=
|
|
983
|
+
if (n.altText && (i += R(t.summaryHeading ?? "Summary", n.altText)), n.longDescription && (i += R("Full description", n.longDescription)), o.length) {
|
|
970
984
|
i += `## Regions (${o.length})
|
|
971
985
|
|
|
972
986
|
`;
|
|
@@ -991,11 +1005,11 @@ ${s.body || "No description."}
|
|
|
991
1005
|
`, i += `- **Source:** ${n.source || "Not specified"}
|
|
992
1006
|
`, i;
|
|
993
1007
|
}
|
|
994
|
-
function
|
|
1008
|
+
function Mt(e) {
|
|
995
1009
|
var n;
|
|
996
1010
|
return `${(((n = e.model) == null ? void 0 : n.title) || "model").trim().replace(/[^\w\-. ]+/g, "").replace(/\s+/g, "_") || "model"}_transcript.md`;
|
|
997
1011
|
}
|
|
998
|
-
function
|
|
1012
|
+
function Ot(e) {
|
|
999
1013
|
e && e.traverse((t) => {
|
|
1000
1014
|
var i, s;
|
|
1001
1015
|
const n = t, o = n.geometry;
|
|
@@ -1015,61 +1029,63 @@ function St(e) {
|
|
|
1015
1029
|
}
|
|
1016
1030
|
export {
|
|
1017
1031
|
S as BACKGROUND_OPTIONS,
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1032
|
+
He as CAMERA_ANNOUNCE_INTERVAL,
|
|
1033
|
+
Pe as DEFAULT_BACKGROUND,
|
|
1034
|
+
Fe as DEFAULT_LIGHTING,
|
|
1021
1035
|
I as LIGHTING_PRESETS,
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1036
|
+
Ee as MARKER_FILL,
|
|
1037
|
+
ut as MARKER_FILL_ACTIVE,
|
|
1038
|
+
Ve as NORMALIZED_SIZE,
|
|
1039
|
+
_e as OCCLUSION_BIAS,
|
|
1040
|
+
qe as TOUR_DISTANCE_INWARD,
|
|
1041
|
+
We as TOUR_DISTANCE_OUTWARD,
|
|
1042
|
+
At as annotationSummary,
|
|
1029
1043
|
M as annotationsInReadingOrder,
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1044
|
+
xt as applyFov,
|
|
1045
|
+
pt as applyLighting,
|
|
1046
|
+
st as applyVisibilityDefaults,
|
|
1047
|
+
rt as buildRegionMeshMap,
|
|
1048
|
+
It as buildTranscript,
|
|
1049
|
+
ct as clearRegionHighlight,
|
|
1036
1050
|
D as computeDisplayBox,
|
|
1037
|
-
|
|
1038
|
-
|
|
1051
|
+
$t as computeTourPlacement,
|
|
1052
|
+
St as createAnnouncer,
|
|
1039
1053
|
Ce as createMarkerSprite,
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1054
|
+
dt as createMarkerSprites,
|
|
1055
|
+
mt as createOcclusionIndex,
|
|
1056
|
+
Ot as disposeSceneGraph,
|
|
1057
|
+
bt as distanceForFov,
|
|
1058
|
+
j as drawMarkerCanvas,
|
|
1045
1059
|
Me as errorsOf,
|
|
1046
1060
|
Te as fetchBytes,
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1061
|
+
wt as flyCamera,
|
|
1062
|
+
yt as frameModel,
|
|
1063
|
+
et as fromViewerPosition,
|
|
1064
|
+
at as highlightRegion,
|
|
1065
|
+
Le as isAuthoredRegion,
|
|
1066
|
+
Je as isGlbParseError,
|
|
1052
1067
|
G as isVec3,
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1068
|
+
ht as lightingPresetNames,
|
|
1069
|
+
Ze as loadModel,
|
|
1070
|
+
Qe as logFindings,
|
|
1071
|
+
je as luminance,
|
|
1072
|
+
vt as modelSummary,
|
|
1073
|
+
P as needsConversion,
|
|
1059
1074
|
ke as normalizeAnnotationIds,
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1075
|
+
ze as normalizeHexColor,
|
|
1076
|
+
ot as normalizeModel,
|
|
1077
|
+
nt as parseBodySegments,
|
|
1078
|
+
ft as projectMarkers,
|
|
1064
1079
|
Ne as readEmbedded,
|
|
1065
|
-
|
|
1080
|
+
tt as readableRegions,
|
|
1081
|
+
it as regionBindingCounts,
|
|
1066
1082
|
L as regionLabel,
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1083
|
+
z as regionsInOrder,
|
|
1084
|
+
gt as resolveBackground,
|
|
1085
|
+
Ke as resourcePathFor,
|
|
1086
|
+
lt as setMarkerSpriteColor,
|
|
1087
|
+
Re as setRegionVisible,
|
|
1072
1088
|
Oe as sidecarUrlFor,
|
|
1073
1089
|
A as toViewerPosition,
|
|
1074
|
-
|
|
1090
|
+
Mt as transcriptFileName
|
|
1075
1091
|
};
|
package/dist/lighting.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Scene lighting.
|
|
3
3
|
*
|
|
4
4
|
* Lighting is not decoration here. These rigs were measured from this
|
|
5
|
-
*
|
|
5
|
+
* organization's Sketchfab viewers through the Viewer API, so a model that
|
|
6
6
|
* was tuned on Sketchfab looks the same after migration. Keeping them in one
|
|
7
7
|
* place means a model lit correctly in one viewer is lit correctly in both.
|
|
8
8
|
*
|
package/dist/markers.d.ts
CHANGED
|
@@ -12,15 +12,15 @@
|
|
|
12
12
|
import { Sprite, Vector3 } from "three";
|
|
13
13
|
import type { Camera, Object3D } from "three";
|
|
14
14
|
import type { MarkerAnchor } from "./normalize.js";
|
|
15
|
-
/**
|
|
15
|
+
/** Colors for a marker's circle and number. */
|
|
16
16
|
export interface MarkerColors {
|
|
17
17
|
fill?: string;
|
|
18
18
|
text?: string;
|
|
19
19
|
textStroke?: string;
|
|
20
20
|
}
|
|
21
|
-
/** Default unselected marker
|
|
21
|
+
/** Default unselected marker color. */
|
|
22
22
|
export declare const MARKER_FILL = "#2196f3";
|
|
23
|
-
/** Default selected marker
|
|
23
|
+
/** Default selected marker color. */
|
|
24
24
|
export declare const MARKER_FILL_ACTIVE = "#ff5722";
|
|
25
25
|
/**
|
|
26
26
|
* Draw a numbered marker: a filled circle, a white ring, and the number.
|
|
@@ -35,7 +35,7 @@ export declare function drawMarkerCanvas(value: number | string, colors?: Marker
|
|
|
35
35
|
* `occlusion.ts` rather than by the depth buffer.
|
|
36
36
|
*/
|
|
37
37
|
export declare function createMarkerSprite(value: number | string, scale?: number, colors?: MarkerColors): Sprite;
|
|
38
|
-
/** Redraw a sprite's texture in a new
|
|
38
|
+
/** Redraw a sprite's texture in a new color, disposing the old one. */
|
|
39
39
|
export declare function setMarkerSpriteColor(sprite: Sprite, value: number | string, fill: string): void;
|
|
40
40
|
/**
|
|
41
41
|
* Add one sprite per marker anchor, parented to the model so they inherit
|
package/dist/normalize.d.ts
CHANGED
|
@@ -57,6 +57,15 @@ export declare function normalizeAnnotationIds(annotations: readonly Annotation[
|
|
|
57
57
|
export declare function annotationsInReadingOrder(payload: EduModelHub): IdentifiedAnnotation[];
|
|
58
58
|
/** Regions sorted by `order`, ties keeping storage order. */
|
|
59
59
|
export declare function regionsInOrder(payload: EduModelHub): Region[];
|
|
60
|
+
/** True when a region carries a description an author actually wrote. */
|
|
61
|
+
export declare function isAuthoredRegion(region: Region | undefined): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Regions worth showing in a reading view: those an author described, or
|
|
64
|
+
* those left undescribed but given a real name. A region the tool invented
|
|
65
|
+
* outright is dropped, so a model nobody annotated shows no region list at
|
|
66
|
+
* all rather than a wall of object names.
|
|
67
|
+
*/
|
|
68
|
+
export declare function readableRegions(regions: readonly Region[] | undefined): Region[];
|
|
60
69
|
/** Display name for a region, falling back to its id. */
|
|
61
70
|
export declare function regionLabel(region: Region | undefined): string;
|
|
62
71
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harpern/edu-modelhub-viewer-core",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
4
|
-
"description": "Framework-agnostic viewer core for EDU_modelhub models: metadata loading, normalization, region binding, annotation markers, and camera
|
|
3
|
+
"version": "0.1.0-beta.3",
|
|
4
|
+
"description": "Framework-agnostic viewer core for EDU_modelhub models: metadata loading, normalization, region binding, annotation markers, and camera behavior shared by every conforming viewer",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Oregon State University Ecampus",
|
|
7
7
|
"contributors": [
|