UNPKG

@leolee9086/my-pat-loader

Version:

AutoCAD PAT(填充图案)文件解析和线段生成工具

1,036 lines (863 loc) 31 kB
/** * @module patCalculatorUltra * 极致性能版本的PAT图案计算模块,在patCalculatorOptimized基础上进一步极端优化 */ // 常量和缓存 - 减少重复计算 const TWO_PI = 2 * Math.PI; const TO_RADIANS = Math.PI / 180; const TO_DEGREES = 180 / Math.PI; const EPSILON = 1e-10; // 特殊角度三角函数值预计算缓存 const COSINE_CACHE = new Float64Array(361); // 0-360度 const SINE_CACHE = new Float64Array(361); // 初始化三角函数缓存 for (let angle = 0; angle <= 360; angle++) { const rad = angle * TO_RADIANS; COSINE_CACHE[angle] = Math.cos(rad); SINE_CACHE[angle] = Math.sin(rad); } // 预分配重用对象 - 减少垃圾回收 const _vec2 = {x: 0, y: 0}; const _vec2b = {x: 0, y: 0}; const _point1 = {x: 0, y: 0}; const _point2 = {x: 0, y: 0}; const _tempVec = {x: 0, y: 0}; // 线段端点对象池和初始容量 const _clippedLine = [0, 0, 0, 0]; const _linePool = []; const _maxPoolSize = 5000; // 增加池大小以处理更复杂的图案 // 线段定义缓存 - 为了避免重复计算相同角度的线段 const _lineDefCache = new Map(); // 键为角度+delta组合, 值为预计算的方向向量等 const _maxCacheSize = 100; // 最多缓存100个不同的线段定义 /** * 高性能向量运算 */ const Vec2 = { set(vec, x, y) { vec.x = x; vec.y = y; return vec; }, copy(target, source) { target.x = source.x; target.y = source.y; return target; }, // 使用预计算的三角函数表进行旋转 rotateByDegrees(vec, degrees) { // 规范化角度到0-360范围 const normDegrees = Math.round(((degrees % 360) + 360) % 360); const cosVal = COSINE_CACHE[normDegrees]; const sinVal = SINE_CACHE[normDegrees]; const x = vec.x; const y = vec.y; vec.x = x * cosVal - y * sinVal; vec.y = x * sinVal + y * cosVal; return vec; }, // 传统旋转,用于非整数角度 rotate(vec, sinAngle, cosAngle) { const x = vec.x; const y = vec.y; vec.x = x * cosAngle - y * sinAngle; vec.y = x * sinAngle + y * cosAngle; return vec; }, scale(vec, scalar) { vec.x *= scalar; vec.y *= scalar; return vec; }, add(vec, dx, dy) { vec.x += dx; vec.y += dy; return vec; }, addVec(target, v1, v2) { target.x = v1.x + v2.x; target.y = v1.y + v2.y; return target; }, // 使用位移批量创建平行向量 createParallelVectors(baseX, baseY, dirX, dirY, perpDirX, perpDirY, count, stepDelta, horizDelta) { const results = new Array(count); // 初始位置 let currentX = baseX; let currentY = baseY; for (let i = 0; i < count; i++) { // 创建一个新点而不是修改基础点 results[i] = {x: currentX, y: currentY}; // 位移到下一个位置 currentX += perpDirX * stepDelta + dirX * horizDelta; currentY += perpDirY * stepDelta + dirY * horizDelta; } return results; }, dist(v1, v2) { const dx = v2.x - v1.x; const dy = v2.y - v1.y; return Math.sqrt(dx * dx + dy * dy); }, lerp(out, v1, v2, t) { out.x = v1.x + (v2.x - v1.x) * t; out.y = v1.y + (v2.y - v1.y) * t; return out; } }; /** * 获取或创建线段定义缓存 * @param {number} angle - 角度(度) * @param {Array} delta - 增量数组 [dx, dy] * @param {number} rotationDegrees - 额外旋转角度 * @returns {object} 缓存的线段定义 */ function getOrCreateLineDef(angle, delta, rotationDegrees) { // 创建缓存键 const cacheKey = `${angle}_${delta[0]}_${delta[1]}_${rotationDegrees}`; // 检查缓存 if (_lineDefCache.has(cacheKey)) { return _lineDefCache.get(cacheKey); } // 如果缓存太大,删除一些旧条目 if (_lineDefCache.size >= _maxCacheSize) { // 删除第一个条目(最旧的) const firstKey = _lineDefCache.keys().next().value; _lineDefCache.delete(firstKey); } // 计算总旋转角度(确保角度在0-360范围内) const totalAngle = ((angle + rotationDegrees) % 360 + 360) % 360; // 将角度转换为弧度 const angleRad = totalAngle * TO_RADIANS; // 直接计算方向向量,不使用缓存表 const dirX = Math.cos(angleRad); const dirY = Math.sin(angleRad); // 计算垂直方向向量 const perpDirX = -dirY; const perpDirY = dirX; // 创建新的线段定义 const lineDef = { dirX, dirY, perpDirX, perpDirY, deltaX: delta[0], deltaY: delta[1] }; // 存入缓存 _lineDefCache.set(cacheKey, lineDef); return lineDef; } /** * 从对象池获取线段对象 */ function getLineFromPool() { if (_linePool.length > 0) { return _linePool.pop(); } return { start: {x: 0, y: 0}, end: {x: 0, y: 0} }; } /** * 回收线段对象到池中 */ function recycleLines(lines) { for (let i = 0; i < lines.length; i++) { if (_linePool.length < _maxPoolSize) { _linePool.push(lines[i]); } } } /** * 计算PAT图案线条 - 极致优化版本 * * @param {object} parsedPatData - 解析后的PAT数据对象 * @param {object} boundary - 矩形边界 * @param {number} [scale=1.0] - 缩放因子 * @param {number} [rotation=0.0] - 旋转角度(度) * @param {Array<number>} [offset=[0,0]] - [x,y]偏移 * @returns {Array<object>} 线段数组 */ export function computePatternLines(parsedPatData, boundary, scale = 1.0, rotation = 0.0, offset = [0, 0]) { // 快速验证输入 if (!parsedPatData?.linesDefs?.length || !boundary || typeof boundary.minX !== 'number' || typeof boundary.minY !== 'number' || typeof boundary.maxX !== 'number' || typeof boundary.maxY !== 'number' || boundary.minX >= boundary.maxX || boundary.minY >= boundary.maxY) { return []; } // 预计算常用值 const { minX, minY, maxX, maxY } = boundary; const boundaryDiagonal = Math.sqrt((maxX - minX) ** 2 + (maxY - minY) ** 2); const centerX = (minX + maxX) * 0.5; const centerY = (minY + maxY) * 0.5; const [offsetX, offsetY] = offset; // 扩展边界,确保捕获边缘线段 // 使用边界对角线长度的一定比例作为扩展距离,确保足够大以覆盖边缘线段 const boundaryExtend = Math.max(boundaryDiagonal * 0.1, 10); // 至少10个单位,或对角线的10% const extendedBoundary = { minX: minX - boundaryExtend, minY: minY - boundaryExtend, maxX: maxX + boundaryExtend, maxY: maxY + boundaryExtend }; // 规范化旋转角度到0-360 const normRotation = Math.round(((rotation % 360) + 360) % 360); // 估算结果数组容量,增加容量以确保不会漏掉线段 const estimatedLines = parsedPatData.linesDefs.length * Math.ceil(boundaryDiagonal / 8); const allGeneratedLines = []; allGeneratedLines.capacity = Math.min(30000, estimatedLines); // 使用Map缓存已处理的线定义,避免重复计算 const processedLines = new Map(); // 处理每个线条定义 for (let i = 0; i < parsedPatData.linesDefs.length; i++) { const lineDef = parsedPatData.linesDefs[i]; const { angle, origin, delta, dashes } = lineDef; // 使用缓存中的预计算线段定义 const cachedLineDef = getOrCreateLineDef(angle, delta, rotation); const { dirX, dirY, perpDirX, perpDirY, deltaX, deltaY } = cachedLineDef; // 生成标准化的虚线定义 const scaledDashes = prepareScaledDashes(dashes, scale); // 使用向量对象表示原点并应用旋转和偏移 Vec2.set(_vec2, origin[0] * scale, origin[1] * scale); // 应用整体图案旋转 - 确保与原始算法一致 const rotationRad = rotation * TO_RADIANS; Vec2.rotate(_vec2, Math.sin(rotationRad), Math.cos(rotationRad)); // 应用偏移 Vec2.add(_vec2, offsetX, offsetY); // 提取旋转后的原点坐标 const rotatedOriginX = _vec2.x; const rotatedOriginY = _vec2.y; // 如果deltaY接近0,只生成一条线 if (Math.abs(deltaY * scale) < EPSILON) { generateSingleLine( rotatedOriginX, rotatedOriginY, dirX, dirY, scaledDashes, extendedBoundary, // 使用扩展边界 boundaryDiagonal, allGeneratedLines ); continue; } // 高效计算平行线族 generateParallelLineFamily( rotatedOriginX, rotatedOriginY, dirX, dirY, perpDirX, perpDirY, deltaX * scale, deltaY * scale, scaledDashes, extendedBoundary, // 使用扩展边界 boundaryDiagonal, centerX, centerY, allGeneratedLines ); } // 过滤掉完全位于扩展区域的线段,只保留与原始边界相交的线段 filterLinesByOriginalBoundary(allGeneratedLines, boundary); return allGeneratedLines; } /** * 过滤线段,确保只保留与原始边界相交的线段 */ function filterLinesByOriginalBoundary(lines, originalBoundary) { // 放宽边界,确保边缘线段被保留 const { minX, minY, maxX, maxY } = originalBoundary; const margin = Math.max((maxX - minX + maxY - minY) * 0.01, 1); // 使用更小的边距,避免过度放宽 const relaxedBoundary = { minX: minX - margin, minY: minY - margin, maxX: maxX + margin, maxY: maxY + margin }; // 检查线段是否与放宽边界相交 for (let i = lines.length - 1; i >= 0; i--) { const line = lines[i]; const { start, end } = line; // 快速检查 - 任何端点在扩展边界内,保留 if (pointInRect(start, relaxedBoundary) || pointInRect(end, relaxedBoundary)) { continue; } // 如果两个端点在边界外,检查线段是否穿过边界 // 使用Cohen-Sutherland算法快速判断 const startCode = computeOutCode(start, originalBoundary); const endCode = computeOutCode(end, originalBoundary); // 如果按位与为0,线段可能穿过边界 if ((startCode & endCode) === 0) { continue; } // 额外检查 - 线段是否与边界相交 if (segmentIntersectsRectangle(start, end, relaxedBoundary.minX, relaxedBoundary.minY, relaxedBoundary.maxX, relaxedBoundary.maxY)) { continue; } // 确认线段确实不与边界相交,回收这个线段对象 if (_linePool.length < _maxPoolSize) { _linePool.push(line); } // 从数组中移除 lines.splice(i, 1); } } /** * 计算点相对于矩形的位置码(Cohen-Sutherland算法) */ function computeOutCode(point, rect) { const { minX, minY, maxX, maxY } = rect; let code = 0; if (point.x < minX) code |= 1; // 左 else if (point.x > maxX) code |= 2; // 右 if (point.y < minY) code |= 4; // 下 else if (point.y > maxY) code |= 8; // 上 return code; } /** * 判断点是否在矩形内 */ function pointInRect(point, rect) { return point.x >= rect.minX && point.x <= rect.maxX && point.y >= rect.minY && point.y <= rect.maxY; } /** * 判断线段是否与矩形相交 - 用于处理特殊情况 */ function segmentIntersectsRectangle(start, end, minX, minY, maxX, maxY) { // 线段参数 const dx = end.x - start.x; const dy = end.y - start.y; // 检查线段是否与矩形的任意一条边相交 // 左边界 if (lineIntersectsSegment(minX, minY, minX, maxY, start, end)) { return true; } // 右边界 if (lineIntersectsSegment(maxX, minY, maxX, maxY, start, end)) { return true; } // 下边界 if (lineIntersectsSegment(minX, minY, maxX, minY, start, end)) { return true; } // 上边界 if (lineIntersectsSegment(minX, maxY, maxX, maxY, start, end)) { return true; } return false; } /** * 判断两条线段是否相交 */ function lineIntersectsSegment(x1, y1, x2, y2, seg1, seg2) { // 快速边界框测试 if (Math.max(x1, x2) < Math.min(seg1.x, seg2.x) || Math.min(x1, x2) > Math.max(seg1.x, seg2.x) || Math.max(y1, y2) < Math.min(seg1.y, seg2.y) || Math.min(y1, y2) > Math.max(seg1.y, seg2.y)) { return false; } // 计算叉积判断相交 const dx1 = x2 - x1; const dy1 = y2 - y1; const dx2 = seg2.x - seg1.x; const dy2 = seg2.y - seg1.y; const denominator = dx1 * dy2 - dy1 * dx2; // 如果平行或共线 if (Math.abs(denominator) < EPSILON) { // 共线情况下的重叠检测 if (Math.abs(dx1) > Math.abs(dy1)) { // 水平性更强,使用x坐标判断 const t1 = (seg1.x - x1) / dx1; const t2 = (seg2.x - x1) / dx1; return (t1 >= 0 && t1 <= 1) || (t2 >= 0 && t2 <= 1) || (t1 <= 0 && t2 >= 1) || (t1 >= 1 && t2 <= 0); } else { // 垂直性更强,使用y坐标判断 const t1 = (seg1.y - y1) / dy1; const t2 = (seg2.y - y1) / dy1; return (t1 >= 0 && t1 <= 1) || (t2 >= 0 && t2 <= 1) || (t1 <= 0 && t2 >= 1) || (t1 >= 1 && t2 <= 0); } } // 计算交点参数 const r = ((seg1.y - y1) * dx2 - (seg1.x - x1) * dy2) / denominator; const s = ((seg1.y - y1) * dx1 - (seg1.x - x1) * dy1) / denominator; // 如果参数都在[0,1]范围内,则相交 return r >= 0 && r <= 1 && s >= 0 && s <= 1; } /** * 准备并缓存缩放后的虚线定义 */ function prepareScaledDashes(dashes, scale) { if (!dashes || dashes.length === 0) { return null; } // 创建缓存键 - 使用数组内容和比例因子 const cacheKey = dashes.join('_') + '_' + scale; // 使用WeakMap缓存虚线定义 if (!prepareScaledDashes.cache) { prepareScaledDashes.cache = new Map(); } // 从缓存获取 if (prepareScaledDashes.cache.has(cacheKey)) { return prepareScaledDashes.cache.get(cacheKey); } // 创建新的缩放虚线定义 const scaledDashes = new Float64Array(dashes.length); let patternLength = 0; for (let i = 0; i < dashes.length; i++) { const value = dashes[i] * scale; scaledDashes[i] = value; patternLength += Math.abs(value); } // 添加总长度信息,优化后续计算 scaledDashes.totalLength = patternLength; // 如果缓存过大,清理一些 if (prepareScaledDashes.cache.size > 100) { // 简单策略:清除整个缓存 prepareScaledDashes.cache.clear(); } // 存入缓存 prepareScaledDashes.cache.set(cacheKey, scaledDashes); return scaledDashes; } /** * 生成平行线族 - 使用批处理优化 */ function generateParallelLineFamily( originX, originY, dirX, dirY, perpDirX, perpDirY, deltaX, deltaY, dashes, boundary, boundaryDiagonal, centerX, centerY, resultArray ) { const { minX, minY, maxX, maxY } = boundary; const absDeltaY = Math.abs(deltaY); if (absDeltaY < EPSILON) { // 防止除零错误,如果deltaY接近0,直接生成一条线 generateSingleLine( originX, originY, dirX, dirY, dashes, boundary, boundaryDiagonal, resultArray ); return; } // 增加边界扩展系数,确保覆盖整个显示区域 const boundaryExtendFactor = 2.0; // 增加到2.0以确保足够覆盖 // 计算边界在垂直方向的投影 - 增加余量 const perpProjection = Math.abs( (maxX - minX) * Math.abs(perpDirX) + (maxY - minY) * Math.abs(perpDirY) ) * boundaryExtendFactor; // 增加平行线数量,确保充分覆盖 const numLines = Math.ceil(perpProjection / absDeltaY) + 8; // 额外增加更多线条 // 优化起始线位置计算 const originPerpDistance = originX * perpDirX + originY * perpDirY; const centerPerpDistance = centerX * perpDirX + centerY * perpDirY; const centerToOriginLines = Math.round((centerPerpDistance - originPerpDistance) / deltaY); // 确定第一条线的索引,使其大致中心在边界中心 // 这里和原patCalculator中完全一致 const startLineIndex = -Math.floor(numLines / 2) + centerToOriginLines; // 不使用批量创建,直接逐行计算以确保与原始算法一致 for (let j = 0; j < numLines; j++) { const lineIndex = startLineIndex + j; // 计算这条线的垂直偏移量 const perpOffset = lineIndex * deltaY; // 计算水平偏移量(按照AutoCAD的规范) const horizOffset = lineIndex * deltaX; // 计算这条线的起始点 const lineStartX = originX + perpOffset * perpDirX + horizOffset * dirX; const lineStartY = originY + perpOffset * perpDirY + horizOffset * dirY; // 生成这条线的所有线段 generateSingleLine( lineStartX, lineStartY, dirX, dirY, dashes, boundary, boundaryDiagonal, resultArray ); } } /** * 生成单条线并添加到结果数组 - 内联函数减少调用开销 */ function generateSingleLine(startX, startY, dirX, dirY, dashes, boundary, boundaryDiagonal, resultArray) { // 连续线处理 (无虚线) if (!dashes || dashes.length === 0) { // 使用足够长的线段 const lineLength = boundaryDiagonal * 2.5; // 增大长度确保覆盖整个边界 // 计算端点 - 确保足够长以穿过整个边界 Vec2.set(_point1, startX - dirX * lineLength, startY - dirY * lineLength); Vec2.set(_point2, startX + dirX * lineLength, startY + dirY * lineLength); // 裁剪前先检查方向向量是否有效 if (Math.abs(dirX) < EPSILON && Math.abs(dirY) < EPSILON) { // 无效方向向量,跳过 return; } // 裁剪 if (clipLineFast(_point1, _point2, boundary, _clippedLine)) { const line = getLineFromPool(); line.start.x = _clippedLine[0]; line.start.y = _clippedLine[1]; line.end.x = _clippedLine[2]; line.end.y = _clippedLine[3]; resultArray.push(line); } return; } // 虚线处理 - 批处理优化 generateDashedLine(startX, startY, dirX, dirY, dashes, boundary, boundaryDiagonal, resultArray); } /** * 优化的虚线生成 - 使用位移代替重复计算 */ function generateDashedLine(startX, startY, dirX, dirY, dashes, boundary, boundaryDiagonal, resultArray) { const extendedLength = boundaryDiagonal * 1.5; // 计算起始点 - 使用位移操作 Vec2.set(_point1, startX - dirX * extendedLength, startY - dirY * extendedLength); // 获取总模式长度 - 从预计算中获取 const patternLength = dashes.totalLength || 0; // 调整起点到虚线周期开始处 if (patternLength > EPSILON) { const distToStart = Math.sqrt((_point1.x - startX) ** 2 + (_point1.y - startY) ** 2); const cyclesBeforeStart = Math.floor(distToStart / patternLength); const remainingDist = distToStart - (cyclesBeforeStart * patternLength); _point1.x += dirX * remainingDist; _point1.y += dirY * remainingDist; } // 优化虚线生成循环 const totalDistance = extendedLength * 2; let distanceTraveled = 0; let dashIndex = 0; // 当前点位置 let currentX = _point1.x; let currentY = _point1.y; // 预先计算常用虚线段 const dashesBatch = []; const dashCount = dashes.length; // 在循环外预先计算所有可能的虚线段位移 for (let i = 0; i < dashCount; i++) { const dashLength = Math.abs(dashes[i]); dashesBatch.push({ length: dashLength, isDraw: dashes[i] >= 0, // 预计算位移量 offsetX: dirX * dashLength, offsetY: dirY * dashLength }); } while (distanceTraveled < totalDistance) { const { length: dashLength, isDraw, offsetX, offsetY } = dashesBatch[dashIndex]; if (isDraw) { // 计算线段终点 - 使用预计算的位移量 const segmentEndX = currentX + offsetX; const segmentEndY = currentY + offsetY; // 设置端点 Vec2.set(_point1, currentX, currentY); Vec2.set(_point2, segmentEndX, segmentEndY); // 裁剪线段 - 使用同一个裁剪结果数组减少内存分配 if (clipLineFast(_point1, _point2, boundary, _clippedLine)) { const line = getLineFromPool(); line.start.x = _clippedLine[0]; line.start.y = _clippedLine[1]; line.end.x = _clippedLine[2]; line.end.y = _clippedLine[3]; resultArray.push(line); } } // 更新当前位置 - 使用预计算的位移 currentX += offsetX; currentY += offsetY; distanceTraveled += dashLength; // 快速循环虚线定义 dashIndex = (dashIndex + 1) % dashCount; } } /** * 高性能线段裁剪 (基于Liang-Barsky算法) - 内联与展开优化 */ function clipLineFast(p1, p2, boundary, result) { const { minX, minY, maxX, maxY } = boundary; // 为裁剪添加微小的容差,确保边缘线不会被错误裁剪 // 太大的容差会导致实线被错误裁剪,使用更小的容差值 const margin = EPSILON * 5; // 更合理的容差值 const clipMinX = minX - margin; const clipMinY = minY - margin; const clipMaxX = maxX + margin; const clipMaxY = maxY + margin; // 线段方向向量 const dx = p2.x - p1.x; const dy = p2.y - p1.y; let tMin = 0; let tMax = 1; // 左边界测试 if (Math.abs(dx) < EPSILON) { if (p1.x < clipMinX) return false; } else { const p = -dx; const q = p1.x - clipMinX; if (p !== 0) { const t = q / p; if (p < 0) { if (t > tMin) tMin = t; } else { if (t < tMax) tMax = t; } } else if (q < 0) { return false; } } if (tMin > tMax) return false; // 右边界测试 if (Math.abs(dx) < EPSILON) { if (p1.x > clipMaxX) return false; } else { const p = dx; const q = clipMaxX - p1.x; if (p !== 0) { const t = q / p; if (p < 0) { if (t > tMin) tMin = t; } else { if (t < tMax) tMax = t; } } else if (q < 0) { return false; } } if (tMin > tMax) return false; // 下边界测试 if (Math.abs(dy) < EPSILON) { if (p1.y < clipMinY) return false; } else { const p = -dy; const q = p1.y - clipMinY; if (p !== 0) { const t = q / p; if (p < 0) { if (t > tMin) tMin = t; } else { if (t < tMax) tMax = t; } } else if (q < 0) { return false; } } if (tMin > tMax) return false; // 上边界测试 if (Math.abs(dy) < EPSILON) { if (p1.y > clipMaxY) return false; } else { const p = dy; const q = clipMaxY - p1.y; if (p !== 0) { const t = q / p; if (p < 0) { if (t > tMin) tMin = t; } else { if (t < tMax) tMax = t; } } else if (q < 0) { return false; } } if (tMin > tMax) return false; // 计算裁剪结果 - 直接使用预分配结果数组 result[0] = p1.x + tMin * dx; result[1] = p1.y + tMin * dy; result[2] = p1.x + tMax * dx; result[3] = p1.y + tMax * dy; return true; } /** * 检查图案在当前设置下是否形成连续模式 - 极致优化版本 */ export function checkPatternContinuity(parsedPatData, boundary, scale = 1.0, rotation = 0.0, offset = [0, 0]) { // 生成边界内的线条 - 重用计算结果 const lines = computePatternLines(parsedPatData, boundary, scale, rotation, offset); if (lines.length === 0) { return { isContinuous: false, continuityScore: 0, edgeContinuity: { top: false, right: false, bottom: false, left: false }, details: "没有生成任何线条,无法评估连续性。" }; } const { minX, minY, maxX, maxY } = boundary; // 数组形式的边界定义和预分配交点数组 const edges = [ { name: 'top', start: minX, end: maxX, isHorizontal: true, coord: maxY }, { name: 'right', start: minY, end: maxY, isHorizontal: false, coord: maxX }, { name: 'bottom', start: minX, end: maxX, isHorizontal: true, coord: minY }, { name: 'left', start: minY, end: maxY, isHorizontal: false, coord: minX } ]; // 使用TypedArray存储交点信息,减少内存开销 const allIntersections = [[], [], [], []]; // 向量对象重用 const tmpStart = {x: 0, y: 0}; const tmpEnd = {x: 0, y: 0}; // 优化批量计算线段与边界的交点 calculateBoundaryIntersections(lines, edges, allIntersections, tmpStart, tmpEnd); // 评估连续性 const continuityResult = evaluateContinuity(edges, allIntersections); // 回收线段对象到对象池 recycleLines(lines); return continuityResult; } /** * 批量计算线段与边界的交点 - 分离以便内联优化 */ function calculateBoundaryIntersections(lines, edges, allIntersections, tmpStart, tmpEnd) { for (let i = 0; i < lines.length; i++) { const { start, end } = lines[i]; // 复制到临时向量对象 Vec2.copy(tmpStart, start); Vec2.copy(tmpEnd, end); for (let j = 0; j < 4; j++) { const edge = edges[j]; const { isHorizontal, coord, start: edgeStart, end: edgeEnd } = edge; // 计算线段与边界的交点 const dx = tmpEnd.x - tmpStart.x; const dy = tmpEnd.y - tmpStart.y; let t; if (isHorizontal) { // 水平边 if (Math.abs(dy) < EPSILON) continue; // 平行于边 t = (coord - tmpStart.y) / dy; } else { // 垂直边 if (Math.abs(dx) < EPSILON) continue; // 平行于边 t = (coord - tmpStart.x) / dx; } // 交点在线段上 if (t >= 0 && t <= 1) { // 使用向量插值计算交点 Vec2.lerp(_tempVec, tmpStart, tmpEnd, t); // 交点在边上 if (isHorizontal) { if (_tempVec.x >= edgeStart - EPSILON && _tempVec.x <= edgeEnd + EPSILON) { allIntersections[j].push({ x: _tempVec.x, y: _tempVec.y, isEndpoint: (t < EPSILON || Math.abs(t - 1) < EPSILON) }); } } else { if (_tempVec.y >= edgeStart - EPSILON && _tempVec.y <= edgeEnd + EPSILON) { allIntersections[j].push({ x: _tempVec.x, y: _tempVec.y, isEndpoint: (t < EPSILON || Math.abs(t - 1) < EPSILON) }); } } } } } // 对交点进行排序 for (let i = 0; i < 4; i++) { const intersections = allIntersections[i]; const isHorizontal = edges[i].isHorizontal; if (isHorizontal) { // 水平边按x排序 intersections.sort((a, b) => a.x - b.x); } else { // 垂直边按y排序 intersections.sort((a, b) => a.y - b.y); } } } /** * 评估连续性 - 分离以便内联优化 */ function evaluateContinuity(edges, allIntersections) { const edgeContinuity = {}; let totalContinuityScore = 0; let edgesWithIntersections = 0; for (let i = 0; i < 4; i++) { const edgeName = edges[i].name; const points = allIntersections[i]; if (points.length < 2) { edgeContinuity[edgeName] = false; continue; } edgesWithIntersections++; // 优化分析交点间距的一致性 const edge = edges[i]; const isHorizontal = edge.isHorizontal; const edgeLength = edge.end - edge.start; // 优化间距计算 const numDistances = points.length - 1; if (numDistances === 0) { edgeContinuity[edgeName] = false; continue; } // 单次循环计算总差距和间距 let totalGaps = 0; let sumSquaredDiff = 0; let previousDistance = 0; const distances = new Float64Array(numDistances); // 第一遍:计算所有距离和总距离 for (let j = 0; j < numDistances; j++) { const prev = points[j]; const curr = points[j + 1]; const distance = isHorizontal ? Math.abs(curr.x - prev.x) : Math.abs(curr.y - prev.y); distances[j] = distance; totalGaps += distance; } // 计算平均距离 const avgDistance = totalGaps / numDistances; // 第二遍:计算方差 for (let j = 0; j < numDistances; j++) { const diff = distances[j] - avgDistance; sumSquaredDiff += diff * diff; } const stdDev = Math.sqrt(sumSquaredDiff / numDistances); const cv = avgDistance > EPSILON ? stdDev / avgDistance : 1; // 计算边覆盖率 const coverage = totalGaps / edgeLength; // 判断连续性和计算分数 const isDistanceConsistent = cv < 0.1; const edgeScore = isDistanceConsistent ? Math.min(1, coverage) * (1 - Math.min(1, cv)) : 0; totalContinuityScore += edgeScore; edgeContinuity[edgeName] = edgeScore > 0.8; } // 计算总体连续性分数和判断 const finalScore = edgesWithIntersections > 0 ? totalContinuityScore / edgesWithIntersections : 0; const isContinuous = finalScore > 0.8; // 生成详细描述 let details = ""; if (isContinuous) { details = `图案在当前设置下形成了连续的模式(连续性分数: ${(finalScore * 100).toFixed(1)}%)。`; } else if (finalScore > 0.5) { details = `图案在当前设置下基本连续,但存在一些不连续处(连续性分数: ${(finalScore * 100).toFixed(1)}%)。`; } else if (finalScore > 0) { details = `图案在当前设置下不连续(连续性分数: ${(finalScore * 100).toFixed(1)}%)。`; } else { details = "图案在当前设置下无法形成连续模式。"; } // 添加每条边的连续性信息 details += "\n边界连续性: "; const edgeNameMap = { top: "顶部", right: "右侧", bottom: "底部", left: "左侧" }; for (const edge of edges) { const edgeName = edge.name; const isCont = edgeContinuity[edgeName]; details += `${edgeNameMap[edgeName]}: ${isCont ? "连续" : "不连续"}; `; } return { isContinuous, continuityScore: finalScore, edgeContinuity, details }; }