diff --git a/src/core/Object3D.ts b/src/core/Object3D.ts index c49e312..cfffa7d 100644 --- a/src/core/Object3D.ts +++ b/src/core/Object3D.ts @@ -1,7 +1,12 @@ import { Object3D } from 'three' -import { defineComponent, watch } from 'vue' +import { ComponentPublicInstance, defineComponent, watch } from 'vue' import { bindProp } from '../tools' +interface Object3DInterface { + o3d?: Object3D + parent?: ComponentPublicInstance +} + export default defineComponent({ name: 'Object3D', inject: ['three', 'scene', 'renderer'], @@ -14,55 +19,57 @@ export default defineComponent({ autoRemove: { type: Boolean, default: true }, userData: { type: Object, default: () => ({}) }, }, - // can't use setup because it will not be used in sub components - // setup() {}, + setup(): Object3DInterface { + return {} + }, unmounted() { if (this.autoRemove) this.removeFromParent() }, methods: { initObject3D(o3d: Object3D) { this.o3d = o3d - this.o3d.userData = this.userData - this.$emit('created', this.o3d) - bindProp(this, 'position', this.o3d) - bindProp(this, 'rotation', this.o3d) - bindProp(this, 'scale', this.o3d) + o3d.userData = this.userData + this.$emit('created', o3d) + + bindProp(this, 'position', o3d) + bindProp(this, 'rotation', o3d) + bindProp(this, 'scale', o3d) // TODO : fix lookat.x - if (this.lookAt) this.o3d.lookAt(this.lookAt.x, this.lookAt.y, this.lookAt.z) - watch(() => this.lookAt, (v) => { this.o3d.lookAt(v.x, v.y, v.z) }, { deep: true }) + if (this.lookAt) o3d.lookAt(this.lookAt.x, this.lookAt.y, this.lookAt.z) + watch(() => this.lookAt, (v) => { o3d.lookAt(v.x, v.y, v.z) }, { deep: true }) - this._parent = this.getParent() + this.parent = this.getParent() if (this.addToParent()) this.$emit('ready', this) else console.error('Missing parent (Scene, Group...)') }, - getParent() { + getParent(): undefined | ComponentPublicInstance { let parent = this.$parent while (parent) { - if (parent.add) return parent + if ((parent as any).add) return parent parent = parent.$parent } - return false + return undefined }, - addToParent(o) { + addToParent(o?: Object3D) { const o3d = o || this.o3d - if (this._parent) { - this._parent.add(o3d) + if (this.parent) { + (this.parent as any).add(o3d) return true } return false }, - removeFromParent(o) { + removeFromParent(o?: Object3D) { const o3d = o || this.o3d - if (this._parent) { - this._parent.remove(o3d) + if (this.parent) { + (this.parent as any).remove(o3d) return true } return false }, - add(o: Object3D) { this.o3d.add(o) }, - remove(o: Object3D) { this.o3d.remove(o) }, + add(o: Object3D) { this.o3d?.add(o) }, + remove(o: Object3D) { this.o3d?.remove(o) }, }, render() { return this.$slots.default ? this.$slots.default() : [] diff --git a/src/core/OrthographicCamera.ts b/src/core/OrthographicCamera.ts index e952579..2a3ba42 100644 --- a/src/core/OrthographicCamera.ts +++ b/src/core/OrthographicCamera.ts @@ -18,21 +18,20 @@ export default defineComponent({ }, setup(props) { const camera = new OrthographicCamera(props.left, props.right, props.top, props.bottom, props.near, props.far) - return { - camera, - } - }, - created() { - bindProp(this, 'position', this.camera) + + bindProp(this, 'position', camera) const watchProps = ['left', 'right', 'top', 'bottom', 'near', 'far', 'zoom'] watchProps.forEach(p => { - watch(() => this[p], () => { - this.camera[p] = this[p] - this.camera.updateProjectionMatrix() + watch(() => props[p], (value) => { + camera[p] = value + camera.updateProjectionMatrix() }) }) + return { camera } + }, + created() { this.three.camera = this.camera }, __hmrId: 'OrthographicCamera', diff --git a/src/core/PerspectiveCamera.ts b/src/core/PerspectiveCamera.ts index cf2ea18..a0b1f2f 100644 --- a/src/core/PerspectiveCamera.ts +++ b/src/core/PerspectiveCamera.ts @@ -16,25 +16,25 @@ export default defineComponent({ lookAt: { type: Object, default: null }, }, setup(props) { - return { - camera: new PerspectiveCamera(props.fov, props.aspect, props.near, props.far), - } - }, - created() { - bindProp(this, 'position', this.camera) + const camera = new PerspectiveCamera(props.fov, props.aspect, props.near, props.far) + + bindProp(props, 'position', camera) // TODO : fix lookAt x - if (this.lookAt) this.camera.lookAt(this.lookAt.x, this.lookAt.y, this.lookAt.z) - watch(() => this.lookAt, (v) => { this.camera.lookAt(v.x, v.y, v.z) }, { deep: true }) + if (props.lookAt) camera.lookAt(props.lookAt.x, props.lookAt.y, props.lookAt.z) + watch(() => props.lookAt, (v) => { camera.lookAt(v.x, v.y, v.z) }, { deep: true }) const watchProps = ['aspect', 'far', 'fov', 'near'] watchProps.forEach(p => { - watch(() => this[p], () => { - this.camera[p] = this[p] - this.camera.updateProjectionMatrix() + watch(() => props[p], (value) => { + camera[p] = value + camera.updateProjectionMatrix() }) }) + return { camera } + }, + created() { this.three.camera = this.camera }, __hmrId: 'PerspectiveCamera', diff --git a/src/core/Renderer.ts b/src/core/Renderer.ts index 777ee69..45b78d4 100644 --- a/src/core/Renderer.ts +++ b/src/core/Renderer.ts @@ -1,7 +1,12 @@ -import { WebGLRenderer } from 'three' -import { defineComponent, h } from 'vue' +import { defineComponent } from 'vue' import useThree, { ThreeConfigInterface } from './useThree' +interface RendererEventInterface { +} + +// interface RendererEventListenerInterface { +// } + export default defineComponent({ name: 'Renderer', props: { @@ -15,20 +20,38 @@ export default defineComponent({ width: String, height: String, xr: Boolean, + onReady: Function, + onFrame: Function, }, - setup() { - const renderer: null | WebGLRenderer = null - const _render: {(): void} = () => {} + setup(props) { + const canvas = document.createElement('canvas') + const config: ThreeConfigInterface = { + canvas, + antialias: props.antialias, + alpha: props.alpha, + autoClear: props.autoClear, + orbitCtrl: props.orbitCtrl, + pointer: props.pointer, + resize: props.resize, + } + + if (props.width) config.width = parseInt(props.width) + if (props.height) config.height = parseInt(props.height) + + const three = useThree(config) + + const renderFn: {(): void} = () => {} const onMountedCallbacks: {(): void}[] = [] const beforeRenderCallbacks: {(): void}[] = [] const afterRenderCallbacks: {(): void}[] = [] return { - three: useThree(), - renderer, + canvas, + three, + renderer: three.renderer, + renderFn, raf: true, - _render, onMountedCallbacks, beforeRenderCallbacks, afterRenderCallbacks, @@ -41,24 +64,14 @@ export default defineComponent({ } }, mounted() { - const params: ThreeConfigInterface = { - canvas: this.$el, - antialias: this.antialias, - alpha: this.alpha, - autoClear: this.autoClear, - orbitCtrl: this.orbitCtrl, - pointer: this.pointer, - resize: this.resize, - } + // appendChild won't work on reload + this.$el.parentNode.insertBefore(this.canvas, this.$el) - if (this.width) params.width = parseInt(this.width) - if (this.height) params.height = parseInt(this.height) - - if (this.three.init(params)) { - this.renderer = this.three.renderer + if (this.three.init()) { + this.onReady?.(this) this.renderer.shadowMap.enabled = this.shadow - this._render = this.three.composer ? this.three.renderC : this.three.render + this.renderFn = this.three.composer ? this.three.renderC : this.three.render if (this.xr) { this.renderer.xr.enabled = true @@ -71,6 +84,7 @@ export default defineComponent({ this.onMountedCallbacks.forEach(c => c()) }, beforeUnmount() { + this.canvas.remove() this.beforeRenderCallbacks = [] this.afterRenderCallbacks = [] this.raf = false @@ -99,9 +113,11 @@ export default defineComponent({ this.three.offAfterResize(cb) }, render(time: number) { - this.beforeRenderCallbacks.forEach(c => c({ time })) - this._render() - this.afterRenderCallbacks.forEach(c => c({ time })) + const cbParams = { time, renderer: this } + this.beforeRenderCallbacks.forEach(cb => cb(cbParams)) + this.onFrame?.(cbParams) + this.renderFn() + this.afterRenderCallbacks.forEach(cb => cb(cbParams)) }, renderLoop(time: number) { if (this.raf) requestAnimationFrame(this.renderLoop) @@ -109,7 +125,8 @@ export default defineComponent({ }, }, render() { - return h('canvas', {}, this.$slots.default ? this.$slots.default() : []) + // return h('canvas', {}, this.$slots.default ? this.$slots.default() : []) + return this.$slots.default ? this.$slots.default() : [] }, __hmrId: 'Renderer', }) diff --git a/src/core/Scene.ts b/src/core/Scene.ts index 855fcb9..ecbc487 100644 --- a/src/core/Scene.ts +++ b/src/core/Scene.ts @@ -21,7 +21,7 @@ export default defineComponent({ scene: this.scene, } }, - mounted() { + created() { if (!this.three.scene) { this.three.scene = this.scene } diff --git a/src/core/index.js b/src/core/index.ts similarity index 100% rename from src/core/index.js rename to src/core/index.ts diff --git a/src/core/useThree.ts b/src/core/useThree.ts index ad31fce..bed343b 100644 --- a/src/core/useThree.ts +++ b/src/core/useThree.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { createRenderer } from '@vue/runtime-core' import { Camera, OrthographicCamera, PerspectiveCamera, Scene, WebGLRenderer } from 'three' import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js' @@ -27,14 +28,14 @@ export interface SizeInterface { export interface ThreeInterface { conf: ThreeConfigInterface - renderer?: WebGLRenderer + renderer: WebGLRenderer + composer?: EffectComposer camera?: Camera cameraCtrl?: OrbitControls scene?: Scene pointer?: PointerInterface size: SizeInterface - composer?: EffectComposer - init(config: ThreeConfigInterface): boolean + init(): boolean dispose(): void render(): void renderC(): void @@ -49,7 +50,7 @@ export interface ThreeInterface { /** * Three.js helper */ -export default function useThree(): ThreeInterface { +export default function useThree(params: ThreeConfigInterface): ThreeInterface { // default conf const conf: ThreeConfigInterface = { antialias: true, @@ -62,6 +63,12 @@ export default function useThree(): ThreeInterface { height: 150, } + if (params) { + Object.entries(params).forEach(([key, value]) => { + conf[key] = value + }) + } + // size const size: SizeInterface = { width: 1, height: 1, @@ -70,9 +77,6 @@ export default function useThree(): ThreeInterface { } // handlers - // const afterInitCallbacks: void[] = [] - // let afterResizeCallbacks: void[] = [] - // let beforeRenderCallbacks: void[] = [] const afterInitCallbacks: {(): void}[] = [] let afterResizeCallbacks: {(): void}[] = [] let beforeRenderCallbacks: {(): void}[] = [] @@ -82,6 +86,7 @@ export default function useThree(): ThreeInterface { // returned object const obj: ThreeInterface = { conf, + renderer: createRenderer(), size, init, dispose, @@ -95,16 +100,19 @@ export default function useThree(): ThreeInterface { return obj + /** + * create WebGLRenderer + */ + function createRenderer(): WebGLRenderer { + const renderer = new WebGLRenderer({ canvas: conf.canvas, antialias: conf.antialias, alpha: conf.alpha }) + renderer.autoClear = conf.autoClear + return renderer + } + /** * init three */ - function init(params: ThreeConfigInterface) { - if (params) { - Object.entries(params).forEach(([key, value]) => { - conf[key] = value - }) - } - + function init() { if (!obj.scene) { console.error('Missing Scene') return false @@ -115,14 +123,11 @@ export default function useThree(): ThreeInterface { return false } - obj.renderer = new WebGLRenderer({ canvas: conf.canvas, antialias: conf.antialias, alpha: conf.alpha }) - obj.renderer.autoClear = conf.autoClear - if (conf.resize) { onResize() window.addEventListener('resize', onResize) - } else { - setSize(conf.width!, conf.height!) + } else if (conf.width && conf.height) { + setSize(conf.width, conf.height) } initPointer() @@ -142,6 +147,9 @@ export default function useThree(): ThreeInterface { return true } + /** + * init pointer + */ function initPointer() { let pointerConf: PointerConfigInterface = { camera: obj.camera!,