ソースを参照

Merge pull request #193 from Tresjs/feature/188-v-if-on-components

feat(core): 188 v-if on components
Tino Koch 2 年 前
コミット
e065115229

+ 1 - 0
playground/components.d.ts

@@ -23,6 +23,7 @@ declare module '@vue/runtime-core' {
     TestSphere: typeof import('./src/components/TestSphere.vue')['default']
     Text3D: typeof import('./src/components/Text3D.vue')['default']
     TheBasic: typeof import('./src/components/TheBasic.vue')['default']
+    TheConditional: typeof import('./src/components/TheConditional.vue')['default']
     TheEnvironment: typeof import('./src/components/TheEnvironment.vue')['default']
     TheEvents: typeof import('./src/components/TheEvents.vue')['default']
     TheExperience: typeof import('./src/components/TheExperience.vue')['default']

+ 53 - 0
playground/src/components/TheConditional.vue

@@ -0,0 +1,53 @@
+<script setup lang="ts">
+import { BasicShadowMap, MeshPhongMaterial, NoToneMapping, sRGBEncoding } from 'three'
+import { reactive } from 'vue'
+import { OrbitControls, useTweakPane } from '@tresjs/cientos'
+import { TresCanvas } from '/@/'
+
+const state = reactive({
+  clearColor: '#201919',
+  shadows: true,
+  alpha: false,
+  shadowMapType: BasicShadowMap,
+  outputEncoding: sRGBEncoding,
+  toneMapping: NoToneMapping,
+})
+
+const paneElements = reactive({
+  boxVisible: true,
+  groupVisible: true,
+  boxPropMaterialVisible: true,
+})
+
+const { pane } = useTweakPane()
+
+pane.addInput(paneElements, 'boxVisible')
+pane.addInput(paneElements, 'groupVisible')
+pane.addInput(paneElements, 'boxPropMaterialVisible')
+
+const material = new MeshPhongMaterial({ color: '#ff0000' })
+</script>
+
+<template>
+  <TresCanvas v-bind="state">
+    <TresPerspectiveCamera :position="[11, 11, 11]" :fov="45" :near="0.1" :far="1000" :look-at="[-8, 3, -3]" />
+    <TresDirectionalLight :position="[0, 8, 4]" :intensity="0.2" cast-shadow />
+    <TresMesh v-if="paneElements.boxPropMaterialVisible" :position="[0, 0, 0]" :material="material">
+      <TresBoxGeometry :args="[1, 1, 1]" />
+    </TresMesh>
+    <TresMesh v-if="paneElements.boxVisible" :position="[4, 0, 0]">
+      <TresBoxGeometry :args="[1, 1, 1]" />
+      <TresMeshToonMaterial color="#efefef" />
+    </TresMesh>
+    <TresGroup v-if="paneElements.groupVisible" :position="[0, -4, -5]">
+      <TresGroup>
+        <TresMesh :position="[0, 0, 0]">
+          <TresBoxGeometry :args="[1, 1, 1]" />
+          <TresMeshBasicMaterial color="#efef11" />
+        </TresMesh>
+      </TresGroup>
+    </TresGroup>
+    <OrbitControls></OrbitControls>
+    <TresAmbientLight :intensity="0.5" />
+  </TresCanvas>
+</template>

+ 37 - 3
src/core/nodeOps.ts

@@ -6,6 +6,8 @@ import { isFunction } from '@vueuse/core'
 import { TresObject } from '../types'
 import { isHTMLTag, kebabToCamel } from '../utils'
 
+import type { Object3D, Material, BufferGeometry } from 'three'
+
 const onRE = /^on[^a-z]/
 export const isOn = (key: string) => onRE.test(key)
 
@@ -15,6 +17,11 @@ function noop(fn: string): any {
 
 let scene: TresObject | null = null
 
+const OBJECT_3D_USER_DATA_KEYS = {
+  GEOMETRY_VIA_PROP: 'tres__geometryViaProp',
+  MATERIAL_VIA_PROP: 'tres__materialViaProp',
+}
+
 export const nodeOps: RendererOptions<TresObject, TresObject> = {
   createElement(tag, _isSVG, _anchor, props) {
     if (tag === 'template') return null
@@ -45,6 +52,15 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
       else if (instance.isBufferGeometry) instance.attach = 'geometry'
     }
 
+    // determine whether the material was passed via prop to
+    // prevent it's disposal when node is removed later in it's lifecycle
+    const { GEOMETRY_VIA_PROP, MATERIAL_VIA_PROP } = OBJECT_3D_USER_DATA_KEYS
+
+    if (instance.isObject3D) {
+      if (props?.material?.isMaterial) (instance as Object3D).userData[MATERIAL_VIA_PROP] = true
+      if (props?.geometry?.isBufferGeometry) (instance as Object3D).userData[GEOMETRY_VIA_PROP] = true
+    }
+
     instance.events = {}
 
     return instance
@@ -68,10 +84,28 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
   },
   remove(node) {
     if (!node) return
-    const parent = node.parentNode
-    if (parent) {
-      parent.removeChild(node)
+
+    // remove is only called on the node being removed and not on child nodes.
+
+    if (node.isObject3D) {
+      const object3D = node as unknown as Object3D
+
+      const disposeMaterialsAndGeometries = (object3D: Object3D) => {
+        const { GEOMETRY_VIA_PROP, MATERIAL_VIA_PROP } = OBJECT_3D_USER_DATA_KEYS
+
+        if (!object3D.userData[MATERIAL_VIA_PROP]) (object3D as Object3D & { material: Material }).material?.dispose()
+        if (!object3D.userData[GEOMETRY_VIA_PROP])
+          (object3D as Object3D & { geometry: BufferGeometry }).geometry?.dispose()
+      }
+
+      object3D.traverse(child => disposeMaterialsAndGeometries(child))
+
+      disposeMaterialsAndGeometries(object3D)
     }
+
+    node.removeFromParent?.()
+
+    node.dispose?.()
   },
   patchProp(node, prop, _prevValue, nextValue) {
     if (node) {

+ 7 - 6
src/core/nodeOpts.test.ts

@@ -68,7 +68,7 @@ describe('nodeOps', () => {
     expect(consoleWarnSpy).toHaveBeenCalled()
   })
 
-  it('createElement should add attach material propety if instance is a material', () => {
+  it('createElement should add attach material property if instance is a material', () => {
     // Setup
     const tag = 'TresMeshStandardMaterial'
     const props = { args: [] }
@@ -81,7 +81,7 @@ describe('nodeOps', () => {
     expect(instance.attach).toBe('material')
   })
 
-  it('createElement should add attach geometry propety if instance is a geometry', () => {
+  it('createElement should add attach geometry property if instance is a geometry', () => {
     // Setup
     const tag = 'TresTorusGeometry'
     const props = { args: [] }
@@ -108,15 +108,16 @@ describe('nodeOps', () => {
 
   it('remove: removes child from parent', async () => {
     // Setup
-    const parent: TresObject = new Scene()
-    const child: TresObject = new Mesh()
-    parent.children.push(child)
+    const parent = new Scene() as unknown as TresObject
+    const child = new Mesh() as unknown as TresObject
+
+    nodeOps.insert(child, parent)
 
     // Test
     nodeOps.remove(child)
 
     // Assert
-    expect(!parent.children.includes(child))
+    expect(!parent.children.includes(child)).toBeTruthy()
   })
 
   it('patchProp should patch property of node', async () => {