1
0
mirror of https://github.com/troisjs/trois.git synced 2024-11-24 12:22:03 +08:00
trois/src/core/useThree.ts

319 lines
7.5 KiB
TypeScript
Raw Normal View History

2021-04-16 06:54:11 +08:00
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'
import usePointer, { IntersectObject, PointerConfigInterface, PointerInterface } from './usePointer'
2021-04-16 07:56:52 +08:00
export interface ThreeConfigInterface {
2021-04-16 06:54:11 +08:00
canvas?: HTMLCanvasElement
antialias: boolean
alpha: boolean
autoClear: boolean
orbitCtrl: boolean | Record<string, unknown>
pointer: boolean | PointerConfigInterface
2021-04-16 07:56:52 +08:00
resize: boolean | string
width?: number
height?: number
2021-04-16 06:54:11 +08:00
[index:string]: any
}
export interface SizeInterface {
width: number
height: number
wWidth: number
wHeight: number
ratio: number
}
export interface ThreeInterface {
2021-04-16 07:56:52 +08:00
conf: ThreeConfigInterface
2021-04-19 04:27:53 +08:00
renderer: WebGLRenderer
composer?: EffectComposer
2021-04-16 06:54:11 +08:00
camera?: Camera
cameraCtrl?: OrbitControls
scene?: Scene
pointer?: PointerInterface
size: SizeInterface
2021-04-19 04:27:53 +08:00
init(): boolean
2021-04-16 06:54:11 +08:00
dispose(): void
render(): void
renderC(): void
setSize(width: number, height: number): void
onAfterInit(callback: {(): void}): void
onAfterResize(callback: {(): void}): void
offAfterResize(callback: {(): void}): void
addIntersectObject(o: IntersectObject): void
removeIntersectObject(o: IntersectObject): void
}
/**
* Three.js helper
*/
2021-04-19 04:27:53 +08:00
export default function useThree(params: ThreeConfigInterface): ThreeInterface {
2021-04-16 06:54:11 +08:00
// default conf
2021-04-16 07:56:52 +08:00
const conf: ThreeConfigInterface = {
2021-04-16 06:54:11 +08:00
antialias: true,
alpha: false,
autoClear: true,
orbitCtrl: false,
pointer: false,
resize: false,
width: 300,
height: 150,
}
2021-04-19 04:27:53 +08:00
if (params) {
Object.entries(params).forEach(([key, value]) => {
conf[key] = value
})
}
2021-04-16 06:54:11 +08:00
// size
const size: SizeInterface = {
width: 1, height: 1,
wWidth: 1, wHeight: 1,
ratio: 1,
}
// handlers
2021-04-16 10:12:41 +08:00
const afterInitCallbacks: {(): void}[] = []
let afterResizeCallbacks: {(): void}[] = []
let beforeRenderCallbacks: {(): void}[] = []
2021-04-16 06:54:11 +08:00
const intersectObjects: IntersectObject[] = []
// returned object
const obj: ThreeInterface = {
conf,
2021-04-19 04:27:53 +08:00
renderer: createRenderer(),
2021-04-16 06:54:11 +08:00
size,
init,
dispose,
render,
renderC,
setSize,
onAfterInit,
onAfterResize, offAfterResize,
addIntersectObject, removeIntersectObject,
}
return obj
/**
2021-04-19 04:27:53 +08:00
* create WebGLRenderer
2021-04-16 06:54:11 +08:00
*/
2021-04-19 04:27:53 +08:00
function createRenderer(): WebGLRenderer {
const renderer = new WebGLRenderer({ canvas: conf.canvas, antialias: conf.antialias, alpha: conf.alpha })
renderer.autoClear = conf.autoClear
return renderer
}
2021-04-16 06:54:11 +08:00
2021-04-19 04:27:53 +08:00
/**
* init three
*/
function init() {
2021-04-16 06:54:11 +08:00
if (!obj.scene) {
console.error('Missing Scene')
return false
}
if (!obj.camera) {
console.error('Missing Camera')
return false
}
if (conf.resize) {
onResize()
window.addEventListener('resize', onResize)
2021-04-19 04:27:53 +08:00
} else if (conf.width && conf.height) {
setSize(conf.width, conf.height)
2021-04-16 06:54:11 +08:00
}
initPointer()
if (conf.orbitCtrl) {
obj.cameraCtrl = new OrbitControls(obj.camera, obj.renderer.domElement)
if (conf.orbitCtrl instanceof Object) {
Object.entries(conf.orbitCtrl).forEach(([key, value]) => {
// @ts-ignore
obj.cameraCtrl[key] = value
})
}
}
afterInitCallbacks.forEach(c => c())
return true
}
2021-04-19 04:27:53 +08:00
/**
* init pointer
*/
2021-04-16 06:54:11 +08:00
function initPointer() {
let pointerConf: PointerConfigInterface = {
camera: obj.camera!,
domElement: obj.renderer!.domElement,
intersectObjects,
}
if (conf.pointer && conf.pointer instanceof Object) {
pointerConf = { ...pointerConf, ...conf.pointer }
}
const pointer = obj.pointer = usePointer(pointerConf)
if (conf.pointer || intersectObjects.length) {
pointer.addListeners()
if (pointerConf.intersectMode === 'frame') {
onBeforeRender(pointer.intersect)
}
}
}
/**
* add after init callback
*/
function onAfterInit(callback: {(): void}) {
afterInitCallbacks.push(callback)
}
/**
* add after resize callback
*/
function onAfterResize(callback: {(): void}) {
afterResizeCallbacks.push(callback)
}
/**
* remove after resize callback
*/
function offAfterResize(callback: {(): void}) {
afterResizeCallbacks = afterResizeCallbacks.filter(c => c !== callback)
}
/**
* add before render callback
*/
function onBeforeRender(callback: {(): void}) {
beforeRenderCallbacks.push(callback)
}
/**
* remove before render callback
*/
// function offBeforeRender(callback: void) {
// beforeRenderCallbacks = beforeRenderCallbacks.filter(c => c !== callback)
// }
/**
* default render
*/
function render() {
if (obj.cameraCtrl) obj.cameraCtrl.update()
beforeRenderCallbacks.forEach(c => c())
obj.renderer!.render(obj.scene!, obj.camera!)
}
/**
* composer render
*/
function renderC() {
if (obj.cameraCtrl) obj.cameraCtrl.update()
beforeRenderCallbacks.forEach(c => c())
obj.composer!.render()
}
/**
* add intersect object
*/
function addIntersectObject(o: IntersectObject) {
if (intersectObjects.indexOf(o) === -1) {
intersectObjects.push(o)
}
// add listeners if needed
if (obj.pointer && !obj.pointer.listeners) {
obj.pointer.addListeners()
}
}
/**
* remove intersect object
*/
function removeIntersectObject(o: IntersectObject) {
const i = intersectObjects.indexOf(o)
if (i !== -1) {
intersectObjects.splice(i, 1)
}
// remove listeners if needed
if (obj.pointer && !conf.pointer && intersectObjects.length === 0) {
obj.pointer.removeListeners()
}
}
/**
* remove listeners and dispose
*/
function dispose() {
beforeRenderCallbacks = []
window.removeEventListener('resize', onResize)
if (obj.pointer) obj.pointer.removeListeners()
if (obj.cameraCtrl) obj.cameraCtrl.dispose()
if (obj.renderer) obj.renderer.dispose()
}
/**
* resize listener
*/
function onResize() {
if (conf.resize === 'window') {
setSize(window.innerWidth, window.innerHeight)
} else {
const elt = obj.renderer!.domElement.parentNode as Element
if (elt) setSize(elt.clientWidth, elt.clientHeight)
}
afterResizeCallbacks.forEach(c => c())
}
/**
* update renderer size and camera
*/
function setSize(width: number, height: number) {
size.width = width
size.height = height
size.ratio = width / height
obj.renderer!.setSize(width, height, false)
// already done in EffectComposer
// if (obj.composer) {
// obj.composer.setSize(width, height)
// }
const camera = (<Camera>obj.camera!)
if (camera.type === 'PerspectiveCamera') {
const pCamera = (<PerspectiveCamera>camera)
pCamera.aspect = size.ratio
pCamera.updateProjectionMatrix()
}
if (camera.type === 'OrthographicCamera') {
const oCamera = (<OrthographicCamera>camera)
size.wWidth = oCamera.right - oCamera.left
size.wHeight = oCamera.top - oCamera.bottom
} else {
const wsize = getCameraSize()
2021-04-16 10:12:41 +08:00
size.wWidth = wsize[0]
size.wHeight = wsize[1]
2021-04-16 06:54:11 +08:00
}
}
/**
* calculate camera visible area size
*/
function getCameraSize() {
const camera = (<PerspectiveCamera>obj.camera!)
const vFOV = (camera.fov * Math.PI) / 180
const h = 2 * Math.tan(vFOV / 2) * Math.abs(camera.position.z)
const w = h * camera.aspect
return [w, h]
}
}