From cc87e699bd8d354fe5f759d507c0565ec21e62ea Mon Sep 17 00:00:00 2001 From: Kevin Levron Date: Sat, 24 Apr 2021 21:45:57 +0200 Subject: [PATCH] improve inject/provide types --- src/components/misc/Stats.js | 3 +- src/components/physics/CannonWorld.js | 8 +++- src/core/Camera.ts | 23 ++++++------ src/core/Object3D.ts | 39 ++++++++++++------- src/core/OrthographicCamera.ts | 15 +++++--- src/core/PerspectiveCamera.ts | 15 +++++--- src/core/Raycaster.ts | 24 +++++++----- src/core/Renderer.ts | 6 ++- src/core/Scene.ts | 52 +++++++++++++++----------- src/effects/BokehPass.ts | 2 + src/effects/EffectComposer.ts | 36 ++++++++++-------- src/effects/EffectPass.ts | 27 ++++++++------ src/effects/FXAAPass.ts | 4 +- src/effects/HalftonePass.ts | 2 + src/effects/RenderPass.ts | 2 + src/effects/SMAAPass.ts | 2 + src/effects/SSAOPass.ts | 2 + src/effects/TiltShiftPass.ts | 4 +- src/effects/UnrealBloomPass.ts | 2 + src/geometries/Geometry.ts | 54 +++++++++++++-------------- src/materials/Material.ts | 13 +++++-- src/materials/Texture.ts | 10 +++-- src/meshes/Image.ts | 9 +++-- src/meshes/InstancedMesh.ts | 2 + src/meshes/Mesh.ts | 16 ++++---- src/meshes/Sprite.ts | 4 +- src/meshes/Text.ts | 3 +- 27 files changed, 229 insertions(+), 150 deletions(-) diff --git a/src/components/misc/Stats.js b/src/components/misc/Stats.js index 774aad8..29ddffa 100644 --- a/src/components/misc/Stats.js +++ b/src/components/misc/Stats.js @@ -1,11 +1,12 @@ import Stats from 'stats.js' +import { RendererInjectionKey } from '../../core/Renderer' export default { props: { noSetup: { type: Boolean, default: false }, }, emits: ['created'], - inject: ['renderer'], + inject: { renderer: RendererInjectionKey }, setup({ noSetup }) { const stats = new Stats() if (!noSetup) { diff --git a/src/components/physics/CannonWorld.js b/src/components/physics/CannonWorld.js index adb0b4c..68f415e 100644 --- a/src/components/physics/CannonWorld.js +++ b/src/components/physics/CannonWorld.js @@ -1,9 +1,13 @@ import { defineComponent } from 'vue' import useCannon from './useCannon.js' -// import { bindProp } from '../../tools'; +import { RendererInjectionKey } from '../../core/Renderer' +import { SceneInjectionKey } from '../../core/Scene' export default defineComponent({ - inject: ['renderer', 'scene'], + inject: { + renderer: RendererInjectionKey, + scene: SceneInjectionKey, + }, props: { gravity: { type: Object, default: () => ({ x: 0, y: 0, z: -9.82 }) }, broadphase: { type: String }, diff --git a/src/core/Camera.ts b/src/core/Camera.ts index 164eccd..e6df469 100644 --- a/src/core/Camera.ts +++ b/src/core/Camera.ts @@ -1,21 +1,22 @@ -import { defineComponent, inject } from 'vue' -import { RendererInterface } from './Renderer' +import { defineComponent } from 'vue' +// import { Camera } from 'three' +// import { RendererInjectionKey, RendererInterface } from './Renderer' // import Object3D from './Object3D' +// export interface CameraSetupInterface { +// renderer?: RendererInterface +// camera: Camera +// } + export default defineComponent({ // TODO: eventually extend Object3D // extends: Object3D, - // don't work with typescript, bug ? - // but works in sub components (injection, not typescript) - inject: ['renderer'], + // inject: { renderer: RendererInjectionKey as symbol }, - setup() { - // this works with typescript in sub component - // but setup is not called - const renderer = inject('renderer') as RendererInterface - return { renderer } - }, + // setup(): CameraSetupInterface { + // return {} + // }, render() { return this.$slots.default ? this.$slots.default() : [] diff --git a/src/core/Object3D.ts b/src/core/Object3D.ts index edf312d..d6be10c 100644 --- a/src/core/Object3D.ts +++ b/src/core/Object3D.ts @@ -1,11 +1,12 @@ import { Object3D, Scene } from 'three' -import { ComponentPublicInstance, defineComponent, inject, PropType, watch } from 'vue' +import { ComponentPublicInstance, defineComponent, PropType, watch } from 'vue' import { bindProp } from '../tools' -import { RendererInterface } from './Renderer' +import { RendererInjectionKey, RendererInterface } from './Renderer' +import { SceneInjectionKey } from './Scene' export interface Object3DSetupInterface { - renderer: RendererInterface - scene: Scene + renderer?: RendererInterface + scene?: Scene o3d?: Object3D parent?: ComponentPublicInstance } @@ -17,11 +18,11 @@ export interface Object3DInterface extends Object3DSetupInterface { remove(o: Object3D): void } -export function object3DSetup(): Object3DSetupInterface { - const renderer = inject('renderer') as RendererInterface - const scene = inject('scene') as Scene - return { scene, renderer } -} +// export function object3DSetup(): Object3DSetupInterface { +// const renderer = inject(RendererInjectionKey) +// const scene = inject(SceneInjectionKey) +// return { scene, renderer } +// } export interface Vector2PropInterface { x?: number @@ -38,7 +39,11 @@ export interface EulerPropInterface extends Vector3PropInterface { export default defineComponent({ name: 'Object3D', - inject: ['renderer', 'scene'], + // inject for sub components + inject: { + renderer: RendererInjectionKey as symbol, + scene: SceneInjectionKey as symbol, + }, emits: ['created', 'ready'], props: { position: { type: Object as PropType, default: () => ({ x: 0, y: 0, z: 0 }) }, @@ -48,10 +53,17 @@ export default defineComponent({ autoRemove: { type: Boolean, default: true }, userData: { type: Object, default: () => ({}) }, }, - setup() { - return object3DSetup() + setup(): Object3DSetupInterface { + // return object3DSetup() + return {} }, - computed: { + created() { + if (!this.renderer) { + console.error('Missing parent Renderer') + } + if (!this.scene) { + console.error('Missing parent Scene') + } }, unmounted() { if (this.autoRemove) this.removeFromParent() @@ -67,7 +79,6 @@ export default defineComponent({ bindProp(this, 'scale', o3d) bindProp(this, 'userData', o3d.userData) - // TODO : fix lookat.x if (this.lookAt) o3d.lookAt(this.lookAt.x ?? 0, this.lookAt.y, this.lookAt.z) watch(() => this.lookAt, (v) => { o3d.lookAt(v.x ?? 0, v.y, v.z) }, { deep: true }) diff --git a/src/core/OrthographicCamera.ts b/src/core/OrthographicCamera.ts index b5cc63b..783eaa8 100644 --- a/src/core/OrthographicCamera.ts +++ b/src/core/OrthographicCamera.ts @@ -1,8 +1,9 @@ -import { defineComponent, PropType, watch } from 'vue' +import { defineComponent, inject, PropType, watch } from 'vue' import { OrthographicCamera } from 'three' import { bindProp } from '../tools' import Camera from './Camera' import { Vector3PropInterface } from './Object3D' +import { RendererInjectionKey } from './Renderer' export default defineComponent({ extends: Camera, @@ -18,7 +19,14 @@ export default defineComponent({ position: { type: Object as PropType, default: () => ({ x: 0, y: 0, z: 0 }) }, }, setup(props) { + const renderer = inject(RendererInjectionKey) + if (!renderer) { + console.error('Renderer not found') + return + } + const camera = new OrthographicCamera(props.left, props.right, props.top, props.bottom, props.near, props.far) + renderer.camera = camera bindProp(props, 'position', camera) @@ -32,10 +40,7 @@ export default defineComponent({ }) }) - return { camera } - }, - created() { - this.renderer.camera = this.camera + return { renderer, camera } }, __hmrId: 'OrthographicCamera', }) diff --git a/src/core/PerspectiveCamera.ts b/src/core/PerspectiveCamera.ts index 62f1586..5147a3f 100644 --- a/src/core/PerspectiveCamera.ts +++ b/src/core/PerspectiveCamera.ts @@ -1,8 +1,9 @@ -import { defineComponent, PropType, watch } from 'vue' +import { defineComponent, inject, PropType, watch } from 'vue' import { PerspectiveCamera } from 'three' import { bindProp } from '../tools' import Camera from './Camera' import { Vector3PropInterface } from './Object3D' +import { RendererInjectionKey } from './Renderer' export default defineComponent({ extends: Camera, @@ -16,7 +17,14 @@ export default defineComponent({ lookAt: { type: Object as PropType, default: null }, }, setup(props) { + const renderer = inject(RendererInjectionKey) + if (!renderer) { + console.error('Renderer not found') + return + } + const camera = new PerspectiveCamera(props.fov, props.aspect, props.near, props.far) + renderer.camera = camera bindProp(props, 'position', camera) @@ -33,10 +41,7 @@ export default defineComponent({ }) }) - return { camera } - }, - created() { - this.renderer.camera = this.camera + return { renderer, camera } }, __hmrId: 'PerspectiveCamera', }) diff --git a/src/core/Raycaster.ts b/src/core/Raycaster.ts index e0c3d5d..f9d9efb 100644 --- a/src/core/Raycaster.ts +++ b/src/core/Raycaster.ts @@ -1,13 +1,13 @@ import { Object3D } from 'three' import { defineComponent, inject, PropType } from 'vue' import usePointer, { IntersectObject, PointerInterface, PointerIntersectCallbackType } from './usePointer' -import { RendererInterface } from './Renderer' +import { RendererInjectionKey, RendererInterface } from './Renderer' // eslint-disable-next-line @typescript-eslint/no-empty-function const emptyCallBack: PointerIntersectCallbackType = () => {} interface RaycasterSetupInterface { - renderer: RendererInterface + renderer?: RendererInterface pointer?: PointerInterface } @@ -22,16 +22,22 @@ export default defineComponent({ intersectMode: { type: String, default: 'move' }, }, setup(): RaycasterSetupInterface { - const renderer = inject('renderer') as RendererInterface + const renderer = inject(RendererInjectionKey) return { renderer } }, mounted() { + if (!this.renderer) { + console.error('Renderer not found') + return + } + const renderer = this.renderer + this.renderer.onMounted(() => { - if (!this.renderer.camera) return + if (!renderer.camera) return this.pointer = usePointer({ - camera: this.renderer.camera, - domElement: this.renderer.canvas, + camera: renderer.camera, + domElement: renderer.canvas, intersectObjects: this.getIntersectObjects(), onIntersectEnter: this.onPointerEnter, onIntersectOver: this.onPointerOver, @@ -42,19 +48,19 @@ export default defineComponent({ this.pointer.addListeners() if (this.intersectMode === 'frame') { - this.renderer.onBeforeRender(this.pointer.intersect) + renderer.onBeforeRender(this.pointer.intersect) } }) }, unmounted() { if (this.pointer) { this.pointer.removeListeners() - this.renderer.offBeforeRender(this.pointer.intersect) + this.renderer?.offBeforeRender(this.pointer.intersect) } }, methods: { getIntersectObjects() { - if (this.renderer.scene) { + if (this.renderer && this.renderer.scene) { const children = this.renderer.scene.children.filter((c: Object3D) => ['Mesh', 'InstancedMesh'].includes(c.type)) return children as IntersectObject[] } diff --git a/src/core/Renderer.ts b/src/core/Renderer.ts index 843a484..cdc1504 100644 --- a/src/core/Renderer.ts +++ b/src/core/Renderer.ts @@ -1,7 +1,7 @@ /* eslint-disable no-use-before-define */ import { Camera, Scene, WebGLRenderer } from 'three' import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer' -import { defineComponent, PropType } from 'vue' +import { defineComponent, InjectionKey, PropType } from 'vue' import useThree, { SizeInterface, ThreeConfigInterface, ThreeInterface } from './useThree' type CallbackType = (event: T) => void @@ -86,6 +86,8 @@ export interface RendererInterface extends RendererSetupInterface { removeListener(t: T, cb: EventCallbackMap[T]): void } +export const RendererInjectionKey: InjectionKey = Symbol('Renderer') + export default defineComponent({ name: 'Renderer', props: { @@ -162,7 +164,7 @@ export default defineComponent({ }, provide() { return { - renderer: this, + [RendererInjectionKey as symbol]: this, } }, mounted() { diff --git a/src/core/Scene.ts b/src/core/Scene.ts index 4f2abd4..4ecfb33 100644 --- a/src/core/Scene.ts +++ b/src/core/Scene.ts @@ -1,33 +1,43 @@ -import { defineComponent, inject, watch } from 'vue' -import { Scene, Color, Object3D } from 'three' -import { RendererInterface } from './Renderer' +import { defineComponent, inject, InjectionKey, provide, watch } from 'vue' +import { Scene, Color, Object3D, Texture } from 'three' +import { RendererInjectionKey } from './Renderer' + +export const SceneInjectionKey: InjectionKey = Symbol('Scene') export default defineComponent({ name: 'Scene', props: { - // id: String, - background: [String, Number], + background: [String, Number, Object], }, setup(props) { - const renderer = inject('renderer') as RendererInterface + const renderer = inject(RendererInjectionKey) const scene = new Scene() - if (props.background) { - scene.background = new Color(props.background) + + if (!renderer) { + console.error('Renderer not found') + return } - watch(() => props.background, (value) => { if (scene.background instanceof Color && value) scene.background.set(value) }) - return { renderer, scene } - }, - provide() { - return { - scene: this.scene, + + renderer.scene = scene + provide(SceneInjectionKey, scene) + + const setBackground = (value: any): void => { + if (!value) return + if (typeof value === 'string' || typeof value === 'number') { + if (scene.background instanceof Color) scene.background.set(value) + else scene.background = new Color(value) + } else if (value instanceof Texture) { + scene.background = value + } } - }, - created() { - this.renderer.scene = this.scene - }, - methods: { - add(o: Object3D) { this.scene.add(o) }, - remove(o: Object3D) { this.scene.remove(o) }, + + setBackground(props.background) + watch(() => props.background, setBackground) + + const add = (o: Object3D): void => { scene.add(o) } + const remove = (o: Object3D): void => { scene.remove(o) } + + return { add, remove } }, render() { return this.$slots.default ? this.$slots.default() : [] diff --git a/src/effects/BokehPass.ts b/src/effects/BokehPass.ts index 649d8b9..ecd101b 100644 --- a/src/effects/BokehPass.ts +++ b/src/effects/BokehPass.ts @@ -12,6 +12,8 @@ export default defineComponent({ extends: EffectPass, props, created() { + if (!this.renderer) return + if (!this.renderer.scene) { console.error('Missing Scene') return diff --git a/src/effects/EffectComposer.ts b/src/effects/EffectComposer.ts index e6ea421..b615b5a 100644 --- a/src/effects/EffectComposer.ts +++ b/src/effects/EffectComposer.ts @@ -1,10 +1,10 @@ -import { defineComponent, inject } from 'vue' +import { defineComponent, inject, InjectionKey } from 'vue' import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js' import { Pass } from 'three/examples/jsm/postprocessing/Pass' -import { RendererInterface } from '../core/Renderer' +import { RendererInjectionKey, RendererInterface } from '../core/Renderer' interface EffectComposerSetupInterface { - renderer: RendererInterface + renderer?: RendererInterface composer?: EffectComposer } @@ -13,34 +13,38 @@ export interface EffectComposerInterface extends EffectComposerSetupInterface { removePass(pass: Pass): void } +export const ComposerInjectionKey: InjectionKey = Symbol('Composer') + export default defineComponent({ setup(): EffectComposerSetupInterface { - const renderer = inject('renderer') as RendererInterface - return { - renderer, - } + const renderer = inject(RendererInjectionKey) + return { renderer } }, provide() { return { - composer: this, + [ComposerInjectionKey as symbol]: this, } }, created() { + if (!this.renderer) { + console.error('Renderer not found') + return + } + const renderer = this.renderer + const composer = new EffectComposer(this.renderer.renderer) this.composer = composer this.renderer.composer = composer // this.renderer.onInit(() => { - this.renderer.addListener('init', () => { - this.renderer.renderer.autoClear = false + renderer.addListener('init', () => { + renderer.renderer.autoClear = false this.resize() - // this.renderer.onResize(this.resize) - this.renderer.addListener('resize', this.resize) + renderer.addListener('resize', this.resize) }) }, unmounted() { - // this.renderer.offResize(this.resize) - this.renderer.removeListener('resize', this.resize) + this.renderer?.removeListener('resize', this.resize) }, methods: { addPass(pass: Pass) { @@ -50,7 +54,9 @@ export default defineComponent({ this.composer?.removePass(pass) }, resize() { - this.composer?.setSize(this.renderer.size.width, this.renderer.size.height) + if (this.composer && this.renderer) { + this.composer.setSize(this.renderer.size.width, this.renderer.size.height) + } }, }, render() { diff --git a/src/effects/EffectPass.ts b/src/effects/EffectPass.ts index dcb72e8..1a4a0d7 100644 --- a/src/effects/EffectPass.ts +++ b/src/effects/EffectPass.ts @@ -1,37 +1,42 @@ import { Pass } from 'three/examples/jsm/postprocessing/Pass' -import { defineComponent, inject } from 'vue' -import { RendererInterface } from '../core/Renderer' -import { EffectComposerInterface } from './EffectComposer' +import { defineComponent } from 'vue' +import { RendererInjectionKey, RendererInterface } from '../core/Renderer' +import { ComposerInjectionKey, EffectComposerInterface } from './EffectComposer' export interface EffectSetupInterface { - renderer: RendererInterface - composer: EffectComposerInterface + renderer?: RendererInterface + composer?: EffectComposerInterface pass?: Pass } export default defineComponent({ - inject: ['renderer', 'composer'], + // inject for sub components + inject: { + renderer: RendererInjectionKey as symbol, + composer: ComposerInjectionKey as symbol, + }, emits: ['ready'], setup(): EffectSetupInterface { - const renderer = inject('renderer') as RendererInterface - const composer = inject('composer') as EffectComposerInterface - return { renderer, composer } + return {} }, created() { if (!this.composer) { console.error('Missing parent EffectComposer') } + if (!this.renderer) { + console.error('Missing parent Renderer') + } }, unmounted() { if (this.pass) { - this.composer.removePass(this.pass); + this.composer?.removePass(this.pass); (this.pass as any).dispose?.() } }, methods: { initEffectPass(pass: Pass) { this.pass = pass - this.composer.addPass(pass) + this.composer?.addPass(pass) this.$emit('ready', pass) }, }, diff --git a/src/effects/FXAAPass.ts b/src/effects/FXAAPass.ts index 9a925ec..374f6c8 100644 --- a/src/effects/FXAAPass.ts +++ b/src/effects/FXAAPass.ts @@ -10,12 +10,12 @@ export default defineComponent({ const pass = new ShaderPass(FXAAShader) // resize will be first called in renderer init - this.renderer.addListener('resize', this.resize) + this.renderer?.addListener('resize', this.resize) this.initEffectPass(pass) }, unmounted() { - this.renderer.removeListener('resize', this.resize) + this.renderer?.removeListener('resize', this.resize) }, methods: { resize({ size }: { size: SizeInterface }) { diff --git a/src/effects/HalftonePass.ts b/src/effects/HalftonePass.ts index 23c09db..00a1f6a 100644 --- a/src/effects/HalftonePass.ts +++ b/src/effects/HalftonePass.ts @@ -15,6 +15,8 @@ export default defineComponent({ extends: EffectPass, props, created() { + if (!this.renderer) return + const pass = new HalftonePass(this.renderer.size.width, this.renderer.size.height, {}) Object.keys(props).forEach(p => { diff --git a/src/effects/RenderPass.ts b/src/effects/RenderPass.ts index f9e55bd..f91d8b9 100644 --- a/src/effects/RenderPass.ts +++ b/src/effects/RenderPass.ts @@ -5,6 +5,8 @@ import EffectPass from './EffectPass' export default defineComponent({ extends: EffectPass, created() { + if (!this.renderer) return + if (!this.renderer.scene) { console.error('Missing Scene') return diff --git a/src/effects/SMAAPass.ts b/src/effects/SMAAPass.ts index 5327af7..c350e97 100644 --- a/src/effects/SMAAPass.ts +++ b/src/effects/SMAAPass.ts @@ -5,6 +5,8 @@ import EffectPass from './EffectPass' export default defineComponent({ extends: EffectPass, created() { + if (!this.renderer) return + const pass = new SMAAPass(this.renderer.size.width, this.renderer.size.height) this.initEffectPass(pass) }, diff --git a/src/effects/SSAOPass.ts b/src/effects/SSAOPass.ts index 61c6f58..70ad51e 100644 --- a/src/effects/SSAOPass.ts +++ b/src/effects/SSAOPass.ts @@ -11,6 +11,8 @@ export default defineComponent({ }, }, created() { + if (!this.renderer) return + if (!this.renderer.scene) { console.error('Missing Scene') return diff --git a/src/effects/TiltShiftPass.ts b/src/effects/TiltShiftPass.ts index e2fd033..cdebe70 100644 --- a/src/effects/TiltShiftPass.ts +++ b/src/effects/TiltShiftPass.ts @@ -26,6 +26,8 @@ export default defineComponent({ return { uniforms1: {}, uniforms2: {} } }, created() { + if (!this.composer) return + this.pass1 = new ShaderPass(TiltShift) this.pass2 = new ShaderPass(TiltShift) @@ -57,7 +59,7 @@ export default defineComponent({ this.composer.addPass(this.pass2) }, unmounted() { - if (this.pass2) this.composer.removePass(this.pass2) + if (this.composer && this.pass2) this.composer.removePass(this.pass2) }, methods: { updateFocusLine() { diff --git a/src/effects/UnrealBloomPass.ts b/src/effects/UnrealBloomPass.ts index cf83aa6..5e0d0c3 100644 --- a/src/effects/UnrealBloomPass.ts +++ b/src/effects/UnrealBloomPass.ts @@ -13,6 +13,8 @@ export default defineComponent({ extends: EffectPass, props, created() { + if (!this.renderer) return + const size = new Vector2(this.renderer.size.width, this.renderer.size.height) const pass = new UnrealBloomPass(size, this.strength, this.radius, this.threshold) diff --git a/src/geometries/Geometry.ts b/src/geometries/Geometry.ts index 28a8f68..906aa16 100644 --- a/src/geometries/Geometry.ts +++ b/src/geometries/Geometry.ts @@ -1,18 +1,18 @@ import { BufferGeometry } from 'three' import { defineComponent, inject, watch } from 'vue' -import { MeshInterface } from '../meshes/Mesh' +import { MeshInjectionKey, MeshInterface } from '../meshes/Mesh' -export interface GeometryInterface { - geometry?: BufferGeometry +export interface GeometrySetupInterface { mesh?: MeshInterface - watchProps: string[] + geometry?: BufferGeometry + watchProps?: string[] } -function defaultSetup(): GeometryInterface { - const mesh = inject('mesh') as MeshInterface - const watchProps: string[] = [] - return { mesh, watchProps } -} +// function defaultSetup(): GeometryInterface { +// const mesh = inject('mesh') as MeshInterface +// const watchProps: string[] = [] +// return { mesh, watchProps } +// } const Geometry = defineComponent({ props: { @@ -20,8 +20,12 @@ const Geometry = defineComponent({ rotateY: Number, rotateZ: Number, }, - setup() { - return defaultSetup() + // inject for sub components + inject: { + mesh: MeshInjectionKey as symbol, + }, + setup(): GeometrySetupInterface { + return {} }, created() { if (!this.mesh) { @@ -29,37 +33,31 @@ const Geometry = defineComponent({ return } - Object.entries(this.$props).forEach(e => this.watchProps.push(e[0])) - this.createGeometry() this.rotateGeometry() if (this.geometry) this.mesh.setGeometry(this.geometry) - this.addWatchers() + Object.keys(this.$props).forEach(prop => { + // @ts-ignore + watch(() => this[prop], this.refreshGeometry) + }) }, unmounted() { this.geometry?.dispose() }, methods: { createGeometry() {}, - addWatchers() { - this.watchProps.forEach(prop => { - // @ts-ignore - watch(() => this[prop], () => { - this.refreshGeometry() - }) - }) - }, rotateGeometry() { - if (this.rotateX) this.geometry?.rotateX(this.rotateX) - if (this.rotateY) this.geometry?.rotateY(this.rotateY) - if (this.rotateZ) this.geometry?.rotateZ(this.rotateZ) + if (!this.geometry) return + if (this.rotateX) this.geometry.rotateX(this.rotateX) + if (this.rotateY) this.geometry.rotateY(this.rotateY) + if (this.rotateZ) this.geometry.rotateZ(this.rotateZ) }, refreshGeometry() { const oldGeo = this.geometry this.createGeometry() this.rotateGeometry() - if (this.geometry) this.mesh?.setGeometry(this.geometry) + if (this.geometry && this.mesh) this.mesh.setGeometry(this.geometry) oldGeo?.dispose() }, }, @@ -74,8 +72,8 @@ export function geometryComponent(name, props, createGeometry) { name, extends: Geometry, props, - setup() { - return defaultSetup() + setup(): GeometrySetupInterface { + return {} }, methods: { createGeometry() { diff --git a/src/materials/Material.ts b/src/materials/Material.ts index fa5e0c7..827c239 100644 --- a/src/materials/Material.ts +++ b/src/materials/Material.ts @@ -1,6 +1,6 @@ -import { defineComponent, watch } from 'vue' +import { defineComponent, InjectionKey, watch } from 'vue' import { FrontSide, Material, Texture } from 'three' -import { MeshInterface } from '../meshes/Mesh' +import { MeshInjectionKey, MeshInterface } from '../meshes/Mesh' export interface MaterialSetupInterface { mesh?: MeshInterface @@ -13,8 +13,13 @@ export interface MaterialInterface extends MaterialSetupInterface { setTexture(texture: Texture | null, key: string): void } +export const MaterialInjectionKey: InjectionKey = Symbol('Material') + export default defineComponent({ - inject: ['mesh'], + // inject for sub components + inject: { + mesh: MeshInjectionKey as symbol, + }, props: { color: { type: [String, Number], default: '#ffffff' }, depthTest: { type: Boolean, default: true }, @@ -30,7 +35,7 @@ export default defineComponent({ }, provide() { return { - material: this, + [MaterialInjectionKey as symbol]: this, } }, created() { diff --git a/src/materials/Texture.ts b/src/materials/Texture.ts index 961c97d..12ae622 100644 --- a/src/materials/Texture.ts +++ b/src/materials/Texture.ts @@ -1,7 +1,7 @@ import { defineComponent, PropType, watch } from 'vue' -import { ClampToEdgeWrapping, LinearFilter, LinearMipmapLinearFilter, RGBAFormat, ShaderMaterial, Texture, TextureLoader, UVMapping } from 'three' +import { ClampToEdgeWrapping, LinearFilter, LinearMipmapLinearFilter, ShaderMaterial, Texture, TextureLoader, UVMapping } from 'three' import { bindProp } from '../tools' -import { MaterialInterface } from './Material' +import { MaterialInjectionKey, MaterialInterface } from './Material' export interface TexureInterface { material?: MaterialInterface @@ -9,7 +9,9 @@ export interface TexureInterface { } export default defineComponent({ - inject: ['material'], + inject: { + material: MaterialInjectionKey as symbol, + }, props: { name: { type: String, default: 'map' }, uniform: String, @@ -53,7 +55,7 @@ export default defineComponent({ if (this.texture && this.material) { this.material.setTexture(this.texture, this.name) if (this.material.material instanceof ShaderMaterial && this.uniform) { - // this.material.uniforms[this.uniform] = { value: this.texture } + (this.material as any).uniforms[this.uniform] = { value: this.texture } } } }, diff --git a/src/meshes/Image.ts b/src/meshes/Image.ts index f1a04eb..3c1d39c 100644 --- a/src/meshes/Image.ts +++ b/src/meshes/Image.ts @@ -1,7 +1,6 @@ import { defineComponent, watch } from 'vue' import { DoubleSide, MeshBasicMaterial, PlaneGeometry, Texture, TextureLoader } from 'three' import Mesh, { MeshSetupInterface } from './Mesh' -import { object3DSetup } from '../core/Object3D' interface ImageSetupInterface extends MeshSetupInterface { material?: MeshBasicMaterial @@ -20,9 +19,11 @@ export default defineComponent({ keepSize: Boolean, }, setup(): ImageSetupInterface { - return object3DSetup() + return {} }, created() { + if (!this.renderer) return + this.geometry = new PlaneGeometry(1, 1, this.widthSegments, this.heightSegments) this.material = new MeshBasicMaterial({ side: DoubleSide, map: this.loadTexture() }) @@ -37,7 +38,7 @@ export default defineComponent({ if (this.keepSize) this.renderer.onResize(this.resize) }, unmounted() { - this.renderer.offResize(this.resize) + this.renderer?.offResize(this.resize) }, methods: { loadTexture() { @@ -56,7 +57,7 @@ export default defineComponent({ this.$emit('loaded', texture) }, resize() { - if (!this.texture) return + if (!this.renderer || !this.texture) return const screen = this.renderer.size const iW = this.texture.image.width const iH = this.texture.image.height diff --git a/src/meshes/InstancedMesh.ts b/src/meshes/InstancedMesh.ts index f3e5fba..2daad89 100644 --- a/src/meshes/InstancedMesh.ts +++ b/src/meshes/InstancedMesh.ts @@ -10,6 +10,8 @@ export default defineComponent({ }, methods: { initMesh() { + if (!this.renderer) return + if (!this.geometry || !this.material) { console.error('Missing geometry and/or material') return false diff --git a/src/meshes/Mesh.ts b/src/meshes/Mesh.ts index a6b0f28..2e593a0 100644 --- a/src/meshes/Mesh.ts +++ b/src/meshes/Mesh.ts @@ -1,6 +1,6 @@ -import { ComponentPropsOptions, defineComponent, watch } from 'vue' +import { ComponentPropsOptions, defineComponent, InjectionKey, watch } from 'vue' import { BufferGeometry, Material, Mesh as TMesh } from 'three' -import Object3D, { object3DSetup, Object3DSetupInterface } from '../core/Object3D' +import Object3D, { Object3DSetupInterface } from '../core/Object3D' import { bindProp } from '../tools' export const pointerProps = { @@ -25,6 +25,8 @@ export interface MeshInterface extends MeshSetupInterface { setMaterial(m: Material): void } +export const MeshInjectionKey: InjectionKey = Symbol('Mesh') + const Mesh = defineComponent({ name: 'Mesh', extends: Object3D, @@ -34,11 +36,11 @@ const Mesh = defineComponent({ ...pointerProps, }, setup(): MeshSetupInterface { - return object3DSetup() + return {} }, provide() { return { - mesh: this, + [MeshInjectionKey as symbol]: this, } }, mounted() { @@ -60,7 +62,7 @@ const Mesh = defineComponent({ this.onPointerDown || this.onPointerUp || this.onClick) { - this.renderer.three.addIntersectObject(mesh) + if (this.renderer) this.renderer.three.addIntersectObject(mesh) } this.mesh = mesh @@ -92,7 +94,7 @@ const Mesh = defineComponent({ }, unmounted() { if (this.mesh) { - this.renderer.three?.removeIntersectObject(this.mesh) + if (this.renderer) this.renderer.three.removeIntersectObject(this.mesh) } // for predefined mesh (geometry/material are not unmounted) if (this.geometry) this.geometry.dispose() @@ -110,7 +112,7 @@ export function meshComponent(name, props, createGeometry) { extends: Mesh, props, setup(): MeshSetupInterface { - return object3DSetup() + return {} }, created() { this.createGeometry() diff --git a/src/meshes/Sprite.ts b/src/meshes/Sprite.ts index 05bb088..af1b84c 100644 --- a/src/meshes/Sprite.ts +++ b/src/meshes/Sprite.ts @@ -1,6 +1,6 @@ import { defineComponent } from 'vue' import { Sprite, SpriteMaterial, Texture, TextureLoader } from 'three' -import Object3D, { object3DSetup, Object3DSetupInterface } from '../core/Object3D' +import Object3D, { Object3DSetupInterface } from '../core/Object3D' interface SpriteSetupInterface extends Object3DSetupInterface { texture?: Texture @@ -15,7 +15,7 @@ export default defineComponent({ src: { type: String, required: true }, }, setup(): SpriteSetupInterface { - return object3DSetup() + return {} }, created() { this.texture = new TextureLoader().load(this.src, this.onLoaded) diff --git a/src/meshes/Text.ts b/src/meshes/Text.ts index 76a10bb..8b3cc77 100644 --- a/src/meshes/Text.ts +++ b/src/meshes/Text.ts @@ -1,7 +1,6 @@ import { defineComponent, watch } from 'vue' import { Font, FontLoader, TextGeometry } from 'three' import Mesh, { MeshSetupInterface } from './Mesh' -import { object3DSetup } from '../core/Object3D' interface TextSetupInterface extends MeshSetupInterface { geometry?: TextGeometry @@ -27,7 +26,7 @@ export default defineComponent({ extends: Mesh, props, setup(): TextSetupInterface { - return object3DSetup() + return {} }, created() { if (!this.fontSrc) {