Explorar o código

feat(core): v-if working on custom renderer

alvarosabu %!s(int64=2) %!d(string=hai) anos
pai
achega
e19da3a52d

+ 3 - 1
packages/tres/src/App.vue

@@ -45,6 +45,8 @@ function enter(e) {
 </script>
 
 <template>
+  <button @click="click">{{ gridVisible }}</button>
+  <p v-if="gridVisible">Soc invisible</p>
   <TresCanvas v-bind="gl">
     <TresPerspectiveCamera :args="[75, 1, 0.1, 1000]" :position="[0, 2, 7]"></TresPerspectiveCamera>
     <TresAmbientLight :color="0xffffff" :intensity="0.75" />
@@ -53,7 +55,7 @@ function enter(e) {
       <TresSphereGeometry :args="[1, 32, 16]"></TresSphereGeometry>
       <TresMeshToonMaterial color="teal"></TresMeshToonMaterial>
     </TresMesh>
-    <TresGridHelper :args="[4, 4]"></TresGridHelper>
+    <TresGridHelper v-if="gridVisible" :args="[4, 4]"></TresGridHelper>
   </TresCanvas>
 </template>
 

+ 19 - 0
packages/tres/src/components/TestRenderer.vue

@@ -0,0 +1,19 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+
+const gridVisible = ref(false)
+
+setTimeout(() => {
+  gridVisible.value = true
+}, 4000)
+</script>
+<template>
+  <TresPerspectiveCamera :args="[75, 1, 0.1, 1000]" :position="[0, 2, 7]"></TresPerspectiveCamera>
+  <TresAmbientLight :color="0xffffff" :intensity="0.75" />
+  <TresDirectionalLight :color="0xffffff" :intensity="2" :position="[-2, 2, 0]" />
+  <TresMesh :position="[0, 1, 0]">
+    <TresSphereGeometry :args="[1, 32, 16]"></TresSphereGeometry>
+    <TresMeshToonMaterial color="teal"></TresMeshToonMaterial>
+  </TresMesh>
+  <TresGridHelper v-if="gridVisible" :args="[4, 4]"></TresGridHelper>
+</template>

+ 17 - 12
packages/tres/src/components/TresCanvas.ts

@@ -1,10 +1,10 @@
-import { defineComponent, h, PropType, ref, watch } from 'vue'
+import { defineComponent, h, PropType, ref, watch, watchEffect, compile } from 'vue'
 /* eslint-disable vue/one-component-per-file */
 import * as THREE from 'three'
 import { ShadowMapType, TextureEncoding, ToneMapping, Scene } from 'three'
 import { createTres } from '/@/core/renderer'
 import { useCamera, useRenderer, useRenderLoop, useRaycaster } from '/@/composables'
-import { TresObject } from '/@/types'
+import TestRenderer from './TestRenderer.vue'
 
 export const TresCanvas = defineComponent({
   name: 'TresCanvas',
@@ -52,21 +52,23 @@ export const TresCanvas = defineComponent({
         renderer.value?.render(scene, activeCamera.value)
       })
 
-      const internal = slots.default ? slots?.default() : [] || []
+      const app = createTres(slots)
+      app.mount(scene)
 
-      const internalComponent = defineComponent({
-        __name: 'tres-wrapper',
-        __scopeId: 'data-v-tres-supreme',
-        setup() {
-          return () => internal
-        },
+      watchEffect(() => {
+        if (slots) {
+          console.log('slots', slots)
+        }
       })
 
-      const app = createTres(internalComponent)
-      app.mount(scene as unknown as TresObject)
+      console.log({
+        app,
+        scene,
+        TestRenderer,
+      })
       expose({
         scene,
-        app,
+        /*  app, */
       })
     })
 
@@ -76,6 +78,8 @@ export const TresCanvas = defineComponent({
           'div',
           {
             ref: container,
+            'data-v-app': true,
+            id: 'container',
             style: {
               position: 'relative',
               width: '100%',
@@ -106,6 +110,7 @@ export const TresCanvas = defineComponent({
                     left: 0,
                   },
                 }),
+                /* ...(slots.default ? slots?.default() : [] || []), */
               ],
             ),
           ],

+ 111 - 48
packages/tres/src/core/nodeOps.ts

@@ -6,6 +6,28 @@ import { Mesh } from 'three'
 import { useEventListener } from '@vueuse/core'
 import { TresEvent, TresObject } from '../types'
 
+const HTML_TAGS =
+  'html,body,base,head,link,meta,style,title,address,article,aside,footer,' +
+  'header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,' +
+  'figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,' +
+  'data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,' +
+  'time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,' +
+  'canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,' +
+  'th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,' +
+  'option,output,progress,select,textarea,details,dialog,menu,' +
+  'summary,template,blockquote,iframe,tfoot'
+
+export const isHTMLTag = /*#__PURE__*/ makeMap(HTML_TAGS)
+
+export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {
+  const map: Record<string, boolean> = Object.create(null)
+  const list: Array<string> = str.split(',')
+  for (let i = 0; i < list.length; i++) {
+    map[list[i]] = true
+  }
+  return expectsLowerCase ? val => !!map[val.toLowerCase()] : val => !!map[val]
+}
+
 const { logWarning } = useLogger()
 
 function hasEvents(obj: any) {
@@ -21,10 +43,26 @@ function noop(fn: string): any {
   fn
 }
 
-export const nodeOps: RendererOptions<TresObject, TresObject> = {
-  createElement(type, _isSVG, _isCustomizedBuiltIn, props) {
-    if (type === 'template') return null
-    if (type === 'div') return null
+const doc = (typeof document !== 'undefined' ? document : null) as Document
+export const svgNS = 'http://www.w3.org/2000/svg'
+
+const templateContainer = doc && /*#__PURE__*/ doc.createElement('template')
+
+let scene = null
+
+export const nodeOps: RendererOptions<any, any> = {
+  createElement(tag, isSVG, anchor, props) {
+    // Vue core
+    /* const el = isSVG ? doc.createElementNS(svgNS, tag) : doc.createElement(tag, anchor ? { anchor } : undefined)
+
+    if (tag === 'select' && props && props.multiple != null) {
+      ;(el as HTMLSelectElement).setAttribute('multiple', props.multiple)
+    }
+
+    return el */
+    // Tres
+    if (tag === 'template') return null
+    if (isHTMLTag(tag)) return null
     let instance
 
     if (props === null) {
@@ -32,9 +70,9 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
     }
 
     if (props?.arg) {
-      instance = new catalogue[type.replace('Tres', '')](...props.args)
+      instance = new catalogue[tag.replace('Tres', '')](...props.args)
     } else {
-      instance = new catalogue[type.replace('Tres', '')]()
+      instance = new catalogue[tag.replace('Tres', '')]()
     }
 
     if (instance.isCamera) {
@@ -55,22 +93,30 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
     }
 
     console.log({
-      type,
+      tag,
+
       instance,
-      threeObj: catalogue[type.replace('Tres', '')],
+      threeObj: catalogue[tag.replace('Tres', '')],
     })
 
     return instance
   },
-  insert(child, parent, beforeChild) {
+  insert(child, parent, anchor) {
+    if (scene === null && parent.isScene) scene = parent
+    if (parent === null) parent = scene
+    //vue core
+    /*  parent.insertBefore(child, anchor || null) */
     if (parent?.isObject3D && child?.isObject3D) {
-      const index = beforeChild ? parent.children.indexOf(beforeChild) : 0
+      console.log('insert', { child, parent, anchor })
+      const index = anchor ? parent.children.indexOf(anchor) : 0
       child.parent = parent
       parent.children.splice(index, 0, child)
       child.dispatchEvent({ type: 'added' })
     } else if (typeof child?.attach === 'string') {
-      child.__previousAttach = child[parent.attach]
-      parent[child.attach] = child
+      child.__previousAttach = child[parent?.attach]
+      if (parent) {
+        parent[child.attach] = child
+      }
     }
 
     const { onLoop } = useRenderLoop()
@@ -82,7 +128,7 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
     const { raycaster } = useRaycaster()
     if (child && child instanceof Mesh && hasEvents(child)) {
       onLoop(() => {
-        if (parent.children && child && raycaster) {
+        if (parent?.children && child && raycaster) {
           const intersects = raycaster.value.intersectObjects(parent.children)
 
           if (intersects.length > 0 && intersects[0].object.uuid === child.uuid) {
@@ -111,7 +157,12 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
     }
   },
   remove(node) {
-    if (!node) return
+    // Vue Core
+    const parent = node.parentNode
+    if (parent) {
+      parent.removeChild(node)
+    }
+    /* if (!node) return
     const parent = node.parent
     if (parent) {
       if (parent.isObject3D && node.isObject3D) {
@@ -126,46 +177,58 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
     node.dispose?.()
     node.traverse?.(node => {
       ;(node as TresObject).dispose?.()
-    })
+    }) */
   },
   patchProp(node, prop, prevValue, nextValue) {
-    let root = node
-    let key = prop
-    let target = root[key]
-
-    // Traverse pierced props (e.g. foo-bar=value => foo.bar = value)
-    /* if (key.includes('-')) {
-      const chain = key.split('-')
-      target = chain.reduce((acc, key) => acc[key], root)
-      key = chain.pop() as string
-
-      if (!target?.set) root = chain.reduce((acc, key) => acc[key], root)
-    } */
-
-    const value = nextValue
-    /*   try {
-      const num = parseFloat(value)
-      value = isNaN(num) ? value : num
-    } catch (_) {} */
-
-    // Set prop, prefer atomic methods if applicable
-    if (!target?.set) root[key] = value
-    else if (target.constructor === value.constructor && target?.copy) target?.copy(value)
-    else if (Array.isArray(value)) target.set(...value)
-    else if (!target.isColor && target.setScalar) target.setScalar(value)
-    else target.set(value)
+    if (node) {
+      let root = node
+      let key = prop
+      let target = root?.[key]
+      // Traverse pierced props (e.g. foo-bar=value => foo.bar = value)
+      /* if (key.includes('-')) {
+        const chain = key.split('-')
+        target = chain.reduce((acc, key) => acc[key], root)
+        key = chain.pop() as string
+  
+        if (!target?.set) root = chain.reduce((acc, key) => acc[key], root)
+      } */
+      const value = nextValue
+      /*   try {
+        const num = parseFloat(value)
+        value = isNaN(num) ? value : num
+      } catch (_) {} */
+      // Set prop, prefer atomic methods if applicable
+      if (!target?.set) root[key] = value
+      else if (target.constructor === value.constructor && target?.copy) target?.copy(value)
+      else if (Array.isArray(value)) target.set(...value)
+      else if (!target.isColor && target.setScalar) target.setScalar(value)
+      else target.set(value)
+    }
   },
 
   parentNode(node) {
-    return node?.parent || null
+    // Vue core
+    return node.parentNode as Element | null
+    /*  return node?.parent || null */
+  },
+  createText: text => doc.createTextNode(text),
+
+  createComment: text => doc.createComment(text),
+
+  setText: (node, text) => {
+    node.nodeValue = text
+  },
+
+  setElementText: (el, text) => {
+    el.textContent = text
+  },
+  nextSibling: node => node.nextSibling,
+
+  querySelector: selector => doc.querySelector(selector),
+
+  setScopeId(el, id) {
+    /* el.setAttribute(id, '') */
   },
-  createText: () => noop('createText'),
-  createComment: () => noop('createComment'),
-  setText: () => noop('setText'),
-  setElementText: () => noop('setElementText'),
-  nextSibling: () => noop('nextSibling'),
-  querySelector: () => noop('querySelector'),
-  setScopeId: () => noop('setScopeId'),
   cloneNode: () => noop('cloneNode'),
   insertStaticContent: () => noop('insertStaticContent'),
 

+ 31 - 4
packages/tres/src/core/renderer.ts

@@ -1,15 +1,42 @@
+import { isString } from '@vueuse/core'
 import * as THREE from 'three'
 
 import { createRenderer } from 'vue'
 import { extend } from './catalogue'
 import { nodeOps } from './nodeOps'
 
+function normalizeContainer(container: Element | ShadowRoot | string): Element | null {
+  if (isString(container)) {
+    const res = document.querySelector(container)
+    /*  if (__DEV__ && !res) {
+      console.warn(`Failed to mount app: mount target selector "${container}" returned null.`)
+    } */
+    return res
+  }
+  /* if (__DEV__ && window.ShadowRoot && container instanceof window.ShadowRoot && container.mode === 'closed') {
+    console.warn(`mounting on a ShadowRoot with \`{mode: "closed"}\` may lead to unpredictable bugs`)
+  } */
+  return container as any
+}
+
 export const { createApp } = createRenderer(nodeOps)
 
-export const createTres = (...args) => {
-  const app = createApp(...args)
-  /*  const { mount } = app
-  app.mount = (container: Element | string) => {} */
+export const createTres = slots => {
+  const app = createApp(internalFnComponent)
+  function internalFnComponent() {
+    return slots.default()
+  }
+
+  const { mount } = app
+  app.mount = containerOrSelector => {
+    const container = normalizeContainer(containerOrSelector)
+    if (!container) return
+    if (container instanceof Element) {
+      container.removeAttribute('v-cloak')
+      container.setAttribute('data-v-app', '')
+    }
+    mount(container, false, false)
+  }
 
   return app
 }

+ 2 - 0
packages/tres/src/main.ts

@@ -6,3 +6,5 @@ import './style.css'
 export const app = createApp(App)
 
 app.mount('#app')
+
+console.log(app)

+ 1 - 0
packages/tres/vite.config.ts

@@ -25,6 +25,7 @@ export default defineConfig({
   resolve: {
     alias: {
       '/@': resolve(__dirname, './src'),
+      vue: 'vue/dist/vue.esm-bundler.js',
     },
     dedupe: ['@tresjs/cientos'],
   },