支持六面方向拖拽、反向、切面填充.
代码:
MouseHandler代码:
import { Vector2 } from "three";
import * as THREE from "three";
import {EventDispatcher} from '../EventDispatcher'
type MouseEventTypeMap = {
objectDragstart: {};
objectDrag: {};
objectDragend: { };
dragstart: {};
drag: {};
dragend: {};
objectLeave: {
intersections: THREE.Intersection[];
};
objectEnter: {
intersections: THREE.Intersection[];
};
pointerdown: {
button: number; // 0 左键,1 中间键,2 右键
};
pointermove: {};
pointerup: {};
pointerupslow:{},
select: {
intersections: THREE.Intersection[];
};
};
type CamelToSnakeCase<S extends string> = S extends `${infer T}${infer U}`
? `${T extends Capitalize<T> ? "-" : ""}${Lowercase<T>}${CamelToSnakeCase<U>}`
: S;
type KeysToSnakeCase<T extends { [K: string]: any }> = {
[Key in keyof T as CamelToSnakeCase<string & Key>]: T[Key];
};
export type MouseEventTypeMapCamel = KeysToSnakeCase<MouseEventTypeMap>;
type HoverCheckHandle = (
nextIntersection: THREE.Intersection[],
prevIntersection: THREE.Intersection[]
) => boolean;
enum POINTER_STATE{
NONE=0,
DOWN=1,
OBJECT_DRAG=2,
DRAG=3
}
export class MouseHandler extends EventDispatcher<MouseEventTypeMapCamel> {
coord = new Vector2();
pointDown = new Vector2();
point = new Vector2();
pointUp = new Vector2();
pointDelta = new Vector2();
pointOffset = new Vector2();
pointLast = new Vector2();
_rect?: DOMRect;
type: string = "";
pointerEvent?: PointerEvent;
raycaster: THREE.Raycaster = new THREE.Raycaster();
selectedIntersections: THREE.Intersection[] = [];
hoverIntersections: THREE.Intersection[] = [];
prevHoverIntersections: THREE.Intersection[] = [];
selectObjects: THREE.Object3D[] = [];
hoverObjects: THREE.Object3D[] = [];
selectRecursive: boolean = false;
hoverRecursive: boolean = false;
enableRaycaster: boolean = true;
enabled=true
pointerState: number = 0;
_onHoverCheck: HoverCheckHandle = (nextIntersection, prevIntersection) => {
return nextIntersection[0].object!==prevIntersection[0]?.object
};
constructor(public domElement: HTMLElement,public win: Document|HTMLElement|Window,public _camera: THREE.Camera|(()=>THREE.Camera)) {
super();
}
get camera(){
return typeof this._camera==='function'?this._camera():this._camera;
}
get clientRect() {
this._rect ??= this.domElement.getBoundingClientRect();
return this._rect;
}
onHoverCheck(hoverCheckHandle: HoverCheckHandle) {
this._onHoverCheck = hoverCheckHandle;
}
updateRaycaster(){
this.raycaster.setFromCamera(this.coord, this.camera);
}
rayObjects(
objects: THREE.Object3D[],
recursive: boolean = false,
intersections:THREE.Intersection[] = []
) {
intersections.length = 0;
this.updateRaycaster()
this.raycaster.firstHitOnly=false
this.raycaster.intersectObjects(objects, recursive, intersections);
return intersections;
}
rayFirstObjects(
objects: THREE.Object3D[],
recursive: boolean = false,
intersections:THREE.Intersection[] = []
) {
intersections.length = 0;
this.updateRaycaster()
this.raycaster.firstHitOnly=true
this.raycaster.intersectObjects(objects, recursive, intersections);
return intersections;
}
rayObject(
object: THREE.Object3D,
recursive: boolean = false,
intersections:THREE.Intersection[] =[]
) {
intersections.length = 0;
this.updateRaycaster();
this.raycaster.firstHitOnly=true
this.raycaster.intersectObject(object, recursive,intersections);
return intersections;
}
getMouse(e: MouseEvent, out: Vector2) {
const x=e.pageX-window.scrollX
const y=e.pageY-window.scrollY
return out.set(
x - this.clientRect.left,
y - this.clientRect.top
);
}
getCoord(point: Vector2, out: Vector2) {
return out.set(
(point.x / this.clientRect.width) * 2 - 1,
(-point.y / this.clientRect.height) * 2 + 1
);
}
handleAnimationMove=()=>{
const e = this.pointerEvent!;
this.getMouse(e, this.point)
this.getCoord(this.point, this.coord)
this.pointOffset.subVectors(this.point,this.pointDown)
this.pointDelta.subVectors(this.point,this.pointLast)
this.pointLast.copy(this.point)
if(this.pointerState!==POINTER_STATE.NONE){
if(this.selectedIntersections.length){
if(this.pointerState===POINTER_STATE.DOWN){
this.pointerState=POINTER_STATE.OBJECT_DRAG
this.dispatchEvent({type:'object-dragstart'})
}else{
this.dispatchEvent({type:'object-drag'})
}
}else{
if(this.pointerState===POINTER_STATE.DOWN){
this.pointerState=POINTER_STATE.DRAG
this.dispatchEvent({type:'dragstart'})
}else{
this.dispatchEvent({type:'drag'})
}
}
}else{
this.rayFirstObjects(this.getHoverObjects(),this.hoverRecursive,this.hoverIntersections)
if(this.hoverIntersections.length){
if(this._onHoverCheck(this.hoverIntersections,this.prevHoverIntersections)){
if(this.prevHoverIntersections.length){
this.dispatchEvent({type:'object-leave',intersections:this.prevHoverIntersections.slice()})
this.prevHoverIntersections.length=0
}
this.dispatchEvent({type:'object-enter',intersections:this.hoverIntersections.slice()})
this.prevHoverIntersections=this.hoverIntersections.slice()
}
}else{
if(this.prevHoverIntersections.length){
this.dispatchEvent({type:'object-leave',intersections:this.prevHoverIntersections.slice()})
this.prevHoverIntersections.length=0
}
}
}
this.dispatchEvent({type:'pointermove'})
this.animationMoveId=0
}
animationMoveId=0
requestAnimationMove=()=>{
if(this.animationMoveId){
return
}
this.animationMoveId=requestAnimationFrame(this.handleAnimationMove)
}
getSelectObject(){
return this.selectObjects
}
getHoverObjects(){
return this.hoverObjects
}
_pointerdownslowTimeId=0
handlePointer = (e: PointerEvent) => {
if(!this.enabled){
return
}
const type = e.type;
this.type = type;
this.pointerEvent=e;
if (type === 'pointerdown') {
e.preventDefault()
this.pointerState=POINTER_STATE.DOWN
this.getMouse(e, this.pointDown)
this.getCoord(this.pointDown, this.coord)
this.pointLast.copy(this.pointDown)