@harpern/edu-modelhub-viewer-core 0.1.0-beta.1 → 0.1.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +7 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +136 -120
- package/dist/normalize.d.ts +9 -0
- package/package.json +1 -1
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 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}],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
|
@@ -13,7 +13,7 @@
|
|
|
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,7 +839,7 @@ 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
|
};
|
|
@@ -836,26 +850,26 @@ const S = [
|
|
|
836
850
|
{ id: "medium-grey", label: "Medium grey", hex: "#808080", light: !1 },
|
|
837
851
|
{ id: "dark-grey", label: "Dark grey", 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/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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harpern/edu-modelhub-viewer-core",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.2",
|
|
4
4
|
"description": "Framework-agnostic viewer core for EDU_modelhub models: metadata loading, normalization, region binding, annotation markers, and camera behaviour shared by every conforming viewer",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Oregon State University Ecampus",
|