import { Matrix4, Vector3 } from "three"; import { Container } from "../packages"; import { openShapeDrag } from "../shared"; import mitt from "mitt"; export type CameraQueryPluginProps = { move?: boolean; wheel?: boolean; bound?: number[]; padding?: number | number[]; size?: number[]; }; export class CameraQueryPlugin { bound: number[]; padding: number[]; tree: Container; props: Omit; cameraMat: Matrix4; clipMat: Matrix4 = new Matrix4(); bus = mitt<{ cameraChange: number[] }>(); constructor(props: CameraQueryPluginProps = {}) { this.props = props; this.props.move = props.move || false; this.props.wheel = props.wheel || false; this.cameraMat = new Matrix4(); if (props.size) { this.setSize(props.size[0], props.size[1]); } if (props.bound) { this.setBound(props.bound, props.padding); } } private dragDestory?: () => void; disableMove() { this.props.move = false; this.dragDestory && this.dragDestory(); } enableMove() { this.disableMove(); this.props.move = true; if (!this.tree) return; const { stage } = this.tree; this.dragDestory = openShapeDrag( stage, { readyHandler: () => this.cameraMat.clone(), moveHandler: (move, initMat) => { const mat = this.clipMat.clone().multiply(initMat).invert(); const v2 = new Vector3(move[0], move[1], 0).applyMatrix4(mat); this.move([v2.x, v2.y], initMat); }, }, false ); } private wheelDestory?: () => void; disableWheel() { this.props.wheel = false; this.wheelDestory && this.wheelDestory(); } enableWheel() { this.disableWheel(); this.props.wheel = true; if (!this.tree) return; const { config: { dom }, } = this.tree; const whellHandler = (ev: WheelEvent) => { const scale = 1 - ev.deltaY / 1000; const center = new Vector3(ev.offsetX, ev.offsetY, 0).applyMatrix4( this.clipMat.clone().multiply(this.cameraMat).invert() ); this.scale([center.x, center.y], scale); }; dom.addEventListener("wheel", whellHandler); this.wheelDestory = () => dom.removeEventListener("wheel", whellHandler); } move(movePosition: number[], initMat = this.cameraMat) { this.mutMat( new Matrix4().setPosition(movePosition[0], movePosition[1], 0), initMat ); } scale(center: number[], scale: number, initMat = this.cameraMat) { this.mutMat( new Matrix4() .makeTranslation(center[0], center[1], 0) .multiply( new Matrix4() .makeScale(scale, scale, 1) .multiply(new Matrix4().makeTranslation(-center[0], -center[1], 0)) ), initMat ); } rotate(center: number[], angleRad: number, initMat = this.cameraMat) { this.mutMat( new Matrix4() .makeTranslation(center[0], center[1], 0) .multiply( new Matrix4() .makeRotationAxis(new Vector3(0, 0, 1), angleRad) .multiply(new Matrix4().makeTranslation(-center[0], -center[1], 0)) ), initMat ); } mutMat(mat: number[] | Matrix4, initMat = this.cameraMat) { if (mat instanceof Matrix4) { initMat = initMat.clone().multiply(mat); } else { initMat = initMat.multiply(new Matrix4().fromArray(mat)); } this.setCameraMat(initMat); this.bus.emit("cameraChange", this.cameraMat.toArray()); } setCameraMat(mat: number[] | Matrix4) { if (mat instanceof Matrix4) { this.cameraMat = mat; } else { this.cameraMat.fromArray(mat); } this.update(); } autoBound(padding?: number | number[], stageNames = [".adsord-point"]) { const positions = stageNames.flatMap((item) => this.tree.stage.find(item).map((s) => { return s.position(); }) ); if (positions.length < 2) return; const bound = [ Number.MAX_VALUE, Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE, ]; for (const position of positions) { bound[0] = Math.min(bound[0], position.x); bound[1] = Math.min(bound[1], position.y); bound[2] = Math.max(bound[2], position.x); bound[3] = Math.max(bound[3], position.y); } this.setBound(bound, padding); } setBound(bound: number[], padding?: number | number[]) { padding = !Array.isArray(padding) ? [padding || 0, padding || 0] : padding; if (padding.length === 1) { padding.push(padding[0]); } const realWidth = bound[2] - bound[0]; const realHeight = bound[3] - bound[1]; const screenWidth = this.tree.stage.width(); const screenHeight = this.tree.stage.height(); // testPoint(this.tree.stage, [ // [bound[0], bound[1]], // [bound[2], bound[3]], // ]); // 计算包含padding的新屏幕尺寸 const effectiveWidth = this.tree.stage.width() - padding[0] * 2; const effectiveHeight = this.tree.stage.height() - padding[1] * 2; // 计算缩放比例 const scaleX = effectiveWidth / realWidth; const scaleY = effectiveHeight / realHeight; const scale = Math.min(scaleX, scaleY); // 选择较小的比例以保持内容比例 const offsetX = (screenWidth - realWidth * scale) / 2 - bound[0] * scale; const offsetY = (screenHeight - realHeight * scale) / 2 - bound[1] * scale; // 创建矩阵并应用缩放 const matrix = new Matrix4() .scale(new Vector3(scale, scale, 1)) .setPosition(offsetX, offsetY, 0); this.bound = bound; this.padding = padding; this.clipMat = matrix; this.update(); } setSize(width: number, height: number) { this.tree.stage.width(width); this.tree.stage.height(height); if (this.bound) { this.setBound(this.bound, this.padding); } } setTree(tree: Container) { this.tree = tree; if (this.props.move) { this.enableMove(); } if (this.props.wheel) { this.enableWheel(); } } update() { this.tree.updateViewMat(this.clipMat.clone().multiply(this.cameraMat)); } }