Răsfoiți Sursa

feat(core): made custom renderer traverse the scene tree to dispose obsolete materials and geometries

Tino Koch 2 ani în urmă
părinte
comite
cae21b1aa3
3 a modificat fișierele cu 40 adăugiri și 24 ștergeri
  1. 17 12
      playground/src/components/TheConditional.vue
  2. 21 10
      src/core/nodeOps.ts
  3. 2 2
      src/core/nodeOpts.test.ts

+ 17 - 12
playground/src/components/TheConditional.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { BasicShadowMap, MeshToonMaterial, NoToneMapping, sRGBEncoding } from 'three'
+import { BasicShadowMap, MeshPhongMaterial, NoToneMapping, sRGBEncoding } from 'three'
 import { reactive } from 'vue'
 import { OrbitControls, useTweakPane } from '@tresjs/cientos'
 import { TresCanvas } from '/@/'
@@ -16,32 +16,37 @@ const state = reactive({
 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 MeshToonMaterial({ color: '#ff0000' })
+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.boxVisible" :position="[0, 0, 0]" :material="material">
+    <TresMesh v-if="paneElements.boxPropMaterialVisible" :position="[0, 0, 0]" :material="material">
       <TresBoxGeometry :args="[1, 1, 1]" />
-      <!-- <TresMeshToonMaterial color="#efefef" /> -->
     </TresMesh>
-
-    <!-- <TresGroup v-if="paneElements.groupVisible" :position="[0, -4, -5]">
-      <TresMesh :position="[0, 0, 0]">
-        <TresBoxGeometry :args="[1, 1, 1]" />
-        <TresMeshToonMaterial color="#efef11" />
-      </TresMesh>
-    </TresGroup> -->
+    <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>

+ 21 - 10
src/core/nodeOps.ts

@@ -1,4 +1,4 @@
-import { BufferAttribute, Mesh, Object3D } from 'three'
+import { BufferAttribute, BufferGeometry, Material, Mesh, Object3D } from 'three'
 import { useCamera, useRaycaster, useRenderLoop, useLogger } from '/@/composables'
 import { RendererOptions } from 'vue'
 import { catalogue } from './catalogue'
@@ -63,8 +63,10 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
     // 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 (props?.material?.isMaterial) (instance as Object3D).userData[MATERIAL_VIA_PROP] = true
-    if (props?.geometry?.isBufferGeometry) (instance as Object3D).userData[GEOMETRY_VIA_PROP] = true
+    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
+    }
 
     return instance
   },
@@ -131,17 +133,26 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
       parent.removeChild(node)
     }
 
-    node.removeFromParent?.()
+    // remove is only called on the node being removed and not on child nodes.
 
-    // TODO how to handle groups?
-    const { GEOMETRY_VIA_PROP, MATERIAL_VIA_PROP } = OBJECT_3D_USER_DATA_KEYS
+    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 (!node.userData[MATERIAL_VIA_PROP]) node.material?.dispose()
-    if (!node.userData[GEOMETRY_VIA_PROP]) node.geometry?.dispose()
+        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))
 
-    //TODO traverse children and dispose them too
+      disposeMaterialsAndGeometries(object3D)
+    }
 
-    node.dispose?.()
+    node.removeFromParent?.()
+    node.dispose?.() // TODO is dispose ever set?
   },
   patchProp(node, prop, _prevValue, nextValue) {
     if (node) {

+ 2 - 2
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: [] }