From 154fd7439c01be152ad38ccfb04c1fd8aaec9d02 Mon Sep 17 00:00:00 2001 From: Kevin Levron Date: Fri, 16 Apr 2021 00:54:11 +0200 Subject: [PATCH] wip --- src/core/usePointer.js | 175 ----------------------- src/core/usePointer.ts | 225 +++++++++++++++++++++++++++++ src/core/useThree.js | 264 ---------------------------------- src/core/useThree.ts | 311 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 536 insertions(+), 439 deletions(-) delete mode 100644 src/core/usePointer.js create mode 100644 src/core/usePointer.ts delete mode 100644 src/core/useThree.js create mode 100644 src/core/useThree.ts diff --git a/src/core/usePointer.js b/src/core/usePointer.js deleted file mode 100644 index a8f8751..0000000 --- a/src/core/usePointer.js +++ /dev/null @@ -1,175 +0,0 @@ -import { InstancedMesh, Vector2, Vector3 } from 'three'; -import useRaycaster from './useRaycaster'; - -export default function usePointer(options) { - const { - camera, - domElement, - intersectObjects, - touch = true, - resetOnEnd = false, - resetPosition = new Vector2(0, 0), - resetPositionV3 = new Vector3(0, 0, 0), - onEnter = () => {}, - onMove = () => {}, - onLeave = () => {}, - onIntersectEnter = () => {}, - onIntersectOver = () => {}, - onIntersectMove = () => {}, - onIntersectLeave = () => {}, - onIntersectClick = () => {}, - } = options; - - const position = resetPosition.clone(); - const positionN = new Vector2(0, 0); - - const raycaster = useRaycaster({ camera }); - const positionV3 = raycaster.position; - - const obj = { - position, - positionN, - positionV3, - intersectObjects, - listeners: false, - addListeners, - removeListeners, - intersect, - }; - - return obj; - - function reset() { - position.copy(resetPosition); - positionV3.copy(resetPositionV3); - }; - - function updatePosition(event) { - let x, y; - if (event.touches && event.touches.length > 0) { - x = event.touches[0].clientX; - y = event.touches[0].clientY; - } else { - x = event.clientX; - y = event.clientY; - } - - const rect = domElement.getBoundingClientRect(); - position.x = x - rect.left; - position.y = y - rect.top; - positionN.x = (position.x / rect.width) * 2 - 1; - positionN.y = -(position.y / rect.height) * 2 + 1; - raycaster.updatePosition(positionN); - }; - - function intersect() { - if (intersectObjects.length) { - const intersects = raycaster.intersect(positionN, intersectObjects); - const offObjects = [...intersectObjects]; - const iMeshes = []; - - intersects.forEach(intersect => { - const { object } = intersect; - const { component } = object; - - // only once for InstancedMesh - if (object instanceof InstancedMesh) { - if (iMeshes.indexOf(object) !== -1) return; - iMeshes.push(object); - } - - if (!object.over) { - object.over = true; - const overEvent = { type: 'pointerover', over: true, component, intersect }; - const enterEvent = { ...overEvent, type: 'pointerenter' }; - onIntersectOver(overEvent); - onIntersectEnter(enterEvent); - if (component.onPointerOver) component.onPointerOver(overEvent); - if (component.onPointerEnter) component.onPointerEnter(enterEvent); - } - - const moveEvent = { type: 'pointermove', component, intersect }; - onIntersectMove(moveEvent); - if (component.onPointerMove) component.onPointerMove(moveEvent); - - offObjects.splice(offObjects.indexOf(object), 1); - }); - - offObjects.forEach(object => { - const { component } = object; - if (object.over) { - object.over = false; - const overEvent = { type: 'pointerover', over: false, component }; - const leaveEvent = { ...overEvent, type: 'pointerleave' }; - onIntersectOver(overEvent); - onIntersectLeave(leaveEvent); - if (component.onPointerOver) component.onPointerOver(overEvent); - if (component.onPointerLeave) component.onPointerLeave(leaveEvent); - } - }); - } - }; - - function pointerEnter(event) { - updatePosition(event); - onEnter({ type: 'pointerenter', position, positionN, positionV3 }); - }; - - function pointerMove(event) { - updatePosition(event); - onMove({ type: 'pointermove', position, positionN, positionV3 }); - intersect(); - }; - - function pointerClick(event) { - updatePosition(event); - if (intersectObjects.length) { - const intersects = raycaster.intersect(positionN, intersectObjects); - const iMeshes = []; - intersects.forEach(intersect => { - const { object } = intersect; - const { component } = object; - - // only once for InstancedMesh - if (object instanceof InstancedMesh) { - if (iMeshes.indexOf(object) !== -1) return; - iMeshes.push(object); - } - - const event = { type: 'click', component, intersect }; - onIntersectClick(event); - if (component.onClick) component.onClick(event); - }); - } - }; - - function pointerLeave() { - if (resetOnEnd) reset(); - onLeave({ type: 'pointerleave' }); - }; - - function addListeners() { - domElement.addEventListener('mouseenter', pointerEnter); - domElement.addEventListener('mousemove', pointerMove); - domElement.addEventListener('mouseleave', pointerLeave); - domElement.addEventListener('click', pointerClick); - if (touch) { - domElement.addEventListener('touchstart', pointerEnter); - domElement.addEventListener('touchmove', pointerMove); - domElement.addEventListener('touchend', pointerLeave); - } - obj.listeners = true; - }; - - function removeListeners() { - domElement.removeEventListener('mouseenter', pointerEnter); - domElement.removeEventListener('mousemove', pointerMove); - domElement.removeEventListener('mouseleave', pointerLeave); - domElement.removeEventListener('click', pointerClick); - - domElement.removeEventListener('touchstart', pointerEnter); - domElement.removeEventListener('touchmove', pointerMove); - domElement.removeEventListener('touchend', pointerLeave); - obj.listeners = false; - }; -}; diff --git a/src/core/usePointer.ts b/src/core/usePointer.ts new file mode 100644 index 0000000..85eb9ff --- /dev/null +++ b/src/core/usePointer.ts @@ -0,0 +1,225 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ +import { Camera, InstancedMesh, Intersection, Mesh, Vector2, Vector3 } from 'three' +import useRaycaster from './useRaycaster' + +export interface PointerEventInterface { + type: 'pointerenter' | 'pointermove' | 'pointerleave' | 'click' + position?: Vector2 + positionN?: Vector2 + positionV3?: Vector3 +} + +export interface PointerIntersectEventInterface { + type: 'pointerenter' | 'pointerover' | 'pointermove' | 'pointerleave' | 'click' + component: any + over?: boolean + intersect?: Intersection +} + +export type IntersectObject = Mesh | InstancedMesh + +export interface PointerConfigInterface { + camera: Camera + domElement: HTMLCanvasElement + intersectObjects: IntersectObject[] + intersectMode?: 'frame' + touch?: boolean + resetOnEnd?: boolean + resetPosition?: Vector2 + resetPositionV3?: Vector3 + onEnter?(e: PointerEventInterface): void + onMove?(e: PointerEventInterface): void + onLeave?(e: PointerEventInterface): void + onClick?(e: PointerEventInterface): void + onIntersectEnter?(e: PointerIntersectEventInterface): void + onIntersectOver?(e: PointerIntersectEventInterface): void + onIntersectMove?(e: PointerIntersectEventInterface): void + onIntersectLeave?(e: PointerIntersectEventInterface): void + onIntersectClick?(e: PointerIntersectEventInterface): void +} + +export interface PointerInterface { + position: Vector2 + positionN: Vector2 + positionV3: Vector3 + intersectObjects: IntersectObject[] + listeners: boolean + addListeners(cb: void): void + removeListeners(cb: void): void + intersect(): void +} + +export default function usePointer(options: PointerConfigInterface): PointerInterface { + const { + camera, + domElement, + intersectObjects, + touch = true, + resetOnEnd = false, + resetPosition = new Vector2(0, 0), + resetPositionV3 = new Vector3(0, 0, 0), + onEnter = () => {}, + onMove = () => {}, + onLeave = () => {}, + onClick = () => {}, + onIntersectEnter = () => {}, + onIntersectOver = () => {}, + onIntersectMove = () => {}, + onIntersectLeave = () => {}, + onIntersectClick = () => {}, + } = options + + const position = resetPosition.clone() + const positionN = new Vector2(0, 0) + + const raycaster = useRaycaster({ camera }) + const positionV3 = raycaster.position + + const obj: PointerInterface = { + position, + positionN, + positionV3, + intersectObjects, + listeners: false, + addListeners, + removeListeners, + intersect, + } + + return obj + + function reset() { + position.copy(resetPosition) + positionV3.copy(resetPositionV3) + } + + function updatePosition(event: TouchEvent | MouseEvent) { + let x, y + if (event instanceof TouchEvent && event.touches && event.touches.length > 0) { + x = (event).touches[0].clientX + y = (event).touches[0].clientY + } else { + x = (event).clientX + y = (event).clientY + } + + const rect = domElement.getBoundingClientRect() + position.x = x - rect.left + position.y = y - rect.top + positionN.x = (position.x / rect.width) * 2 - 1 + positionN.y = -(position.y / rect.height) * 2 + 1 + raycaster.updatePosition(positionN) + } + + function intersect() { + if (intersectObjects.length) { + const intersects = raycaster.intersect(positionN, intersectObjects) + const offObjects: IntersectObject[] = [...intersectObjects] + const iMeshes: InstancedMesh[] = [] + + intersects.forEach(intersect => { + const { object } = intersect + const { component } = object.userData + + // only once for InstancedMesh + if (object instanceof InstancedMesh) { + if (iMeshes.indexOf(object) !== -1) return + iMeshes.push(object) + } + + if (!object.userData.over) { + object.userData.over = true + const overEvent: PointerIntersectEventInterface = { type: 'pointerover', over: true, component, intersect } + const enterEvent: PointerIntersectEventInterface = { ...overEvent, type: 'pointerenter' } + onIntersectOver(overEvent) + onIntersectEnter(enterEvent) + component.onPointerOver?.(overEvent) + component.onPointerEnter?.(enterEvent) + } + + const moveEvent: PointerIntersectEventInterface = { type: 'pointermove', component, intersect } + onIntersectMove(moveEvent) + component.onPointerMove?.(moveEvent) + + offObjects.splice(offObjects.indexOf((object)), 1) + }) + + offObjects.forEach(object => { + const { component } = object.userData + if (object.userData.over) { + object.userData.over = false + const overEvent: PointerIntersectEventInterface = { type: 'pointerover', over: false, component } + const leaveEvent: PointerIntersectEventInterface = { ...overEvent, type: 'pointerleave' } + onIntersectOver(overEvent) + onIntersectLeave(leaveEvent) + component.onPointerOver?.(overEvent) + component.onPointerLeave?.(leaveEvent) + } + }) + } + } + + function pointerEnter(event: TouchEvent | MouseEvent) { + updatePosition(event) + onEnter({ type: 'pointerenter', position, positionN, positionV3 }) + } + + function pointerMove(event: TouchEvent | MouseEvent) { + updatePosition(event) + onMove({ type: 'pointermove', position, positionN, positionV3 }) + intersect() + } + + function pointerClick(event: TouchEvent | MouseEvent) { + updatePosition(event) + if (intersectObjects.length) { + const intersects = raycaster.intersect(positionN, intersectObjects) + const iMeshes: InstancedMesh[] = [] + intersects.forEach(intersect => { + const { object } = intersect + const { component } = object.userData + + // only once for InstancedMesh + if (object instanceof InstancedMesh) { + if (iMeshes.indexOf(object) !== -1) return + iMeshes.push(object) + } + + const event: PointerIntersectEventInterface = { type: 'click', component, intersect } + onIntersectClick(event) + component.onClick?.(event) + }) + } + onClick({ type: 'click', position, positionN, positionV3 }) + } + + function pointerLeave() { + if (resetOnEnd) reset() + onLeave({ type: 'pointerleave' }) + } + + function addListeners() { + domElement.addEventListener('mouseenter', pointerEnter) + domElement.addEventListener('mousemove', pointerMove) + domElement.addEventListener('mouseleave', pointerLeave) + domElement.addEventListener('click', pointerClick) + if (touch) { + domElement.addEventListener('touchstart', pointerEnter) + domElement.addEventListener('touchmove', pointerMove) + domElement.addEventListener('touchend', pointerLeave) + } + obj.listeners = true + } + + function removeListeners() { + domElement.removeEventListener('mouseenter', pointerEnter) + domElement.removeEventListener('mousemove', pointerMove) + domElement.removeEventListener('mouseleave', pointerLeave) + domElement.removeEventListener('click', pointerClick) + + domElement.removeEventListener('touchstart', pointerEnter) + domElement.removeEventListener('touchmove', pointerMove) + domElement.removeEventListener('touchend', pointerLeave) + obj.listeners = false + } +} diff --git a/src/core/useThree.js b/src/core/useThree.js deleted file mode 100644 index 6295902..0000000 --- a/src/core/useThree.js +++ /dev/null @@ -1,264 +0,0 @@ -import { WebGLRenderer } from 'three'; -import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; -import usePointer from './usePointer'; - -/** - * Three.js helper - */ -export default function useThree() { - // default conf - const conf = { - canvas: null, - antialias: true, - alpha: false, - autoClear: true, - orbit_ctrl: false, - pointer: false, - resize: false, - width: 300, - height: 150, - }; - - // size - const size = { - width: 1, height: 1, - wWidth: 1, wHeight: 1, - ratio: 1, - }; - - // handlers - const afterInitCallbacks = []; - let afterResizeCallbacks = []; - let beforeRenderCallbacks = []; - - const intersectObjects = []; - - // returned object - const obj = { - conf, - renderer: null, - camera: null, - cameraCtrl: null, - scene: null, - pointer: null, - size, - init, - dispose, - render, - renderC, - setSize, - onAfterInit, - onAfterResize, offAfterResize, - // onBeforeRender, offBeforeRender, - addIntersectObject, removeIntersectObject, - }; - - /** - * init three - */ - function init(params) { - if (params) { - Object.entries(params).forEach(([key, value]) => { - conf[key] = value; - }); - } - - if (!obj.scene) { - console.error('Missing Scene'); - return; - } - - if (!obj.camera) { - console.error('Missing Camera'); - return; - } - - 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); - } - - initPointer(); - - if (conf.orbit_ctrl) { - obj.orbitCtrl = new OrbitControls(obj.camera, obj.renderer.domElement); - if (conf.orbit_ctrl instanceof Object) { - Object.entries(conf.orbit_ctrl).forEach(([key, value]) => { - obj.orbitCtrl[key] = value; - }); - } - } - - afterInitCallbacks.forEach(c => c()); - - return true; - }; - - function initPointer() { - let pointerConf = { - camera: obj.camera, - domElement: obj.renderer.domElement, - intersectObjects, - }; - - if (conf.pointer && conf.pointer instanceof Object) { - pointerConf = { ...pointerConf, ...conf.pointer }; - } - - obj.pointer = usePointer(pointerConf); - if (conf.pointer || intersectObjects.length) { - obj.pointer.addListeners(); - if (conf.pointer.intersectMode === 'frame') { - onBeforeRender(() => { - obj.pointer.intersect(); - }); - } - } - } - - /** - * add after init callback - */ - function onAfterInit(callback) { - afterInitCallbacks.push(callback); - } - - /** - * add after resize callback - */ - function onAfterResize(callback) { - afterResizeCallbacks.push(callback); - } - - /** - * remove after resize callback - */ - function offAfterResize(callback) { - afterResizeCallbacks = afterResizeCallbacks.filter(c => c !== callback); - } - - /** - * add before render callback - */ - function onBeforeRender(callback) { - beforeRenderCallbacks.push(callback); - } - - /** - * remove before render callback - */ - function offBeforeRender(callback) { - beforeRenderCallbacks = beforeRenderCallbacks.filter(c => c !== callback); - } - - /** - * default render - */ - function render() { - if (obj.orbitCtrl) obj.orbitCtrl.update(); - beforeRenderCallbacks.forEach(c => c()); - obj.renderer.render(obj.scene, obj.camera); - } - - /** - * composer render - */ - function renderC() { - if (obj.orbitCtrl) obj.orbitCtrl.update(); - beforeRenderCallbacks.forEach(c => c()); - obj.composer.render(); - } - - /** - * add intersect object - */ - function addIntersectObject(o) { - 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) { - 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.orbitCtrl) obj.orbitCtrl.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; - setSize(elt.clientWidth, elt.clientHeight); - } - afterResizeCallbacks.forEach(c => c()); - } - - /** - * update renderer size and camera - */ - function setSize(width, height) { - size.width = width; - size.height = height; - size.ratio = width / height; - - obj.renderer.setSize(width, height, false); - obj.camera.aspect = size.ratio; - obj.camera.updateProjectionMatrix(); - - if (obj.composer) { - obj.composer.setSize(width, height); - } - - if (obj.camera.type === 'OrthographicCamera') { - size.wWidth = obj.camera.right - obj.camera.left; - size.wHeight = obj.camera.top - obj.camera.bottom; - } else { - const wsize = getCameraSize(); - size.wWidth = wsize[0]; size.wHeight = wsize[1]; - } - } - - /** - * calculate camera visible area size - */ - function getCameraSize() { - const vFOV = (obj.camera.fov * Math.PI) / 180; - const h = 2 * Math.tan(vFOV / 2) * Math.abs(obj.camera.position.z); - const w = h * obj.camera.aspect; - return [w, h]; - } - - return obj; -} diff --git a/src/core/useThree.ts b/src/core/useThree.ts new file mode 100644 index 0000000..dc0c22c --- /dev/null +++ b/src/core/useThree.ts @@ -0,0 +1,311 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +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' + +export interface ConfigInterface { + canvas?: HTMLCanvasElement + antialias: boolean + alpha: boolean + autoClear: boolean + orbitCtrl: boolean | Record + pointer: boolean | PointerConfigInterface + resize: boolean | 'window' + width: number + height: number + [index:string]: any +} + +export interface SizeInterface { + width: number + height: number + wWidth: number + wHeight: number + ratio: number +} + +export interface ThreeInterface { + conf: ConfigInterface + renderer?: WebGLRenderer + camera?: Camera + cameraCtrl?: OrbitControls + scene?: Scene + pointer?: PointerInterface + size: SizeInterface + composer?: EffectComposer + init(config: ConfigInterface): boolean + 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 + */ +export default function useThree(): ThreeInterface { + // default conf + const conf: ConfigInterface = { + antialias: true, + alpha: false, + autoClear: true, + orbitCtrl: false, + pointer: false, + resize: false, + width: 300, + height: 150, + } + + // size + const size: SizeInterface = { + width: 1, height: 1, + wWidth: 1, wHeight: 1, + ratio: 1, + } + + // handlers + // const afterInitCallbacks: void[] = [] + // let afterResizeCallbacks: void[] = [] + // let beforeRenderCallbacks: void[] = [] + const afterInitCallbacks: {(): void;}[] = [] + let afterResizeCallbacks: {(): void;}[] = [] + let beforeRenderCallbacks: {(): void;}[] = [] + + const intersectObjects: IntersectObject[] = [] + + // returned object + const obj: ThreeInterface = { + conf, + size, + init, + dispose, + render, + renderC, + setSize, + onAfterInit, + onAfterResize, offAfterResize, + addIntersectObject, removeIntersectObject, + } + + return obj + + /** + * init three + */ + function init(params: ConfigInterface) { + if (params) { + Object.entries(params).forEach(([key, value]) => { + conf[key] = value + }) + } + + if (!obj.scene) { + console.error('Missing Scene') + return false + } + + if (!obj.camera) { + console.error('Missing Camera') + 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) + } + + 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 + } + + 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 = (obj.camera!) + if (camera.type === 'PerspectiveCamera') { + const pCamera = (camera) + pCamera.aspect = size.ratio + pCamera.updateProjectionMatrix() + } + + if (camera.type === 'OrthographicCamera') { + const oCamera = (camera) + size.wWidth = oCamera.right - oCamera.left + size.wHeight = oCamera.top - oCamera.bottom + } else { + const wsize = getCameraSize() + size.wWidth = wsize[0]; size.wHeight = wsize[1] + } + } + + /** + * calculate camera visible area size + */ + function getCameraSize() { + const camera = (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] + } +}