lunchboxjs 0.1.4014 → 0.1.4017

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.
@@ -64,16 +64,30 @@
64
64
  }
65
65
  };
66
66
 
67
- const prepCanvas = (container, canvasElement, onBeforeUnmount) => {
67
+ const getInnerDimensions = (node) => {
68
+ const computedStyle = getComputedStyle(node);
69
+ const width = node.clientWidth - parseFloat(computedStyle.paddingLeft) - parseFloat(computedStyle.paddingRight);
70
+ const height = node.clientHeight - parseFloat(computedStyle.paddingTop) - parseFloat(computedStyle.paddingBottom);
71
+ return { width, height };
72
+ };
73
+ const prepCanvas = (container, canvasElement, onBeforeUnmount, sizePolicy) => {
68
74
  const containerElement = container.value?.domElement;
69
75
  if (!containerElement)
70
76
  throw new Error('missing container');
71
77
  // save...
72
78
  // ...and size element
73
- resizeCanvas();
79
+ const resizeCanvasByPolicy = () => {
80
+ if (sizePolicy === "container") {
81
+ const dims = getInnerDimensions(containerElement);
82
+ resizeCanvas(dims.width, dims.height);
83
+ }
84
+ else
85
+ resizeCanvas();
86
+ };
87
+ resizeCanvasByPolicy();
74
88
  // attach listeners
75
89
  const observer = new ResizeObserver(([canvas]) => {
76
- resizeCanvas();
90
+ resizeCanvasByPolicy();
77
91
  });
78
92
  // window.addEventListener('resize', resizeCanvas)
79
93
  if (containerElement) {
@@ -87,6 +101,8 @@
87
101
  });
88
102
  };
89
103
 
104
+ // TODO:
105
+ // Continue r3f prop - what else (besides camera fov) makes r3f look good?
90
106
  /** fixed & fill styling for container */
91
107
  const fillStyle = (position) => {
92
108
  return {
@@ -97,6 +113,7 @@
97
113
  left: 0,
98
114
  width: '100%',
99
115
  height: '100%',
116
+ display: 'block',
100
117
  };
101
118
  };
102
119
  const LunchboxWrapper = {
@@ -111,11 +128,14 @@
111
128
  dpr: Number,
112
129
  ortho: Boolean,
113
130
  orthographic: Boolean,
131
+ r3f: Boolean,
114
132
  rendererArguments: Object,
115
133
  rendererProperties: Object,
134
+ sizePolicy: String,
116
135
  shadow: [Boolean, Object],
117
136
  transparent: Boolean,
118
137
  zoom: Number,
138
+ updateSource: Object,
119
139
  },
120
140
  setup(props, context) {
121
141
  const canvas = vue.ref();
@@ -125,6 +145,10 @@
125
145
  let renderer;
126
146
  let camera;
127
147
  let scene;
148
+ // https://threejs.org/docs/index.html#manual/en/introduction/Color-management
149
+ if (props.r3f && THREE__namespace?.ColorManagement) {
150
+ THREE__namespace.ColorManagement.legacyMode = false;
151
+ }
128
152
  // MOUNT
129
153
  // ====================
130
154
  vue.onMounted(() => {
@@ -145,6 +169,9 @@
145
169
  alpha: props.transparent,
146
170
  antialias: true,
147
171
  canvas: canvas.value.domElement,
172
+ powerPreference: !!props.r3f
173
+ ? 'high-performance'
174
+ : 'default',
148
175
  ...(props.rendererArguments ?? {}),
149
176
  };
150
177
  // create new renderer
@@ -158,6 +185,15 @@
158
185
  // we've initialized the renderer, so anything depending on it can execute now
159
186
  rendererReady.value = true;
160
187
  const rendererAsWebGlRenderer = ensureRenderer;
188
+ // apply r3f settings if desired
189
+ if (props.r3f) {
190
+ if (rendererAsWebGlRenderer.value.instance) {
191
+ rendererAsWebGlRenderer.value.instance.outputEncoding =
192
+ THREE__namespace.sRGBEncoding;
193
+ rendererAsWebGlRenderer.value.instance.toneMapping =
194
+ THREE__namespace.ACESFilmicToneMapping;
195
+ }
196
+ }
161
197
  // update render sugar
162
198
  const sugar = {
163
199
  shadow: props.shadow,
@@ -205,7 +241,12 @@
205
241
  else {
206
242
  ensuredCamera.value = createNode({
207
243
  props: {
208
- args: props.cameraArgs ?? [45, 0.5625, 1, 1000],
244
+ args: props.cameraArgs ?? [
245
+ props.r3f ? 75 : 45,
246
+ 0.5625,
247
+ 1,
248
+ 1000,
249
+ ],
209
250
  },
210
251
  type: 'PerspectiveCamera',
211
252
  uuid: fallbackCameraUuid,
@@ -238,7 +279,7 @@
238
279
  scene = ensuredScene.value;
239
280
  // set background color
240
281
  if (scene && scene.instance && props.background) {
241
- scene.instance.background = new THREE.Color(props.background);
282
+ scene.instance.background = new THREE__namespace.Color(props.background);
242
283
  }
243
284
  // MISC PROPERTIES
244
285
  // ====================
@@ -249,7 +290,7 @@
249
290
  renderer.instance.setPixelRatio(dpr.value);
250
291
  globals.dpr.value = dpr.value;
251
292
  // prep canvas (sizing, observe, unmount, etc)
252
- prepCanvas(container, renderer.instance.domElement, vue.onBeforeUnmount);
293
+ prepCanvas(container, renderer.instance.domElement, vue.onBeforeUnmount, props.sizePolicy);
253
294
  }
254
295
  else {
255
296
  throw new Error('missing renderer');
@@ -274,24 +315,28 @@
274
315
  camera: camera.instance,
275
316
  renderer: renderer.instance,
276
317
  scene: scene.instance,
318
+ updateSource: props.updateSource,
277
319
  });
278
320
  });
279
321
  // UNMOUNT
280
322
  // ====================
281
323
  vue.onBeforeUnmount(() => {
282
324
  cancelUpdate();
325
+ cancelUpdateSource();
283
326
  });
284
327
  // RENDER FUNCTION
285
328
  // ====================
329
+ const containerFillStyle = props.sizePolicy === 'container' ? 'static' : 'absolute';
330
+ const canvasFillStyle = props.sizePolicy === 'container' ? 'static' : 'fixed';
286
331
  return () => [
287
332
  context.slots.default?.() ?? null,
288
333
  vue.h('div', {
289
- style: fillStyle('absolute'),
334
+ style: fillStyle(containerFillStyle),
290
335
  ref: container,
291
336
  }, [
292
337
  useFallbackRenderer.value
293
338
  ? vue.h('canvas', {
294
- style: fillStyle('fixed'),
339
+ style: fillStyle(canvasFillStyle),
295
340
  class: 'lunchbox-canvas',
296
341
  ref: canvas,
297
342
  })
@@ -625,6 +670,7 @@
625
670
  let mouseUpListener;
626
671
  const mousePos = vue.ref({ x: Infinity, y: Infinity });
627
672
  let autoRaycasterEventsInitialized = false;
673
+ let frameID$1;
628
674
  const setupAutoRaycaster = (node) => {
629
675
  const instance = node.instance;
630
676
  if (!instance)
@@ -657,8 +703,14 @@
657
703
  renderer.instance.domElement.addEventListener('mousedown', mouseDownListener);
658
704
  renderer.instance.domElement.addEventListener('mouseup', mouseUpListener);
659
705
  // TODO: add touch events
660
- // add to update loop
661
- onBeforeRender(autoRaycasterBeforeRender);
706
+ // process mouse events asynchronously, whenever the mouse state changes
707
+ vue.watch(() => [inputActive.value, mousePos.value.x, mousePos.value.y], () => {
708
+ if (frameID$1)
709
+ cancelAnimationFrame(frameID$1);
710
+ frameID$1 = requestAnimationFrame(() => {
711
+ autoRaycasterBeforeRender();
712
+ });
713
+ });
662
714
  // mark complete
663
715
  autoRaycasterEventsInitialized = true;
664
716
  // cancel setup watcher
@@ -987,7 +1039,7 @@
987
1039
  };
988
1040
 
989
1041
  /** Process props into either themselves or the $attached value */
990
- function processProp({ node, prop }) {
1042
+ function processProp({ node, prop, }) {
991
1043
  // return $attachedArray value if needed
992
1044
  if (typeof prop === 'string' && prop.startsWith('$attachedArray')) {
993
1045
  return node.attachedArray[prop.replace('$attachedArray.', '')];
@@ -999,10 +1051,12 @@
999
1051
  // otherwise, return plain value
1000
1052
  return prop;
1001
1053
  }
1002
- function processPropAsArray({ node, prop }) {
1054
+ function processPropAsArray({ node, prop, }) {
1003
1055
  const isAttachedArray = typeof prop === 'string' && prop.startsWith('$attachedArray');
1004
1056
  const output = processProp({ node, prop });
1005
- return Array.isArray(output) && isAttachedArray ? output : [output];
1057
+ return Array.isArray(output) && isAttachedArray
1058
+ ? output
1059
+ : [output];
1006
1060
  }
1007
1061
 
1008
1062
  function instantiateThreeObject(node) {
@@ -1301,16 +1355,34 @@
1301
1355
  };
1302
1356
 
1303
1357
  let frameID;
1358
+ let watchStopHandle;
1304
1359
  const beforeRender = [];
1305
1360
  const afterRender = [];
1306
- const update = (opts) => {
1307
- // request next frame
1361
+ const requestUpdate = (opts) => {
1362
+ cancelUpdate();
1308
1363
  frameID = requestAnimationFrame(() => update({
1309
1364
  app: opts.app,
1310
1365
  renderer: ensureRenderer.value?.instance,
1311
1366
  scene: ensuredScene.value.instance,
1312
1367
  camera: ensuredCamera.value?.instance,
1368
+ updateSource: opts.updateSource,
1313
1369
  }));
1370
+ };
1371
+ const update = (opts) => {
1372
+ if (opts.updateSource) {
1373
+ if (!watchStopHandle) {
1374
+ // request next frame only when state changes
1375
+ watchStopHandle = vue.watch(opts.updateSource, () => {
1376
+ requestUpdate(opts);
1377
+ }, {
1378
+ deep: true,
1379
+ });
1380
+ }
1381
+ }
1382
+ else {
1383
+ // request next frame on a continuous loop
1384
+ requestUpdate(opts);
1385
+ }
1314
1386
  // prep options
1315
1387
  const { app, renderer, scene, camera } = opts;
1316
1388
  // BEFORE RENDER
@@ -1373,6 +1445,10 @@
1373
1445
  if (frameID)
1374
1446
  cancelAnimationFrame(frameID);
1375
1447
  };
1448
+ const cancelUpdateSource = () => {
1449
+ if (watchStopHandle)
1450
+ watchStopHandle();
1451
+ };
1376
1452
 
1377
1453
  /** Update a single prop on a given node. */
1378
1454
  function updateObjectProp({ node, key, value, }) {
@@ -1 +1 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("vue"),require("three"),require("lodash")):"function"==typeof define&&define.amd?define(["exports","vue","three","lodash"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).LunchboxRenderer={},e.vue,e.three,e.lodash)}(this,(function(e,t,n,r){"use strict";function o(e){if(e&&e.__esModule)return e;var t=Object.create(null);return e&&Object.keys(e).forEach((function(n){if("default"!==n){var r=Object.getOwnPropertyDescriptor(e,n);Object.defineProperty(t,n,r.get?r:{enumerable:!0,get:function(){return e[n]}})}})),t.default=e,Object.freeze(t)}var a=o(n);const s=[],i=(e,n)=>{const r=W.value?.instance,o=I.value.instance,a=D.value;if(!r?.domElement||!o||!a)return;const s=(e=e??window.innerWidth)/(n=n??window.innerHeight);if("perspectivecamera"===a.type?.toLowerCase()){const e=a.instance;e.aspect=s,e.updateProjectionMatrix()}else if("orthographiccamera"===a.type?.toLowerCase()){const t=a.instance,r=n/e;t.top=10*r,t.bottom=10*-r,t.right=10,t.left=-10,t.updateProjectionMatrix()}else console.log("TODO: non-ortho or perspective camera");r.setSize(e,n),o&&a.instance&&r.render(t.toRaw(o),t.toRaw(a.instance))},c=e=>({position:e,top:0,right:0,bottom:0,left:0,width:"100%",height:"100%"}),d={name:"Lunchbox",props:{background:String,cameraArgs:Array,cameraLook:Array,cameraLookAt:Array,cameraPosition:Array,dpr:Number,ortho:Boolean,orthographic:Boolean,rendererArguments:Object,rendererProperties:Object,shadow:[Boolean,Object],transparent:Boolean,zoom:Number},setup(e,o){const a=t.ref(),s=t.ref(!0),d=t.ref(e.dpr??-1),u=t.ref();let l,p,m;return t.onMounted((()=>{if(!a.value)throw new Error("missing canvas");if(l=B(["WebGLRenderer"]),l)s.value=!1,U.value=!0;else{const t={alpha:e.transparent,antialias:!0,canvas:a.value.domElement,...e.rendererArguments??{}};W.value=K({type:"WebGLRenderer",uuid:F,props:{args:[t]}}),U.value=!0;const n=W,o={shadow:e.shadow};n.value.instance&&o?.shadow&&(n.value.instance.shadowMap.enabled=!0,"object"==typeof o.shadow&&(n.value.instance.shadowMap.type=o.shadow.type)),e.rendererProperties&&Object.keys(e.rendererProperties).forEach((t=>{r.set(n.value,t,e.rendererProperties[t])})),l=n.value}if(p=B(["PerspectiveCamera","OrthographicCamera"]),p?S.value=!0:(e.ortho||e.orthographic?D.value=K({props:{args:e.cameraArgs??[]},type:"OrthographicCamera",uuid:j}):D.value=K({props:{args:e.cameraArgs??[45,.5625,1,1e3]},type:"PerspectiveCamera",uuid:j}),S.value=!0,p=D.value),!p.instance)throw new Error("Error creating camera.");if(p&&e.cameraPosition&&p.instance.position.set(...e.cameraPosition),p&&(e.cameraLookAt||e.cameraLook)){const t=e.cameraLookAt||e.cameraLook;p.instance.lookAt(...t)}if(p&&void 0!==e.zoom&&(p.instance.zoom=e.zoom),m=I.value,m&&m.instance&&e.background&&(m.instance.background=new n.Color(e.background)),-1===d.value&&(d.value=window.devicePixelRatio),!l?.instance)throw new Error("missing renderer");l.instance.setPixelRatio(d.value),he.dpr.value=d.value,((e,t,n)=>{const r=e.value?.domElement;if(!r)throw new Error("missing container");i();const o=new ResizeObserver((([e])=>{i()}));r&&o.observe(r),n((()=>{t&&o.unobserve(t)}))})(u,l.instance.domElement,t.onBeforeUnmount);const o=t.getCurrentInstance().appContext.app;for(let e of te)e({app:o,camera:p.instance,renderer:l.instance,scene:m.instance});ae({app:o,camera:p.instance,renderer:l.instance,scene:m.instance})})),t.onBeforeUnmount((()=>{ie()})),()=>[o.slots.default?.()??null,t.h("div",{style:c("absolute"),ref:u},[s.value?t.h("canvas",{style:c("fixed"),class:"lunchbox-canvas",ref:a}):null])]}},u={},l=["canvas","div","LunchboxWrapper"],p={...["mesh","instancedMesh","scene","sprite","object3D","instancedBufferGeometry","bufferGeometry","boxBufferGeometry","circleBufferGeometry","coneBufferGeometry","cylinderBufferGeometry","dodecahedronBufferGeometry","extrudeBufferGeometry","icosahedronBufferGeometry","latheBufferGeometry","octahedronBufferGeometry","parametricBufferGeometry","planeBufferGeometry","polyhedronBufferGeometry","ringBufferGeometry","shapeBufferGeometry","sphereBufferGeometry","tetrahedronBufferGeometry","textBufferGeometry","torusBufferGeometry","torusKnotBufferGeometry","tubeBufferGeometry","wireframeGeometry","parametricGeometry","tetrahedronGeometry","octahedronGeometry","icosahedronGeometry","dodecahedronGeometry","polyhedronGeometry","tubeGeometry","torusKnotGeometry","torusGeometry","sphereGeometry","ringGeometry","planeGeometry","latheGeometry","shapeGeometry","extrudeGeometry","edgesGeometry","coneGeometry","cylinderGeometry","circleGeometry","boxGeometry","material","shadowMaterial","spriteMaterial","rawShaderMaterial","shaderMaterial","pointsMaterial","meshPhysicalMaterial","meshStandardMaterial","meshPhongMaterial","meshToonMaterial","meshNormalMaterial","meshLambertMaterial","meshDepthMaterial","meshDistanceMaterial","meshBasicMaterial","meshMatcapMaterial","lineDashedMaterial","lineBasicMaterial","light","spotLightShadow","spotLight","pointLight","rectAreaLight","hemisphereLight","directionalLightShadow","directionalLight","ambientLight","lightShadow","ambientLightProbe","hemisphereLightProbe","lightProbe","texture","videoTexture","dataTexture","dataTexture3D","compressedTexture","cubeTexture","canvasTexture","depthTexture","textureLoader","group","catmullRomCurve3","points","cameraHelper","camera","perspectiveCamera","orthographicCamera","cubeCamera","arrayCamera","webGLRenderer"].map((e=>t.defineComponent({inheritAttrs:!1,name:e,setup:(n,r)=>()=>t.h(e,r.attrs,r.slots?.default?.()||[])}))).reduce(((e,t)=>(e[t.name]=t,e)),{}),Lunchbox:d};const m=e=>e?.$el&&e?.$el?.hasOwnProperty?.("instance"),h=e=>{if("domMeta"===e?.metaType)return!0;const t="string"==typeof e?e:e?.type;return l.includes(t??"")},f=e=>"standardMeta"===e?.metaType,y=e=>e.isLunchboxRootNode,v=[],g=t.ref(!1);function b({node:e,key:n,value:r}){var o;if(e.eventListeners[n]||(e.eventListeners[n]=[]),e.eventListenerRemoveFunctions[n]||(e.eventListenerRemoveFunctions[n]=[]),e.eventListeners[n].push(r),x.includes(n)&&(z.value,e.instance&&!v.includes(e)&&(o=e,v.push(o),e.eventListenerRemoveFunctions[n].push((()=>(e=>{const t=v.indexOf(e);-1!==t&&v.splice(t,1)})(e))))),"onClick"===n||"onPointerDown"===n||"onPointerUp"===n){const r=t.watch((()=>g.value),(t=>{const r=L.map((e=>e.element)).findIndex((t=>t.instance&&t.instance.uuid===e.instance?.uuid));-1!==r&&((!t||"onClick"!==n&&"onPointerDown"!==n)&&(t||"onPointerUp"!==n)||e.eventListeners[n].forEach((e=>{e({intersection:L[r].intersection})})))}));e.eventListenerRemoveFunctions[n].push(r)}return e}const x=["onClick","onPointerUp","onPointerDown","onPointerOver","onPointerOut","onPointerEnter","onPointerLeave","onPointerMove"];let R,w,A;const C=t.ref({x:1/0,y:1/0});let E=!1;let L=[];const P=()=>{const e=z.value?.instance,t=D.value?.instance;if(!e||!t)return;e.setFromCamera(he.mousePos.value,t);const n=e.intersectObjects(v.map((e=>e.instance)));let r=[],o=[],a=[];r=L.map((e=>e.intersection)),n?.forEach((e=>{if(-1===L.findIndex((t=>t.intersection.object===e.object))){const t=v.find((t=>t.instance?.uuid===e.object.uuid));t&&o.push({element:t,intersection:e})}else{const t=v.find((t=>t.instance?.uuid===e.object.uuid));t&&a.push({element:t,intersection:e})}const t=r.findIndex((t=>t.object.uuid===e.object.uuid));-1!==t&&r.splice(t,1)}));const s=r.map((e=>({element:v.find((t=>t.instance?.uuid===e.object.uuid)),intersection:e})));o.forEach((({element:e,intersection:t})=>{M({element:e,eventKeys:["onPointerEnter"],intersection:t})})),a.forEach((({element:e,intersection:t})=>{M({element:e,eventKeys:["onPointerOver","onPointerMove"],intersection:t})})),s.forEach((({element:e,intersection:t})=>{M({element:e,eventKeys:["onPointerLeave","onPointerOut"],intersection:t})})),L=[].concat(o,a)},M=({element:e,eventKeys:t,intersection:n})=>{e&&t.forEach((t=>{e.eventListeners[t]&&e.eventListeners[t].forEach((e=>{e({intersection:n})}))}))};function N(t={}){return e.lunchboxTree||(e.lunchboxTree=new J.RendererRootNode(t)),e.lunchboxTree}function B(e){Array.isArray(e)||(e=[e]);for(let t of e)if(T[t])return T[t];for(let n of e){const e=G[n]||s.find((e=>e.type?.toLowerCase()===n.toLowerCase()));if(!(t=e,"RendererNode"!==t?.minidomType||!1!==e.props["is-default"]&&!1!=!e.props.isDefault))return null;if(e){const t=e;return G[n]=t,t}}var t;return null}e.lunchboxTree=void 0;const G=t.reactive({}),T=t.reactive({});function O(e,n,r={},o=null){Array.isArray(e)||(e=[e]);for(let t of e)G[t]||(G[t]=null),T[t]||(T[t]=null);return t.computed({get(){const t=B(e);if(t)return t;const a=N(),s=K({type:e[0],uuid:n,props:r});return a.addChild(s),G[e[0]]=s,o&&o(s),s},set(e){const t=e.type??"",n=t[0].toUpperCase()+t.slice(1);T[n]=e}})}const j="FALLBACK_CAMERA",k=O(["PerspectiveCamera","OrthographicCamera"],j,{args:[45,.5625,1,1e3]}),S=t.ref(!1),D=t.computed({get:()=>S.value?k.value:null,set(e){const t=e.type??"",n=t[0].toUpperCase()+t.slice(1);T[n]=e}}),F="FALLBACK_RENDERER",$=O(["WebGLRenderer"],F,{}),U=t.ref(!1),W=t.computed({get:()=>U.value?$.value:null,set(e){const t=e.type??"",n=t[0].toUpperCase()+t.slice(1);T[n]=e}}),I=O("Scene","FALLBACK_SCENE"),z=O("Raycaster","AUTO_RAYCASTER",{},(e=>(e=>{if(!e.instance)return;let n=null;n=t.watch((()=>W.value),(e=>{e?.instance&&(E||(R=t=>{const n=(e.instance.domElement.width??1)/he.dpr.value,r=(e.instance.domElement.height??1)/he.dpr.value;C.value.x=t.offsetX/n*2-1,C.value.y=-t.offsetY/r*2+1},w=()=>g.value=!0,A=()=>g.value=!1,e.instance.domElement.addEventListener("mousemove",R),e.instance.domElement.addEventListener("mousedown",w),e.instance.domElement.addEventListener("mouseup",A),se(P),E=!0),n&&n())}),{immediate:!0})})(e)));function K(e={},t={}){const n={attached:e.attached??[],attachedArray:e.attachedArray??{},instance:e.instance??null},r=new J.RendererStandardNode({...e,...n,metaType:"standardMeta"});return!r.type||y(r)||r.instance||(r.instance=function(e){if(!e.type)return null;const t=e.type[0].toUpperCase()+e.type.slice(1),n=u[e.type]||a[t];if(!n)throw`${t} is not part of the THREE namespace! Did you forget to extend? import {extend} from 'lunchbox'; extend({app, YourComponent, ...})`;const r=(e.props.args??[]).map((t=>function({node:e,prop:t}){const n="string"==typeof t&&t.startsWith("$attachedArray"),r=function({node:e,prop:t}){if("string"==typeof t&&t.startsWith("$attachedArray"))return e.attachedArray[t.replace("$attachedArray.","")];if("string"==typeof t&&t.startsWith("$attached"))return e.attached[t.replace("$attached.","")];return t}({node:e,prop:t});return Array.isArray(r)&&n?r:[r]}({node:e,prop:t})));let o=[];r.forEach((e=>{o=o.concat(e)}));return new n(...o)}({...r,props:{...r.props,...t}})),"scene"===r.type?.toLowerCase()?I.value=r:r.type?.toLowerCase().endsWith("camera")&&(D.value=r),r}const _=e=>t.defineComponent({inheritAttrs:!1,name:e,render(){return t.h(e,this.$attrs,this.$slots?.default?.()||[])}});var V,q=new Uint8Array(16);function H(){if(!V&&!(V="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto)||"undefined"!=typeof msCrypto&&"function"==typeof msCrypto.getRandomValues&&msCrypto.getRandomValues.bind(msCrypto)))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return V(q)}var Y=/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i;function X(e){return"string"==typeof e&&Y.test(e)}for(var J,Q=[],Z=0;Z<256;++Z)Q.push((Z+256).toString(16).substr(1));function ee(e,t,n){var r=(e=e||{}).random||(e.rng||H)();if(r[6]=15&r[6]|64,r[8]=63&r[8]|128,t){n=n||0;for(var o=0;o<16;++o)t[n+o]=r[o];return t}return function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,n=(Q[e[t+0]]+Q[e[t+1]]+Q[e[t+2]]+Q[e[t+3]]+"-"+Q[e[t+4]]+Q[e[t+5]]+"-"+Q[e[t+6]]+Q[e[t+7]]+"-"+Q[e[t+8]]+Q[e[t+9]]+"-"+Q[e[t+10]]+Q[e[t+11]]+Q[e[t+12]]+Q[e[t+13]]+Q[e[t+14]]+Q[e[t+15]]).toLowerCase();if(!X(n))throw TypeError("Stringified UUID is invalid");return n}(r)}!function(e){e.BaseNode=class{constructor(e={},t){this.parentNode=e?.parentNode??t??null,this.minidomType="MinidomBaseNode",this.uuid=e?.uuid??ee(),s.push(this)}uuid;parentNode;get nextSibling(){if(!this.parentNode)return null;const e=this.parentNode.children.findIndex((e=>e.uuid===this.uuid));return-1!==e&&e<this.parentNode.children.length-1?this.parentNode.children[e+1]:null}insertBefore(e,t){e.removeAsChildFromAnyParents(),e.parentNode=this;const n=this.children.findIndex((e=>e.uuid===t?.uuid));-1!==n?this.children.splice(n,0,e):this.children.push(e)}removeChild(e){const t=this.children.findIndex((t=>t?.uuid===e?.uuid));-1!==t&&this.children.splice(t,1)}children=[];addChild(e){return e&&(e.removeAsChildFromAnyParents(),e.parentNode=this,this.insertBefore(e,null)),this}getPath(){const e=[];let t=this;for(;t;)e.unshift(t),t=t.parentNode;return e}drop(){this.parentNode=null,this.removeAsChildFromAnyParents()}walk(e){const t=[this,...this.children],n=[];let r=!0;for(;t.length&&r;){const o=t.shift();if(o){if(n.includes(o))continue;n.push(o),t.push(...o.children.filter((e=>!n.includes(e)))),r=e(o)}else r=!1}}minidomType;removeAsChildFromAnyParents(){s.forEach((e=>e.removeChild(this)))}};class t extends e.BaseNode{constructor(e={},t){super(e,t),this.minidomType="RendererNode",this.eventListeners={},this.eventListenerRemoveFunctions={},this.name=e.name??"",this.metaType=e.metaType??"standardMeta",this.props=e.props??[],this.type=e.type??""}eventListeners;eventListenerRemoveFunctions;name;metaType;props;type;drop(){super.drop(),Object.keys(this.eventListenerRemoveFunctions).forEach((e=>{this.eventListenerRemoveFunctions[e].forEach((e=>e()))}))}}e.RendererBaseNode=t;class n extends e.RendererBaseNode{constructor(e={},t){super(e,t),this.domElement=e.domElement??document.createElement("div")}domElement;isLunchboxRootNode=!0}e.RendererRootNode=n;class r extends e.RendererBaseNode{constructor(e={},t){super(e,t),this.text=e.text??""}text}e.RendererCommentNode=r;class o extends e.RendererBaseNode{constructor(e={},t){super(e,t),this.domElement=e.domElement??document.createElement("div")}domElement}e.RendererDomNode=o;class a extends e.RendererBaseNode{constructor(e={},t){super(e,t),this.text=e.text??""}text}e.RendererTextNode=a;class i extends e.RendererBaseNode{constructor(e={},t){super(e,t),this.attached=e.attached??[],this.attachedArray=e.attachedArray??{},this.instance=e.instance??null}attached;attachedArray;instance}e.RendererStandardNode=i}(J||(J={}));(new J.RendererRootNode).minidomType="RootNode";const te=[];let ne;const re=[],oe=[],ae=e=>{ne=requestAnimationFrame((()=>ae({app:e.app,renderer:W.value?.instance,scene:I.value.instance,camera:D.value?.instance})));const{app:n,renderer:r,scene:o,camera:a}=e;re.forEach((t=>{t&&t(e)})),r&&o&&a&&(n.customRender?n.customRender(e):r.render(t.toRaw(o),t.toRaw(a))),oe.forEach((t=>{t&&t(e)}))},se=(e,t=1/0)=>{t===1/0?re.push(e):re.splice(t,0,e)},ie=()=>{ne&&cancelAnimationFrame(ne)};const ce={x:"position.x",y:"position.y",z:"position.z"},de=["","parameters"],ue=["args","attach","attachArray","is.default","isDefault","key","onAdded","ref","src"],le=["geometry","material"];function pe(e,t,n,r){const o=r??e.instance,a=t.instance;e.props.attach===n&&(t.attached={[n]:o,...t.attached||{}},a[n]=r??e.instance),e.props.attachArray===n&&(t.attachedArray[e.props.attachArray]||(t.attachedArray[e.props.attachArray]=[]),t.attachedArray[e.props.attachArray].push(o),a[n]=[a[n]])}const me={createElement:(e,t,n,r)=>{const o={type:e};r&&(o.props=r);if(h(e)){const e=function(e={}){const t={domElement:document.createElement(e.type??"")};return new J.RendererDomNode({...t,...e,metaType:"domMeta"})}(o);return e}const a=K(o);return le.forEach((t=>{e.toLowerCase().endsWith(t)&&(a.props.attach=t)})),a},createText:e=>function(e={}){const t={text:e.text??""};return new J.RendererTextNode({...e,...t,metaType:"textMeta"})}({text:e}),createComment:e=>function(e={}){const t={text:e.text??""};return new J.RendererCommentNode({...t,...e,metaType:"commentMeta"})}({text:e}),insert:(e,t,n)=>{let r=t??N();if(r.insertBefore(e,n),"commentMeta"!==e.metaType&&"textMeta"!==e.metaType&&(h(e)&&(h(t)||y(t))&&t.domElement.appendChild(e.domElement),f(e))){let n=r.metaType;if("textMeta"===n||"commentMeta"===n){const e=r.getPath();for(let t=e.length-1;t>=0;t--)if("textMeta"!==e[t].metaType&&"commentMeta"!==e[t].metaType){r=e[t];break}}if("standardMeta"===e.metaType&&"scene"!==e.type&&y(r)){const t=I.value;t.instance&&e&&t.addChild(e),e.instance&&e.instance.isObject3D&&t.instance&&t!==e&&t.instance.add(e.instance)}else f(e)&&e.instance?.isObject3D&&f(r)&&r.instance?.isObject3D&&r.instance?.add?.(e.instance);if(e?.props?.attach&&f(t)&&t?.instance){e.type?.toLowerCase().endsWith("loader")&&e.props.src&&(e.props.attach||e.props.attachArray)?function(e,t){const n=e.instance;if(t.attached=t.attached||{},t.attachedArray=t.attachedArray||{},!e.props.attach)return;if("textureloader"===e.type?.toLowerCase()){const r=n.load(e.props.src);pe(e,t,e.props.attach,r)}else n.load(e.props.src,(n=>{pe(e,t,e.props.attach,n)}),null,(e=>{throw new Error(e)}))}(e,t):pe(e,t,e.props.attach)}e.props?.onAdded&&e.props.onAdded({instance:e.instance})}},nextSibling(e){const t=e.nextSibling;return t||null},parentNode(e){const t=e.parentNode;return t||null},patchProp(e,t,n,o){h(e)?"style"===t?Object.keys(o).forEach((t=>{e.domElement.style[t]=o[t]})):e.domElement.setAttribute(t,o):y(e)||t.startsWith("$")||function({node:e,key:t,value:n}){if((e=>["onClick","onContextMenu","onDoubleClick","onPointerUp","onPointerDown","onPointerOver","onPointerOut","onPointerEnter","onPointerLeave","onPointerMove","onWheel"].includes(e))(t))return b({node:e,key:t,value:n});const o=t.replace(/-/g,"."),a=ce[o]||o;if(ue.includes(t)||ue.includes(a))return e;if(!f(e))return e;if("string"==typeof n&&n.startsWith("$attached")){const t=n.replace("$attached.","");n=r.get(e.attached,t,null)}const s=e.instance;if(!s)return e;let i;for(let e=0;e<de.length&&!i;e++){const t=[de[e],a].filter(Boolean).join(".");i=i=r.get(s,t)}if(i&&r.isNumber(n)&&i.setScalar)i.setScalar(n);else if(i&&i.set){const e=Array.isArray(n)?n:[n];s[a].set(...e)}else"function"==typeof i?i.bind(e.instance)(...n):void 0!==r.get(s,a,void 0)?r.set(s,a,""===n||n):console.log(`No property ${a} found on ${s}`);const c=s?.texture?.type||s?.type;if("string"==typeof c){const e=c.toLowerCase();switch(!0){case e.includes("material"):s.needsUpdate=!0;break;case e.includes("camera")&&s.updateProjectionMatrix:s.updateProjectionMatrix()}}}({node:e,key:t,value:o})},remove:e=>{if(!e)return;const t=Object.keys(T),n=[];e.walk((e=>(n.push(e),!0))),n.forEach((e=>{const n=t.find((t=>T[t]?.uuid===e.uuid));if(n&&(T[n]=null),f(e)){e.instance?.removeFromParent?.();const t="scene"!==e.type&&e.instance?.dispose;t&&t.bind(e.instance)(),e.instance=null}e.drop();const r=s.findIndex((t=>t.uuid===e.uuid));-1!==r&&s.splice(r,1)}))},setElementText(){},setText(){}},he={dpr:t.ref(1),inputActive:g,mousePos:C},fe=t.computed((()=>D.value?.instance??null));const ye=t.computed((()=>W.value?.instance??null));const ve=t.computed((()=>I.value.instance));let ge=null,be=null;e.camera=fe,e.clearCustomRender=()=>{ge?ge.clearCustomRender():be=null},e.createApp=e=>{ge=t.createRenderer(me).createApp(e),Object.keys(p).forEach((e=>{ge?.component(e,p[e])}));const{mount:n}=ge;return ge.mount=(e,...t)=>{const r=N({domElement:"string"==typeof e?document.querySelector(e):e,isLunchboxRootNode:!0,name:"root",metaType:"rootMeta",type:"root",uuid:"LUNCHBOX_ROOT"});ge.rootNode=r;return n(r,...t)},ge.extend=e=>((({app:e,...t})=>{Object.keys(t).forEach((n=>{e.component(n,_(n)),u[n]=t[n]}))})({app:ge,...e}),ge),ge.setCustomRender=e=>{ge.customRender=e},be&&(ge.setCustomRender(be),be=null),ge.clearCustomRender=()=>{ge.customRender=null},ge},e.find=function(e){return e=t.isRef(e)?e.value:e,f(e)?e?.instance:m(e)?e?.$el?.instance:t.isVNode(e)?e.el?.instance:null},e.globals=he,e.offAfterRender=e=>{if(isFinite(e))oe.splice(e,1);else{const t=oe.findIndex((t=>t==e));oe.splice(t,1)}},e.offBeforeRender=e=>{if(isFinite(e))re.splice(e,1);else{const t=re.findIndex((t=>t==e));re.splice(t,1)}},e.onAfterRender=(e,t=1/0)=>{t===1/0?oe.push(e):oe.splice(t,0,e)},e.onBeforeRender=se,e.onStart=(e,t=1/0)=>{t===1/0?te.push(e):te.splice(t,0,e)},e.renderer=ye,e.scene=ve,e.setCustomRender=e=>{ge?ge.setCustomRender(e):be=e},e.useCamera=function(e){return t.watch(fe,(t=>{t&&e(t)}),{immediate:!0})},e.useRenderer=function(e){return t.watch(ye,(t=>{t&&e(t)}),{immediate:!0})},e.useScene=function(e){return t.watch(ve,(t=>{t&&e(t)}),{immediate:!0})},Object.defineProperty(e,"__esModule",{value:!0})}));
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("vue"),require("three"),require("lodash")):"function"==typeof define&&define.amd?define(["exports","vue","three","lodash"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).LunchboxRenderer={},e.vue,e.three,e.lodash)}(this,(function(e,t,n,r){"use strict";function o(e){if(e&&e.__esModule)return e;var t=Object.create(null);return e&&Object.keys(e).forEach((function(n){if("default"!==n){var r=Object.getOwnPropertyDescriptor(e,n);Object.defineProperty(t,n,r.get?r:{enumerable:!0,get:function(){return e[n]}})}})),t.default=e,Object.freeze(t)}var a=o(n);const s=[],i=(e,n)=>{const r=I.value?.instance,o=K.value.instance,a=$.value;if(!r?.domElement||!o||!a)return;const s=(e=e??window.innerWidth)/(n=n??window.innerHeight);if("perspectivecamera"===a.type?.toLowerCase()){const e=a.instance;e.aspect=s,e.updateProjectionMatrix()}else if("orthographiccamera"===a.type?.toLowerCase()){const t=a.instance,r=n/e;t.top=10*r,t.bottom=10*-r,t.right=10,t.left=-10,t.updateProjectionMatrix()}else console.log("TODO: non-ortho or perspective camera");r.setSize(e,n),o&&a.instance&&r.render(t.toRaw(o),t.toRaw(a.instance))},c=(e,t,n,r)=>{const o=e.value?.domElement;if(!o)throw new Error("missing container");const a=()=>{if("container"===r){const e=(e=>{const t=getComputedStyle(e);return{width:e.clientWidth-parseFloat(t.paddingLeft)-parseFloat(t.paddingRight),height:e.clientHeight-parseFloat(t.paddingTop)-parseFloat(t.paddingBottom)}})(o);i(e.width,e.height)}else i()};a();const s=new ResizeObserver((([e])=>{a()}));o&&s.observe(o),n((()=>{t&&s.unobserve(t)}))},d=e=>({position:e,top:0,right:0,bottom:0,left:0,width:"100%",height:"100%",display:"block"}),u={name:"Lunchbox",props:{background:String,cameraArgs:Array,cameraLook:Array,cameraLookAt:Array,cameraPosition:Array,dpr:Number,ortho:Boolean,orthographic:Boolean,r3f:Boolean,rendererArguments:Object,rendererProperties:Object,sizePolicy:String,shadow:[Boolean,Object],transparent:Boolean,zoom:Number,updateSource:Object},setup(e,n){const o=t.ref(),s=t.ref(!0),i=t.ref(e.dpr??-1),u=t.ref();let l,p,m;e.r3f&&a?.ColorManagement&&(a.ColorManagement.legacyMode=!1),t.onMounted((()=>{if(!o.value)throw new Error("missing canvas");if(l=T(["WebGLRenderer"]),l)s.value=!1,z.value=!0;else{const t={alpha:e.transparent,antialias:!0,canvas:o.value.domElement,powerPreference:e.r3f?"high-performance":"default",...e.rendererArguments??{}};I.value=q({type:"WebGLRenderer",uuid:U,props:{args:[t]}}),z.value=!0;const n=I;e.r3f&&n.value.instance&&(n.value.instance.outputEncoding=a.sRGBEncoding,n.value.instance.toneMapping=a.ACESFilmicToneMapping);const s={shadow:e.shadow};n.value.instance&&s?.shadow&&(n.value.instance.shadowMap.enabled=!0,"object"==typeof s.shadow&&(n.value.instance.shadowMap.type=s.shadow.type)),e.rendererProperties&&Object.keys(e.rendererProperties).forEach((t=>{r.set(n.value,t,e.rendererProperties[t])})),l=n.value}if(p=T(["PerspectiveCamera","OrthographicCamera"]),p?D.value=!0:(e.ortho||e.orthographic?$.value=q({props:{args:e.cameraArgs??[]},type:"OrthographicCamera",uuid:S}):$.value=q({props:{args:e.cameraArgs??[e.r3f?75:45,.5625,1,1e3]},type:"PerspectiveCamera",uuid:S}),D.value=!0,p=$.value),!p.instance)throw new Error("Error creating camera.");if(p&&e.cameraPosition&&p.instance.position.set(...e.cameraPosition),p&&(e.cameraLookAt||e.cameraLook)){const t=e.cameraLookAt||e.cameraLook;p.instance.lookAt(...t)}if(p&&void 0!==e.zoom&&(p.instance.zoom=e.zoom),m=K.value,m&&m.instance&&e.background&&(m.instance.background=new a.Color(e.background)),-1===i.value&&(i.value=window.devicePixelRatio),!l?.instance)throw new Error("missing renderer");l.instance.setPixelRatio(i.value),ge.dpr.value=i.value,c(u,l.instance.domElement,t.onBeforeUnmount,e.sizePolicy);const n=t.getCurrentInstance().appContext.app;for(let e of re)e({app:n,camera:p.instance,renderer:l.instance,scene:m.instance});de({app:n,camera:p.instance,renderer:l.instance,scene:m.instance,updateSource:e.updateSource})})),t.onBeforeUnmount((()=>{ue(),le()}));const h="container"===e.sizePolicy?"static":"absolute",f="container"===e.sizePolicy?"static":"fixed";return()=>[n.slots.default?.()??null,t.h("div",{style:d(h),ref:u},[s.value?t.h("canvas",{style:d(f),class:"lunchbox-canvas",ref:o}):null])]}},l={},p=["canvas","div","LunchboxWrapper"],m={...["mesh","instancedMesh","scene","sprite","object3D","instancedBufferGeometry","bufferGeometry","boxBufferGeometry","circleBufferGeometry","coneBufferGeometry","cylinderBufferGeometry","dodecahedronBufferGeometry","extrudeBufferGeometry","icosahedronBufferGeometry","latheBufferGeometry","octahedronBufferGeometry","parametricBufferGeometry","planeBufferGeometry","polyhedronBufferGeometry","ringBufferGeometry","shapeBufferGeometry","sphereBufferGeometry","tetrahedronBufferGeometry","textBufferGeometry","torusBufferGeometry","torusKnotBufferGeometry","tubeBufferGeometry","wireframeGeometry","parametricGeometry","tetrahedronGeometry","octahedronGeometry","icosahedronGeometry","dodecahedronGeometry","polyhedronGeometry","tubeGeometry","torusKnotGeometry","torusGeometry","sphereGeometry","ringGeometry","planeGeometry","latheGeometry","shapeGeometry","extrudeGeometry","edgesGeometry","coneGeometry","cylinderGeometry","circleGeometry","boxGeometry","material","shadowMaterial","spriteMaterial","rawShaderMaterial","shaderMaterial","pointsMaterial","meshPhysicalMaterial","meshStandardMaterial","meshPhongMaterial","meshToonMaterial","meshNormalMaterial","meshLambertMaterial","meshDepthMaterial","meshDistanceMaterial","meshBasicMaterial","meshMatcapMaterial","lineDashedMaterial","lineBasicMaterial","light","spotLightShadow","spotLight","pointLight","rectAreaLight","hemisphereLight","directionalLightShadow","directionalLight","ambientLight","lightShadow","ambientLightProbe","hemisphereLightProbe","lightProbe","texture","videoTexture","dataTexture","dataTexture3D","compressedTexture","cubeTexture","canvasTexture","depthTexture","textureLoader","group","catmullRomCurve3","points","cameraHelper","camera","perspectiveCamera","orthographicCamera","cubeCamera","arrayCamera","webGLRenderer"].map((e=>t.defineComponent({inheritAttrs:!1,name:e,setup:(n,r)=>()=>t.h(e,r.attrs,r.slots?.default?.()||[])}))).reduce(((e,t)=>(e[t.name]=t,e)),{}),Lunchbox:u};const h=e=>e?.$el&&e?.$el?.hasOwnProperty?.("instance"),f=e=>{if("domMeta"===e?.metaType)return!0;const t="string"==typeof e?e:e?.type;return p.includes(t??"")},y=e=>"standardMeta"===e?.metaType,v=e=>e.isLunchboxRootNode,g=[],b=t.ref(!1);function x({node:e,key:n,value:r}){var o;if(e.eventListeners[n]||(e.eventListeners[n]=[]),e.eventListenerRemoveFunctions[n]||(e.eventListenerRemoveFunctions[n]=[]),e.eventListeners[n].push(r),w.includes(n)&&(_.value,e.instance&&!g.includes(e)&&(o=e,g.push(o),e.eventListenerRemoveFunctions[n].push((()=>(e=>{const t=g.indexOf(e);-1!==t&&g.splice(t,1)})(e))))),"onClick"===n||"onPointerDown"===n||"onPointerUp"===n){const r=t.watch((()=>b.value),(t=>{const r=M.map((e=>e.element)).findIndex((t=>t.instance&&t.instance.uuid===e.instance?.uuid));-1!==r&&((!t||"onClick"!==n&&"onPointerDown"!==n)&&(t||"onPointerUp"!==n)||e.eventListeners[n].forEach((e=>{e({intersection:M[r].intersection})})))}));e.eventListenerRemoveFunctions[n].push(r)}return e}const w=["onClick","onPointerUp","onPointerDown","onPointerOver","onPointerOut","onPointerEnter","onPointerLeave","onPointerMove"];let R,A,C;const E=t.ref({x:1/0,y:1/0});let L,P=!1;let M=[];const B=()=>{const e=_.value?.instance,t=$.value?.instance;if(!e||!t)return;e.setFromCamera(ge.mousePos.value,t);const n=e.intersectObjects(g.map((e=>e.instance)));let r=[],o=[],a=[];r=M.map((e=>e.intersection)),n?.forEach((e=>{if(-1===M.findIndex((t=>t.intersection.object===e.object))){const t=g.find((t=>t.instance?.uuid===e.object.uuid));t&&o.push({element:t,intersection:e})}else{const t=g.find((t=>t.instance?.uuid===e.object.uuid));t&&a.push({element:t,intersection:e})}const t=r.findIndex((t=>t.object.uuid===e.object.uuid));-1!==t&&r.splice(t,1)}));const s=r.map((e=>({element:g.find((t=>t.instance?.uuid===e.object.uuid)),intersection:e})));o.forEach((({element:e,intersection:t})=>{N({element:e,eventKeys:["onPointerEnter"],intersection:t})})),a.forEach((({element:e,intersection:t})=>{N({element:e,eventKeys:["onPointerOver","onPointerMove"],intersection:t})})),s.forEach((({element:e,intersection:t})=>{N({element:e,eventKeys:["onPointerLeave","onPointerOut"],intersection:t})})),M=[].concat(o,a)},N=({element:e,eventKeys:t,intersection:n})=>{e&&t.forEach((t=>{e.eventListeners[t]&&e.eventListeners[t].forEach((e=>{e({intersection:n})}))}))};function G(t={}){return e.lunchboxTree||(e.lunchboxTree=new Z.RendererRootNode(t)),e.lunchboxTree}function T(e){Array.isArray(e)||(e=[e]);for(let t of e)if(j[t])return j[t];for(let n of e){const e=O[n]||s.find((e=>e.type?.toLowerCase()===n.toLowerCase()));if(!(t=e,"RendererNode"!==t?.minidomType||!1!==e.props["is-default"]&&!1!=!e.props.isDefault))return null;if(e){const t=e;return O[n]=t,t}}var t;return null}e.lunchboxTree=void 0;const O=t.reactive({}),j=t.reactive({});function k(e,n,r={},o=null){Array.isArray(e)||(e=[e]);for(let t of e)O[t]||(O[t]=null),j[t]||(j[t]=null);return t.computed({get(){const t=T(e);if(t)return t;const a=G(),s=q({type:e[0],uuid:n,props:r});return a.addChild(s),O[e[0]]=s,o&&o(s),s},set(e){const t=e.type??"",n=t[0].toUpperCase()+t.slice(1);j[n]=e}})}const S="FALLBACK_CAMERA",F=k(["PerspectiveCamera","OrthographicCamera"],S,{args:[45,.5625,1,1e3]}),D=t.ref(!1),$=t.computed({get:()=>D.value?F.value:null,set(e){const t=e.type??"",n=t[0].toUpperCase()+t.slice(1);j[n]=e}}),U="FALLBACK_RENDERER",W=k(["WebGLRenderer"],U,{}),z=t.ref(!1),I=t.computed({get:()=>z.value?W.value:null,set(e){const t=e.type??"",n=t[0].toUpperCase()+t.slice(1);j[n]=e}}),K=k("Scene","FALLBACK_SCENE"),_=k("Raycaster","AUTO_RAYCASTER",{},(e=>(e=>{if(!e.instance)return;let n=null;n=t.watch((()=>I.value),(e=>{e?.instance&&(P||(R=t=>{const n=(e.instance.domElement.width??1)/ge.dpr.value,r=(e.instance.domElement.height??1)/ge.dpr.value;E.value.x=t.offsetX/n*2-1,E.value.y=-t.offsetY/r*2+1},A=()=>b.value=!0,C=()=>b.value=!1,e.instance.domElement.addEventListener("mousemove",R),e.instance.domElement.addEventListener("mousedown",A),e.instance.domElement.addEventListener("mouseup",C),t.watch((()=>[b.value,E.value.x,E.value.y]),(()=>{L&&cancelAnimationFrame(L),L=requestAnimationFrame((()=>{B()}))})),P=!0),n&&n())}),{immediate:!0})})(e)));function q(e={},t={}){const n={attached:e.attached??[],attachedArray:e.attachedArray??{},instance:e.instance??null},r=new Z.RendererStandardNode({...e,...n,metaType:"standardMeta"});return!r.type||v(r)||r.instance||(r.instance=function(e){if(!e.type)return null;const t=e.type[0].toUpperCase()+e.type.slice(1),n=l[e.type]||a[t];if(!n)throw`${t} is not part of the THREE namespace! Did you forget to extend? import {extend} from 'lunchbox'; extend({app, YourComponent, ...})`;const r=(e.props.args??[]).map((t=>function({node:e,prop:t}){const n="string"==typeof t&&t.startsWith("$attachedArray"),r=function({node:e,prop:t}){if("string"==typeof t&&t.startsWith("$attachedArray"))return e.attachedArray[t.replace("$attachedArray.","")];if("string"==typeof t&&t.startsWith("$attached"))return e.attached[t.replace("$attached.","")];return t}({node:e,prop:t});return Array.isArray(r)&&n?r:[r]}({node:e,prop:t})));let o=[];r.forEach((e=>{o=o.concat(e)}));return new n(...o)}({...r,props:{...r.props,...t}})),"scene"===r.type?.toLowerCase()?K.value=r:r.type?.toLowerCase().endsWith("camera")&&($.value=r),r}const V=e=>t.defineComponent({inheritAttrs:!1,name:e,render(){return t.h(e,this.$attrs,this.$slots?.default?.()||[])}});var H,Y=new Uint8Array(16);function X(){if(!H&&!(H="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto)||"undefined"!=typeof msCrypto&&"function"==typeof msCrypto.getRandomValues&&msCrypto.getRandomValues.bind(msCrypto)))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return H(Y)}var J=/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i;function Q(e){return"string"==typeof e&&J.test(e)}for(var Z,ee=[],te=0;te<256;++te)ee.push((te+256).toString(16).substr(1));function ne(e,t,n){var r=(e=e||{}).random||(e.rng||X)();if(r[6]=15&r[6]|64,r[8]=63&r[8]|128,t){n=n||0;for(var o=0;o<16;++o)t[n+o]=r[o];return t}return function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,n=(ee[e[t+0]]+ee[e[t+1]]+ee[e[t+2]]+ee[e[t+3]]+"-"+ee[e[t+4]]+ee[e[t+5]]+"-"+ee[e[t+6]]+ee[e[t+7]]+"-"+ee[e[t+8]]+ee[e[t+9]]+"-"+ee[e[t+10]]+ee[e[t+11]]+ee[e[t+12]]+ee[e[t+13]]+ee[e[t+14]]+ee[e[t+15]]).toLowerCase();if(!Q(n))throw TypeError("Stringified UUID is invalid");return n}(r)}!function(e){e.BaseNode=class{constructor(e={},t){this.parentNode=e?.parentNode??t??null,this.minidomType="MinidomBaseNode",this.uuid=e?.uuid??ne(),s.push(this)}uuid;parentNode;get nextSibling(){if(!this.parentNode)return null;const e=this.parentNode.children.findIndex((e=>e.uuid===this.uuid));return-1!==e&&e<this.parentNode.children.length-1?this.parentNode.children[e+1]:null}insertBefore(e,t){e.removeAsChildFromAnyParents(),e.parentNode=this;const n=this.children.findIndex((e=>e.uuid===t?.uuid));-1!==n?this.children.splice(n,0,e):this.children.push(e)}removeChild(e){const t=this.children.findIndex((t=>t?.uuid===e?.uuid));-1!==t&&this.children.splice(t,1)}children=[];addChild(e){return e&&(e.removeAsChildFromAnyParents(),e.parentNode=this,this.insertBefore(e,null)),this}getPath(){const e=[];let t=this;for(;t;)e.unshift(t),t=t.parentNode;return e}drop(){this.parentNode=null,this.removeAsChildFromAnyParents()}walk(e){const t=[this,...this.children],n=[];let r=!0;for(;t.length&&r;){const o=t.shift();if(o){if(n.includes(o))continue;n.push(o),t.push(...o.children.filter((e=>!n.includes(e)))),r=e(o)}else r=!1}}minidomType;removeAsChildFromAnyParents(){s.forEach((e=>e.removeChild(this)))}};class t extends e.BaseNode{constructor(e={},t){super(e,t),this.minidomType="RendererNode",this.eventListeners={},this.eventListenerRemoveFunctions={},this.name=e.name??"",this.metaType=e.metaType??"standardMeta",this.props=e.props??[],this.type=e.type??""}eventListeners;eventListenerRemoveFunctions;name;metaType;props;type;drop(){super.drop(),Object.keys(this.eventListenerRemoveFunctions).forEach((e=>{this.eventListenerRemoveFunctions[e].forEach((e=>e()))}))}}e.RendererBaseNode=t;class n extends e.RendererBaseNode{constructor(e={},t){super(e,t),this.domElement=e.domElement??document.createElement("div")}domElement;isLunchboxRootNode=!0}e.RendererRootNode=n;class r extends e.RendererBaseNode{constructor(e={},t){super(e,t),this.text=e.text??""}text}e.RendererCommentNode=r;class o extends e.RendererBaseNode{constructor(e={},t){super(e,t),this.domElement=e.domElement??document.createElement("div")}domElement}e.RendererDomNode=o;class a extends e.RendererBaseNode{constructor(e={},t){super(e,t),this.text=e.text??""}text}e.RendererTextNode=a;class i extends e.RendererBaseNode{constructor(e={},t){super(e,t),this.attached=e.attached??[],this.attachedArray=e.attachedArray??{},this.instance=e.instance??null}attached;attachedArray;instance}e.RendererStandardNode=i}(Z||(Z={}));(new Z.RendererRootNode).minidomType="RootNode";const re=[];let oe,ae;const se=[],ie=[],ce=e=>{ue(),oe=requestAnimationFrame((()=>de({app:e.app,renderer:I.value?.instance,scene:K.value.instance,camera:$.value?.instance,updateSource:e.updateSource})))},de=e=>{e.updateSource?ae||(ae=t.watch(e.updateSource,(()=>{ce(e)}),{deep:!0})):ce(e);const{app:n,renderer:r,scene:o,camera:a}=e;se.forEach((t=>{t&&t(e)})),r&&o&&a&&(n.customRender?n.customRender(e):r.render(t.toRaw(o),t.toRaw(a))),ie.forEach((t=>{t&&t(e)}))},ue=()=>{oe&&cancelAnimationFrame(oe)},le=()=>{ae&&ae()};const pe={x:"position.x",y:"position.y",z:"position.z"},me=["","parameters"],he=["args","attach","attachArray","is.default","isDefault","key","onAdded","ref","src"],fe=["geometry","material"];function ye(e,t,n,r){const o=r??e.instance,a=t.instance;e.props.attach===n&&(t.attached={[n]:o,...t.attached||{}},a[n]=r??e.instance),e.props.attachArray===n&&(t.attachedArray[e.props.attachArray]||(t.attachedArray[e.props.attachArray]=[]),t.attachedArray[e.props.attachArray].push(o),a[n]=[a[n]])}const ve={createElement:(e,t,n,r)=>{const o={type:e};r&&(o.props=r);if(f(e)){const e=function(e={}){const t={domElement:document.createElement(e.type??"")};return new Z.RendererDomNode({...t,...e,metaType:"domMeta"})}(o);return e}const a=q(o);return fe.forEach((t=>{e.toLowerCase().endsWith(t)&&(a.props.attach=t)})),a},createText:e=>function(e={}){const t={text:e.text??""};return new Z.RendererTextNode({...e,...t,metaType:"textMeta"})}({text:e}),createComment:e=>function(e={}){const t={text:e.text??""};return new Z.RendererCommentNode({...t,...e,metaType:"commentMeta"})}({text:e}),insert:(e,t,n)=>{let r=t??G();if(r.insertBefore(e,n),"commentMeta"!==e.metaType&&"textMeta"!==e.metaType&&(f(e)&&(f(t)||v(t))&&t.domElement.appendChild(e.domElement),y(e))){let n=r.metaType;if("textMeta"===n||"commentMeta"===n){const e=r.getPath();for(let t=e.length-1;t>=0;t--)if("textMeta"!==e[t].metaType&&"commentMeta"!==e[t].metaType){r=e[t];break}}if("standardMeta"===e.metaType&&"scene"!==e.type&&v(r)){const t=K.value;t.instance&&e&&t.addChild(e),e.instance&&e.instance.isObject3D&&t.instance&&t!==e&&t.instance.add(e.instance)}else y(e)&&e.instance?.isObject3D&&y(r)&&r.instance?.isObject3D&&r.instance?.add?.(e.instance);if(e?.props?.attach&&y(t)&&t?.instance){e.type?.toLowerCase().endsWith("loader")&&e.props.src&&(e.props.attach||e.props.attachArray)?function(e,t){const n=e.instance;if(t.attached=t.attached||{},t.attachedArray=t.attachedArray||{},!e.props.attach)return;if("textureloader"===e.type?.toLowerCase()){const r=n.load(e.props.src);ye(e,t,e.props.attach,r)}else n.load(e.props.src,(n=>{ye(e,t,e.props.attach,n)}),null,(e=>{throw new Error(e)}))}(e,t):ye(e,t,e.props.attach)}e.props?.onAdded&&e.props.onAdded({instance:e.instance})}},nextSibling(e){const t=e.nextSibling;return t||null},parentNode(e){const t=e.parentNode;return t||null},patchProp(e,t,n,o){f(e)?"style"===t?Object.keys(o).forEach((t=>{e.domElement.style[t]=o[t]})):e.domElement.setAttribute(t,o):v(e)||t.startsWith("$")||function({node:e,key:t,value:n}){if((e=>["onClick","onContextMenu","onDoubleClick","onPointerUp","onPointerDown","onPointerOver","onPointerOut","onPointerEnter","onPointerLeave","onPointerMove","onWheel"].includes(e))(t))return x({node:e,key:t,value:n});const o=t.replace(/-/g,"."),a=pe[o]||o;if(he.includes(t)||he.includes(a))return e;if(!y(e))return e;if("string"==typeof n&&n.startsWith("$attached")){const t=n.replace("$attached.","");n=r.get(e.attached,t,null)}const s=e.instance;if(!s)return e;let i;for(let e=0;e<me.length&&!i;e++){const t=[me[e],a].filter(Boolean).join(".");i=i=r.get(s,t)}if(i&&r.isNumber(n)&&i.setScalar)i.setScalar(n);else if(i&&i.set){const e=Array.isArray(n)?n:[n];s[a].set(...e)}else"function"==typeof i?i.bind(e.instance)(...n):void 0!==r.get(s,a,void 0)?r.set(s,a,""===n||n):console.log(`No property ${a} found on ${s}`);const c=s?.texture?.type||s?.type;if("string"==typeof c){const e=c.toLowerCase();switch(!0){case e.includes("material"):s.needsUpdate=!0;break;case e.includes("camera")&&s.updateProjectionMatrix:s.updateProjectionMatrix()}}}({node:e,key:t,value:o})},remove:e=>{if(!e)return;const t=Object.keys(j),n=[];e.walk((e=>(n.push(e),!0))),n.forEach((e=>{const n=t.find((t=>j[t]?.uuid===e.uuid));if(n&&(j[n]=null),y(e)){e.instance?.removeFromParent?.();const t="scene"!==e.type&&e.instance?.dispose;t&&t.bind(e.instance)(),e.instance=null}e.drop();const r=s.findIndex((t=>t.uuid===e.uuid));-1!==r&&s.splice(r,1)}))},setElementText(){},setText(){}},ge={dpr:t.ref(1),inputActive:b,mousePos:E},be=t.computed((()=>$.value?.instance??null));const xe=t.computed((()=>I.value?.instance??null));const we=t.computed((()=>K.value.instance));let Re=null,Ae=null;e.camera=be,e.clearCustomRender=()=>{Re?Re.clearCustomRender():Ae=null},e.createApp=e=>{Re=t.createRenderer(ve).createApp(e),Object.keys(m).forEach((e=>{Re?.component(e,m[e])}));const{mount:n}=Re;return Re.mount=(e,...t)=>{const r=G({domElement:"string"==typeof e?document.querySelector(e):e,isLunchboxRootNode:!0,name:"root",metaType:"rootMeta",type:"root",uuid:"LUNCHBOX_ROOT"});Re.rootNode=r;return n(r,...t)},Re.extend=e=>((({app:e,...t})=>{Object.keys(t).forEach((n=>{e.component(n,V(n)),l[n]=t[n]}))})({app:Re,...e}),Re),Re.setCustomRender=e=>{Re.customRender=e},Ae&&(Re.setCustomRender(Ae),Ae=null),Re.clearCustomRender=()=>{Re.customRender=null},Re},e.find=function(e){return e=t.isRef(e)?e.value:e,y(e)?e?.instance:h(e)?e?.$el?.instance:t.isVNode(e)?e.el?.instance:null},e.globals=ge,e.offAfterRender=e=>{if(isFinite(e))ie.splice(e,1);else{const t=ie.findIndex((t=>t==e));ie.splice(t,1)}},e.offBeforeRender=e=>{if(isFinite(e))se.splice(e,1);else{const t=se.findIndex((t=>t==e));se.splice(t,1)}},e.onAfterRender=(e,t=1/0)=>{t===1/0?ie.push(e):ie.splice(t,0,e)},e.onBeforeRender=(e,t=1/0)=>{t===1/0?se.push(e):se.splice(t,0,e)},e.onStart=(e,t=1/0)=>{t===1/0?re.push(e):re.splice(t,0,e)},e.renderer=xe,e.scene=we,e.setCustomRender=e=>{Re?Re.setCustomRender(e):Ae=e},e.useCamera=function(e){return t.watch(be,(t=>{t&&e(t)}),{immediate:!0})},e.useRenderer=function(e){return t.watch(xe,(t=>{t&&e(t)}),{immediate:!0})},e.useScene=function(e){return t.watch(we,(t=>{t&&e(t)}),{immediate:!0})},Object.defineProperty(e,"__esModule",{value:!0})}));
@@ -1,6 +1,5 @@
1
- import { toRaw, ref, onMounted, getCurrentInstance, onBeforeUnmount, h, defineComponent, isRef, isVNode, watch, reactive, computed, createRenderer } from 'vue';
1
+ import { toRaw, ref, onMounted, onBeforeUnmount, getCurrentInstance, h, defineComponent, isRef, isVNode, watch, reactive, computed, createRenderer } from 'vue';
2
2
  import * as THREE from 'three';
3
- import { Color } from 'three';
4
3
  import { set, get, isNumber } from 'lodash';
5
4
 
6
5
  // this needs to be in a separate file to ensure it's created immediately
@@ -43,16 +42,30 @@ const resizeCanvas = (width, height) => {
43
42
  }
44
43
  };
45
44
 
46
- const prepCanvas = (container, canvasElement, onBeforeUnmount) => {
45
+ const getInnerDimensions = (node) => {
46
+ const computedStyle = getComputedStyle(node);
47
+ const width = node.clientWidth - parseFloat(computedStyle.paddingLeft) - parseFloat(computedStyle.paddingRight);
48
+ const height = node.clientHeight - parseFloat(computedStyle.paddingTop) - parseFloat(computedStyle.paddingBottom);
49
+ return { width, height };
50
+ };
51
+ const prepCanvas = (container, canvasElement, onBeforeUnmount, sizePolicy) => {
47
52
  const containerElement = container.value?.domElement;
48
53
  if (!containerElement)
49
54
  throw new Error('missing container');
50
55
  // save...
51
56
  // ...and size element
52
- resizeCanvas();
57
+ const resizeCanvasByPolicy = () => {
58
+ if (sizePolicy === "container") {
59
+ const dims = getInnerDimensions(containerElement);
60
+ resizeCanvas(dims.width, dims.height);
61
+ }
62
+ else
63
+ resizeCanvas();
64
+ };
65
+ resizeCanvasByPolicy();
53
66
  // attach listeners
54
67
  const observer = new ResizeObserver(([canvas]) => {
55
- resizeCanvas();
68
+ resizeCanvasByPolicy();
56
69
  });
57
70
  // window.addEventListener('resize', resizeCanvas)
58
71
  if (containerElement) {
@@ -66,6 +79,8 @@ const prepCanvas = (container, canvasElement, onBeforeUnmount) => {
66
79
  });
67
80
  };
68
81
 
82
+ // TODO:
83
+ // Continue r3f prop - what else (besides camera fov) makes r3f look good?
69
84
  /** fixed & fill styling for container */
70
85
  const fillStyle = (position) => {
71
86
  return {
@@ -76,6 +91,7 @@ const fillStyle = (position) => {
76
91
  left: 0,
77
92
  width: '100%',
78
93
  height: '100%',
94
+ display: 'block',
79
95
  };
80
96
  };
81
97
  const LunchboxWrapper = {
@@ -90,11 +106,14 @@ const LunchboxWrapper = {
90
106
  dpr: Number,
91
107
  ortho: Boolean,
92
108
  orthographic: Boolean,
109
+ r3f: Boolean,
93
110
  rendererArguments: Object,
94
111
  rendererProperties: Object,
112
+ sizePolicy: String,
95
113
  shadow: [Boolean, Object],
96
114
  transparent: Boolean,
97
115
  zoom: Number,
116
+ updateSource: Object,
98
117
  },
99
118
  setup(props, context) {
100
119
  const canvas = ref();
@@ -104,6 +123,10 @@ const LunchboxWrapper = {
104
123
  let renderer;
105
124
  let camera;
106
125
  let scene;
126
+ // https://threejs.org/docs/index.html#manual/en/introduction/Color-management
127
+ if (props.r3f && THREE?.ColorManagement) {
128
+ THREE.ColorManagement.legacyMode = false;
129
+ }
107
130
  // MOUNT
108
131
  // ====================
109
132
  onMounted(() => {
@@ -124,6 +147,9 @@ const LunchboxWrapper = {
124
147
  alpha: props.transparent,
125
148
  antialias: true,
126
149
  canvas: canvas.value.domElement,
150
+ powerPreference: !!props.r3f
151
+ ? 'high-performance'
152
+ : 'default',
127
153
  ...(props.rendererArguments ?? {}),
128
154
  };
129
155
  // create new renderer
@@ -137,6 +163,15 @@ const LunchboxWrapper = {
137
163
  // we've initialized the renderer, so anything depending on it can execute now
138
164
  rendererReady.value = true;
139
165
  const rendererAsWebGlRenderer = ensureRenderer;
166
+ // apply r3f settings if desired
167
+ if (props.r3f) {
168
+ if (rendererAsWebGlRenderer.value.instance) {
169
+ rendererAsWebGlRenderer.value.instance.outputEncoding =
170
+ THREE.sRGBEncoding;
171
+ rendererAsWebGlRenderer.value.instance.toneMapping =
172
+ THREE.ACESFilmicToneMapping;
173
+ }
174
+ }
140
175
  // update render sugar
141
176
  const sugar = {
142
177
  shadow: props.shadow,
@@ -184,7 +219,12 @@ const LunchboxWrapper = {
184
219
  else {
185
220
  ensuredCamera.value = createNode({
186
221
  props: {
187
- args: props.cameraArgs ?? [45, 0.5625, 1, 1000],
222
+ args: props.cameraArgs ?? [
223
+ props.r3f ? 75 : 45,
224
+ 0.5625,
225
+ 1,
226
+ 1000,
227
+ ],
188
228
  },
189
229
  type: 'PerspectiveCamera',
190
230
  uuid: fallbackCameraUuid,
@@ -217,7 +257,7 @@ const LunchboxWrapper = {
217
257
  scene = ensuredScene.value;
218
258
  // set background color
219
259
  if (scene && scene.instance && props.background) {
220
- scene.instance.background = new Color(props.background);
260
+ scene.instance.background = new THREE.Color(props.background);
221
261
  }
222
262
  // MISC PROPERTIES
223
263
  // ====================
@@ -228,7 +268,7 @@ const LunchboxWrapper = {
228
268
  renderer.instance.setPixelRatio(dpr.value);
229
269
  globals.dpr.value = dpr.value;
230
270
  // prep canvas (sizing, observe, unmount, etc)
231
- prepCanvas(container, renderer.instance.domElement, onBeforeUnmount);
271
+ prepCanvas(container, renderer.instance.domElement, onBeforeUnmount, props.sizePolicy);
232
272
  }
233
273
  else {
234
274
  throw new Error('missing renderer');
@@ -253,24 +293,28 @@ const LunchboxWrapper = {
253
293
  camera: camera.instance,
254
294
  renderer: renderer.instance,
255
295
  scene: scene.instance,
296
+ updateSource: props.updateSource,
256
297
  });
257
298
  });
258
299
  // UNMOUNT
259
300
  // ====================
260
301
  onBeforeUnmount(() => {
261
302
  cancelUpdate();
303
+ cancelUpdateSource();
262
304
  });
263
305
  // RENDER FUNCTION
264
306
  // ====================
307
+ const containerFillStyle = props.sizePolicy === 'container' ? 'static' : 'absolute';
308
+ const canvasFillStyle = props.sizePolicy === 'container' ? 'static' : 'fixed';
265
309
  return () => [
266
310
  context.slots.default?.() ?? null,
267
311
  h('div', {
268
- style: fillStyle('absolute'),
312
+ style: fillStyle(containerFillStyle),
269
313
  ref: container,
270
314
  }, [
271
315
  useFallbackRenderer.value
272
316
  ? h('canvas', {
273
- style: fillStyle('fixed'),
317
+ style: fillStyle(canvasFillStyle),
274
318
  class: 'lunchbox-canvas',
275
319
  ref: canvas,
276
320
  })
@@ -604,6 +648,7 @@ let mouseDownListener;
604
648
  let mouseUpListener;
605
649
  const mousePos = ref({ x: Infinity, y: Infinity });
606
650
  let autoRaycasterEventsInitialized = false;
651
+ let frameID$1;
607
652
  const setupAutoRaycaster = (node) => {
608
653
  const instance = node.instance;
609
654
  if (!instance)
@@ -636,8 +681,14 @@ const setupAutoRaycaster = (node) => {
636
681
  renderer.instance.domElement.addEventListener('mousedown', mouseDownListener);
637
682
  renderer.instance.domElement.addEventListener('mouseup', mouseUpListener);
638
683
  // TODO: add touch events
639
- // add to update loop
640
- onBeforeRender(autoRaycasterBeforeRender);
684
+ // process mouse events asynchronously, whenever the mouse state changes
685
+ watch(() => [inputActive.value, mousePos.value.x, mousePos.value.y], () => {
686
+ if (frameID$1)
687
+ cancelAnimationFrame(frameID$1);
688
+ frameID$1 = requestAnimationFrame(() => {
689
+ autoRaycasterBeforeRender();
690
+ });
691
+ });
641
692
  // mark complete
642
693
  autoRaycasterEventsInitialized = true;
643
694
  // cancel setup watcher
@@ -966,7 +1017,7 @@ const extend = ({ app, ...targets }) => {
966
1017
  };
967
1018
 
968
1019
  /** Process props into either themselves or the $attached value */
969
- function processProp({ node, prop }) {
1020
+ function processProp({ node, prop, }) {
970
1021
  // return $attachedArray value if needed
971
1022
  if (typeof prop === 'string' && prop.startsWith('$attachedArray')) {
972
1023
  return node.attachedArray[prop.replace('$attachedArray.', '')];
@@ -978,10 +1029,12 @@ function processProp({ node, prop }) {
978
1029
  // otherwise, return plain value
979
1030
  return prop;
980
1031
  }
981
- function processPropAsArray({ node, prop }) {
1032
+ function processPropAsArray({ node, prop, }) {
982
1033
  const isAttachedArray = typeof prop === 'string' && prop.startsWith('$attachedArray');
983
1034
  const output = processProp({ node, prop });
984
- return Array.isArray(output) && isAttachedArray ? output : [output];
1035
+ return Array.isArray(output) && isAttachedArray
1036
+ ? output
1037
+ : [output];
985
1038
  }
986
1039
 
987
1040
  function instantiateThreeObject(node) {
@@ -1280,16 +1333,34 @@ const onStart = (cb, index = Infinity) => {
1280
1333
  };
1281
1334
 
1282
1335
  let frameID;
1336
+ let watchStopHandle;
1283
1337
  const beforeRender = [];
1284
1338
  const afterRender = [];
1285
- const update = (opts) => {
1286
- // request next frame
1339
+ const requestUpdate = (opts) => {
1340
+ cancelUpdate();
1287
1341
  frameID = requestAnimationFrame(() => update({
1288
1342
  app: opts.app,
1289
1343
  renderer: ensureRenderer.value?.instance,
1290
1344
  scene: ensuredScene.value.instance,
1291
1345
  camera: ensuredCamera.value?.instance,
1346
+ updateSource: opts.updateSource,
1292
1347
  }));
1348
+ };
1349
+ const update = (opts) => {
1350
+ if (opts.updateSource) {
1351
+ if (!watchStopHandle) {
1352
+ // request next frame only when state changes
1353
+ watchStopHandle = watch(opts.updateSource, () => {
1354
+ requestUpdate(opts);
1355
+ }, {
1356
+ deep: true,
1357
+ });
1358
+ }
1359
+ }
1360
+ else {
1361
+ // request next frame on a continuous loop
1362
+ requestUpdate(opts);
1363
+ }
1293
1364
  // prep options
1294
1365
  const { app, renderer, scene, camera } = opts;
1295
1366
  // BEFORE RENDER
@@ -1352,6 +1423,10 @@ const cancelUpdate = () => {
1352
1423
  if (frameID)
1353
1424
  cancelAnimationFrame(frameID);
1354
1425
  };
1426
+ const cancelUpdateSource = () => {
1427
+ if (watchStopHandle)
1428
+ watchStopHandle();
1429
+ };
1355
1430
 
1356
1431
  /** Update a single prop on a given node. */
1357
1432
  function updateObjectProp({ node, key, value, }) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lunchboxjs",
3
- "version": "0.1.4014",
3
+ "version": "0.1.4017",
4
4
  "scripts": {
5
5
  "dev": "vite -c utils/vite.config.ts",
6
6
  "build": "vue-tsc --noEmit && vite build -c utils/vite.config.ts",
@@ -22,7 +22,7 @@
22
22
  "@rollup/plugin-node-resolve": "13.0.5",
23
23
  "@rollup/plugin-typescript": "8.3.0",
24
24
  "@types/lodash": "4.14.175",
25
- "@types/three": "0.133.0",
25
+ "@types/three": "0.141.0",
26
26
  "@types/uuid": "8.3.1",
27
27
  "@vitejs/plugin-vue": "^1.9.3",
28
28
  "chroma-js": "2.1.2",
@@ -33,6 +33,7 @@
33
33
  "prompts": "2.4.2",
34
34
  "rollup-plugin-delete": "2.0.0",
35
35
  "rollup-plugin-terser": "7.0.2",
36
+ "three": "0.141.0",
36
37
  "typescript": "^4.4.3",
37
38
  "vite": "^2.6.4",
38
39
  "vite-plugin-glsl": "0.0.5",
@@ -40,7 +41,7 @@
40
41
  "vue-tsc": "^0.3.0"
41
42
  },
42
43
  "peerDependencies": {
43
- "three": "0.137.5"
44
+ "three": "0.141.0"
44
45
  },
45
46
  "files": [
46
47
  "dist",
@@ -5,11 +5,13 @@ import {
5
5
  onBeforeUnmount,
6
6
  onMounted,
7
7
  ref,
8
+ WatchSource,
8
9
  WritableComputedRef,
9
10
  } from 'vue'
10
11
  import {
11
12
  cameraReady,
12
13
  cancelUpdate,
14
+ cancelUpdateSource,
13
15
  createNode,
14
16
  ensuredCamera,
15
17
  ensureRenderer,
@@ -24,9 +26,13 @@ import {
24
26
  } from '../../core'
25
27
  import { set } from 'lodash'
26
28
  import { globals, Lunch } from '../..'
27
- import { Color, Vector2 } from 'three'
29
+ // import { Color, Vector2, sRGBEncoding, ACESFilmicToneMapping } from 'three'
30
+ import * as THREE from 'three'
28
31
  import { prepCanvas } from './prepCanvas'
29
32
 
33
+ // TODO:
34
+ // Continue r3f prop - what else (besides camera fov) makes r3f look good?
35
+
30
36
  /** fixed & fill styling for container */
31
37
  const fillStyle = (position: string) => {
32
38
  return {
@@ -37,6 +43,7 @@ const fillStyle = (position: string) => {
37
43
  left: 0,
38
44
  width: '100%',
39
45
  height: '100%',
46
+ display: 'block',
40
47
  }
41
48
  }
42
49
 
@@ -52,11 +59,14 @@ export const LunchboxWrapper: ComponentOptions = {
52
59
  dpr: Number,
53
60
  ortho: Boolean,
54
61
  orthographic: Boolean,
62
+ r3f: Boolean,
55
63
  rendererArguments: Object,
56
64
  rendererProperties: Object,
65
+ sizePolicy: String,
57
66
  shadow: [Boolean, Object],
58
67
  transparent: Boolean,
59
68
  zoom: Number,
69
+ updateSource: Object,
60
70
  },
61
71
  setup(props: Lunch.WrapperProps, context) {
62
72
  const canvas = ref<MiniDom.RendererDomNode>()
@@ -67,6 +77,11 @@ export const LunchboxWrapper: ComponentOptions = {
67
77
  let camera: Lunch.Node<THREE.Camera> | null
68
78
  let scene: MiniDom.RendererStandardNode<THREE.Scene>
69
79
 
80
+ // https://threejs.org/docs/index.html#manual/en/introduction/Color-management
81
+ if (props.r3f && (THREE as any)?.ColorManagement) {
82
+ ;(THREE as any).ColorManagement.legacyMode = false
83
+ }
84
+
70
85
  // MOUNT
71
86
  // ====================
72
87
  onMounted(() => {
@@ -88,6 +103,9 @@ export const LunchboxWrapper: ComponentOptions = {
88
103
  alpha: props.transparent,
89
104
  antialias: true,
90
105
  canvas: canvas.value.domElement,
106
+ powerPreference: !!props.r3f
107
+ ? 'high-performance'
108
+ : 'default',
91
109
  ...(props.rendererArguments ?? {}),
92
110
  }
93
111
 
@@ -108,6 +126,16 @@ export const LunchboxWrapper: ComponentOptions = {
108
126
  Lunch.Node<THREE.WebGLRenderer>
109
127
  >
110
128
 
129
+ // apply r3f settings if desired
130
+ if (props.r3f) {
131
+ if (rendererAsWebGlRenderer.value.instance) {
132
+ rendererAsWebGlRenderer.value.instance.outputEncoding =
133
+ THREE.sRGBEncoding
134
+ rendererAsWebGlRenderer.value.instance.toneMapping =
135
+ THREE.ACESFilmicToneMapping
136
+ }
137
+ }
138
+
111
139
  // update render sugar
112
140
  const sugar = {
113
141
  shadow: props.shadow,
@@ -160,7 +188,12 @@ export const LunchboxWrapper: ComponentOptions = {
160
188
  } else {
161
189
  ensuredCamera.value = createNode<THREE.PerspectiveCamera>({
162
190
  props: {
163
- args: props.cameraArgs ?? [45, 0.5625, 1, 1000],
191
+ args: props.cameraArgs ?? [
192
+ props.r3f ? 75 : 45,
193
+ 0.5625,
194
+ 1,
195
+ 1000,
196
+ ],
164
197
  },
165
198
  type: 'PerspectiveCamera',
166
199
  uuid: fallbackCameraUuid,
@@ -195,7 +228,7 @@ export const LunchboxWrapper: ComponentOptions = {
195
228
  scene = ensuredScene.value
196
229
  // set background color
197
230
  if (scene && scene.instance && props.background) {
198
- scene.instance.background = new Color(props.background)
231
+ scene.instance.background = new THREE.Color(props.background)
199
232
  }
200
233
 
201
234
  // MISC PROPERTIES
@@ -211,7 +244,8 @@ export const LunchboxWrapper: ComponentOptions = {
211
244
  prepCanvas(
212
245
  container,
213
246
  renderer.instance.domElement,
214
- onBeforeUnmount
247
+ onBeforeUnmount,
248
+ props.sizePolicy,
215
249
  )
216
250
  } else {
217
251
  throw new Error('missing renderer')
@@ -239,6 +273,7 @@ export const LunchboxWrapper: ComponentOptions = {
239
273
  camera: camera.instance,
240
274
  renderer: renderer.instance,
241
275
  scene: scene.instance,
276
+ updateSource: props.updateSource,
242
277
  })
243
278
  })
244
279
 
@@ -246,22 +281,26 @@ export const LunchboxWrapper: ComponentOptions = {
246
281
  // ====================
247
282
  onBeforeUnmount(() => {
248
283
  cancelUpdate()
284
+ cancelUpdateSource()
249
285
  })
250
286
 
251
287
  // RENDER FUNCTION
252
288
  // ====================
289
+ const containerFillStyle = props.sizePolicy === 'container' ? 'static' : 'absolute'
290
+ const canvasFillStyle = props.sizePolicy === 'container' ? 'static' : 'fixed'
291
+
253
292
  return () => [
254
293
  context.slots.default?.() ?? null,
255
294
  h(
256
295
  'div',
257
296
  {
258
- style: fillStyle('absolute'),
297
+ style: fillStyle(containerFillStyle),
259
298
  ref: container,
260
299
  },
261
300
  [
262
301
  useFallbackRenderer.value
263
302
  ? h('canvas', {
264
- style: fillStyle('fixed'),
303
+ style: fillStyle(canvasFillStyle),
265
304
  class: 'lunchbox-canvas',
266
305
  ref: canvas,
267
306
  })
@@ -1,22 +1,39 @@
1
1
  import { MiniDom } from '../../core'
2
+ import { Lunch } from '../../types'
2
3
  import { Ref } from 'vue'
3
4
  import { resizeCanvas } from './resizeCanvas'
4
5
 
6
+ const getInnerDimensions = (node: Element) => {
7
+ const computedStyle = getComputedStyle(node)
8
+ const width = node.clientWidth - parseFloat(computedStyle.paddingLeft) - parseFloat(computedStyle.paddingRight)
9
+ const height = node.clientHeight - parseFloat(computedStyle.paddingTop) - parseFloat(computedStyle.paddingBottom)
10
+ return { width, height }
11
+ }
12
+
5
13
  export const prepCanvas = (
6
14
  container: Ref<MiniDom.RendererDomNode | undefined>,
7
15
  canvasElement: HTMLCanvasElement,
8
16
  onBeforeUnmount: Function,
17
+ sizePolicy?: Lunch.SizePolicy
9
18
  ) => {
10
19
  const containerElement = container.value?.domElement
11
20
  if (!containerElement) throw new Error('missing container')
12
21
 
13
22
  // save...
14
23
  // ...and size element
15
- resizeCanvas()
24
+ const resizeCanvasByPolicy = () => {
25
+ if (sizePolicy === "container") {
26
+ const dims = getInnerDimensions(containerElement);
27
+ resizeCanvas(dims.width, dims.height);
28
+ }
29
+ else
30
+ resizeCanvas()
31
+ };
32
+ resizeCanvasByPolicy();
16
33
 
17
34
  // attach listeners
18
35
  const observer = new ResizeObserver(([canvas]) => {
19
- resizeCanvas()
36
+ resizeCanvasByPolicy();
20
37
  })
21
38
  // window.addEventListener('resize', resizeCanvas)
22
39
  if (containerElement) {
@@ -29,4 +46,4 @@ export const prepCanvas = (
29
46
  observer.unobserve(canvasElement)
30
47
  }
31
48
  })
32
- }
49
+ }
@@ -1,3 +1,3 @@
1
1
  import { Lunch } from '..'
2
2
 
3
- export const catalogue: Lunch.Catalogue = {}
3
+ export const catalogue: Lunch.Catalogue = {}
@@ -1,10 +1,18 @@
1
1
  import { Lunch } from '../..'
2
2
 
3
3
  /** Process props into either themselves or the $attached value */
4
- export function processProp<T, U = THREE.Object3D>({ node, prop }: { node: Lunch.StandardMeta<U>, prop: any }) {
4
+ export function processProp<T, U = THREE.Object3D>({
5
+ node,
6
+ prop,
7
+ }: {
8
+ node: Lunch.StandardMeta<U>
9
+ prop: any
10
+ }) {
5
11
  // return $attachedArray value if needed
6
12
  if (typeof prop === 'string' && prop.startsWith('$attachedArray')) {
7
- return node.attachedArray[prop.replace('$attachedArray.', '')] as any as T
13
+ return node.attachedArray[
14
+ prop.replace('$attachedArray.', '')
15
+ ] as any as T
8
16
  }
9
17
 
10
18
  // return $attached value if needed
@@ -16,8 +24,17 @@ export function processProp<T, U = THREE.Object3D>({ node, prop }: { node: Lunch
16
24
  return prop as T
17
25
  }
18
26
 
19
- export function processPropAsArray<T, U = THREE.Object3D>({ node, prop }: { node: Lunch.StandardMeta<U>, prop: any }) {
20
- const isAttachedArray = typeof prop === 'string' && prop.startsWith('$attachedArray')
27
+ export function processPropAsArray<T, U = THREE.Object3D>({
28
+ node,
29
+ prop,
30
+ }: {
31
+ node: Lunch.StandardMeta<U>
32
+ prop: any
33
+ }) {
34
+ const isAttachedArray =
35
+ typeof prop === 'string' && prop.startsWith('$attachedArray')
21
36
  const output = processProp<T, U>({ node, prop })
22
- return Array.isArray(output) && isAttachedArray ? output as Array<T> : [output]
23
- }
37
+ return Array.isArray(output) && isAttachedArray
38
+ ? (output as Array<T>)
39
+ : [output]
40
+ }
@@ -1,4 +1,4 @@
1
1
  import { ref } from 'vue'
2
2
 
3
3
  /** Mouse is down, touch is pressed, etc */
4
- export const inputActive = ref(false)
4
+ export const inputActive = ref(false)
@@ -17,6 +17,8 @@ let mouseUpListener: (event: MouseEvent) => void
17
17
  export const mousePos = ref({ x: Infinity, y: Infinity })
18
18
  let autoRaycasterEventsInitialized = false
19
19
 
20
+ let frameID: number
21
+
20
22
  export const setupAutoRaycaster = (node: Lunch.Node<THREE.Raycaster>) => {
21
23
  const instance = node.instance
22
24
 
@@ -67,8 +69,16 @@ export const setupAutoRaycaster = (node: Lunch.Node<THREE.Raycaster>) => {
67
69
 
68
70
  // TODO: add touch events
69
71
 
70
- // add to update loop
71
- onBeforeRender(autoRaycasterBeforeRender)
72
+ // process mouse events asynchronously, whenever the mouse state changes
73
+ watch(
74
+ () => [inputActive.value, mousePos.value.x, mousePos.value.y],
75
+ () => {
76
+ if (frameID) cancelAnimationFrame(frameID)
77
+ frameID = requestAnimationFrame(() => {
78
+ autoRaycasterBeforeRender()
79
+ })
80
+ }
81
+ )
72
82
 
73
83
  // mark complete
74
84
  autoRaycasterEventsInitialized = true
@@ -1,22 +1,44 @@
1
1
  import { ensureRenderer, ensuredScene, ensuredCamera } from '.'
2
2
  import { Lunch } from '..'
3
- import { toRaw } from 'vue'
3
+ import { toRaw, watch, WatchStopHandle } from 'vue'
4
4
 
5
5
  let frameID: number
6
+ let watchStopHandle: WatchStopHandle
6
7
 
7
8
  export const beforeRender = [] as Lunch.UpdateCallback[]
8
9
  export const afterRender = [] as Lunch.UpdateCallback[]
9
10
 
10
- export const update: Lunch.UpdateCallback = (opts) => {
11
- // request next frame
11
+ const requestUpdate = (opts: Lunch.UpdateCallbackProperties) => {
12
+ cancelUpdate()
12
13
  frameID = requestAnimationFrame(() =>
13
14
  update({
14
15
  app: opts.app,
15
16
  renderer: ensureRenderer.value?.instance,
16
17
  scene: ensuredScene.value.instance,
17
18
  camera: ensuredCamera.value?.instance,
19
+ updateSource: opts.updateSource,
18
20
  })
19
21
  )
22
+ }
23
+
24
+ export const update: Lunch.UpdateCallback = (opts) => {
25
+ if (opts.updateSource) {
26
+ if (!watchStopHandle) {
27
+ // request next frame only when state changes
28
+ watchStopHandle = watch(
29
+ opts.updateSource,
30
+ () => {
31
+ requestUpdate(opts)
32
+ },
33
+ {
34
+ deep: true,
35
+ }
36
+ )
37
+ }
38
+ } else {
39
+ // request next frame on a continuous loop
40
+ requestUpdate(opts)
41
+ }
20
42
 
21
43
  // prep options
22
44
  const { app, renderer, scene, camera } = opts
@@ -82,3 +104,7 @@ export const offAfterRender = (cb: Lunch.UpdateCallback | number) => {
82
104
  export const cancelUpdate = () => {
83
105
  if (frameID) cancelAnimationFrame(frameID)
84
106
  }
107
+
108
+ export const cancelUpdateSource = () => {
109
+ if (watchStopHandle) watchStopHandle()
110
+ }
package/src/types.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  type RootNode = import('./core/minidom').MiniDom.RendererRootNode
2
2
  type VNodeProps = import('vue').VNodeProps
3
3
  type VueApp<T> = import('vue').App<T>
4
+ type WatchSource = import('vue').WatchSource
4
5
  type RendererStandardNode<T = THREE.Object3D> =
5
6
  import('./core').MiniDom.RendererStandardNode<T>
6
7
 
@@ -127,6 +128,7 @@ export declare namespace Lunch {
127
128
  scene?: THREE.Scene | null
128
129
  renderer?: THREE.Renderer | null
129
130
  camera?: THREE.Camera | null
131
+ updateSource?: WatchSource | null
130
132
 
131
133
  // sceneNode: Node<THREE.Scene> | null
132
134
  // rendererNode: Node<THREE.Renderer> | null
@@ -136,6 +138,8 @@ export declare namespace Lunch {
136
138
  /** Universally unique identifier. */
137
139
  type Uuid = string
138
140
 
141
+ type SizePolicy = 'full' | 'container'
142
+
139
143
  interface WrapperProps {
140
144
  background?: string
141
145
  cameraArgs?: any[]
@@ -145,11 +149,14 @@ export declare namespace Lunch {
145
149
  dpr?: number
146
150
  ortho?: boolean
147
151
  orthographic?: boolean
152
+ r3f?: boolean
148
153
  // TODO: Why doesn't ConstructorParameters<THREE.WebGLRenderer> work here?
149
154
  rendererArguments?: object
150
155
  rendererProperties?: Partial<THREE.WebGLRenderer>
156
+ sizePolicy?: SizePolicy
151
157
  shadow?: ShadowSugar
152
158
  transparent?: boolean
153
159
  zoom?: number
160
+ updateSource?: WatchSource | null
154
161
  }
155
162
  }