我正在创建一个从 svg 到 dxf 的自定义转换器,需要一个数学向导来帮助我找出我的逻辑有缺陷的地方。
这是我当前的代码:
function svgArcToLWPolyline(rx, ry, rotation, largeArcFlag, sweepFlag, x1, y1, x2, y2) {
const scaleX = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) / (2 * rx);
const scaleY = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) / (2 * ry);
const arcInfo = svgArcToOvalArc(rx * scaleX, ry * scaleY, rotation, largeArcFlag, sweepFlag, x1, y1, x2, y2);
const numPoints = calculateNumberOfPoints(rx * scaleX, ry * scaleY, arcInfo.startAngle, arcInfo.endAngle);
// Calculate bulge factors
const bulgeFactors = [];
const angleIncrement = (arcInfo.endAngle - arcInfo.startAngle) / (numPoints - 1);
let currentAngle = arcInfo.startAngle;
for (let i = 0; i < numPoints - 1; i++) {
const nextAngle = currentAngle + angleIncrement;
const bulge = Math.tan((nextAngle - currentAngle) * Math.PI / 360);
bulgeFactors.push(bulge);
currentAngle = nextAngle;
}
bulgeFactors.push(0); // Last point has zero bulge
// Construct LWPOLYLINE points
const lwpolylinePoints = [];
for (let i = 0; i < numPoints; i++) {
const angle = arcInfo.startAngle + i * angleIncrement;
const x = arcInfo.cx + rx * scaleX * Math.cos(angle * Math.PI / 180);
const y = arcInfo.cy + ry * scaleY * Math.sin(angle * Math.PI / 180);
lwpolylinePoints.push([x, y, bulgeFactors[i]]);
}
return lwpolylinePoints;
}
function svgArcToOvalArc(rx, ry, rotation, largeArcFlag, sweepFlag, x1, y1, x2, y2) {
// Convert rotation angle to radians
const angle = rotation * Math.PI / 180;
// Calculate intermediate values
const dx = (x1 - x2) / 2;
const dy = (y1 - y2) / 2;
const x1p = Math.cos(angle) * dx + Math.sin(angle) * dy;
const y1p = -Math.sin(angle) * dx + Math.cos(angle) * dy;
const rxSq = rx * rx;
const rySq = ry * ry;
const x1pSq = x1p * x1p;
const y1pSq = y1p * y1p;
let radicand = (rxSq * rySq - rxSq * y1pSq - rySq * x1pSq) / (rxSq * y1pSq + rySq * x1pSq);
// Ensure non-negative radicand
if (radicand < 0) {
radicand = 0;
}
// Calculate root
let root = Math.sqrt(radicand);
if (largeArcFlag === sweepFlag) {
root = -root;
}
const cxp = root * rx * y1p / ry;
const cyp = -root * ry * x1p / rx;
// Calculate center
const cx = Math.cos(angle) * cxp - Math.sin(angle) * cyp + (x1 + x2) / 2;
const cy = Math.sin(angle) * cxp + Math.cos(angle) * cyp + (y1 + y2) / 2;
// Calculate start and end angles
let startAngle = Math.atan2((y1p - cyp) / ry, (x1p - cxp) / rx);
let endAngle = Math.atan2((-y1p - cyp) / ry, (-x1p - cxp) / rx);
// Convert angles to degrees
startAngle *= 180 / Math.PI;
endAngle *= 180 / Math.PI;
// Adjust angles to be in the range [0, 360]
if (startAngle < 0) {
startAngle += 360;
}
if (endAngle < 0) {
endAngle += 360;
}
return { cx: cx, cy: cy, startAngle: startAngle, endAngle: endAngle };
}
function calculateNumberOfPoints(rx, ry, startAngle, endAngle) {
// Calculate arc length
let arcLength;
if (startAngle <= endAngle) {
arcLength = (endAngle - startAngle) * Math.PI / 180;
} else {
arcLength = (360 - startAngle + endAngle) * Math.PI / 180;
}
arcLength *= (rx + ry) / 2;
// Choose a fixed length for each segment
const segmentLength = 1.0; // Adjust as needed
// Calculate the number of points
const numPoints = Math.max(Math.floor(arcLength / segmentLength), 2); // Minimum of 2 points
return numPoints;
}
这是使用 d="M10,10L20,25A1,3,0,0,0,50,50L90,90V80Z" 的 svg 的样子 SVG: svg 路径为 M10,10L20,25A1,3,0,0,0,50,50L90,90V80Z DXF: 路径M10,10L20,25A1,3,0,0,0,50,50L90,90V80Z的dxf转换
如您所见,没有考虑 ry 和 rx。当我更改路径以添加 xRotationalAxis 时,我的 dxf 中断更多: d="M10,10L20,25A1,3,45,0,0,50,50L90,90V80Z": SVG: svg 路径为 M10,10L20,25A1,3,45,0,0,50,50L90,90V80Z DXF: M10,10L20,25A1,3,45,0,0,50,50L90,90V80Z的dxf转换
我花了 12 个小时尝试调整这个并从数学上弄清楚如何让它工作(在 ChatGPT 的帮助下)所以我能得到的任何帮助都会非常好!
恐怕计算助手中存在多个错误。
最重要的是,您需要通过参数化计算实际的
rx
和 ry
值。
换句话说
A 1,3, 45,0,0,50,50
不包含 rx/ry 的绝对值 – rx=1 和 ry=3 是相对值。// arc command values
let p0 = {
x: 20,
y: 25
}
let values = [1, 3, 45, 0, 0, 50, 50]
let vertices = 12;
let polylinePts = arcToPolyline(p0, values, vertices)
//render polyline
polyline.setAttribute('points', polylinePts.map(pt => {
return `${pt.x} ${pt.y}`
}))
function arcToPolyline(p0, values, vertices = 12) {
let [rxC, ryC, xAxisRotation, largeArc, sweep, x, y] = values;
// parametrize arc command to get actual rx,ry and center
let param = svgArcToCenterParam(p0.x, p0.y, rxC, ryC, xAxisRotation, largeArc, sweep, x, y);
let {
cx,
cy,
rx,
ry,
startAngle,
deltaAngle,
endAngle
} = param;
let splitAngle = deltaAngle / vertices;
let pts = []
for (let i = 0; i <= vertices; i++) {
let angle = startAngle - splitAngle * i;
let xAxisRotation_radian = xAxisRotation * Math.PI / 180;
let pt = getEllipsePointForAngle(cx, cy, rx, ry, xAxisRotation_radian, deltaAngle + angle)
pts.push(pt)
}
return pts;
}
/**
* based on @cuixiping;
* https://stackoverflow.com/questions/9017100/calculate-center-of-svg-arc/12329083#12329083
*/
function svgArcToCenterParam(p0x, p0y, rx, ry, angle, largeArc, sweep, px, py) {
const radian = (ux, uy, vx, vy) => {
let dot = ux * vx + uy * vy;
let mod = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));
let rad = Math.acos(dot / mod);
if (ux * vy - uy * vx < 0) {
rad = -rad;
}
return rad;
};
// degree to radian – if rx equals ry the x-axis rotation has no effect
let phi = rx === ry ? 0 : (angle * Math.PI) / 180;
let cx, cy, startAngle, deltaAngle, endAngle;
let PI = Math.PI;
//let PIpx = PI * 2;
// invalid arguments
if (rx == 0 || ry == 0) {
throw Error("rx and ry can not be 0");
}
if (rx < 0) {
rx = -rx;
}
if (ry < 0) {
ry = -ry;
}
let s_phi = Math.sin(phi);
let c_phi = Math.cos(phi);
let hd_x = (p0x - px) / 2; // half diff of x
let hd_y = (p0y - py) / 2; // half diff of y
let hs_x = (p0x + px) / 2; // half sum of x
let hs_y = (p0y + py) / 2; // half sum of y
// F6.5.1
let p0x_ = c_phi * hd_x + s_phi * hd_y;
let p0y_ = c_phi * hd_y - s_phi * hd_x;
// F.6.6 Correction of out-of-range radii
// Step 3: Ensure radii are large enough
let lambda = (p0x_ * p0x_) / (rx * rx) + (p0y_ * p0y_) / (ry * ry);
if (lambda > 1) {
rx = rx * Math.sqrt(lambda);
ry = ry * Math.sqrt(lambda);
}
let rxry = rx * ry;
let rxp0y_ = rx * p0y_;
let ryp0x_ = ry * p0x_;
let sum_of_sq = rxp0y_ * rxp0y_ + ryp0x_ * ryp0x_; // sum of square
if (!sum_of_sq) {
console.log("start point can not be same as end point");
}
let coe = Math.sqrt(Math.abs((rxry * rxry - sum_of_sq) / sum_of_sq));
if (largeArc == sweep) {
coe = -coe;
}
// F6.5.2
let cx_ = (coe * rxp0y_) / ry;
let cy_ = (-coe * ryp0x_) / rx;
// F6.5.3
cx = c_phi * cx_ - s_phi * cy_ + hs_x;
cy = s_phi * cx_ + c_phi * cy_ + hs_y;
let xcr1 = (p0x_ - cx_) / rx;
let xcr2 = (p0x_ + cx_) / rx;
let ycr1 = (p0y_ - cy_) / ry;
let ycr2 = (p0y_ + cy_) / ry;
// F6.5.5
startAngle = radian(1, 0, xcr1, ycr1);
// F6.5.6
deltaAngle = radian(xcr1, ycr1, -xcr2, -ycr2);
if (deltaAngle > PI * 2) {
deltaAngle -= PI * 2;
} else if (deltaAngle < 0) {
deltaAngle += PI * 2;
}
if (sweep == false || sweep == 0) {
deltaAngle -= PI * 2;
}
endAngle = startAngle + deltaAngle;
if (endAngle > PI * 2) {
endAngle -= PI * 2;
} else if (endAngle < 0) {
endAngle += PI * 2;
}
let outputObj = {
cx: cx,
cy: cy,
rx: rx,
ry: ry,
startAngle: startAngle,
deltaAngle: deltaAngle,
endAngle: endAngle,
sweep: sweep
};
return outputObj;
}
/*
* based on
* https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/modules/canvaskit/htmlcanvas/path2d.js
* and https://observablehq.com/@toja/ellipse-and-elliptical-arc-conversion
*/
function getEllipsePointForAngle(cx, cy, rx, ry, rotation_angle, angle) {
let M = Math.abs(rx) * Math.cos(angle),
N = Math.abs(ry) * Math.sin(angle);
return {
x: cx + Math.cos(rotation_angle) * M - Math.sin(rotation_angle) * N,
y: cy + Math.sin(rotation_angle) * M + Math.cos(rotation_angle) * N
};
}
svg {
overflow: visible;
border: 1px solid #ccc;
}
<svg id="svg" viewBox="-9 0 100 100">
<path id="path" d="M 10 10
L 20 25
A 1 3 45 0 0 50 50
L 90 90
V 80
Z" fill="none" stroke="#000" />
<polyline id="polyline" fill="none" stroke="red"></polyline>
</svg>
svgArcToCenterParam(p0x, p0y, rx, ry, angle, largeArc, sweep, px, py)
返回参数化椭圆参数getEllipsePointForAngle(cx, cy, rx, ry, rotation_angle, angle)
您也可以从我的多边形路径助手(或简单示例)中获得灵感。它基于自定义路径数据解析器并计算每个路径段的长度。这样我们就可以根据路径点命令计算多边形顶点。