1
0
mirror of https://github.com/troisjs/trois.git synced 2024-11-24 04:12:02 +08:00

improve inject/provide types

This commit is contained in:
Kevin Levron 2021-04-24 21:45:57 +02:00
parent 9c41f1db6d
commit cc87e699bd
27 changed files with 229 additions and 150 deletions

View File

@ -1,11 +1,12 @@
import Stats from 'stats.js' import Stats from 'stats.js'
import { RendererInjectionKey } from '../../core/Renderer'
export default { export default {
props: { props: {
noSetup: { type: Boolean, default: false }, noSetup: { type: Boolean, default: false },
}, },
emits: ['created'], emits: ['created'],
inject: ['renderer'], inject: { renderer: RendererInjectionKey },
setup({ noSetup }) { setup({ noSetup }) {
const stats = new Stats() const stats = new Stats()
if (!noSetup) { if (!noSetup) {

View File

@ -1,9 +1,13 @@
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import useCannon from './useCannon.js' import useCannon from './useCannon.js'
// import { bindProp } from '../../tools'; import { RendererInjectionKey } from '../../core/Renderer'
import { SceneInjectionKey } from '../../core/Scene'
export default defineComponent({ export default defineComponent({
inject: ['renderer', 'scene'], inject: {
renderer: RendererInjectionKey,
scene: SceneInjectionKey,
},
props: { props: {
gravity: { type: Object, default: () => ({ x: 0, y: 0, z: -9.82 }) }, gravity: { type: Object, default: () => ({ x: 0, y: 0, z: -9.82 }) },
broadphase: { type: String }, broadphase: { type: String },

View File

@ -1,21 +1,22 @@
import { defineComponent, inject } from 'vue' import { defineComponent } from 'vue'
import { RendererInterface } from './Renderer' // import { Camera } from 'three'
// import { RendererInjectionKey, RendererInterface } from './Renderer'
// import Object3D from './Object3D' // import Object3D from './Object3D'
// export interface CameraSetupInterface {
// renderer?: RendererInterface
// camera: Camera
// }
export default defineComponent({ export default defineComponent({
// TODO: eventually extend Object3D // TODO: eventually extend Object3D
// extends: Object3D, // extends: Object3D,
// don't work with typescript, bug ? // inject: { renderer: RendererInjectionKey as symbol },
// but works in sub components (injection, not typescript)
inject: ['renderer'],
setup() { // setup(): CameraSetupInterface {
// this works with typescript in sub component // return {}
// but setup is not called // },
const renderer = inject('renderer') as RendererInterface
return { renderer }
},
render() { render() {
return this.$slots.default ? this.$slots.default() : [] return this.$slots.default ? this.$slots.default() : []

View File

@ -1,11 +1,12 @@
import { Object3D, Scene } from 'three' 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 { bindProp } from '../tools'
import { RendererInterface } from './Renderer' import { RendererInjectionKey, RendererInterface } from './Renderer'
import { SceneInjectionKey } from './Scene'
export interface Object3DSetupInterface { export interface Object3DSetupInterface {
renderer: RendererInterface renderer?: RendererInterface
scene: Scene scene?: Scene
o3d?: Object3D o3d?: Object3D
parent?: ComponentPublicInstance parent?: ComponentPublicInstance
} }
@ -17,11 +18,11 @@ export interface Object3DInterface extends Object3DSetupInterface {
remove(o: Object3D): void remove(o: Object3D): void
} }
export function object3DSetup(): Object3DSetupInterface { // export function object3DSetup(): Object3DSetupInterface {
const renderer = inject('renderer') as RendererInterface // const renderer = inject(RendererInjectionKey)
const scene = inject('scene') as Scene // const scene = inject(SceneInjectionKey)
return { scene, renderer } // return { scene, renderer }
} // }
export interface Vector2PropInterface { export interface Vector2PropInterface {
x?: number x?: number
@ -38,7 +39,11 @@ export interface EulerPropInterface extends Vector3PropInterface {
export default defineComponent({ export default defineComponent({
name: 'Object3D', name: 'Object3D',
inject: ['renderer', 'scene'], // inject for sub components
inject: {
renderer: RendererInjectionKey as symbol,
scene: SceneInjectionKey as symbol,
},
emits: ['created', 'ready'], emits: ['created', 'ready'],
props: { props: {
position: { type: Object as PropType<Vector3PropInterface>, default: () => ({ x: 0, y: 0, z: 0 }) }, position: { type: Object as PropType<Vector3PropInterface>, default: () => ({ x: 0, y: 0, z: 0 }) },
@ -48,10 +53,17 @@ export default defineComponent({
autoRemove: { type: Boolean, default: true }, autoRemove: { type: Boolean, default: true },
userData: { type: Object, default: () => ({}) }, userData: { type: Object, default: () => ({}) },
}, },
setup() { setup(): Object3DSetupInterface {
return object3DSetup() // return object3DSetup()
return {}
}, },
computed: { created() {
if (!this.renderer) {
console.error('Missing parent Renderer')
}
if (!this.scene) {
console.error('Missing parent Scene')
}
}, },
unmounted() { unmounted() {
if (this.autoRemove) this.removeFromParent() if (this.autoRemove) this.removeFromParent()
@ -67,7 +79,6 @@ export default defineComponent({
bindProp(this, 'scale', o3d) bindProp(this, 'scale', o3d)
bindProp(this, 'userData', o3d.userData) bindProp(this, 'userData', o3d.userData)
// TODO : fix lookat.x
if (this.lookAt) o3d.lookAt(this.lookAt.x ?? 0, this.lookAt.y, this.lookAt.z) 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 }) watch(() => this.lookAt, (v) => { o3d.lookAt(v.x ?? 0, v.y, v.z) }, { deep: true })

View File

@ -1,8 +1,9 @@
import { defineComponent, PropType, watch } from 'vue' import { defineComponent, inject, PropType, watch } from 'vue'
import { OrthographicCamera } from 'three' import { OrthographicCamera } from 'three'
import { bindProp } from '../tools' import { bindProp } from '../tools'
import Camera from './Camera' import Camera from './Camera'
import { Vector3PropInterface } from './Object3D' import { Vector3PropInterface } from './Object3D'
import { RendererInjectionKey } from './Renderer'
export default defineComponent({ export default defineComponent({
extends: Camera, extends: Camera,
@ -18,7 +19,14 @@ export default defineComponent({
position: { type: Object as PropType<Vector3PropInterface>, default: () => ({ x: 0, y: 0, z: 0 }) }, position: { type: Object as PropType<Vector3PropInterface>, default: () => ({ x: 0, y: 0, z: 0 }) },
}, },
setup(props) { 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) const camera = new OrthographicCamera(props.left, props.right, props.top, props.bottom, props.near, props.far)
renderer.camera = camera
bindProp(props, 'position', camera) bindProp(props, 'position', camera)
@ -32,10 +40,7 @@ export default defineComponent({
}) })
}) })
return { camera } return { renderer, camera }
},
created() {
this.renderer.camera = this.camera
}, },
__hmrId: 'OrthographicCamera', __hmrId: 'OrthographicCamera',
}) })

View File

@ -1,8 +1,9 @@
import { defineComponent, PropType, watch } from 'vue' import { defineComponent, inject, PropType, watch } from 'vue'
import { PerspectiveCamera } from 'three' import { PerspectiveCamera } from 'three'
import { bindProp } from '../tools' import { bindProp } from '../tools'
import Camera from './Camera' import Camera from './Camera'
import { Vector3PropInterface } from './Object3D' import { Vector3PropInterface } from './Object3D'
import { RendererInjectionKey } from './Renderer'
export default defineComponent({ export default defineComponent({
extends: Camera, extends: Camera,
@ -16,7 +17,14 @@ export default defineComponent({
lookAt: { type: Object as PropType<Vector3PropInterface>, default: null }, lookAt: { type: Object as PropType<Vector3PropInterface>, default: null },
}, },
setup(props) { 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) const camera = new PerspectiveCamera(props.fov, props.aspect, props.near, props.far)
renderer.camera = camera
bindProp(props, 'position', camera) bindProp(props, 'position', camera)
@ -33,10 +41,7 @@ export default defineComponent({
}) })
}) })
return { camera } return { renderer, camera }
},
created() {
this.renderer.camera = this.camera
}, },
__hmrId: 'PerspectiveCamera', __hmrId: 'PerspectiveCamera',
}) })

View File

@ -1,13 +1,13 @@
import { Object3D } from 'three' import { Object3D } from 'three'
import { defineComponent, inject, PropType } from 'vue' import { defineComponent, inject, PropType } from 'vue'
import usePointer, { IntersectObject, PointerInterface, PointerIntersectCallbackType } from './usePointer' 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 // eslint-disable-next-line @typescript-eslint/no-empty-function
const emptyCallBack: PointerIntersectCallbackType = () => {} const emptyCallBack: PointerIntersectCallbackType = () => {}
interface RaycasterSetupInterface { interface RaycasterSetupInterface {
renderer: RendererInterface renderer?: RendererInterface
pointer?: PointerInterface pointer?: PointerInterface
} }
@ -22,16 +22,22 @@ export default defineComponent({
intersectMode: { type: String, default: 'move' }, intersectMode: { type: String, default: 'move' },
}, },
setup(): RaycasterSetupInterface { setup(): RaycasterSetupInterface {
const renderer = inject('renderer') as RendererInterface const renderer = inject(RendererInjectionKey)
return { renderer } return { renderer }
}, },
mounted() { mounted() {
if (!this.renderer) {
console.error('Renderer not found')
return
}
const renderer = this.renderer
this.renderer.onMounted(() => { this.renderer.onMounted(() => {
if (!this.renderer.camera) return if (!renderer.camera) return
this.pointer = usePointer({ this.pointer = usePointer({
camera: this.renderer.camera, camera: renderer.camera,
domElement: this.renderer.canvas, domElement: renderer.canvas,
intersectObjects: this.getIntersectObjects(), intersectObjects: this.getIntersectObjects(),
onIntersectEnter: this.onPointerEnter, onIntersectEnter: this.onPointerEnter,
onIntersectOver: this.onPointerOver, onIntersectOver: this.onPointerOver,
@ -42,19 +48,19 @@ export default defineComponent({
this.pointer.addListeners() this.pointer.addListeners()
if (this.intersectMode === 'frame') { if (this.intersectMode === 'frame') {
this.renderer.onBeforeRender(this.pointer.intersect) renderer.onBeforeRender(this.pointer.intersect)
} }
}) })
}, },
unmounted() { unmounted() {
if (this.pointer) { if (this.pointer) {
this.pointer.removeListeners() this.pointer.removeListeners()
this.renderer.offBeforeRender(this.pointer.intersect) this.renderer?.offBeforeRender(this.pointer.intersect)
} }
}, },
methods: { methods: {
getIntersectObjects() { 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)) const children = this.renderer.scene.children.filter((c: Object3D) => ['Mesh', 'InstancedMesh'].includes(c.type))
return children as IntersectObject[] return children as IntersectObject[]
} }

View File

@ -1,7 +1,7 @@
/* eslint-disable no-use-before-define */ /* eslint-disable no-use-before-define */
import { Camera, Scene, WebGLRenderer } from 'three' import { Camera, Scene, WebGLRenderer } from 'three'
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer' 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' import useThree, { SizeInterface, ThreeConfigInterface, ThreeInterface } from './useThree'
type CallbackType<T> = (event: T) => void type CallbackType<T> = (event: T) => void
@ -86,6 +86,8 @@ export interface RendererInterface extends RendererSetupInterface {
removeListener<T extends keyof EventCallbackMap>(t: T, cb: EventCallbackMap[T]): void removeListener<T extends keyof EventCallbackMap>(t: T, cb: EventCallbackMap[T]): void
} }
export const RendererInjectionKey: InjectionKey<RendererInterface> = Symbol('Renderer')
export default defineComponent({ export default defineComponent({
name: 'Renderer', name: 'Renderer',
props: { props: {
@ -162,7 +164,7 @@ export default defineComponent({
}, },
provide() { provide() {
return { return {
renderer: this, [RendererInjectionKey as symbol]: this,
} }
}, },
mounted() { mounted() {

View File

@ -1,33 +1,43 @@
import { defineComponent, inject, watch } from 'vue' import { defineComponent, inject, InjectionKey, provide, watch } from 'vue'
import { Scene, Color, Object3D } from 'three' import { Scene, Color, Object3D, Texture } from 'three'
import { RendererInterface } from './Renderer' import { RendererInjectionKey } from './Renderer'
export const SceneInjectionKey: InjectionKey<Scene> = Symbol('Scene')
export default defineComponent({ export default defineComponent({
name: 'Scene', name: 'Scene',
props: { props: {
// id: String, background: [String, Number, Object],
background: [String, Number],
}, },
setup(props) { setup(props) {
const renderer = inject('renderer') as RendererInterface const renderer = inject(RendererInjectionKey)
const scene = new Scene() 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 } renderer.scene = scene
}, provide(SceneInjectionKey, scene)
provide() {
return { const setBackground = (value: any): void => {
scene: this.scene, 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 setBackground(props.background)
}, watch(() => props.background, setBackground)
methods: {
add(o: Object3D) { this.scene.add(o) }, const add = (o: Object3D): void => { scene.add(o) }
remove(o: Object3D) { this.scene.remove(o) }, const remove = (o: Object3D): void => { scene.remove(o) }
return { add, remove }
}, },
render() { render() {
return this.$slots.default ? this.$slots.default() : [] return this.$slots.default ? this.$slots.default() : []

View File

@ -12,6 +12,8 @@ export default defineComponent({
extends: EffectPass, extends: EffectPass,
props, props,
created() { created() {
if (!this.renderer) return
if (!this.renderer.scene) { if (!this.renderer.scene) {
console.error('Missing Scene') console.error('Missing Scene')
return return

View File

@ -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 { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
import { Pass } from 'three/examples/jsm/postprocessing/Pass' import { Pass } from 'three/examples/jsm/postprocessing/Pass'
import { RendererInterface } from '../core/Renderer' import { RendererInjectionKey, RendererInterface } from '../core/Renderer'
interface EffectComposerSetupInterface { interface EffectComposerSetupInterface {
renderer: RendererInterface renderer?: RendererInterface
composer?: EffectComposer composer?: EffectComposer
} }
@ -13,34 +13,38 @@ export interface EffectComposerInterface extends EffectComposerSetupInterface {
removePass(pass: Pass): void removePass(pass: Pass): void
} }
export const ComposerInjectionKey: InjectionKey<EffectComposerInterface> = Symbol('Composer')
export default defineComponent({ export default defineComponent({
setup(): EffectComposerSetupInterface { setup(): EffectComposerSetupInterface {
const renderer = inject('renderer') as RendererInterface const renderer = inject(RendererInjectionKey)
return { return { renderer }
renderer,
}
}, },
provide() { provide() {
return { return {
composer: this, [ComposerInjectionKey as symbol]: this,
} }
}, },
created() { created() {
if (!this.renderer) {
console.error('Renderer not found')
return
}
const renderer = this.renderer
const composer = new EffectComposer(this.renderer.renderer) const composer = new EffectComposer(this.renderer.renderer)
this.composer = composer this.composer = composer
this.renderer.composer = composer this.renderer.composer = composer
// this.renderer.onInit(() => { // this.renderer.onInit(() => {
this.renderer.addListener('init', () => { renderer.addListener('init', () => {
this.renderer.renderer.autoClear = false renderer.renderer.autoClear = false
this.resize() this.resize()
// this.renderer.onResize(this.resize) renderer.addListener('resize', this.resize)
this.renderer.addListener('resize', this.resize)
}) })
}, },
unmounted() { unmounted() {
// this.renderer.offResize(this.resize) this.renderer?.removeListener('resize', this.resize)
this.renderer.removeListener('resize', this.resize)
}, },
methods: { methods: {
addPass(pass: Pass) { addPass(pass: Pass) {
@ -50,7 +54,9 @@ export default defineComponent({
this.composer?.removePass(pass) this.composer?.removePass(pass)
}, },
resize() { 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() { render() {

View File

@ -1,37 +1,42 @@
import { Pass } from 'three/examples/jsm/postprocessing/Pass' import { Pass } from 'three/examples/jsm/postprocessing/Pass'
import { defineComponent, inject } from 'vue' import { defineComponent } from 'vue'
import { RendererInterface } from '../core/Renderer' import { RendererInjectionKey, RendererInterface } from '../core/Renderer'
import { EffectComposerInterface } from './EffectComposer' import { ComposerInjectionKey, EffectComposerInterface } from './EffectComposer'
export interface EffectSetupInterface { export interface EffectSetupInterface {
renderer: RendererInterface renderer?: RendererInterface
composer: EffectComposerInterface composer?: EffectComposerInterface
pass?: Pass pass?: Pass
} }
export default defineComponent({ export default defineComponent({
inject: ['renderer', 'composer'], // inject for sub components
inject: {
renderer: RendererInjectionKey as symbol,
composer: ComposerInjectionKey as symbol,
},
emits: ['ready'], emits: ['ready'],
setup(): EffectSetupInterface { setup(): EffectSetupInterface {
const renderer = inject('renderer') as RendererInterface return {}
const composer = inject('composer') as EffectComposerInterface
return { renderer, composer }
}, },
created() { created() {
if (!this.composer) { if (!this.composer) {
console.error('Missing parent EffectComposer') console.error('Missing parent EffectComposer')
} }
if (!this.renderer) {
console.error('Missing parent Renderer')
}
}, },
unmounted() { unmounted() {
if (this.pass) { if (this.pass) {
this.composer.removePass(this.pass); this.composer?.removePass(this.pass);
(this.pass as any).dispose?.() (this.pass as any).dispose?.()
} }
}, },
methods: { methods: {
initEffectPass(pass: Pass) { initEffectPass(pass: Pass) {
this.pass = pass this.pass = pass
this.composer.addPass(pass) this.composer?.addPass(pass)
this.$emit('ready', pass) this.$emit('ready', pass)
}, },
}, },

View File

@ -10,12 +10,12 @@ export default defineComponent({
const pass = new ShaderPass(FXAAShader) const pass = new ShaderPass(FXAAShader)
// resize will be first called in renderer init // resize will be first called in renderer init
this.renderer.addListener('resize', this.resize) this.renderer?.addListener('resize', this.resize)
this.initEffectPass(pass) this.initEffectPass(pass)
}, },
unmounted() { unmounted() {
this.renderer.removeListener('resize', this.resize) this.renderer?.removeListener('resize', this.resize)
}, },
methods: { methods: {
resize({ size }: { size: SizeInterface }) { resize({ size }: { size: SizeInterface }) {

View File

@ -15,6 +15,8 @@ export default defineComponent({
extends: EffectPass, extends: EffectPass,
props, props,
created() { created() {
if (!this.renderer) return
const pass = new HalftonePass(this.renderer.size.width, this.renderer.size.height, {}) const pass = new HalftonePass(this.renderer.size.width, this.renderer.size.height, {})
Object.keys(props).forEach(p => { Object.keys(props).forEach(p => {

View File

@ -5,6 +5,8 @@ import EffectPass from './EffectPass'
export default defineComponent({ export default defineComponent({
extends: EffectPass, extends: EffectPass,
created() { created() {
if (!this.renderer) return
if (!this.renderer.scene) { if (!this.renderer.scene) {
console.error('Missing Scene') console.error('Missing Scene')
return return

View File

@ -5,6 +5,8 @@ import EffectPass from './EffectPass'
export default defineComponent({ export default defineComponent({
extends: EffectPass, extends: EffectPass,
created() { created() {
if (!this.renderer) return
const pass = new SMAAPass(this.renderer.size.width, this.renderer.size.height) const pass = new SMAAPass(this.renderer.size.width, this.renderer.size.height)
this.initEffectPass(pass) this.initEffectPass(pass)
}, },

View File

@ -11,6 +11,8 @@ export default defineComponent({
}, },
}, },
created() { created() {
if (!this.renderer) return
if (!this.renderer.scene) { if (!this.renderer.scene) {
console.error('Missing Scene') console.error('Missing Scene')
return return

View File

@ -26,6 +26,8 @@ export default defineComponent({
return { uniforms1: {}, uniforms2: {} } return { uniforms1: {}, uniforms2: {} }
}, },
created() { created() {
if (!this.composer) return
this.pass1 = new ShaderPass(TiltShift) this.pass1 = new ShaderPass(TiltShift)
this.pass2 = new ShaderPass(TiltShift) this.pass2 = new ShaderPass(TiltShift)
@ -57,7 +59,7 @@ export default defineComponent({
this.composer.addPass(this.pass2) this.composer.addPass(this.pass2)
}, },
unmounted() { unmounted() {
if (this.pass2) this.composer.removePass(this.pass2) if (this.composer && this.pass2) this.composer.removePass(this.pass2)
}, },
methods: { methods: {
updateFocusLine() { updateFocusLine() {

View File

@ -13,6 +13,8 @@ export default defineComponent({
extends: EffectPass, extends: EffectPass,
props, props,
created() { created() {
if (!this.renderer) return
const size = new Vector2(this.renderer.size.width, this.renderer.size.height) const size = new Vector2(this.renderer.size.width, this.renderer.size.height)
const pass = new UnrealBloomPass(size, this.strength, this.radius, this.threshold) const pass = new UnrealBloomPass(size, this.strength, this.radius, this.threshold)

View File

@ -1,18 +1,18 @@
import { BufferGeometry } from 'three' import { BufferGeometry } from 'three'
import { defineComponent, inject, watch } from 'vue' import { defineComponent, inject, watch } from 'vue'
import { MeshInterface } from '../meshes/Mesh' import { MeshInjectionKey, MeshInterface } from '../meshes/Mesh'
export interface GeometryInterface { export interface GeometrySetupInterface {
geometry?: BufferGeometry
mesh?: MeshInterface mesh?: MeshInterface
watchProps: string[] geometry?: BufferGeometry
watchProps?: string[]
} }
function defaultSetup(): GeometryInterface { // function defaultSetup(): GeometryInterface {
const mesh = inject('mesh') as MeshInterface // const mesh = inject('mesh') as MeshInterface
const watchProps: string[] = [] // const watchProps: string[] = []
return { mesh, watchProps } // return { mesh, watchProps }
} // }
const Geometry = defineComponent({ const Geometry = defineComponent({
props: { props: {
@ -20,8 +20,12 @@ const Geometry = defineComponent({
rotateY: Number, rotateY: Number,
rotateZ: Number, rotateZ: Number,
}, },
setup() { // inject for sub components
return defaultSetup() inject: {
mesh: MeshInjectionKey as symbol,
},
setup(): GeometrySetupInterface {
return {}
}, },
created() { created() {
if (!this.mesh) { if (!this.mesh) {
@ -29,37 +33,31 @@ const Geometry = defineComponent({
return return
} }
Object.entries(this.$props).forEach(e => this.watchProps.push(e[0]))
this.createGeometry() this.createGeometry()
this.rotateGeometry() this.rotateGeometry()
if (this.geometry) this.mesh.setGeometry(this.geometry) if (this.geometry) this.mesh.setGeometry(this.geometry)
this.addWatchers() Object.keys(this.$props).forEach(prop => {
// @ts-ignore
watch(() => this[prop], this.refreshGeometry)
})
}, },
unmounted() { unmounted() {
this.geometry?.dispose() this.geometry?.dispose()
}, },
methods: { methods: {
createGeometry() {}, createGeometry() {},
addWatchers() {
this.watchProps.forEach(prop => {
// @ts-ignore
watch(() => this[prop], () => {
this.refreshGeometry()
})
})
},
rotateGeometry() { rotateGeometry() {
if (this.rotateX) this.geometry?.rotateX(this.rotateX) if (!this.geometry) return
if (this.rotateY) this.geometry?.rotateY(this.rotateY) if (this.rotateX) this.geometry.rotateX(this.rotateX)
if (this.rotateZ) this.geometry?.rotateZ(this.rotateZ) if (this.rotateY) this.geometry.rotateY(this.rotateY)
if (this.rotateZ) this.geometry.rotateZ(this.rotateZ)
}, },
refreshGeometry() { refreshGeometry() {
const oldGeo = this.geometry const oldGeo = this.geometry
this.createGeometry() this.createGeometry()
this.rotateGeometry() this.rotateGeometry()
if (this.geometry) this.mesh?.setGeometry(this.geometry) if (this.geometry && this.mesh) this.mesh.setGeometry(this.geometry)
oldGeo?.dispose() oldGeo?.dispose()
}, },
}, },
@ -74,8 +72,8 @@ export function geometryComponent(name, props, createGeometry) {
name, name,
extends: Geometry, extends: Geometry,
props, props,
setup() { setup(): GeometrySetupInterface {
return defaultSetup() return {}
}, },
methods: { methods: {
createGeometry() { createGeometry() {

View File

@ -1,6 +1,6 @@
import { defineComponent, watch } from 'vue' import { defineComponent, InjectionKey, watch } from 'vue'
import { FrontSide, Material, Texture } from 'three' import { FrontSide, Material, Texture } from 'three'
import { MeshInterface } from '../meshes/Mesh' import { MeshInjectionKey, MeshInterface } from '../meshes/Mesh'
export interface MaterialSetupInterface { export interface MaterialSetupInterface {
mesh?: MeshInterface mesh?: MeshInterface
@ -13,8 +13,13 @@ export interface MaterialInterface extends MaterialSetupInterface {
setTexture(texture: Texture | null, key: string): void setTexture(texture: Texture | null, key: string): void
} }
export const MaterialInjectionKey: InjectionKey<MaterialInterface> = Symbol('Material')
export default defineComponent({ export default defineComponent({
inject: ['mesh'], // inject for sub components
inject: {
mesh: MeshInjectionKey as symbol,
},
props: { props: {
color: { type: [String, Number], default: '#ffffff' }, color: { type: [String, Number], default: '#ffffff' },
depthTest: { type: Boolean, default: true }, depthTest: { type: Boolean, default: true },
@ -30,7 +35,7 @@ export default defineComponent({
}, },
provide() { provide() {
return { return {
material: this, [MaterialInjectionKey as symbol]: this,
} }
}, },
created() { created() {

View File

@ -1,7 +1,7 @@
import { defineComponent, PropType, watch } from 'vue' 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 { bindProp } from '../tools'
import { MaterialInterface } from './Material' import { MaterialInjectionKey, MaterialInterface } from './Material'
export interface TexureInterface { export interface TexureInterface {
material?: MaterialInterface material?: MaterialInterface
@ -9,7 +9,9 @@ export interface TexureInterface {
} }
export default defineComponent({ export default defineComponent({
inject: ['material'], inject: {
material: MaterialInjectionKey as symbol,
},
props: { props: {
name: { type: String, default: 'map' }, name: { type: String, default: 'map' },
uniform: String, uniform: String,
@ -53,7 +55,7 @@ export default defineComponent({
if (this.texture && this.material) { if (this.texture && this.material) {
this.material.setTexture(this.texture, this.name) this.material.setTexture(this.texture, this.name)
if (this.material.material instanceof ShaderMaterial && this.uniform) { 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 }
} }
} }
}, },

View File

@ -1,7 +1,6 @@
import { defineComponent, watch } from 'vue' import { defineComponent, watch } from 'vue'
import { DoubleSide, MeshBasicMaterial, PlaneGeometry, Texture, TextureLoader } from 'three' import { DoubleSide, MeshBasicMaterial, PlaneGeometry, Texture, TextureLoader } from 'three'
import Mesh, { MeshSetupInterface } from './Mesh' import Mesh, { MeshSetupInterface } from './Mesh'
import { object3DSetup } from '../core/Object3D'
interface ImageSetupInterface extends MeshSetupInterface { interface ImageSetupInterface extends MeshSetupInterface {
material?: MeshBasicMaterial material?: MeshBasicMaterial
@ -20,9 +19,11 @@ export default defineComponent({
keepSize: Boolean, keepSize: Boolean,
}, },
setup(): ImageSetupInterface { setup(): ImageSetupInterface {
return object3DSetup() return {}
}, },
created() { created() {
if (!this.renderer) return
this.geometry = new PlaneGeometry(1, 1, this.widthSegments, this.heightSegments) this.geometry = new PlaneGeometry(1, 1, this.widthSegments, this.heightSegments)
this.material = new MeshBasicMaterial({ side: DoubleSide, map: this.loadTexture() }) this.material = new MeshBasicMaterial({ side: DoubleSide, map: this.loadTexture() })
@ -37,7 +38,7 @@ export default defineComponent({
if (this.keepSize) this.renderer.onResize(this.resize) if (this.keepSize) this.renderer.onResize(this.resize)
}, },
unmounted() { unmounted() {
this.renderer.offResize(this.resize) this.renderer?.offResize(this.resize)
}, },
methods: { methods: {
loadTexture() { loadTexture() {
@ -56,7 +57,7 @@ export default defineComponent({
this.$emit('loaded', texture) this.$emit('loaded', texture)
}, },
resize() { resize() {
if (!this.texture) return if (!this.renderer || !this.texture) return
const screen = this.renderer.size const screen = this.renderer.size
const iW = this.texture.image.width const iW = this.texture.image.width
const iH = this.texture.image.height const iH = this.texture.image.height

View File

@ -10,6 +10,8 @@ export default defineComponent({
}, },
methods: { methods: {
initMesh() { initMesh() {
if (!this.renderer) return
if (!this.geometry || !this.material) { if (!this.geometry || !this.material) {
console.error('Missing geometry and/or material') console.error('Missing geometry and/or material')
return false return false

View File

@ -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 { BufferGeometry, Material, Mesh as TMesh } from 'three'
import Object3D, { object3DSetup, Object3DSetupInterface } from '../core/Object3D' import Object3D, { Object3DSetupInterface } from '../core/Object3D'
import { bindProp } from '../tools' import { bindProp } from '../tools'
export const pointerProps = { export const pointerProps = {
@ -25,6 +25,8 @@ export interface MeshInterface extends MeshSetupInterface {
setMaterial(m: Material): void setMaterial(m: Material): void
} }
export const MeshInjectionKey: InjectionKey<MeshInterface> = Symbol('Mesh')
const Mesh = defineComponent({ const Mesh = defineComponent({
name: 'Mesh', name: 'Mesh',
extends: Object3D, extends: Object3D,
@ -34,11 +36,11 @@ const Mesh = defineComponent({
...pointerProps, ...pointerProps,
}, },
setup(): MeshSetupInterface { setup(): MeshSetupInterface {
return object3DSetup() return {}
}, },
provide() { provide() {
return { return {
mesh: this, [MeshInjectionKey as symbol]: this,
} }
}, },
mounted() { mounted() {
@ -60,7 +62,7 @@ const Mesh = defineComponent({
this.onPointerDown || this.onPointerDown ||
this.onPointerUp || this.onPointerUp ||
this.onClick) { this.onClick) {
this.renderer.three.addIntersectObject(mesh) if (this.renderer) this.renderer.three.addIntersectObject(mesh)
} }
this.mesh = mesh this.mesh = mesh
@ -92,7 +94,7 @@ const Mesh = defineComponent({
}, },
unmounted() { unmounted() {
if (this.mesh) { 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) // for predefined mesh (geometry/material are not unmounted)
if (this.geometry) this.geometry.dispose() if (this.geometry) this.geometry.dispose()
@ -110,7 +112,7 @@ export function meshComponent(name, props, createGeometry) {
extends: Mesh, extends: Mesh,
props, props,
setup(): MeshSetupInterface { setup(): MeshSetupInterface {
return object3DSetup() return {}
}, },
created() { created() {
this.createGeometry() this.createGeometry()

View File

@ -1,6 +1,6 @@
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import { Sprite, SpriteMaterial, Texture, TextureLoader } from 'three' import { Sprite, SpriteMaterial, Texture, TextureLoader } from 'three'
import Object3D, { object3DSetup, Object3DSetupInterface } from '../core/Object3D' import Object3D, { Object3DSetupInterface } from '../core/Object3D'
interface SpriteSetupInterface extends Object3DSetupInterface { interface SpriteSetupInterface extends Object3DSetupInterface {
texture?: Texture texture?: Texture
@ -15,7 +15,7 @@ export default defineComponent({
src: { type: String, required: true }, src: { type: String, required: true },
}, },
setup(): SpriteSetupInterface { setup(): SpriteSetupInterface {
return object3DSetup() return {}
}, },
created() { created() {
this.texture = new TextureLoader().load(this.src, this.onLoaded) this.texture = new TextureLoader().load(this.src, this.onLoaded)

View File

@ -1,7 +1,6 @@
import { defineComponent, watch } from 'vue' import { defineComponent, watch } from 'vue'
import { Font, FontLoader, TextGeometry } from 'three' import { Font, FontLoader, TextGeometry } from 'three'
import Mesh, { MeshSetupInterface } from './Mesh' import Mesh, { MeshSetupInterface } from './Mesh'
import { object3DSetup } from '../core/Object3D'
interface TextSetupInterface extends MeshSetupInterface { interface TextSetupInterface extends MeshSetupInterface {
geometry?: TextGeometry geometry?: TextGeometry
@ -27,7 +26,7 @@ export default defineComponent({
extends: Mesh, extends: Mesh,
props, props,
setup(): TextSetupInterface { setup(): TextSetupInterface {
return object3DSetup() return {}
}, },
created() { created() {
if (!this.fontSrc) { if (!this.fontSrc) {