mirror of
https://github.com/troisjs/trois.git
synced 2024-11-23 20:02:32 +08:00
wip
This commit is contained in:
parent
6bbb80710d
commit
154fd7439c
@ -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;
|
||||
};
|
||||
};
|
225
src/core/usePointer.ts
Normal file
225
src/core/usePointer.ts
Normal file
@ -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 = (<TouchEvent>event).touches[0].clientX
|
||||
y = (<TouchEvent>event).touches[0].clientY
|
||||
} else {
|
||||
x = (<MouseEvent>event).clientX
|
||||
y = (<MouseEvent>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((<IntersectObject>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
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
311
src/core/useThree.ts
Normal file
311
src/core/useThree.ts
Normal file
@ -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<string, unknown>
|
||||
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 = (<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()
|
||||
size.wWidth = wsize[0]; size.wHeight = wsize[1]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user