lunchboxjs 0.1.4012 → 0.1.4015

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/LICENSE.md ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2022 Breakfast Studio, LLC
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,17 @@
1
1
  Custom Vue 3 renderer for ThreeJS.
2
2
 
3
+ ## Full Docs
4
+
3
5
  [Docs](//docs.lunchboxjs.com)
6
+
7
+ ## Local Dev
8
+
9
+ `npm run dev` to run dev server. From there, edit source code in `/src/...` and examples in `/demo/...` to build and test features.
10
+
11
+ ### Creating Examples
12
+
13
+ Run `npm run demo:create` to create a new demo.
14
+
15
+ ## Docs Dev
16
+
17
+ `npm run docs:dev` to run docs dev. Docs exist as a Vitepress site in the `/docs` folder.
@@ -87,6 +87,8 @@
87
87
  });
88
88
  };
89
89
 
90
+ // TODO:
91
+ // Continue r3f prop - what else (besides camera fov) makes r3f look good?
90
92
  /** fixed & fill styling for container */
91
93
  const fillStyle = (position) => {
92
94
  return {
@@ -111,6 +113,7 @@
111
113
  dpr: Number,
112
114
  ortho: Boolean,
113
115
  orthographic: Boolean,
116
+ r3f: Boolean,
114
117
  rendererArguments: Object,
115
118
  rendererProperties: Object,
116
119
  shadow: [Boolean, Object],
@@ -125,6 +128,10 @@
125
128
  let renderer;
126
129
  let camera;
127
130
  let scene;
131
+ // https://threejs.org/docs/index.html#manual/en/introduction/Color-management
132
+ if (props.r3f && THREE__namespace?.ColorManagement) {
133
+ THREE__namespace.ColorManagement.legacyMode = false;
134
+ }
128
135
  // MOUNT
129
136
  // ====================
130
137
  vue.onMounted(() => {
@@ -145,6 +152,9 @@
145
152
  alpha: props.transparent,
146
153
  antialias: true,
147
154
  canvas: canvas.value.domElement,
155
+ powerPreference: !!props.r3f
156
+ ? 'high-performance'
157
+ : 'default',
148
158
  ...(props.rendererArguments ?? {}),
149
159
  };
150
160
  // create new renderer
@@ -158,6 +168,15 @@
158
168
  // we've initialized the renderer, so anything depending on it can execute now
159
169
  rendererReady.value = true;
160
170
  const rendererAsWebGlRenderer = ensureRenderer;
171
+ // apply r3f settings if desired
172
+ if (props.r3f) {
173
+ if (rendererAsWebGlRenderer.value.instance) {
174
+ rendererAsWebGlRenderer.value.instance.outputEncoding =
175
+ THREE__namespace.sRGBEncoding;
176
+ rendererAsWebGlRenderer.value.instance.toneMapping =
177
+ THREE__namespace.ACESFilmicToneMapping;
178
+ }
179
+ }
161
180
  // update render sugar
162
181
  const sugar = {
163
182
  shadow: props.shadow,
@@ -205,7 +224,12 @@
205
224
  else {
206
225
  ensuredCamera.value = createNode({
207
226
  props: {
208
- args: props.cameraArgs ?? [45, 0.5625, 1, 1000],
227
+ args: props.cameraArgs ?? [
228
+ props.r3f ? 75 : 45,
229
+ 0.5625,
230
+ 1,
231
+ 1000,
232
+ ],
209
233
  },
210
234
  type: 'PerspectiveCamera',
211
235
  uuid: fallbackCameraUuid,
@@ -238,7 +262,7 @@
238
262
  scene = ensuredScene.value;
239
263
  // set background color
240
264
  if (scene && scene.instance && props.background) {
241
- scene.instance.background = new THREE.Color(props.background);
265
+ scene.instance.background = new THREE__namespace.Color(props.background);
242
266
  }
243
267
  // MISC PROPERTIES
244
268
  // ====================
@@ -544,76 +568,6 @@
544
568
  return node.isLunchboxRootNode;
545
569
  };
546
570
 
547
- /** Create a new Lunchbox comment node. */
548
- function createCommentNode(options = {}) {
549
- const defaults = {
550
- text: options.text ?? '',
551
- };
552
- return new MiniDom.RendererCommentNode({
553
- ...defaults,
554
- ...options,
555
- metaType: 'commentMeta',
556
- });
557
- }
558
- /** Create a new DOM node. */
559
- function createDomNode(options = {}) {
560
- const domElement = document.createElement(options.type ?? '');
561
- const defaults = {
562
- domElement,
563
- };
564
- const node = new MiniDom.RendererDomNode({
565
- ...defaults,
566
- ...options,
567
- metaType: 'domMeta',
568
- });
569
- return node;
570
- }
571
- /** Create a new Lunchbox text node. */
572
- function createTextNode(options = {}) {
573
- const defaults = {
574
- text: options.text ?? '',
575
- };
576
- return new MiniDom.RendererTextNode({
577
- ...options,
578
- ...defaults,
579
- metaType: 'textMeta',
580
- });
581
- }
582
- /** Create a new Lunchbox standard node. */
583
- function createNode(options = {}, props = {}) {
584
- const defaults = {
585
- attached: options.attached ?? [],
586
- attachedArray: options.attachedArray ?? {},
587
- instance: options.instance ?? null,
588
- };
589
- const node = new MiniDom.RendererStandardNode({
590
- ...options,
591
- ...defaults,
592
- metaType: 'standardMeta',
593
- });
594
- if (node.type && !isLunchboxRootNode(node) && !node.instance) {
595
- // if (node.type.includes('Camera')) {
596
- // console.log(node.type, {
597
- // ...node.props,
598
- // ...props,
599
- // })
600
- // console.trace()
601
- // }
602
- node.instance = instantiateThreeObject({
603
- ...node,
604
- props: {
605
- ...node.props,
606
- ...props,
607
- },
608
- });
609
- }
610
- if (node.type === 'scene') {
611
- // manually set scene override
612
- ensuredScene.value = node;
613
- }
614
- return node;
615
- }
616
-
617
571
  const interactables = [];
618
572
  const addInteractable = (target) => {
619
573
  interactables.push(target);
@@ -939,17 +893,10 @@
939
893
  overrides[pascalType] = val;
940
894
  },
941
895
  });
942
- // export const ensuredCamera = buildEnsured<THREE.Camera>(
943
- // ['PerspectiveCamera', 'OrthographicCamera'],
944
- // fallbackCameraUuid,
945
- // {
946
- // args: [45, 0.5625, 1, 1000],
947
- // }
948
- // )
949
896
  // ENSURE RENDERER
950
897
  // ====================
951
898
  const fallbackRendererUuid = 'FALLBACK_RENDERER';
952
- const v = buildEnsured(
899
+ const ensuredRenderer = buildEnsured(
953
900
  // TODO: ensure support for css/svg renderers
954
901
  ['WebGLRenderer'], //, 'CSS2DRenderer', 'CSS3DRenderer', 'SVGRenderer'],
955
902
  fallbackRendererUuid, {});
@@ -958,7 +905,7 @@
958
905
  const rendererReady = vue.ref(false);
959
906
  const ensureRenderer = vue.computed({
960
907
  get() {
961
- return (rendererReady.value ? v.value : null);
908
+ return (rendererReady.value ? ensuredRenderer.value : null);
962
909
  },
963
910
  set(val) {
964
911
  const t = val.type ?? '';
@@ -975,6 +922,80 @@
975
922
  // `unknown` is intentional here - we need to typecast the node since Raycaster isn't an Object3D
976
923
  const ensuredRaycaster = buildEnsured('Raycaster', autoRaycasterUuid, {}, (node) => setupAutoRaycaster(node));
977
924
 
925
+ /** Create a new Lunchbox comment node. */
926
+ function createCommentNode(options = {}) {
927
+ const defaults = {
928
+ text: options.text ?? '',
929
+ };
930
+ return new MiniDom.RendererCommentNode({
931
+ ...defaults,
932
+ ...options,
933
+ metaType: 'commentMeta',
934
+ });
935
+ }
936
+ /** Create a new DOM node. */
937
+ function createDomNode(options = {}) {
938
+ const domElement = document.createElement(options.type ?? '');
939
+ const defaults = {
940
+ domElement,
941
+ };
942
+ const node = new MiniDom.RendererDomNode({
943
+ ...defaults,
944
+ ...options,
945
+ metaType: 'domMeta',
946
+ });
947
+ return node;
948
+ }
949
+ /** Create a new Lunchbox text node. */
950
+ function createTextNode(options = {}) {
951
+ const defaults = {
952
+ text: options.text ?? '',
953
+ };
954
+ return new MiniDom.RendererTextNode({
955
+ ...options,
956
+ ...defaults,
957
+ metaType: 'textMeta',
958
+ });
959
+ }
960
+ /** Create a new Lunchbox standard node. */
961
+ function createNode(options = {}, props = {}) {
962
+ const defaults = {
963
+ attached: options.attached ?? [],
964
+ attachedArray: options.attachedArray ?? {},
965
+ instance: options.instance ?? null,
966
+ };
967
+ const node = new MiniDom.RendererStandardNode({
968
+ ...options,
969
+ ...defaults,
970
+ metaType: 'standardMeta',
971
+ });
972
+ if (node.type && !isLunchboxRootNode(node) && !node.instance) {
973
+ // if (node.type.includes('Camera')) {
974
+ // console.log(node.type, {
975
+ // ...node.props,
976
+ // ...props,
977
+ // })
978
+ // console.trace()
979
+ // }
980
+ node.instance = instantiateThreeObject({
981
+ ...node,
982
+ props: {
983
+ ...node.props,
984
+ ...props,
985
+ },
986
+ });
987
+ }
988
+ // TODO: these manual overrides are a bit brittle - replace?
989
+ if (node.type?.toLowerCase() === 'scene') {
990
+ // manually set scene override
991
+ ensuredScene.value = node;
992
+ }
993
+ else if (node.type?.toLowerCase().endsWith('camera')) {
994
+ ensuredCamera.value = node;
995
+ }
996
+ return node;
997
+ }
998
+
978
999
  const createComponent = (tag) => vue.defineComponent({
979
1000
  inheritAttrs: false,
980
1001
  name: tag,
@@ -1731,46 +1752,31 @@
1731
1752
  /** The current camera. Often easier to use `useCamera` instead of this. */
1732
1753
  const camera = vue.computed(() => ensuredCamera.value?.instance ?? null);
1733
1754
  /** Run a function using the current camera when it's present. */
1734
- function useCamera(callback, once = true) {
1735
- let destroy;
1736
- destroy = vue.watch(camera, (newVal) => {
1755
+ function useCamera(callback) {
1756
+ return vue.watch(camera, (newVal) => {
1737
1757
  if (!newVal)
1738
1758
  return;
1739
- // TODO: better fix than `any`?
1740
1759
  callback(newVal);
1741
- if (once) {
1742
- destroy?.();
1743
- }
1744
1760
  }, { immediate: true });
1745
1761
  }
1746
1762
  /** The current renderer. Often easier to use `useRenderer` instead of this. */
1747
1763
  const renderer = vue.computed(() => ensureRenderer.value?.instance ?? null);
1748
1764
  /** Run a function using the current renderer when it's present. */
1749
- function useRenderer(callback, once = true) {
1750
- let destroy;
1751
- destroy = vue.watch(renderer, (newVal) => {
1765
+ function useRenderer(callback) {
1766
+ return vue.watch(renderer, (newVal) => {
1752
1767
  if (!newVal)
1753
1768
  return;
1754
- // TODO: better fix than `any`?
1755
1769
  callback(newVal);
1756
- if (once) {
1757
- destroy?.();
1758
- }
1759
1770
  }, { immediate: true });
1760
1771
  }
1761
1772
  /** The current scene. Often easier to use `useScene` instead of this. */
1762
1773
  const scene = vue.computed(() => ensuredScene.value.instance);
1763
1774
  /** Run a function using the current scene when it's present. */
1764
- function useScene(callback, once = true) {
1765
- let destroy;
1766
- destroy = vue.watch(scene, (newVal) => {
1775
+ function useScene(callback) {
1776
+ return vue.watch(scene, (newVal) => {
1767
1777
  if (!newVal)
1768
1778
  return;
1769
- // TODO: better fix than `any`?
1770
1779
  callback(newVal);
1771
- if (once) {
1772
- destroy?.();
1773
- }
1774
1780
  }, { immediate: true });
1775
1781
  }
1776
1782
  // CUSTOM RENDER SUPPORT
@@ -1799,12 +1805,14 @@
1799
1805
  app = vue.createRenderer(nodeOps).createApp(root);
1800
1806
  // register all components
1801
1807
  Object.keys(components).forEach((key) => {
1802
- app.component(key, components[key]);
1808
+ app?.component(key, components[key]);
1803
1809
  });
1804
1810
  // update mount function to match Lunchbox.Node
1805
1811
  const { mount } = app;
1806
1812
  app.mount = (root, ...args) => {
1813
+ // find DOM element to use as app root
1807
1814
  const domElement = (typeof root === 'string' ? document.querySelector(root) : root);
1815
+ // create or find root node
1808
1816
  const rootNode = ensureRootNode({
1809
1817
  domElement,
1810
1818
  isLunchboxRootNode: true,
@@ -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=I.value?.instance,o=z.value.instance,a=F.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=G(["WebGLRenderer"]),l)s.value=!1,W.value=!0;else{const t={alpha:e.transparent,antialias:!0,canvas:a.value.domElement,...e.rendererArguments??{}};I.value=v({type:"WebGLRenderer",uuid:$,props:{args:[t]}}),W.value=!0;const n=I,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=G(["PerspectiveCamera","OrthographicCamera"]),p?D.value=!0:(e.ortho||e.orthographic?F.value=v({props:{args:e.cameraArgs??[]},type:"OrthographicCamera",uuid:k}):F.value=v({props:{args:e.cameraArgs??[45,.5625,1,1e3]},type:"PerspectiveCamera",uuid:k}),D.value=!0,p=F.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=z.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;function v(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&&(z.value=r),r}const 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),R.includes(n)&&(K.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=P.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:P[r].intersection})})))}));e.eventListenerRemoveFunctions[n].push(r)}return e}const R=["onClick","onPointerUp","onPointerDown","onPointerOver","onPointerOut","onPointerEnter","onPointerLeave","onPointerMove"];let w,A,C;const E=t.ref({x:1/0,y:1/0});let L=!1;let P=[];const M=()=>{const e=K.value?.instance,t=F.value?.instance;if(!e||!t)return;e.setFromCamera(he.mousePos.value,t);const n=e.intersectObjects(g.map((e=>e.instance)));let r=[],o=[],a=[];r=P.map((e=>e.intersection)),n?.forEach((e=>{const t=P.findIndex((t=>t.intersection.object===e.object));if(-1===t){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 n=r.findIndex((t=>t.object.uuid===e.object.uuid));-1!==n&&r.splice(n,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})})),P=[].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 B(t={}){return e.lunchboxTree||(e.lunchboxTree=new J.RendererRootNode(t)),e.lunchboxTree}function G(e){Array.isArray(e)||(e=[e]);for(let t of e)if(O[t])return O[t];for(let n of e){const e=T[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 T[n]=t,t}}var t;return null}e.lunchboxTree=void 0;const T=t.reactive({}),O=t.reactive({});function j(e,n,r={},o=null){Array.isArray(e)||(e=[e]);for(let t of e)T[t]||(T[t]=null),O[t]||(O[t]=null);return t.computed({get(){const t=G(e);if(t)return t;const a=B(),s=v({type:e[0],uuid:n,props:r});return a.addChild(s),T[e[0]]=s,o&&o(s),s},set(e){const t=e.type??"",n=t[0].toUpperCase()+t.slice(1);O[n]=e}})}const k="FALLBACK_CAMERA",S=j(["PerspectiveCamera","OrthographicCamera"],k,{args:[45,.5625,1,1e3]}),D=t.ref(!1),F=t.computed({get:()=>D.value?S.value:null,set(e){const t=e.type??"",n=t[0].toUpperCase()+t.slice(1);O[n]=e}}),$="FALLBACK_RENDERER",U=j(["WebGLRenderer"],$,{}),W=t.ref(!1),I=t.computed({get:()=>W.value?U.value:null,set(e){const t=e.type??"",n=t[0].toUpperCase()+t.slice(1);O[n]=e}}),z=j("Scene","FALLBACK_SCENE"),K=j("Raycaster","AUTO_RAYCASTER",{},(e=>(e=>{if(!e.instance)return;let n=null;n=t.watch((()=>I.value),(e=>{e?.instance&&(L||(w=t=>{const n=(e.instance.domElement.width??1)/he.dpr.value,r=(e.instance.domElement.height??1)/he.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",w),e.instance.domElement.addEventListener("mousedown",A),e.instance.domElement.addEventListener("mouseup",C),se(M),L=!0),n&&n())}),{immediate:!0})})(e))),_=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:I.value?.instance,scene:z.value.instance,camera:F.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=v(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??B();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=z.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 x({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(O),n=[];e.walk((e=>(n.push(e),!0))),n.forEach((e=>{const n=t.find((t=>O[t]?.uuid===e.uuid));if(n&&(O[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:b,mousePos:E},fe=t.computed((()=>F.value?.instance??null));const ye=t.computed((()=>I.value?.instance??null));const ve=t.computed((()=>z.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=B({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,n=!0){let r;r=t.watch(fe,(t=>{t&&(e(t),n&&r?.())}),{immediate:!0})},e.useRenderer=function(e,n=!0){let r;r=t.watch(ye,(t=>{t&&(e(t),n&&r?.())}),{immediate:!0})},e.useScene=function(e,n=!0){let r;r=t.watch(ve,(t=>{t&&(e(t),n&&r?.())}),{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=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,r3f:Boolean,rendererArguments:Object,rendererProperties:Object,shadow:[Boolean,Object],transparent:Boolean,zoom:Number},setup(e,n){const o=t.ref(),s=t.ref(!0),d=t.ref(e.dpr??-1),u=t.ref();let l,p,m;return e.r3f&&a?.ColorManagement&&(a.ColorManagement.legacyMode=!1),t.onMounted((()=>{if(!o.value)throw new Error("missing canvas");if(l=N(["WebGLRenderer"]),l)s.value=!1,U.value=!0;else{const t={alpha:e.transparent,antialias:!0,canvas:o.value.domElement,powerPreference:e.r3f?"high-performance":"default",...e.rendererArguments??{}};W.value=K({type:"WebGLRenderer",uuid:F,props:{args:[t]}}),U.value=!0;const n=W;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=N(["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??[e.r3f?75: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 a.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 n=t.getCurrentInstance().appContext.app;for(let e of te)e({app:n,camera:p.instance,renderer:l.instance,scene:m.instance});ae({app:n,camera:p.instance,renderer:l.instance,scene:m.instance})})),t.onBeforeUnmount((()=>{ie()})),()=>[n.slots.default?.()??null,t.h("div",{style:c("absolute"),ref:u},[s.value?t.h("canvas",{style:c("fixed"),class:"lunchbox-canvas",ref:o}):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 B(t={}){return e.lunchboxTree||(e.lunchboxTree=new J.RendererRootNode(t)),e.lunchboxTree}function N(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=N(e);if(t)return t;const a=B(),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??B();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=B({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,6 +1,5 @@
1
1
  import { toRaw, ref, onMounted, getCurrentInstance, onBeforeUnmount, 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
@@ -66,6 +65,8 @@ const prepCanvas = (container, canvasElement, onBeforeUnmount) => {
66
65
  });
67
66
  };
68
67
 
68
+ // TODO:
69
+ // Continue r3f prop - what else (besides camera fov) makes r3f look good?
69
70
  /** fixed & fill styling for container */
70
71
  const fillStyle = (position) => {
71
72
  return {
@@ -90,6 +91,7 @@ const LunchboxWrapper = {
90
91
  dpr: Number,
91
92
  ortho: Boolean,
92
93
  orthographic: Boolean,
94
+ r3f: Boolean,
93
95
  rendererArguments: Object,
94
96
  rendererProperties: Object,
95
97
  shadow: [Boolean, Object],
@@ -104,6 +106,10 @@ const LunchboxWrapper = {
104
106
  let renderer;
105
107
  let camera;
106
108
  let scene;
109
+ // https://threejs.org/docs/index.html#manual/en/introduction/Color-management
110
+ if (props.r3f && THREE?.ColorManagement) {
111
+ THREE.ColorManagement.legacyMode = false;
112
+ }
107
113
  // MOUNT
108
114
  // ====================
109
115
  onMounted(() => {
@@ -124,6 +130,9 @@ const LunchboxWrapper = {
124
130
  alpha: props.transparent,
125
131
  antialias: true,
126
132
  canvas: canvas.value.domElement,
133
+ powerPreference: !!props.r3f
134
+ ? 'high-performance'
135
+ : 'default',
127
136
  ...(props.rendererArguments ?? {}),
128
137
  };
129
138
  // create new renderer
@@ -137,6 +146,15 @@ const LunchboxWrapper = {
137
146
  // we've initialized the renderer, so anything depending on it can execute now
138
147
  rendererReady.value = true;
139
148
  const rendererAsWebGlRenderer = ensureRenderer;
149
+ // apply r3f settings if desired
150
+ if (props.r3f) {
151
+ if (rendererAsWebGlRenderer.value.instance) {
152
+ rendererAsWebGlRenderer.value.instance.outputEncoding =
153
+ THREE.sRGBEncoding;
154
+ rendererAsWebGlRenderer.value.instance.toneMapping =
155
+ THREE.ACESFilmicToneMapping;
156
+ }
157
+ }
140
158
  // update render sugar
141
159
  const sugar = {
142
160
  shadow: props.shadow,
@@ -184,7 +202,12 @@ const LunchboxWrapper = {
184
202
  else {
185
203
  ensuredCamera.value = createNode({
186
204
  props: {
187
- args: props.cameraArgs ?? [45, 0.5625, 1, 1000],
205
+ args: props.cameraArgs ?? [
206
+ props.r3f ? 75 : 45,
207
+ 0.5625,
208
+ 1,
209
+ 1000,
210
+ ],
188
211
  },
189
212
  type: 'PerspectiveCamera',
190
213
  uuid: fallbackCameraUuid,
@@ -217,7 +240,7 @@ const LunchboxWrapper = {
217
240
  scene = ensuredScene.value;
218
241
  // set background color
219
242
  if (scene && scene.instance && props.background) {
220
- scene.instance.background = new Color(props.background);
243
+ scene.instance.background = new THREE.Color(props.background);
221
244
  }
222
245
  // MISC PROPERTIES
223
246
  // ====================
@@ -523,76 +546,6 @@ const isLunchboxRootNode = (node) => {
523
546
  return node.isLunchboxRootNode;
524
547
  };
525
548
 
526
- /** Create a new Lunchbox comment node. */
527
- function createCommentNode(options = {}) {
528
- const defaults = {
529
- text: options.text ?? '',
530
- };
531
- return new MiniDom.RendererCommentNode({
532
- ...defaults,
533
- ...options,
534
- metaType: 'commentMeta',
535
- });
536
- }
537
- /** Create a new DOM node. */
538
- function createDomNode(options = {}) {
539
- const domElement = document.createElement(options.type ?? '');
540
- const defaults = {
541
- domElement,
542
- };
543
- const node = new MiniDom.RendererDomNode({
544
- ...defaults,
545
- ...options,
546
- metaType: 'domMeta',
547
- });
548
- return node;
549
- }
550
- /** Create a new Lunchbox text node. */
551
- function createTextNode(options = {}) {
552
- const defaults = {
553
- text: options.text ?? '',
554
- };
555
- return new MiniDom.RendererTextNode({
556
- ...options,
557
- ...defaults,
558
- metaType: 'textMeta',
559
- });
560
- }
561
- /** Create a new Lunchbox standard node. */
562
- function createNode(options = {}, props = {}) {
563
- const defaults = {
564
- attached: options.attached ?? [],
565
- attachedArray: options.attachedArray ?? {},
566
- instance: options.instance ?? null,
567
- };
568
- const node = new MiniDom.RendererStandardNode({
569
- ...options,
570
- ...defaults,
571
- metaType: 'standardMeta',
572
- });
573
- if (node.type && !isLunchboxRootNode(node) && !node.instance) {
574
- // if (node.type.includes('Camera')) {
575
- // console.log(node.type, {
576
- // ...node.props,
577
- // ...props,
578
- // })
579
- // console.trace()
580
- // }
581
- node.instance = instantiateThreeObject({
582
- ...node,
583
- props: {
584
- ...node.props,
585
- ...props,
586
- },
587
- });
588
- }
589
- if (node.type === 'scene') {
590
- // manually set scene override
591
- ensuredScene.value = node;
592
- }
593
- return node;
594
- }
595
-
596
549
  const interactables = [];
597
550
  const addInteractable = (target) => {
598
551
  interactables.push(target);
@@ -918,17 +871,10 @@ const ensuredCamera = computed({
918
871
  overrides[pascalType] = val;
919
872
  },
920
873
  });
921
- // export const ensuredCamera = buildEnsured<THREE.Camera>(
922
- // ['PerspectiveCamera', 'OrthographicCamera'],
923
- // fallbackCameraUuid,
924
- // {
925
- // args: [45, 0.5625, 1, 1000],
926
- // }
927
- // )
928
874
  // ENSURE RENDERER
929
875
  // ====================
930
876
  const fallbackRendererUuid = 'FALLBACK_RENDERER';
931
- const v = buildEnsured(
877
+ const ensuredRenderer = buildEnsured(
932
878
  // TODO: ensure support for css/svg renderers
933
879
  ['WebGLRenderer'], //, 'CSS2DRenderer', 'CSS3DRenderer', 'SVGRenderer'],
934
880
  fallbackRendererUuid, {});
@@ -937,7 +883,7 @@ fallbackRendererUuid, {});
937
883
  const rendererReady = ref(false);
938
884
  const ensureRenderer = computed({
939
885
  get() {
940
- return (rendererReady.value ? v.value : null);
886
+ return (rendererReady.value ? ensuredRenderer.value : null);
941
887
  },
942
888
  set(val) {
943
889
  const t = val.type ?? '';
@@ -954,6 +900,80 @@ const autoRaycasterUuid = 'AUTO_RAYCASTER';
954
900
  // `unknown` is intentional here - we need to typecast the node since Raycaster isn't an Object3D
955
901
  const ensuredRaycaster = buildEnsured('Raycaster', autoRaycasterUuid, {}, (node) => setupAutoRaycaster(node));
956
902
 
903
+ /** Create a new Lunchbox comment node. */
904
+ function createCommentNode(options = {}) {
905
+ const defaults = {
906
+ text: options.text ?? '',
907
+ };
908
+ return new MiniDom.RendererCommentNode({
909
+ ...defaults,
910
+ ...options,
911
+ metaType: 'commentMeta',
912
+ });
913
+ }
914
+ /** Create a new DOM node. */
915
+ function createDomNode(options = {}) {
916
+ const domElement = document.createElement(options.type ?? '');
917
+ const defaults = {
918
+ domElement,
919
+ };
920
+ const node = new MiniDom.RendererDomNode({
921
+ ...defaults,
922
+ ...options,
923
+ metaType: 'domMeta',
924
+ });
925
+ return node;
926
+ }
927
+ /** Create a new Lunchbox text node. */
928
+ function createTextNode(options = {}) {
929
+ const defaults = {
930
+ text: options.text ?? '',
931
+ };
932
+ return new MiniDom.RendererTextNode({
933
+ ...options,
934
+ ...defaults,
935
+ metaType: 'textMeta',
936
+ });
937
+ }
938
+ /** Create a new Lunchbox standard node. */
939
+ function createNode(options = {}, props = {}) {
940
+ const defaults = {
941
+ attached: options.attached ?? [],
942
+ attachedArray: options.attachedArray ?? {},
943
+ instance: options.instance ?? null,
944
+ };
945
+ const node = new MiniDom.RendererStandardNode({
946
+ ...options,
947
+ ...defaults,
948
+ metaType: 'standardMeta',
949
+ });
950
+ if (node.type && !isLunchboxRootNode(node) && !node.instance) {
951
+ // if (node.type.includes('Camera')) {
952
+ // console.log(node.type, {
953
+ // ...node.props,
954
+ // ...props,
955
+ // })
956
+ // console.trace()
957
+ // }
958
+ node.instance = instantiateThreeObject({
959
+ ...node,
960
+ props: {
961
+ ...node.props,
962
+ ...props,
963
+ },
964
+ });
965
+ }
966
+ // TODO: these manual overrides are a bit brittle - replace?
967
+ if (node.type?.toLowerCase() === 'scene') {
968
+ // manually set scene override
969
+ ensuredScene.value = node;
970
+ }
971
+ else if (node.type?.toLowerCase().endsWith('camera')) {
972
+ ensuredCamera.value = node;
973
+ }
974
+ return node;
975
+ }
976
+
957
977
  const createComponent = (tag) => defineComponent({
958
978
  inheritAttrs: false,
959
979
  name: tag,
@@ -1710,46 +1730,31 @@ const globals = {
1710
1730
  /** The current camera. Often easier to use `useCamera` instead of this. */
1711
1731
  const camera = computed(() => ensuredCamera.value?.instance ?? null);
1712
1732
  /** Run a function using the current camera when it's present. */
1713
- function useCamera(callback, once = true) {
1714
- let destroy;
1715
- destroy = watch(camera, (newVal) => {
1733
+ function useCamera(callback) {
1734
+ return watch(camera, (newVal) => {
1716
1735
  if (!newVal)
1717
1736
  return;
1718
- // TODO: better fix than `any`?
1719
1737
  callback(newVal);
1720
- if (once) {
1721
- destroy?.();
1722
- }
1723
1738
  }, { immediate: true });
1724
1739
  }
1725
1740
  /** The current renderer. Often easier to use `useRenderer` instead of this. */
1726
1741
  const renderer = computed(() => ensureRenderer.value?.instance ?? null);
1727
1742
  /** Run a function using the current renderer when it's present. */
1728
- function useRenderer(callback, once = true) {
1729
- let destroy;
1730
- destroy = watch(renderer, (newVal) => {
1743
+ function useRenderer(callback) {
1744
+ return watch(renderer, (newVal) => {
1731
1745
  if (!newVal)
1732
1746
  return;
1733
- // TODO: better fix than `any`?
1734
1747
  callback(newVal);
1735
- if (once) {
1736
- destroy?.();
1737
- }
1738
1748
  }, { immediate: true });
1739
1749
  }
1740
1750
  /** The current scene. Often easier to use `useScene` instead of this. */
1741
1751
  const scene = computed(() => ensuredScene.value.instance);
1742
1752
  /** Run a function using the current scene when it's present. */
1743
- function useScene(callback, once = true) {
1744
- let destroy;
1745
- destroy = watch(scene, (newVal) => {
1753
+ function useScene(callback) {
1754
+ return watch(scene, (newVal) => {
1746
1755
  if (!newVal)
1747
1756
  return;
1748
- // TODO: better fix than `any`?
1749
1757
  callback(newVal);
1750
- if (once) {
1751
- destroy?.();
1752
- }
1753
1758
  }, { immediate: true });
1754
1759
  }
1755
1760
  // CUSTOM RENDER SUPPORT
@@ -1778,12 +1783,14 @@ const createApp = (root) => {
1778
1783
  app = createRenderer(nodeOps).createApp(root);
1779
1784
  // register all components
1780
1785
  Object.keys(components).forEach((key) => {
1781
- app.component(key, components[key]);
1786
+ app?.component(key, components[key]);
1782
1787
  });
1783
1788
  // update mount function to match Lunchbox.Node
1784
1789
  const { mount } = app;
1785
1790
  app.mount = (root, ...args) => {
1791
+ // find DOM element to use as app root
1786
1792
  const domElement = (typeof root === 'string' ? document.querySelector(root) : root);
1793
+ // create or find root node
1787
1794
  const rootNode = ensureRootNode({
1788
1795
  domElement,
1789
1796
  isLunchboxRootNode: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lunchboxjs",
3
- "version": "0.1.4012",
3
+ "version": "0.1.4015",
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",
@@ -11,10 +11,10 @@
11
11
  "prepare": "npm run build:lib",
12
12
  "docs:dev": "vitepress dev docs",
13
13
  "docs:build": "vitepress build docs",
14
- "docs:serve": "vitepress serve docs"
14
+ "docs:serve": "vitepress serve docs",
15
+ "demo:create": "node utils/createExample"
15
16
  },
16
17
  "dependencies": {
17
- "lodash": "4.17.21",
18
18
  "uuid": "8.3.2",
19
19
  "vue": "^3.2.16"
20
20
  },
@@ -22,13 +22,18 @@
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",
29
+ "kolorist": "1.5.1",
30
+ "lodash": "4.17.21",
29
31
  "nice-color-palettes": "3.0.0",
32
+ "prompt": "1.3.0",
33
+ "prompts": "2.4.2",
30
34
  "rollup-plugin-delete": "2.0.0",
31
35
  "rollup-plugin-terser": "7.0.2",
36
+ "three": "0.141.0",
32
37
  "typescript": "^4.4.3",
33
38
  "vite": "^2.6.4",
34
39
  "vite-plugin-glsl": "0.0.5",
@@ -36,7 +41,7 @@
36
41
  "vue-tsc": "^0.3.0"
37
42
  },
38
43
  "peerDependencies": {
39
- "three": "0.137.5"
44
+ "three": "0.141.0"
40
45
  },
41
46
  "files": [
42
47
  "dist",
@@ -24,9 +24,13 @@ import {
24
24
  } from '../../core'
25
25
  import { set } from 'lodash'
26
26
  import { globals, Lunch } from '../..'
27
- import { Color, Vector2 } from 'three'
27
+ // import { Color, Vector2, sRGBEncoding, ACESFilmicToneMapping } from 'three'
28
+ import * as THREE from 'three'
28
29
  import { prepCanvas } from './prepCanvas'
29
30
 
31
+ // TODO:
32
+ // Continue r3f prop - what else (besides camera fov) makes r3f look good?
33
+
30
34
  /** fixed & fill styling for container */
31
35
  const fillStyle = (position: string) => {
32
36
  return {
@@ -52,6 +56,7 @@ export const LunchboxWrapper: ComponentOptions = {
52
56
  dpr: Number,
53
57
  ortho: Boolean,
54
58
  orthographic: Boolean,
59
+ r3f: Boolean,
55
60
  rendererArguments: Object,
56
61
  rendererProperties: Object,
57
62
  shadow: [Boolean, Object],
@@ -67,6 +72,11 @@ export const LunchboxWrapper: ComponentOptions = {
67
72
  let camera: Lunch.Node<THREE.Camera> | null
68
73
  let scene: MiniDom.RendererStandardNode<THREE.Scene>
69
74
 
75
+ // https://threejs.org/docs/index.html#manual/en/introduction/Color-management
76
+ if (props.r3f && (THREE as any)?.ColorManagement) {
77
+ ;(THREE as any).ColorManagement.legacyMode = false
78
+ }
79
+
70
80
  // MOUNT
71
81
  // ====================
72
82
  onMounted(() => {
@@ -88,6 +98,9 @@ export const LunchboxWrapper: ComponentOptions = {
88
98
  alpha: props.transparent,
89
99
  antialias: true,
90
100
  canvas: canvas.value.domElement,
101
+ powerPreference: !!props.r3f
102
+ ? 'high-performance'
103
+ : 'default',
91
104
  ...(props.rendererArguments ?? {}),
92
105
  }
93
106
 
@@ -108,6 +121,16 @@ export const LunchboxWrapper: ComponentOptions = {
108
121
  Lunch.Node<THREE.WebGLRenderer>
109
122
  >
110
123
 
124
+ // apply r3f settings if desired
125
+ if (props.r3f) {
126
+ if (rendererAsWebGlRenderer.value.instance) {
127
+ rendererAsWebGlRenderer.value.instance.outputEncoding =
128
+ THREE.sRGBEncoding
129
+ rendererAsWebGlRenderer.value.instance.toneMapping =
130
+ THREE.ACESFilmicToneMapping
131
+ }
132
+ }
133
+
111
134
  // update render sugar
112
135
  const sugar = {
113
136
  shadow: props.shadow,
@@ -160,7 +183,12 @@ export const LunchboxWrapper: ComponentOptions = {
160
183
  } else {
161
184
  ensuredCamera.value = createNode<THREE.PerspectiveCamera>({
162
185
  props: {
163
- args: props.cameraArgs ?? [45, 0.5625, 1, 1000],
186
+ args: props.cameraArgs ?? [
187
+ props.r3f ? 75 : 45,
188
+ 0.5625,
189
+ 1,
190
+ 1000,
191
+ ],
164
192
  },
165
193
  type: 'PerspectiveCamera',
166
194
  uuid: fallbackCameraUuid,
@@ -195,7 +223,7 @@ export const LunchboxWrapper: ComponentOptions = {
195
223
  scene = ensuredScene.value
196
224
  // set background color
197
225
  if (scene && scene.instance && props.background) {
198
- scene.instance.background = new Color(props.background)
226
+ scene.instance.background = new THREE.Color(props.background)
199
227
  }
200
228
 
201
229
  // MISC PROPERTIES
@@ -1,6 +1,7 @@
1
1
  import { isLunchboxRootNode } from '../utils'
2
2
  import { instantiateThreeObject, MiniDom, ensuredScene } from '.'
3
3
  import { Lunch } from '..'
4
+ import { ensuredCamera } from './ensure'
4
5
 
5
6
  /** Create a new Lunchbox comment node. */
6
7
  export function createCommentNode(options: Partial<Lunch.CommentMeta> = {}) {
@@ -74,9 +75,12 @@ export function createNode<T extends object = THREE.Object3D>(
74
75
  })
75
76
  }
76
77
 
77
- if (node.type === 'scene') {
78
+ // TODO: these manual overrides are a bit brittle - replace?
79
+ if (node.type?.toLowerCase() === 'scene') {
78
80
  // manually set scene override
79
81
  ensuredScene.value = node as Lunch.Node<THREE.Scene>
82
+ } else if (node.type?.toLowerCase().endsWith('camera')) {
83
+ ensuredCamera.value = node as Lunch.Node<THREE.Camera>
80
84
  }
81
85
 
82
86
  return node
@@ -158,18 +158,10 @@ export const ensuredCamera = computed<Lunch.Node<THREE.Camera> | null>({
158
158
  },
159
159
  })
160
160
 
161
- // export const ensuredCamera = buildEnsured<THREE.Camera>(
162
- // ['PerspectiveCamera', 'OrthographicCamera'],
163
- // fallbackCameraUuid,
164
- // {
165
- // args: [45, 0.5625, 1, 1000],
166
- // }
167
- // )
168
-
169
161
  // ENSURE RENDERER
170
162
  // ====================
171
163
  export const fallbackRendererUuid = 'FALLBACK_RENDERER'
172
- export const v = buildEnsured(
164
+ export const ensuredRenderer = buildEnsured(
173
165
  // TODO: ensure support for css/svg renderers
174
166
  ['WebGLRenderer'], //, 'CSS2DRenderer', 'CSS3DRenderer', 'SVGRenderer'],
175
167
  fallbackRendererUuid,
@@ -181,7 +173,9 @@ export const rendererReady = ref(false)
181
173
 
182
174
  export const ensureRenderer = computed<Lunch.Node<THREE.WebGLRenderer> | null>({
183
175
  get() {
184
- return (rendererReady.value ? (v.value as any) : (null as any)) as any
176
+ return (
177
+ rendererReady.value ? (ensuredRenderer.value as any) : (null as any)
178
+ ) as any
185
179
  },
186
180
  set(val: any) {
187
181
  const t = val.type ?? ''
package/src/index.ts CHANGED
@@ -1,11 +1,4 @@
1
- import {
2
- computed,
3
- createRenderer,
4
- Component,
5
- ref,
6
- watch,
7
- WatchStopHandle,
8
- } from 'vue'
1
+ import { computed, createRenderer, Component, ref, watch } from 'vue'
9
2
  import { nodeOps } from './nodeOps'
10
3
  import {
11
4
  // createdCamera,
@@ -47,20 +40,13 @@ export const globals = {
47
40
  export const camera = computed(() => ensuredCamera.value?.instance ?? null)
48
41
  /** Run a function using the current camera when it's present. */
49
42
  export function useCamera<T extends THREE.Camera = THREE.PerspectiveCamera>(
50
- callback: (cam: T) => void,
51
- once = true
43
+ callback: (cam: T) => void
52
44
  ) {
53
- let destroy: WatchStopHandle
54
- destroy = watch(
45
+ return watch(
55
46
  camera,
56
47
  (newVal) => {
57
48
  if (!newVal) return
58
-
59
- // TODO: better fix than `any`?
60
- callback(newVal as any)
61
- if (once) {
62
- destroy?.()
63
- }
49
+ callback(newVal as unknown as T)
64
50
  },
65
51
  { immediate: true }
66
52
  )
@@ -70,20 +56,13 @@ export function useCamera<T extends THREE.Camera = THREE.PerspectiveCamera>(
70
56
  export const renderer = computed(() => ensureRenderer.value?.instance ?? null)
71
57
  /** Run a function using the current renderer when it's present. */
72
58
  export function useRenderer<T extends THREE.Renderer = THREE.WebGLRenderer>(
73
- callback: (rend: T) => void,
74
- once = true
59
+ callback: (rend: T) => void
75
60
  ) {
76
- let destroy: WatchStopHandle
77
- destroy = watch(
61
+ return watch(
78
62
  renderer,
79
63
  (newVal) => {
80
64
  if (!newVal) return
81
-
82
- // TODO: better fix than `any`?
83
- callback(newVal as any)
84
- if (once) {
85
- destroy?.()
86
- }
65
+ callback(newVal as unknown as T)
87
66
  },
88
67
  { immediate: true }
89
68
  )
@@ -92,21 +71,12 @@ export function useRenderer<T extends THREE.Renderer = THREE.WebGLRenderer>(
92
71
  /** The current scene. Often easier to use `useScene` instead of this. */
93
72
  export const scene = computed(() => ensuredScene.value.instance)
94
73
  /** Run a function using the current scene when it's present. */
95
- export function useScene(
96
- callback: (newScene: THREE.Scene) => void,
97
- once = true
98
- ) {
99
- let destroy: WatchStopHandle
100
- destroy = watch(
74
+ export function useScene(callback: (newScene: THREE.Scene) => void) {
75
+ return watch(
101
76
  scene,
102
77
  (newVal) => {
103
78
  if (!newVal) return
104
-
105
- // TODO: better fix than `any`?
106
79
  callback(newVal as any)
107
- if (once) {
108
- destroy?.()
109
- }
110
80
  },
111
81
  { immediate: true }
112
82
  )
@@ -142,15 +112,17 @@ export const createApp = (root: Component) => {
142
112
 
143
113
  // register all components
144
114
  Object.keys(components).forEach((key) => {
145
- app!.component(key, (components as any)[key])
115
+ app?.component(key, (components as any)[key])
146
116
  })
147
117
 
148
118
  // update mount function to match Lunchbox.Node
149
119
  const { mount } = app
150
120
  app.mount = (root, ...args) => {
121
+ // find DOM element to use as app root
151
122
  const domElement = (
152
123
  typeof root === 'string' ? document.querySelector(root) : root
153
124
  ) as HTMLElement
125
+ // create or find root node
154
126
  const rootNode = ensureRootNode({
155
127
  domElement,
156
128
  isLunchboxRootNode: true,
@@ -5,7 +5,6 @@ import { overrides } from '../core'
5
5
  export const remove = (node: MiniDom.RendererBaseNode) => {
6
6
  if (!node) return
7
7
  const overrideKeys = Object.keys(overrides)
8
-
9
8
  // prep subtree
10
9
  const subtree: MiniDom.BaseNode[] = []
11
10
  node.walk((descendant) => {
package/src/types.ts CHANGED
@@ -145,6 +145,7 @@ export declare namespace Lunch {
145
145
  dpr?: number
146
146
  ortho?: boolean
147
147
  orthographic?: boolean
148
+ r3f?: boolean
148
149
  // TODO: Why doesn't ConstructorParameters<THREE.WebGLRenderer> work here?
149
150
  rendererArguments?: object
150
151
  rendererProperties?: Partial<THREE.WebGLRenderer>