| | |
| | | /** |
| | | * 环穿 / 平滑轨道几何:直角多边形转圆弧路径、PIXI 绘制、设备朝向等。 |
| | | * 环穿持久化的 pathList 为中轨;外圈、内圈仅在绘制时由 buildAnnulusPaths 计算。 |
| | | * 供 basMap 编辑器与监控 MapCanvas 共用。需在页面中先于 editor.js / MapCanvas.js 引入。 |
| | | * 设备外观(CRN / RGV)绘制与 MapCanvas 贴图一致,见 drawCrnDeviceGraphics / drawRgvDeviceGraphics。 |
| | | */ |
| | | (function (global) { |
| | | 'use strict'; |
| | |
| | | return deviceForm; |
| | | } |
| | | var allDistance = getAllDistance(pathList); |
| | | var ab = annulusBandContext(rect, rect.shape || 'rect'); |
| | | deviceForm.deviceList.forEach(function (item) { |
| | | var deltaDistance = (allDistance * item.progress) / 100; |
| | | var startPoint = { |
| | |
| | | pathIndex: 0, |
| | | deltaDistance: deltaDistance |
| | | }); |
| | | var centered = shiftAnnulusPointToBandCenter( |
| | | moved.x, |
| | | moved.y, |
| | | moved.path, |
| | | ab.inset, |
| | | ab.refInside |
| | | ); |
| | | |
| | | item.x = centered.x; |
| | | item.y = centered.y; |
| | | item.x = moved.x; |
| | | item.y = moved.y; |
| | | item.path = moved.path; |
| | | item.width = item.deviceLength; |
| | | item.height = item.deviceWidth; |
| | |
| | | } |
| | | |
| | | /** |
| | | * 射线法判点是否在简单多边形内(用于找参考内点,算内向法向)。 |
| | | * @param {{ x: number, y: number }} pt |
| | | * @param {{ x: number, y: number }[]} ring |
| | | * @returns {boolean} |
| | | */ |
| | | function pointInPolygon(pt, ring) { |
| | | var inside = false; |
| | | var n = ring.length; |
| | | var i; |
| | | for (i = 0; i < n; i++) { |
| | | var a = ring[i]; |
| | | var b = ring[(i + 1) % n]; |
| | | var intersects = |
| | | a.y > pt.y !== b.y > pt.y && |
| | | pt.x < ((b.x - a.x) * (pt.y - a.y)) / (b.y - a.y + 1e-12) + a.x; |
| | | if (intersects) { |
| | | inside = !inside; |
| | | } |
| | | } |
| | | return inside; |
| | | } |
| | | |
| | | /** |
| | | * 在尖点环包围盒内采样,得到多边形内部一点,供直线段上判断「指向内侧」的法向。 |
| | | * @param {{ x: number, y: number }[]} pointList |
| | | * @returns {{ x: number, y: number }} |
| | | */ |
| | | function findInteriorRefPoint(pointList) { |
| | | var minX = Infinity; |
| | | var minY = Infinity; |
| | | var maxX = -Infinity; |
| | | var maxY = -Infinity; |
| | | var i; |
| | | for (i = 0; i < pointList.length; i++) { |
| | | var p = pointList[i]; |
| | | minX = Math.min(minX, p.x); |
| | | minY = Math.min(minY, p.y); |
| | | maxX = Math.max(maxX, p.x); |
| | | maxY = Math.max(maxY, p.y); |
| | | } |
| | | var cx = (minX + maxX) / 2; |
| | | var cy = (minY + maxY) / 2; |
| | | if (pointInPolygon({ x: cx, y: cy }, pointList)) { |
| | | return { x: cx, y: cy }; |
| | | } |
| | | var g; |
| | | for (g = 1; g <= 6; g++) { |
| | | var sj; |
| | | for (sj = 1; sj < g; sj++) { |
| | | var si; |
| | | for (si = 1; si < g; si++) { |
| | | var tx = minX + ((maxX - minX) * si) / g; |
| | | var ty = minY + ((maxY - minY) * sj) / g; |
| | | if (pointInPolygon({ x: tx, y: ty }, pointList)) { |
| | | return { x: tx, y: ty }; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | return { x: cx, y: cy }; |
| | | } |
| | | |
| | | /** |
| | | * 有向边 (ax,ay)→(bx,by) 沿环前进方向的单位内向法向量(逆时针环时内侧在前进方向左侧)。 |
| | | * @param {number} ax |
| | | * @param {number} ay |
| | |
| | | return out; |
| | | } |
| | | |
| | | // 暂时注释掉。以后再考虑加上。先不删除。 |
| | | /** |
| | | * 解析环穿单侧内缩像素距离;若传入 `precomputedSharp` 则不再重复求尖点环。 |
| | | * @param {{ width: number, height: number, shape?: string, annulusBandInset?: number, value?: string|object }} sprite |
| | |
| | | // } |
| | | |
| | | /** |
| | | * 环穿共用:尖点环、内侧参考点、inset(只算一遍尖点,避免多处重复 getSharpCornerList)。 |
| | | * 环穿共用:尖点环、inset(只算一遍尖点)。 |
| | | * @param {object} sprite x/y/width/height/shape/turningPoint/annulusBandInset |
| | | * @param {string} defaultShape |
| | | * @returns {{ sharp: object[], refInside: {x:number,y:number}, inset: number }} |
| | | * @returns {{ sharp: object[], inset: number }} |
| | | */ |
| | | function annulusBandContext(sprite, defaultShape) { |
| | | sprite.shape = sprite.shape || defaultShape; |
| | | var sharp = getSharpCornerList(sprite, defaultShape); |
| | | return { |
| | | sharp: sharp, |
| | | refInside: findInteriorRefPoint(sharp), |
| | | inset: 6 |
| | | }; |
| | | } |
| | |
| | | } |
| | | |
| | | /** |
| | | * 生成外圈与内圈 smooth pathList;内圈由正交尖点环 offset 后再 setRadius/smooth。 |
| | | * 外圈、中轨、内圈:外/内仅用于绘制;中轨为 pathList 与运动学所用。 |
| | | * @param {{ x: number, y: number, width: number, height: number, shape?: string, turningPoint?: object, annulusBandInset?: number }} sprite |
| | | * @param {string} defaultShape |
| | | * @returns {{ outerPath: object[], innerPath: object[]|null, inset: number }} |
| | | * @returns {{ outerPath: object[], innerPath: object[]|null, centerPath: object[], inset: number }} |
| | | */ |
| | | function buildOuterAndInnerPaths(sprite, defaultShape) { |
| | | function buildAnnulusPaths(sprite, defaultShape) { |
| | | var ctx = annulusBandContext(sprite, defaultShape); |
| | | var sharp = ctx.sharp; |
| | | var sharpClone = sharp.map(function (p) { |
| | |
| | | var inset = ctx.inset; |
| | | var minSide = Math.min(sprite.width, sprite.height); |
| | | if (inset <= 0 || minSide <= inset * 2 + 4) { |
| | | return { outerPath: outerPath, innerPath: null, inset: inset }; |
| | | return { outerPath: outerPath, innerPath: null, centerPath: outerPath, inset: inset }; |
| | | } |
| | | var centerSharp = offsetOrthogonalSharpRing( |
| | | sharpClone.map(function (p) { |
| | | return { x: p.x, y: p.y, direction: p.direction }; |
| | | }), |
| | | inset / 2 |
| | | ); |
| | | var centerMeta = {}; |
| | | setRadiusInPoint(centerSharp, centerMeta); |
| | | var centerPath = smoothRightAnglePath(centerSharp); |
| | | var innerSharp = offsetOrthogonalSharpRing(sharpClone, inset); |
| | | var innerMeta = {}; |
| | | setRadiusInPoint(innerSharp, innerMeta); |
| | | var innerPath = smoothRightAnglePath(innerSharp); |
| | | return { outerPath: outerPath, innerPath: innerPath, inset: inset }; |
| | | return { outerPath: outerPath, innerPath: innerPath, centerPath: centerPath, inset: inset }; |
| | | } |
| | | |
| | | /** |
| | | * 外轮廓上一点沿法向(直线段)或径向(圆弧段)内移 half,落在双轨几何中线。 |
| | | * @param {number} x |
| | | * @param {number} y |
| | | * @param {object} path 当前段 line|arc |
| | | * @param {number} inset 单侧带宽 |
| | | * @param {{ x: number, y: number }} refInside 多边形内参考点 |
| | | * @returns {{ x: number, y: number }} |
| | | */ |
| | | function shiftAnnulusPointToBandCenter(x, y, path, inset, refInside) { |
| | | var half = inset / 2; |
| | | if (half <= 0) { |
| | | return { x: x, y: y }; |
| | | } |
| | | if (path.type === 'line') { |
| | | var tx = path.x - path.startX; |
| | | var ty = path.y - path.startY; |
| | | var tlen = Math.sqrt(tx * tx + ty * ty) || 1; |
| | | var nx = -ty / tlen; |
| | | var ny = tx / tlen; |
| | | var mx = (path.startX + path.x) / 2; |
| | | var my = (path.startY + path.y) / 2; |
| | | if ((refInside.x - mx) * nx + (refInside.y - my) * ny < 0) { |
| | | nx = -nx; |
| | | ny = -ny; |
| | | } |
| | | return { x: x + nx * half, y: y + ny * half }; |
| | | } |
| | | if (path.type === 'arc') { |
| | | var ox = x - path.x; |
| | | var oy = y - path.y; |
| | | var olen = Math.sqrt(ox * ox + oy * oy) || 1; |
| | | return { x: x - (ox / olen) * half, y: y - (oy / olen) * half }; |
| | | } |
| | | return { x: x, y: y }; |
| | | } |
| | | |
| | | /** |
| | | * 将外圈 path 上坐标映射到轨道带中线(监控/条码与编辑器一致)。 |
| | | * @param {{ type: string, x: number, y: number, width: number, height: number, shape?: string, turningPoint?: object, annulusBandInset?: number }} element |
| | | * @param {number} x |
| | | * @param {number} y |
| | | * @param {object} path |
| | | * @returns {{ x: number, y: number }} |
| | | */ |
| | | function centerAnnulusBandPoint(element, x, y, path) { |
| | | if (!element || element.type !== 'annulus' || path == null) { |
| | | return { x: x, y: y }; |
| | | } |
| | | var ctx = annulusBandContext(element, element.shape || 'rect'); |
| | | return shiftAnnulusPointToBandCenter(x, y, path, ctx.inset, ctx.refInside); |
| | | } |
| | | |
| | | /** |
| | | * 环穿:把 getPositionAfterMove 等返回的 position 压到轨带中线;非环穿或无效 position 原样返回。 |
| | | * @param {{ type: string }} trackInfo |
| | | * @param {{ x: number, y: number, path?: object }|null|undefined} position |
| | | * @returns {typeof position} |
| | | */ |
| | | function applyAnnulusBandCenterToPosition(trackInfo, position) { |
| | | if (!position || !trackInfo || trackInfo.type !== 'annulus') { |
| | | return position; |
| | | } |
| | | var c = centerAnnulusBandPoint(trackInfo, position.x, position.y, position.path); |
| | | return Object.assign({}, position, { x: c.x, y: c.y }); |
| | | } |
| | | |
| | | /** |
| | | * 沿 pathList 移动后再对环穿做轨带中线修正(与 getPositionAfterMove + applyAnnulusBandCenterToPosition 等价)。 |
| | | * @param {{ type: string }} trackInfo |
| | | * @param {{ x: number, y: number }} point 起点;环穿上应在外圈 path 上 |
| | | * @param {object[]} pathList |
| | | * @param {object} path |
| | | * @param {number} deltaDistance |
| | | * @param {number} [angle] |
| | | * @returns {{ x: number, y: number, path: object, angle?: number }} 与 getPositionAfterMove 返回形态一致 |
| | | */ |
| | | function computeFinalPosition(trackInfo, point, pathList, path, deltaDistance, angle) { |
| | | var position = getPositionAfterMove({ |
| | | point: point, |
| | | pathList: pathList, |
| | | path: path, |
| | | deltaDistance: deltaDistance, |
| | | angle: angle |
| | | }); |
| | | return applyAnnulusBandCenterToPosition(trackInfo, position); |
| | | } |
| | | |
| | | /** |
| | | * 将坐标压回环穿外圈 path(直线段投影、圆弧段落到半径上)。 |
| | | * sprite 已做 centerAnnulusBandPoint 时不能直接作为 getPositionAfterMove 的起点,否则会沿「点→段终点」斜移漂移。 |
| | | * 将坐标压回环穿 pathList(中轨)当前段上。 |
| | | * @param {number} x |
| | | * @param {number} y |
| | | * @param {{ type: string, startX?: number, startY?: number, x?: number, y?: number, radius?: number }|null|undefined} path |
| | | * @returns {{ x: number, y: number }} |
| | | */ |
| | | function snapToAnnulusOuterPath(trackInfo, x, y, path) { |
| | | function snapToAnnulusPath(trackInfo, x, y, path) { |
| | | if (!path || trackInfo.type !== 'annulus') { |
| | | return { x: x, y: y }; |
| | | } |
| | |
| | | } |
| | | |
| | | /** |
| | | * 监控轨道层:已保存的 pathList 描外圈 + 按几何重算内圈再描一圈。 |
| | | * 监控轨道层:按几何算外圈、内圈并描边(pathList 为中轨,不用于此描边)。 |
| | | * @param {PIXI.Graphics} smoothedGraphics |
| | | * @param {{ pathList: object[], x: number, y: number, width: number, height: number, shape?: string, turningPoint?: object, annulusBandInset?: number }} item |
| | | * @param {string} defaultShape |
| | | */ |
| | | function strokeAnnulusDualOutline(smoothedGraphics, item, defaultShape) { |
| | | var outerPath = item && item.pathList; |
| | | if (!outerPath || !outerPath.length) { |
| | | return; |
| | | } |
| | | var tmp = { |
| | | x: item.x, |
| | | y: item.y, |
| | |
| | | turningPoint: item.turningPoint, |
| | | annulusBandInset: item.annulusBandInset |
| | | }; |
| | | var pair = buildOuterAndInnerPaths(tmp, item.shape || defaultShape); |
| | | traceSmoothedPath(smoothedGraphics, outerPath); |
| | | var pair = buildAnnulusPaths(tmp, item.shape || defaultShape); |
| | | traceSmoothedPath(smoothedGraphics, pair.outerPath); |
| | | if (pair.innerPath && pair.innerPath.length) { |
| | | traceSmoothedPath(smoothedGraphics, pair.innerPath); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 编辑器环穿填充:写回 sprite.pathList(外圈),绘制外填+内洞。 |
| | | * PIXI 中带洞填充通常不会给洞边界描边,故在 endFill 后再沿内圈 trace 一遍,与监控轨 strokeAnnulusDualOutline 一致, |
| | | * 使 trackLayer / guideLayer / selectionLayer 均能看到内圈轮廓。 |
| | | * 编辑器环穿:写回 sprite.pathList(中轨),绘制外填+内洞。 |
| | | * @param {PIXI.Graphics} smoothedGraphics |
| | | * @param {object} sprite 元素或预览 rect |
| | | * @param {string} defaultShape 如 annulusShape |
| | | */ |
| | | function startDrawSmoothedPath(smoothedGraphics, sprite, defaultShape) { |
| | | var pair = buildOuterAndInnerPaths(sprite, defaultShape); |
| | | sprite.pathList = pair.outerPath; |
| | | var pair = buildAnnulusPaths(sprite, defaultShape); |
| | | sprite.pathList = pair.centerPath; |
| | | if (!pair.innerPath || !pair.innerPath.length) { |
| | | drawSmoothedPath(smoothedGraphics, pair.outerPath); |
| | | return; |
| | |
| | | drawSmoothedPath: drawSmoothedPath, |
| | | traceSmoothedPath: traceSmoothedPath, |
| | | strokeAnnulusDualOutline: strokeAnnulusDualOutline, |
| | | centerAnnulusBandPoint: centerAnnulusBandPoint, |
| | | snapToAnnulusOuterPath: snapToAnnulusOuterPath, |
| | | snapToAnnulusPath: snapToAnnulusPath, |
| | | startDrawSmoothedPath: startDrawSmoothedPath, |
| | | getRotate: getRotate, |
| | | computeFinalPosition:computeFinalPosition, |
| | | applyAnnulusBandCenterToPosition:applyAnnulusBandCenterToPosition, |
| | | drawCrnDeviceGraphics: drawCrnDeviceGraphics, |
| | | drawRgvDeviceGraphics: drawRgvDeviceGraphics |
| | | }; |