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

wip: core

This commit is contained in:
Kevin Levron 2021-04-18 22:27:53 +02:00
parent 5df3223bd6
commit b5ab6567e3
7 changed files with 120 additions and 89 deletions

View File

@ -1,7 +1,12 @@
import { Object3D } from 'three' import { Object3D } from 'three'
import { defineComponent, watch } from 'vue' import { ComponentPublicInstance, defineComponent, watch } from 'vue'
import { bindProp } from '../tools' import { bindProp } from '../tools'
interface Object3DInterface {
o3d?: Object3D
parent?: ComponentPublicInstance
}
export default defineComponent({ export default defineComponent({
name: 'Object3D', name: 'Object3D',
inject: ['three', 'scene', 'renderer'], inject: ['three', 'scene', 'renderer'],
@ -14,55 +19,57 @@ export default defineComponent({
autoRemove: { type: Boolean, default: true }, autoRemove: { type: Boolean, default: true },
userData: { type: Object, default: () => ({}) }, userData: { type: Object, default: () => ({}) },
}, },
// can't use setup because it will not be used in sub components setup(): Object3DInterface {
// setup() {}, return {}
},
unmounted() { unmounted() {
if (this.autoRemove) this.removeFromParent() if (this.autoRemove) this.removeFromParent()
}, },
methods: { methods: {
initObject3D(o3d: Object3D) { initObject3D(o3d: Object3D) {
this.o3d = o3d this.o3d = o3d
this.o3d.userData = this.userData
this.$emit('created', this.o3d)
bindProp(this, 'position', this.o3d) o3d.userData = this.userData
bindProp(this, 'rotation', this.o3d) this.$emit('created', o3d)
bindProp(this, 'scale', this.o3d)
bindProp(this, 'position', o3d)
bindProp(this, 'rotation', o3d)
bindProp(this, 'scale', o3d)
// TODO : fix lookat.x // TODO : fix lookat.x
if (this.lookAt) this.o3d.lookAt(this.lookAt.x, this.lookAt.y, this.lookAt.z) if (this.lookAt) 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 }) 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) if (this.addToParent()) this.$emit('ready', this)
else console.error('Missing parent (Scene, Group...)') else console.error('Missing parent (Scene, Group...)')
}, },
getParent() { getParent(): undefined | ComponentPublicInstance {
let parent = this.$parent let parent = this.$parent
while (parent) { while (parent) {
if (parent.add) return parent if ((parent as any).add) return parent
parent = parent.$parent parent = parent.$parent
} }
return false return undefined
}, },
addToParent(o) { addToParent(o?: Object3D) {
const o3d = o || this.o3d const o3d = o || this.o3d
if (this._parent) { if (this.parent) {
this._parent.add(o3d) (this.parent as any).add(o3d)
return true return true
} }
return false return false
}, },
removeFromParent(o) { removeFromParent(o?: Object3D) {
const o3d = o || this.o3d const o3d = o || this.o3d
if (this._parent) { if (this.parent) {
this._parent.remove(o3d) (this.parent as any).remove(o3d)
return true return true
} }
return false return false
}, },
add(o: Object3D) { this.o3d.add(o) }, add(o: Object3D) { this.o3d?.add(o) },
remove(o: Object3D) { this.o3d.remove(o) }, remove(o: Object3D) { this.o3d?.remove(o) },
}, },
render() { render() {
return this.$slots.default ? this.$slots.default() : [] return this.$slots.default ? this.$slots.default() : []

View File

@ -18,21 +18,20 @@ export default defineComponent({
}, },
setup(props) { setup(props) {
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)
return {
camera, bindProp(this, 'position', camera)
}
},
created() {
bindProp(this, 'position', this.camera)
const watchProps = ['left', 'right', 'top', 'bottom', 'near', 'far', 'zoom'] const watchProps = ['left', 'right', 'top', 'bottom', 'near', 'far', 'zoom']
watchProps.forEach(p => { watchProps.forEach(p => {
watch(() => this[p], () => { watch(() => props[p], (value) => {
this.camera[p] = this[p] camera[p] = value
this.camera.updateProjectionMatrix() camera.updateProjectionMatrix()
}) })
}) })
return { camera }
},
created() {
this.three.camera = this.camera this.three.camera = this.camera
}, },
__hmrId: 'OrthographicCamera', __hmrId: 'OrthographicCamera',

View File

@ -16,25 +16,25 @@ export default defineComponent({
lookAt: { type: Object, default: null }, lookAt: { type: Object, default: null },
}, },
setup(props) { setup(props) {
return { const camera = new PerspectiveCamera(props.fov, props.aspect, props.near, props.far)
camera: new PerspectiveCamera(props.fov, props.aspect, props.near, props.far),
} bindProp(props, 'position', camera)
},
created() {
bindProp(this, 'position', this.camera)
// TODO : fix lookAt x // TODO : fix lookAt x
if (this.lookAt) this.camera.lookAt(this.lookAt.x, this.lookAt.y, this.lookAt.z) if (props.lookAt) camera.lookAt(props.lookAt.x, props.lookAt.y, props.lookAt.z)
watch(() => this.lookAt, (v) => { this.camera.lookAt(v.x, v.y, v.z) }, { deep: true }) watch(() => props.lookAt, (v) => { camera.lookAt(v.x, v.y, v.z) }, { deep: true })
const watchProps = ['aspect', 'far', 'fov', 'near'] const watchProps = ['aspect', 'far', 'fov', 'near']
watchProps.forEach(p => { watchProps.forEach(p => {
watch(() => this[p], () => { watch(() => props[p], (value) => {
this.camera[p] = this[p] camera[p] = value
this.camera.updateProjectionMatrix() camera.updateProjectionMatrix()
}) })
}) })
return { camera }
},
created() {
this.three.camera = this.camera this.three.camera = this.camera
}, },
__hmrId: 'PerspectiveCamera', __hmrId: 'PerspectiveCamera',

View File

@ -1,7 +1,12 @@
import { WebGLRenderer } from 'three' import { defineComponent } from 'vue'
import { defineComponent, h } from 'vue'
import useThree, { ThreeConfigInterface } from './useThree' import useThree, { ThreeConfigInterface } from './useThree'
interface RendererEventInterface {
}
// interface RendererEventListenerInterface {
// }
export default defineComponent({ export default defineComponent({
name: 'Renderer', name: 'Renderer',
props: { props: {
@ -15,20 +20,38 @@ export default defineComponent({
width: String, width: String,
height: String, height: String,
xr: Boolean, xr: Boolean,
onReady: Function,
onFrame: Function,
}, },
setup() { setup(props) {
const renderer: null | WebGLRenderer = null const canvas = document.createElement('canvas')
const _render: {(): void} = () => {} 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 onMountedCallbacks: {(): void}[] = []
const beforeRenderCallbacks: {(): void}[] = [] const beforeRenderCallbacks: {(): void}[] = []
const afterRenderCallbacks: {(): void}[] = [] const afterRenderCallbacks: {(): void}[] = []
return { return {
three: useThree(), canvas,
renderer, three,
renderer: three.renderer,
renderFn,
raf: true, raf: true,
_render,
onMountedCallbacks, onMountedCallbacks,
beforeRenderCallbacks, beforeRenderCallbacks,
afterRenderCallbacks, afterRenderCallbacks,
@ -41,24 +64,14 @@ export default defineComponent({
} }
}, },
mounted() { mounted() {
const params: ThreeConfigInterface = { // appendChild won't work on reload
canvas: this.$el, this.$el.parentNode.insertBefore(this.canvas, this.$el)
antialias: this.antialias,
alpha: this.alpha,
autoClear: this.autoClear,
orbitCtrl: this.orbitCtrl,
pointer: this.pointer,
resize: this.resize,
}
if (this.width) params.width = parseInt(this.width) if (this.three.init()) {
if (this.height) params.height = parseInt(this.height) this.onReady?.(this)
if (this.three.init(params)) {
this.renderer = this.three.renderer
this.renderer.shadowMap.enabled = this.shadow 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) { if (this.xr) {
this.renderer.xr.enabled = true this.renderer.xr.enabled = true
@ -71,6 +84,7 @@ export default defineComponent({
this.onMountedCallbacks.forEach(c => c()) this.onMountedCallbacks.forEach(c => c())
}, },
beforeUnmount() { beforeUnmount() {
this.canvas.remove()
this.beforeRenderCallbacks = [] this.beforeRenderCallbacks = []
this.afterRenderCallbacks = [] this.afterRenderCallbacks = []
this.raf = false this.raf = false
@ -99,9 +113,11 @@ export default defineComponent({
this.three.offAfterResize(cb) this.three.offAfterResize(cb)
}, },
render(time: number) { render(time: number) {
this.beforeRenderCallbacks.forEach(c => c({ time })) const cbParams = { time, renderer: this }
this._render() this.beforeRenderCallbacks.forEach(cb => cb(cbParams))
this.afterRenderCallbacks.forEach(c => c({ time })) this.onFrame?.(cbParams)
this.renderFn()
this.afterRenderCallbacks.forEach(cb => cb(cbParams))
}, },
renderLoop(time: number) { renderLoop(time: number) {
if (this.raf) requestAnimationFrame(this.renderLoop) if (this.raf) requestAnimationFrame(this.renderLoop)
@ -109,7 +125,8 @@ export default defineComponent({
}, },
}, },
render() { 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', __hmrId: 'Renderer',
}) })

View File

@ -21,7 +21,7 @@ export default defineComponent({
scene: this.scene, scene: this.scene,
} }
}, },
mounted() { created() {
if (!this.three.scene) { if (!this.three.scene) {
this.three.scene = this.scene this.three.scene = this.scene
} }

View File

@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-non-null-assertion */
import { createRenderer } from '@vue/runtime-core'
import { Camera, OrthographicCamera, PerspectiveCamera, Scene, WebGLRenderer } from 'three' import { Camera, OrthographicCamera, PerspectiveCamera, Scene, WebGLRenderer } from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js' import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
@ -27,14 +28,14 @@ export interface SizeInterface {
export interface ThreeInterface { export interface ThreeInterface {
conf: ThreeConfigInterface conf: ThreeConfigInterface
renderer?: WebGLRenderer renderer: WebGLRenderer
composer?: EffectComposer
camera?: Camera camera?: Camera
cameraCtrl?: OrbitControls cameraCtrl?: OrbitControls
scene?: Scene scene?: Scene
pointer?: PointerInterface pointer?: PointerInterface
size: SizeInterface size: SizeInterface
composer?: EffectComposer init(): boolean
init(config: ThreeConfigInterface): boolean
dispose(): void dispose(): void
render(): void render(): void
renderC(): void renderC(): void
@ -49,7 +50,7 @@ export interface ThreeInterface {
/** /**
* Three.js helper * Three.js helper
*/ */
export default function useThree(): ThreeInterface { export default function useThree(params: ThreeConfigInterface): ThreeInterface {
// default conf // default conf
const conf: ThreeConfigInterface = { const conf: ThreeConfigInterface = {
antialias: true, antialias: true,
@ -62,6 +63,12 @@ export default function useThree(): ThreeInterface {
height: 150, height: 150,
} }
if (params) {
Object.entries(params).forEach(([key, value]) => {
conf[key] = value
})
}
// size // size
const size: SizeInterface = { const size: SizeInterface = {
width: 1, height: 1, width: 1, height: 1,
@ -70,9 +77,6 @@ export default function useThree(): ThreeInterface {
} }
// handlers // handlers
// const afterInitCallbacks: void[] = []
// let afterResizeCallbacks: void[] = []
// let beforeRenderCallbacks: void[] = []
const afterInitCallbacks: {(): void}[] = [] const afterInitCallbacks: {(): void}[] = []
let afterResizeCallbacks: {(): void}[] = [] let afterResizeCallbacks: {(): void}[] = []
let beforeRenderCallbacks: {(): void}[] = [] let beforeRenderCallbacks: {(): void}[] = []
@ -82,6 +86,7 @@ export default function useThree(): ThreeInterface {
// returned object // returned object
const obj: ThreeInterface = { const obj: ThreeInterface = {
conf, conf,
renderer: createRenderer(),
size, size,
init, init,
dispose, dispose,
@ -96,15 +101,18 @@ export default function useThree(): ThreeInterface {
return obj return obj
/** /**
* init three * create WebGLRenderer
*/ */
function init(params: ThreeConfigInterface) { function createRenderer(): WebGLRenderer {
if (params) { const renderer = new WebGLRenderer({ canvas: conf.canvas, antialias: conf.antialias, alpha: conf.alpha })
Object.entries(params).forEach(([key, value]) => { renderer.autoClear = conf.autoClear
conf[key] = value return renderer
})
} }
/**
* init three
*/
function init() {
if (!obj.scene) { if (!obj.scene) {
console.error('Missing Scene') console.error('Missing Scene')
return false return false
@ -115,14 +123,11 @@ export default function useThree(): ThreeInterface {
return false return false
} }
obj.renderer = new WebGLRenderer({ canvas: conf.canvas, antialias: conf.antialias, alpha: conf.alpha })
obj.renderer.autoClear = conf.autoClear
if (conf.resize) { if (conf.resize) {
onResize() onResize()
window.addEventListener('resize', onResize) window.addEventListener('resize', onResize)
} else { } else if (conf.width && conf.height) {
setSize(conf.width!, conf.height!) setSize(conf.width, conf.height)
} }
initPointer() initPointer()
@ -142,6 +147,9 @@ export default function useThree(): ThreeInterface {
return true return true
} }
/**
* init pointer
*/
function initPointer() { function initPointer() {
let pointerConf: PointerConfigInterface = { let pointerConf: PointerConfigInterface = {
camera: obj.camera!, camera: obj.camera!,