import { type MapAreaInfo, MapAreaType, type MapPen, MapPointType, type Point, } from '@api/map'; 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 { 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; } } // 门区域:自动绑定与矩形相交或端点落入矩形的路段 // 原逻辑仅在两端点都在矩形内时绑定; // 现在扩展为:端点都在内 OR 线段与矩形任一边相交,则绑定。 if ((type as any) === (DOOR_AREA_TYPE as any)) { 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; // 线段与矩形是否相交(含端点在内)的简易判断: // 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'); 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; 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!); } }); } 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): 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 }); } }