From 79d8a762da6b6e23771a20314f7902eff4635acf Mon Sep 17 00:00:00 2001 From: Alvaro Saburido Date: Wed, 21 Feb 2024 21:47:00 +0100 Subject: [PATCH] feat: 503 conditional rendering of primitives (#514) * feat(nodeOps): switch instance logic for reactive `object` prop * chore: playground primitives with models * chore: fix linter * chore: fix tests and linters, primitive object is now reactive * chore: refactor instance swaping logic to overwrite set and copy properties * chore: tests * chore: remove console.log * chore: remove unused import watch * feat: add primitive conditional to patch object prop --- playground/components.d.ts | 1 + playground/src/components/DynamicModel.vue | 21 +++ playground/src/components/TheExperience.vue | 31 +++-- playground/src/pages/primitives.vue | 135 ++++++++++++++++++++ playground/src/router.ts | 5 + pnpm-lock.yaml | 28 ++-- src/core/nodeOps.ts | 40 +++++- 7 files changed, 225 insertions(+), 36 deletions(-) create mode 100644 playground/src/components/DynamicModel.vue create mode 100644 playground/src/pages/primitives.vue diff --git a/playground/components.d.ts b/playground/components.d.ts index 33c0bcd9c..2040947f7 100644 --- a/playground/components.d.ts +++ b/playground/components.d.ts @@ -14,6 +14,7 @@ declare module 'vue' { DanielTest: typeof import('./src/components/DanielTest.vue')['default'] DebugUI: typeof import('./src/components/DebugUI.vue')['default'] DeleteMe: typeof import('./src/components/DeleteMe.vue')['default'] + DynamicModel: typeof import('./src/components/DynamicModel.vue')['default'] FBXModels: typeof import('./src/components/FBXModels.vue')['default'] Gltf: typeof import('./src/components/gltf/index.vue')['default'] LocalOrbitControls: typeof import('./src/components/LocalOrbitControls.vue')['default'] diff --git a/playground/src/components/DynamicModel.vue b/playground/src/components/DynamicModel.vue new file mode 100644 index 000000000..18f3f5590 --- /dev/null +++ b/playground/src/components/DynamicModel.vue @@ -0,0 +1,21 @@ + + + \ No newline at end of file diff --git a/playground/src/components/TheExperience.vue b/playground/src/components/TheExperience.vue index 1e5192143..891e5ed6e 100644 --- a/playground/src/components/TheExperience.vue +++ b/playground/src/components/TheExperience.vue @@ -3,8 +3,8 @@ import { ref, watchEffect } from 'vue' import { TresCanvas } from '@tresjs/core' import { OrbitControls } from '@tresjs/cientos' import { TresLeches, useControls } from '@tresjs/leches' -import '@tresjs/leches/styles' import TheSphere from './TheSphere.vue' +import '@tresjs/leches/styles' const gl = { clearColor: '#82DBC5', @@ -12,17 +12,14 @@ const gl = { } const wireframe = ref(true) - -const canvas = ref() -const meshRef = ref() - const { isVisible } = useControls({ isVisible: true, }) +const canvas = ref() watchEffect(() => { - if (meshRef.value) { - console.log(meshRef.value) + if (canvas.value) { + console.log(canvas.value.context) } }) @@ -39,16 +36,21 @@ watchEffect(() => { :look-at="[0, 4, 0]" /> + @@ -59,23 +61,20 @@ watchEffect(() => { /> - - + + - + + diff --git a/playground/src/pages/primitives.vue b/playground/src/pages/primitives.vue new file mode 100644 index 000000000..086d4d868 --- /dev/null +++ b/playground/src/pages/primitives.vue @@ -0,0 +1,135 @@ + + + diff --git a/playground/src/router.ts b/playground/src/router.ts index 06409bf1d..29e8f29a2 100644 --- a/playground/src/router.ts +++ b/playground/src/router.ts @@ -81,6 +81,11 @@ const routes = [ name: 'Perf', component: () => import('./pages/perf/index.vue'), }, + { + path: '/primitives', + name: 'Primitives', + component: () => import('./pages/primitives.vue'), + }, { path: '/rendering-modes', name: 'Rendering Modes', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fba73b6ec..238e02ae8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -89,7 +89,7 @@ importers: version: 0.160.1 unocss: specifier: ^0.58.3 - version: 0.58.4(postcss@8.4.32)(vite@5.0.10) + version: 0.58.4(postcss@8.4.33)(vite@5.0.10) unplugin: specifier: ^1.6.0 version: 1.6.0 @@ -116,7 +116,7 @@ importers: version: 5.1.0(vue@3.4.15) vitepress: specifier: 1.0.0-rc.34 - version: 1.0.0-rc.34(@algolia/client-search@4.22.0)(postcss@8.4.32)(search-insights@2.13.0)(typescript@5.3.3) + version: 1.0.0-rc.34(@algolia/client-search@4.22.0)(postcss@8.4.33)(search-insights@2.13.0)(typescript@5.3.3) vitest: specifier: ^1.1.1 version: 1.2.2(@vitest/ui@1.2.2)(jsdom@23.0.1) @@ -135,7 +135,7 @@ importers: devDependencies: unocss: specifier: ^0.58.3 - version: 0.58.4(postcss@8.4.32)(vite@5.0.10) + version: 0.58.4(postcss@8.4.33)(vite@5.0.10) vite-svg-loader: specifier: ^5.1.0 version: 5.1.0(vue@3.4.15) @@ -2119,7 +2119,7 @@ packages: sirv: 2.0.4 dev: true - /@unocss/postcss@0.58.4(postcss@8.4.32): + /@unocss/postcss@0.58.4(postcss@8.4.33): resolution: {integrity: sha512-pg2qCGakV1TyMApPdvuvqqmPDhgogPWF14J97BT5zIfGYITAJSmBsm7d3+06w6EuqIS+vcYRw+qCV3oX6qTeiA==} engines: {node: '>=14'} peerDependencies: @@ -2131,7 +2131,7 @@ packages: css-tree: 2.3.1 fast-glob: 3.3.2 magic-string: 0.30.5 - postcss: 8.4.32 + postcss: 8.4.33 dev: true /@unocss/preset-attributify@0.58.4: @@ -2927,7 +2927,7 @@ packages: resolution: {integrity: sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==} engines: {node: '>= 10.0.0'} dependencies: - '@babel/types': 7.23.6 + '@babel/types': 7.23.9 dev: true /balanced-match@1.0.2: @@ -3395,8 +3395,8 @@ packages: /constantinople@4.0.1: resolution: {integrity: sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==} dependencies: - '@babel/parser': 7.23.6 - '@babel/types': 7.23.6 + '@babel/parser': 7.23.9 + '@babel/types': 7.23.9 dev: true /conventional-changelog-angular@7.0.0: @@ -7919,7 +7919,7 @@ packages: engines: {node: '>= 10.0.0'} dev: true - /unocss@0.58.4(postcss@8.4.32)(vite@5.0.10): + /unocss@0.58.4(postcss@8.4.33)(vite@5.0.10): resolution: {integrity: sha512-JYeQddAIObJPr6nuxahOgku0MIzjIaQ2P73KtJr0zSuzx6kiq20jf67FgDIOP1Ks6s7iJd7Ga3yuY2h49XjDjg==} engines: {node: '>=14'} peerDependencies: @@ -7935,7 +7935,7 @@ packages: '@unocss/cli': 0.58.4 '@unocss/core': 0.58.4 '@unocss/extractor-arbitrary-variants': 0.58.4 - '@unocss/postcss': 0.58.4(postcss@8.4.32) + '@unocss/postcss': 0.58.4(postcss@8.4.33) '@unocss/preset-attributify': 0.58.4 '@unocss/preset-icons': 0.58.4 '@unocss/preset-mini': 0.58.4 @@ -8337,7 +8337,7 @@ packages: fsevents: 2.3.3 dev: true - /vitepress@1.0.0-rc.34(@algolia/client-search@4.22.0)(postcss@8.4.32)(search-insights@2.13.0)(typescript@5.3.3): + /vitepress@1.0.0-rc.34(@algolia/client-search@4.22.0)(postcss@8.4.33)(search-insights@2.13.0)(typescript@5.3.3): resolution: {integrity: sha512-TUbTiSdAZFni2XlHlpx61KikgkQ5uG4Wtmw2R0SXhIOG6qGqzDJczAFjkMc4i45I9c3KyatwOYe8oEfCnzVYwQ==} hasBin: true peerDependencies: @@ -8360,7 +8360,7 @@ packages: mark.js: 8.11.1 minisearch: 6.3.0 mrmime: 2.0.0 - postcss: 8.4.32 + postcss: 8.4.33 shikiji: 0.9.19 shikiji-core: 0.9.19 shikiji-transformers: 0.9.19 @@ -8660,8 +8660,8 @@ packages: resolution: {integrity: sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==} engines: {node: '>= 10.0.0'} dependencies: - '@babel/parser': 7.23.6 - '@babel/types': 7.23.6 + '@babel/parser': 7.23.9 + '@babel/types': 7.23.9 assert-never: 1.2.1 babel-walk: 3.0.0-canary-5 dev: true diff --git a/src/core/nodeOps.ts b/src/core/nodeOps.ts index c1def0f9a..d16f80192 100644 --- a/src/core/nodeOps.ts +++ b/src/core/nodeOps.ts @@ -1,4 +1,4 @@ -import type { RendererOptions } from 'vue' +import { type RendererOptions } from 'vue' import { BufferAttribute } from 'three' import { isFunction } from '@alvarosabu/utils' import type { Object3D, Camera } from 'three' @@ -49,7 +49,7 @@ export const nodeOps: RendererOptions = { if (props?.object === undefined) logError('Tres primitives need a prop \'object\'') const object = props.object as TresObject name = object.type - instance = Object.assign(object, { type: name, attach: props.attach }) + instance = Object.assign(object.clone(), { type: name }) as TresObject } else { const target = catalogue.value[name] @@ -136,6 +136,8 @@ export const nodeOps: RendererOptions = { if (!node) return const ctx = node.__tres // remove is only called on the node being removed and not on child nodes. + node.parent = node.parent || scene + const { deregisterObjectAtPointerEventHandler, deregisterBlockingObjectAtPointerEventHandler, @@ -180,15 +182,41 @@ export const nodeOps: RendererOptions = { disposeMaterialsAndGeometries(node) deregisterCameraIfRequired(node as Object3D) deregisterAtPointerEventHandlerIfRequired?.(node as TresObject) + invalidateInstance(node as TresObject) + node.dispose?.() + } - invalidateInstance(node as TresObject) - node.dispose?.() }, - patchProp(node, prop, _prevValue, nextValue) { + patchProp(node, prop, prevValue, nextValue) { if (node) { let root = node let key = prop + if (node.__tres.primitive && key === 'object' && prevValue !== null) { + // If the prop 'object' is changed, we need to re-instance the object and swap the old one with the new one + const newInstance = nodeOps.createElement('primitive', undefined, undefined, { + object: nextValue, + }) + for (const subkey in newInstance) { + if (subkey === 'uuid') continue + const target = node[subkey] + const value = newInstance[subkey] + if (!target?.set && !isFunction(target)) node[subkey] = 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) + } + newInstance.__tres.root = scene?.__tres.root + // This code is needed to handle the case where the prop 'object' type change from a group to a mesh or vice versa, otherwise the object will not be rendered correctly (models will be invisible) + if (newInstance.isGroup) { + node.geometry = undefined + node.material = undefined + } + else { + delete node.isGroup + } + } if (node.__tres.root) { const { @@ -211,7 +239,7 @@ export const nodeOps: RendererOptions = { if (key === 'args') { const prevNode = node as TresObject3D - const prevArgs = _prevValue ?? [] + const prevArgs = prevValue ?? [] const args = nextValue ?? [] const instanceName = node.__tres.type || node.type