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

286 lines
6.7 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'
2021-04-27 00:37:45 +08:00
import usePointer, { IntersectObject, PointerConfigInterface, PointerPublicConfigInterface, PointerInterface } from './usePointer'
2021-04-16 06:54:11 +08:00
2021-04-21 07:10:20 +08:00
export interface SizeInterface {
width: number
height: number
wWidth: number
wHeight: number
ratio: number
}
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>
2021-04-27 00:37:45 +08:00
pointer: boolean | PointerPublicConfigInterface
2021-04-16 07:56:52 +08:00
resize: boolean | string
width?: number
height?: number
2021-04-21 07:10:20 +08:00
onResize?(size: SizeInterface): void
2021-04-16 06:54:11 +08:00
[index:string]: any
}
export interface ThreeInterface {
2021-04-21 07:10:20 +08:00
config: 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
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-21 07:10:20 +08:00
// default config
const config: 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]) => {
2021-04-21 07:10:20 +08:00
config[key] = value
2021-04-19 04:27:53 +08:00
})
}
2021-04-16 06:54:11 +08:00
// size
const size: SizeInterface = {
width: 1, height: 1,
wWidth: 1, wHeight: 1,
ratio: 1,
}
2021-04-21 07:10:20 +08:00
const beforeRenderCallbacks: {(): void}[] = []
2021-04-16 06:54:11 +08:00
const intersectObjects: IntersectObject[] = []
2021-04-21 07:10:20 +08:00
const renderer = createRenderer()
2021-04-16 06:54:11 +08:00
// returned object
const obj: ThreeInterface = {
2021-04-21 07:10:20 +08:00
config,
renderer,
2021-04-16 06:54:11 +08:00
size,
init,
dispose,
render,
renderC,
setSize,
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 {
2021-04-21 07:10:20 +08:00
const renderer = new WebGLRenderer({ canvas: config.canvas, antialias: config.antialias, alpha: config.alpha })
renderer.autoClear = config.autoClear
2021-04-19 04:27:53 +08:00
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
}
2021-04-21 07:10:20 +08:00
if (config.resize) {
2021-04-16 06:54:11 +08:00
onResize()
window.addEventListener('resize', onResize)
2021-04-21 07:10:20 +08:00
} else if (config.width && config.height) {
setSize(config.width, config.height)
2021-04-16 06:54:11 +08:00
}
initPointer()
2021-04-21 07:10:20 +08:00
if (config.orbitCtrl) {
const cameraCtrl = new OrbitControls(obj.camera, obj.renderer.domElement)
if (config.orbitCtrl instanceof Object) {
Object.entries(config.orbitCtrl).forEach(([key, value]) => {
2021-04-16 06:54:11 +08:00
// @ts-ignore
2021-04-21 07:10:20 +08:00
cameraCtrl[key] = value
2021-04-16 06:54:11 +08:00
})
}
2021-04-21 07:10:20 +08:00
onBeforeRender(() => { cameraCtrl.update() })
obj.cameraCtrl = cameraCtrl
2021-04-16 06:54:11 +08:00
}
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,
}
2021-04-21 07:10:20 +08:00
if (config.pointer && config.pointer instanceof Object) {
pointerConf = { ...pointerConf, ...config.pointer }
2021-04-16 06:54:11 +08:00
}
const pointer = obj.pointer = usePointer(pointerConf)
2021-04-21 07:10:20 +08:00
if (config.pointer || intersectObjects.length) {
2021-04-16 06:54:11 +08:00
pointer.addListeners()
if (pointerConf.intersectMode === 'frame') {
onBeforeRender(pointer.intersect)
}
}
}
/**
* add before render callback
*/
2021-04-20 23:50:06 +08:00
function onBeforeRender(cb: {(): void}) {
beforeRenderCallbacks.push(cb)
2021-04-16 06:54:11 +08:00
}
/**
* default render
*/
function render() {
2021-04-21 07:10:20 +08:00
// if (obj.cameraCtrl) obj.cameraCtrl.update()
2021-04-16 06:54:11 +08:00
beforeRenderCallbacks.forEach(c => c())
obj.renderer!.render(obj.scene!, obj.camera!)
}
/**
* composer render
*/
function renderC() {
2021-04-21 07:10:20 +08:00
// if (obj.cameraCtrl) obj.cameraCtrl.update()
2021-04-16 06:54:11 +08:00
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
2021-04-21 07:10:20 +08:00
if (obj.pointer && !config.pointer && intersectObjects.length === 0) {
2021-04-16 06:54:11 +08:00
obj.pointer.removeListeners()
}
}
/**
* remove listeners and dispose
*/
function dispose() {
2021-04-21 07:10:20 +08:00
// beforeRenderCallbacks = []
2021-04-16 06:54:11 +08:00
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() {
2021-04-21 07:10:20 +08:00
if (config.resize === 'window') {
2021-04-16 06:54:11 +08:00
setSize(window.innerWidth, window.innerHeight)
} else {
const elt = obj.renderer!.domElement.parentNode as Element
if (elt) setSize(elt.clientWidth, elt.clientHeight)
}
2021-04-21 07:10:20 +08:00
config.onResize?.(size)
2021-04-16 06:54:11 +08:00
}
/**
* 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]
}
}