2025-10-20 18:31:20 +08:00
|
|
|
import {
|
|
|
|
|
type MapAreaInfo,
|
|
|
|
|
MapAreaType,
|
|
|
|
|
type MapPen,
|
|
|
|
|
MapPointType,
|
2025-10-15 15:19:14 +08:00
|
|
|
type Point,
|
|
|
|
|
} from '@api/map';
|
2025-10-20 18:31:20 +08:00
|
|
|
import { DOOR_AREA_TYPE } from '@api/map/door-area';
|
|
|
|
|
import { LockState, s8 } from '@meta2d/core';
|
|
|
|
|
|
|
|
|
|
import type { EditorService } from '../editor.service';
|
|
|
|
|
import type { AutoStorageGenerator } from '../utils/auto-storage-generator';
|
|
|
|
|
|
|
|
|
|
export class AreaManager {
|
|
|
|
|
constructor(
|
|
|
|
|
private readonly editor: EditorService,
|
|
|
|
|
private readonly autoStorageGenerator: AutoStorageGenerator,
|
|
|
|
|
) {}
|
|
|
|
|
|
|
|
|
|
public getBoundAreas(id: string, name: 'point' | 'line', type: MapAreaType): MapPen[] {
|
|
|
|
|
if (!id) return [];
|
|
|
|
|
return this.editor.find(`area-${type}`).filter(({ area }) => {
|
|
|
|
|
if (name === 'point') return area?.points?.includes(id);
|
|
|
|
|
if (name === 'line') return area?.routes?.includes(id);
|
|
|
|
|
return false;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async addArea(p1: Point, p2: Point, type = MapAreaType.库区, id?: string): Promise<void> {
|
|
|
|
|
const w = Math.abs(p1.x - p2.x);
|
|
|
|
|
const h = Math.abs(p1.y - p2.y);
|
|
|
|
|
if (w < 50 || h < 60) return;
|
|
|
|
|
|
|
|
|
|
const points: string[] = [];
|
|
|
|
|
const routes: string[] = [];
|
|
|
|
|
|
|
|
|
|
if (!id) {
|
|
|
|
|
id = s8();
|
|
|
|
|
const selected = this.editor.store.active as MapPen[] | undefined;
|
|
|
|
|
switch (type) {
|
|
|
|
|
case MapAreaType.库区:
|
|
|
|
|
selected
|
|
|
|
|
?.filter(({ point }) => point?.type === MapPointType.动作点)
|
|
|
|
|
.forEach(({ id: pointId }) => points.push(pointId!));
|
|
|
|
|
break;
|
|
|
|
|
case MapAreaType.互斥区:
|
|
|
|
|
case MapAreaType.非互斥区:
|
|
|
|
|
case MapAreaType.约束区:
|
|
|
|
|
selected?.filter(({ point }) => point?.type).forEach(({ id: pointId }) => points.push(pointId!));
|
|
|
|
|
break;
|
|
|
|
|
case MapAreaType.描述区:
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-21 14:49:00 +08:00
|
|
|
// 门区域:自动绑定与矩形相交或端点落入矩形的路段
|
|
|
|
|
// 原逻辑仅在两端点都在矩形内时绑定;
|
|
|
|
|
// 现在扩展为:端点都在内 OR 线段与矩形任一边相交,则绑定。
|
|
|
|
|
if ((type as any) === (DOOR_AREA_TYPE as any)) {
|
2025-10-20 18:31:20 +08:00
|
|
|
const areaRect = {
|
|
|
|
|
x: Math.min(p1.x, p2.x),
|
|
|
|
|
y: Math.min(p1.y, p2.y),
|
|
|
|
|
width: Math.abs(p1.x - p2.x),
|
|
|
|
|
height: Math.abs(p1.y - p2.y),
|
|
|
|
|
};
|
|
|
|
|
const isInRect = (x: number, y: number) =>
|
|
|
|
|
x >= areaRect.x && x <= areaRect.x + areaRect.width && y >= areaRect.y && y <= areaRect.y + areaRect.height;
|
|
|
|
|
|
2025-10-21 14:49:00 +08:00
|
|
|
// 线段与矩形是否相交(含端点在内)的简易判断:
|
|
|
|
|
// 1) 任一点在矩形内
|
|
|
|
|
// 2) 与矩形四条边任意一条有线段相交
|
|
|
|
|
const segmentIntersectsRect = (
|
|
|
|
|
x1: number,
|
|
|
|
|
y1: number,
|
|
|
|
|
x2: number,
|
|
|
|
|
y2: number,
|
|
|
|
|
rx: number,
|
|
|
|
|
ry: number,
|
|
|
|
|
rw: number,
|
|
|
|
|
rh: number,
|
|
|
|
|
): boolean => {
|
|
|
|
|
const xMin = rx;
|
|
|
|
|
const xMax = rx + rw;
|
|
|
|
|
const yMin = ry;
|
|
|
|
|
const yMax = ry + rh;
|
|
|
|
|
|
|
|
|
|
const pointInRect = (x: number, y: number) => x >= xMin && x <= xMax && y >= yMin && y <= yMax;
|
|
|
|
|
if (pointInRect(x1, y1) || pointInRect(x2, y2)) return true;
|
|
|
|
|
|
|
|
|
|
// 线段相交判定(跨立实验)
|
|
|
|
|
const segmentsIntersect = (
|
|
|
|
|
ax1: number,
|
|
|
|
|
ay1: number,
|
|
|
|
|
ax2: number,
|
|
|
|
|
ay2: number,
|
|
|
|
|
bx1: number,
|
|
|
|
|
by1: number,
|
|
|
|
|
bx2: number,
|
|
|
|
|
by2: number,
|
|
|
|
|
): boolean => {
|
|
|
|
|
const d = (x: number, y: number, x1: number, y1: number, x2: number, y2: number) =>
|
|
|
|
|
(x - x1) * (y2 - y1) - (y - y1) * (x2 - x1);
|
|
|
|
|
const onSegment = (x: number, y: number, x1: number, y1: number, x2: number, y2: number) =>
|
|
|
|
|
Math.min(x1, x2) <= x && x <= Math.max(x1, x2) && Math.min(y1, y2) <= y && y <= Math.max(y1, y2);
|
|
|
|
|
const d1 = d(ax1, ay1, bx1, by1, bx2, by2);
|
|
|
|
|
const d2 = d(ax2, ay2, bx1, by1, bx2, by2);
|
|
|
|
|
const d3 = d(bx1, by1, ax1, ay1, ax2, ay2);
|
|
|
|
|
const d4 = d(bx2, by2, ax1, ay1, ax2, ay2);
|
|
|
|
|
if (((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) && ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))) return true;
|
|
|
|
|
// 共线且端点在线段投影上
|
|
|
|
|
if (d1 === 0 && onSegment(ax1, ay1, bx1, by1, bx2, by2)) return true;
|
|
|
|
|
if (d2 === 0 && onSegment(ax2, ay2, bx1, by1, bx2, by2)) return true;
|
|
|
|
|
if (d3 === 0 && onSegment(bx1, by1, ax1, ay1, ax2, ay2)) return true;
|
|
|
|
|
if (d4 === 0 && onSegment(bx2, by2, ax1, ay1, ax2, ay2)) return true;
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 矩形四条边
|
|
|
|
|
const leftX = xMin;
|
|
|
|
|
const rightX = xMax;
|
|
|
|
|
const topY = yMin;
|
|
|
|
|
const bottomY = yMax;
|
|
|
|
|
|
|
|
|
|
if (segmentsIntersect(x1, y1, x2, y2, leftX, topY, leftX, bottomY)) return true; // 左边
|
|
|
|
|
if (segmentsIntersect(x1, y1, x2, y2, rightX, topY, rightX, bottomY)) return true; // 右边
|
|
|
|
|
if (segmentsIntersect(x1, y1, x2, y2, leftX, topY, rightX, topY)) return true; // 上边
|
|
|
|
|
if (segmentsIntersect(x1, y1, x2, y2, leftX, bottomY, rightX, bottomY)) return true; // 下边
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const allRoutes = this.editor.find('route');
|
2025-10-20 18:31:20 +08:00
|
|
|
allRoutes.forEach((r) => {
|
|
|
|
|
const [a1, a2] = r.anchors ?? [];
|
|
|
|
|
if (!a1?.connectTo || !a2?.connectTo) return;
|
|
|
|
|
const pA = this.editor.getPenById(a1.connectTo) as MapPen | undefined;
|
|
|
|
|
const pB = this.editor.getPenById(a2.connectTo) as MapPen | undefined;
|
|
|
|
|
if (!pA || !pB) return;
|
|
|
|
|
const ra = this.editor.getPointRect(pA);
|
|
|
|
|
const rb = this.editor.getPointRect(pB);
|
|
|
|
|
if (!ra || !rb) return;
|
|
|
|
|
const cax = (ra.x ?? 0) + (ra.width ?? 0) / 2;
|
|
|
|
|
const cay = (ra.y ?? 0) + (ra.height ?? 0) / 2;
|
|
|
|
|
const cbx = (rb.x ?? 0) + (rb.width ?? 0) / 2;
|
|
|
|
|
const cby = (rb.y ?? 0) + (rb.height ?? 0) / 2;
|
2025-10-21 14:49:00 +08:00
|
|
|
const bothInside = isInRect(cax, cay) && isInRect(cbx, cby);
|
|
|
|
|
const intersects = segmentIntersectsRect(
|
|
|
|
|
cax,
|
|
|
|
|
cay,
|
|
|
|
|
cbx,
|
|
|
|
|
cby,
|
|
|
|
|
areaRect.x,
|
|
|
|
|
areaRect.y,
|
|
|
|
|
areaRect.width,
|
|
|
|
|
areaRect.height,
|
|
|
|
|
);
|
|
|
|
|
if (bothInside || intersects) {
|
|
|
|
|
routes.push(r.id!);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-10-20 18:31:20 +08:00
|
|
|
|
|
|
|
|
const areaInfo: MapAreaInfo = { type, points, routes };
|
|
|
|
|
if (type === MapAreaType.库区) {
|
|
|
|
|
areaInfo.inoutflag = 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pen: MapPen = {
|
|
|
|
|
id,
|
|
|
|
|
name: 'area',
|
|
|
|
|
tags: ['area', `area-${type}`],
|
|
|
|
|
label: `A${id}`,
|
|
|
|
|
x: Math.min(p1.x, p2.x),
|
|
|
|
|
y: Math.min(p1.y, p2.y),
|
|
|
|
|
width: w,
|
|
|
|
|
height: h,
|
|
|
|
|
lineWidth: 1,
|
|
|
|
|
area: areaInfo,
|
|
|
|
|
locked: LockState.None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const area = await this.editor.addPen(pen, true, true, true);
|
|
|
|
|
this.editor.bottom(area);
|
|
|
|
|
|
|
|
|
|
if (type === MapAreaType.库区 && points.length > 0) {
|
|
|
|
|
this.autoStorageGenerator.triggerAutoCreateStorageDialog(pen, points);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public updateArea(id: string, info: Partial<MapAreaInfo>): void {
|
|
|
|
|
const { area } = this.editor.getPenById(id) ?? {};
|
|
|
|
|
if (!area?.type) return;
|
|
|
|
|
const mergedArea = { ...area, ...info };
|
|
|
|
|
this.editor.setValue({ id, area: mergedArea }, { render: true, history: true, doEvent: true });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|