UNPKG

@meta2d/core

Version:

@meta2d/core: Powerful, Beautiful, Simple, Open - Web-Based 2D At Its Best .

432 lines 13.3 kB
import { deleteTempAnchor, getFromAnchor, getGradientAnimatePath, getToAnchor } from '../../pen'; import { hitPoint } from '../../point'; import { getRectOfPoints, pointInSimpleRect } from '../../rect'; import { getBezierPoint, getQuadraticPoint } from './curve'; export function line(pen, ctx) { const path = !ctx ? new Path2D() : ctx; if (pen.lineName === 'line' || pen.lineName === 'polyline') { if (pen.calculative.lineSmooth) { let _path = getGradientAnimatePath(pen); if (path instanceof Path2D) path.addPath(_path); if (path instanceof Path2D) return path; } } const worldAnchors = pen.calculative.worldAnchors; if (worldAnchors.length > 1) { let from; // 上一个点 worldAnchors.forEach((pt) => { if (from) { draw(path, from, pt); } else { pt.start = true; } from = pt; }); if (pen.close) { if (pen.lineName === 'curve') { draw(path, from, worldAnchors[0]); } else { path.closePath(); } } } if (path instanceof Path2D) return path; } export function lineSegment(store, pen, mousedwon) { if (!pen.calculative.worldAnchors) { pen.calculative.worldAnchors = []; } if (pen.calculative.worldAnchors.length < 2 || pen.anchors?.length > 1) { return; } const from = getFromAnchor(pen); const to = getToAnchor(pen); if (!from || !to || !to.id || from === to) { return; } from.next = undefined; deleteTempAnchor(pen); to.prev = undefined; pen.calculative.worldAnchors.push(to); } function draw(path, from, to) { if (!to || to.isTemp) { return; } from.start && path.moveTo(from.x, from.y); if (from.next) { if (to.prev) { path.bezierCurveTo(from.next.x, from.next.y, to.prev.x, to.prev.y, to.x, to.y); } else { path.quadraticCurveTo(from.next.x, from.next.y, to.x, to.y); } } else { if (to.prev) { path.quadraticCurveTo(to.prev.x, to.prev.y, to.x, to.y); } else { path.lineTo(to.x, to.y); } } } export function getLineRect(pen) { getLineLength(pen); return getRectOfPoints(getLinePoints(pen)); } /** * 获取连线的 points ,并非 worldAnchors ,worldAnchors 之前的路径点也会记录 */ export function getLinePoints(pen) { const pts = []; let from; // 上一个点 pen.calculative.worldAnchors.forEach((pt) => { if (!from) { pts.push(pt); } else { pts.push(...getPoints(from, pt, pen)); pts.push(pt); } from = pt; }); if (pen.close && pen.calculative.worldAnchors.length > 1) { pts.push(...getPoints(from, pen.calculative.worldAnchors[0], pen)); } return pts; } export function getLineR(pen) { return pen?.lineWidth ? pen.lineWidth / 2 + 4 : 4; } export function getPoints(from, to, pen) { const pts = []; if (!to) { return pts; } let step = 0.02; if (from.lineLength && !pen.parentId) { const r = getLineR(pen); step = r / from.lineLength; } if (from.next) { if (to.prev) { for (let i = step; i < 1; i += step) { pts.push(getBezierPoint(i, from, from.next, to.prev, to)); } } else { for (let i = step; i < 1; i += step) { pts.push(getQuadraticPoint(i, from, from.next, to)); } } } else { if (to.prev) { for (let i = step; i < 1; i += step) { pts.push(getQuadraticPoint(i, from, to.prev, to)); } } else { pts.push({ x: to.x, y: to.y }); } } if (pts.length > 1) { from.curvePoints = pts; } return pts; } export function pointInLine(pt, pen) { const r = getLineR(pen); let i = 0; let from; // 上一个点 let point; for (const anchor of pen.calculative.worldAnchors) { if (from) { point = pointInLineSegment(pt, from, anchor, r); if (point) { return { i, point, }; } ++i; } from = anchor; } if (pen.close && pen.calculative.worldAnchors.length > 1 && (point = pointInLineSegment(pt, from, pen.calculative.worldAnchors[0], r))) { return { i, point, }; } } export function pointInLineSegment(pt, pt1, pt2, r = 4) { if (!pt1.next && !pt2.prev) { const { x: x1, y: y1 } = pt1; const { x: x2, y: y2 } = pt2; const minX = Math.min(x1, x2); const maxX = Math.max(x1, x2); const minY = Math.min(y1, y2); const maxY = Math.max(y1, y2); if (!(pt.x >= minX - r && pt.x <= maxX + r && pt.y >= minY - r && pt.y <= maxY + r)) { return; } return pointToLine(pt, pt1, pt2, r); } else if (pt1.curvePoints) { for (const point of pt1.curvePoints) { if (hitPoint(pt, point, r)) { return point; } } } } export function pointToLine(pt, pt1, pt2, r = 4) { // 竖线 if (pt1.x === pt2.x) { const len = Math.abs(pt.x - pt1.x); if (len <= r) { return { x: pt1.x, y: pt.y, }; } } else { const A = (pt1.y - pt2.y) / (pt1.x - pt2.x); const B = pt1.y - A * pt1.x; const len = Math.abs((A * pt.x + B - pt.y) / Math.sqrt(A * A + 1)); if (len <= r) { const m = pt.x + A * pt.y; const x = (m - A * B) / (A * A + 1); return { x, y: A * x + B, }; } } } function lineLen(from, cp1, cp2, to) { if (!cp1 && !cp2) { return (Math.sqrt(Math.pow(Math.abs(from.x - to.x), 2) + Math.pow(Math.abs(from.y - to.y), 2)) || 0); } const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); if (cp1 && cp2) { path.setAttribute('d', `M${from.x} ${from.y} C${cp1.x} ${cp1.y} ${cp2.x} ${cp2.y} ${to.x} ${to.y}`); } else if (cp1) { path.setAttribute('d', `M${from.x} ${from.y} Q${cp1.x} ${cp1.y} ${to.x} ${to.y}`); } else { path.setAttribute('d', `M${from.x} ${from.y} Q${cp2.x} ${cp2.y} ${to.x} ${to.y}`); } return path.getTotalLength() || 0; } export function getLineLength(pen) { if (pen.calculative.worldAnchors.length < 2) { return 0; } let len = 0; let from; // 上一个点 pen.calculative.worldAnchors.forEach((pt) => { if (from) { from.lineLength = lineLen(from, from.next, pt.prev, pt); len += from.lineLength; } from = pt; }); if (pen.close) { // pen.close ,下一个点即第一个点 const to = getFromAnchor(pen); from.lineLength = lineLen(from, from.next, to.prev, to); len += from.lineLength; } if (pen.calculative.animatePos) { pen.calculative.animatePos = (len / pen.length) * pen.calculative.animatePos; } pen.length = len; return len; } export function createLineSvgPath(line) { let path; let from = null; line.calculative.worldAnchors.forEach(pt => { if (from) { path = createSvgPath(path, from, from.next, pt.prev, pt); } from = pt; }); if (line.close) { let pt = line.calculative.worldAnchors[0]; path = createSvgPath(path, from, from.next, pt.prev, pt); } return path; } /** * 连线在 rect 内, 连线与 rect 相交 */ export function lineInRect(line, rect) { // 判断是直线还是贝塞尔 const worldAnchors = line.calculative.worldAnchors; for (let index = 0; index < worldAnchors.length - 1; index++) { const current = worldAnchors[index]; const next = worldAnchors[index + 1]; if (!current.next && !next.prev) { // 线段 if (isLineIntersectRectangle(current, next, rect)) { return true; } } else { // 贝塞尔 if (isBezierIntersectRectangle(current, next, rect)) { return true; } } } return false; } /** * 线段与矩形是否相交 * @param rect 矩形 */ export function isLineIntersectRectangle(pt1, pt2, rect) { if (pointInSimpleRect(pt1, rect) || pointInSimpleRect(pt2, rect)) { // 存在一个点在矩形内部 return true; } const linePointX1 = pt1.x; const linePointY1 = pt1.y; const linePointX2 = pt2.x; const linePointY2 = pt2.y; let rectangleLeftTopX = rect.x; let rectangleLeftTopY = rect.y; let rectangleRightBottomX = rect.ex; let rectangleRightBottomY = rect.ey; const lineHeight = linePointY1 - linePointY2; const lineWidth = linePointX2 - linePointX1; // 计算叉乘 const c = linePointX1 * linePointY2 - linePointX2 * linePointY1; if ((lineHeight * rectangleLeftTopX + lineWidth * rectangleLeftTopY + c >= 0 && lineHeight * rectangleRightBottomX + lineWidth * rectangleRightBottomY + c <= 0) || (lineHeight * rectangleLeftTopX + lineWidth * rectangleLeftTopY + c <= 0 && lineHeight * rectangleRightBottomX + lineWidth * rectangleRightBottomY + c >= 0) || (lineHeight * rectangleLeftTopX + lineWidth * rectangleRightBottomY + c >= 0 && lineHeight * rectangleRightBottomX + lineWidth * rectangleLeftTopY + c <= 0) || (lineHeight * rectangleLeftTopX + lineWidth * rectangleRightBottomY + c <= 0 && lineHeight * rectangleRightBottomX + lineWidth * rectangleLeftTopY + c >= 0)) { if (rectangleLeftTopX > rectangleRightBottomX) { const temp = rectangleLeftTopX; rectangleLeftTopX = rectangleRightBottomX; rectangleRightBottomX = temp; } if (rectangleLeftTopY < rectangleRightBottomY) { const temp1 = rectangleLeftTopY; rectangleLeftTopY = rectangleRightBottomY; rectangleRightBottomY = temp1; } if ((linePointX1 < rectangleLeftTopX && linePointX2 < rectangleLeftTopX) || (linePointX1 > rectangleRightBottomX && linePointX2 > rectangleRightBottomX) || (linePointY1 > rectangleLeftTopY && linePointY2 > rectangleLeftTopY) || (linePointY1 < rectangleRightBottomY && linePointY2 < rectangleRightBottomY)) { return false; } else { return true; } } else { return false; } } /** * 贝塞尔曲线与矩形是否相交 * @param from 前点 * @param to 后点 * @param rect 矩形 */ export function isBezierIntersectRectangle(from, to, rect) { const step = 0.02; if (!from.next && !to.prev) { // 直线 return isLineIntersectRectangle(from, to, rect); } else if (from.next && to.prev) { for (let i = step; i < 1; i += step) { const point = getBezierPoint(i, from, from.next, to.prev, to); if (pointInSimpleRect(point, rect)) { return true; } } } else if (from.next || to.prev) { for (let i = step; i < 1; i += step) { const point = getQuadraticPoint(i, from, from.next || to.prev, to); if (pointInSimpleRect(point, rect)) { return true; } } } return false; } export function createSvgPath(path, from, cp1, cp2, to) { let d = ''; if (!path) { d += `M${from.x} ${from.y} `; path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path.setAttribute('d', d); } d = path.getAttribute('d') || ''; if (cp1 && cp2) { d += `C${cp1.x} ${cp1.y} ${cp2.x} ${cp2.y} ${to.x} ${to.y}`; } else if (cp1) { d += `Q${cp1.x} ${cp1.y} ${to.x} ${to.y}`; } else { d += `Q${cp2?.x || from.x} ${cp2?.y || from.y} ${to.x} ${to.y}`; } path.setAttribute('d', d); return path; } // 获取线段的某个点的导数和位置 export function getLinePointPosAndAngle(path, distance) { const totalLength = path.getTotalLength(); if (distance < 0 || distance > totalLength) return null; const delta = 0.01; const point1 = path.getPointAtLength(distance); const point2 = path.getPointAtLength(distance - delta); const determinant = Math.atan2(point1.y - point2.y, point1.x - point2.x); return { x: point1.x, y: point1.y, rotate: determinant / Math.PI * 180, progress: distance / totalLength }; } //# sourceMappingURL=line.js.map