svg-path-simplify 0.4.4 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +9 -1
- package/dist/svg-path-simplify.esm.js +1649 -1205
- package/dist/svg-path-simplify.esm.min.js +2 -2
- package/dist/svg-path-simplify.js +1649 -1204
- package/dist/svg-path-simplify.min.js +2 -2
- package/dist/svg-path-simplify.pathdata.esm.js +301 -637
- package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
- package/dist/svg-path-simplify.poly.cjs +8 -9
- package/drawing.svg +62 -0
- package/index.html +13 -8
- package/package.json +1 -1
- package/src/index.js +3 -1
- package/src/pathData_get_intersections.js +777 -0
- package/src/pathData_offset.js +201 -0
- package/src/pathData_simplify_cubic.js +19 -21
- package/src/pathData_simplify_cubic_extrapolate.js +147 -8
- package/src/pathData_simplify_cubicsToArcs.js +65 -21
- package/src/pathSimplify-main.js +267 -64
- package/src/pathSimplify-presets.js +1 -1
- package/src/poly-fit-curve-schneider.js +171 -132
- package/src/poly-fit-curve-schneider_check_bulge.js +131 -0
- package/src/poly-offset.js +133 -0
- package/src/simplify_poly_RC.js +29 -7
- package/src/svg-getAttributes.js +1 -1
- package/src/svgii/geometry.js +130 -27
- package/src/svgii/geometry_bbox.js +1 -1
- package/src/svgii/geometry_length.js +1 -1
- package/src/svgii/pathData_analyze.js +6 -28
- package/src/svgii/pathData_convert.js +63 -15
- package/src/svgii/pathData_remove_collinear.js +32 -36
- package/src/svgii/pathData_reorder.js +3 -2
- package/src/svgii/pathData_simplify_refineExtremes.js +1 -3
- package/src/svgii/pathData_stringify.js +132 -6
- package/src/svgii/pathData_toPolygon.js +33 -23
- package/src/svgii/poly_analyze.js +272 -125
- package/src/svgii/poly_analyze_cleanup.js +311 -0
- package/src/svgii/poly_analyze_getTangents.js +125 -0
- package/src/svgii/poly_analyze_get_chunks.js +3 -1
- package/src/svgii/poly_normalize.js +21 -8
- package/src/svgii/poly_to_pathdata.js +477 -8
- package/src/svgii/rounding.js +35 -14
- package/src/svgii/svg_cleanup.js +44 -51
- package/src/svgii/svg_cleanup_convertPathLength.js +5 -0
- package/src/svgii/svg_cleanup_normalize_transforms.js +2 -2
- package/src/svgii/visualize.js +33 -2
- package/v/0.4.5/index.html +1040 -0
- package/v/0.4.5/svg-path-simplify.js +13227 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { findPathDataIntersections } from "./pathData_get_intersections";
|
|
2
|
+
import { getParallelPoly } from "./poly-offset";
|
|
3
|
+
import { checkLineIntersection, pointAtT } from "./svgii/geometry";
|
|
4
|
+
import { getPolygonArea } from "./svgii/geometry_area";
|
|
5
|
+
import { pathDataToD } from "./svgii/pathData_stringify";
|
|
6
|
+
import { renderPath, renderPoint, renderPoly } from "./svgii/visualize";
|
|
7
|
+
|
|
8
|
+
export function offsetPathData(pathData = [], strokeWidth = 18, side = top) {
|
|
9
|
+
|
|
10
|
+
console.log('offsetPathData');
|
|
11
|
+
//alert('off')
|
|
12
|
+
|
|
13
|
+
let pathDataN = [];
|
|
14
|
+
let l = pathData.length;
|
|
15
|
+
let closed = pathData[l - 1].type.toLowerCase() === 'z'
|
|
16
|
+
|
|
17
|
+
for (let i = 0; i < l; i++) {
|
|
18
|
+
let comPrev = i > 0 ? pathData[i - 1] : null;
|
|
19
|
+
let com = pathData[i];
|
|
20
|
+
let { type, values, p0, cp1, cp2, p } = com;
|
|
21
|
+
//let comN = com
|
|
22
|
+
|
|
23
|
+
if (type === 'C') {
|
|
24
|
+
|
|
25
|
+
//split path
|
|
26
|
+
let segments = pointAtT([p0, cp1, cp2, p], 0.5, false, true).segments;
|
|
27
|
+
//console.log(segments);
|
|
28
|
+
|
|
29
|
+
let pts = [segments[0].p0];
|
|
30
|
+
segments.forEach(com => {
|
|
31
|
+
let { p0, cp1, cp2, p } = com;
|
|
32
|
+
|
|
33
|
+
pts.push(cp1, cp2, p)
|
|
34
|
+
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
let area = getPolygonArea(pts)
|
|
38
|
+
let sweep = area < 0 ? 0 : 1
|
|
39
|
+
let polyOffset = getParallelPoly(pts, strokeWidth, sweep, closed)
|
|
40
|
+
let { polyTop, polyBottom } = polyOffset
|
|
41
|
+
//console.log(polyOffset);
|
|
42
|
+
|
|
43
|
+
com.polyTop = polyTop
|
|
44
|
+
com.polyBottom = polyBottom
|
|
45
|
+
com.t = 0
|
|
46
|
+
com.segs = []
|
|
47
|
+
com.inter = null
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
// convert pts to cpts
|
|
51
|
+
let pathData2 = [
|
|
52
|
+
{ type: 'M', values: [polyBottom[0].x, polyBottom[0].y] }
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
p0 = polyBottom[0];
|
|
56
|
+
for (let j = 3; j < polyBottom.length; j += 3) {
|
|
57
|
+
let [cp1, cp2, p] = [polyBottom[j - 2], polyBottom[j - 1], polyBottom[j]]
|
|
58
|
+
pathData2.push(
|
|
59
|
+
{ type: 'C', values: [cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y] }
|
|
60
|
+
)
|
|
61
|
+
com.segs.push({ p0, cp1, cp2, p })
|
|
62
|
+
p0 = p;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let d = pathDataToD(pathData2);
|
|
66
|
+
renderPath(markers, d, '#ccc')
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if (comPrev && comPrev.polyBottom) {
|
|
70
|
+
let polyprev = comPrev.polyBottom
|
|
71
|
+
console.log(comPrev);
|
|
72
|
+
let pt1 = polyprev[polyprev.length - 1]
|
|
73
|
+
let pt2 = polyprev[polyprev.length - 2]
|
|
74
|
+
|
|
75
|
+
let pt3 = polyBottom[0]
|
|
76
|
+
let pt4 = polyBottom[1]
|
|
77
|
+
|
|
78
|
+
let ptI = checkLineIntersection(pt4, pt3, pt2, pt1)
|
|
79
|
+
if (ptI) {
|
|
80
|
+
//console.log(ptI);
|
|
81
|
+
//comPrev.t = ptI.t2 * 1.2
|
|
82
|
+
comPrev.segs[comPrev.segs.length - 1].t = ptI.t2 * 1.2
|
|
83
|
+
|
|
84
|
+
comPrev.t = ptI.t2 * 1.2
|
|
85
|
+
com.t = 1 - ptI.t2 * 1.2
|
|
86
|
+
com.segs[0].t = 1 - ptI.t2 * 1.2
|
|
87
|
+
|
|
88
|
+
comPrev.inter = ptI
|
|
89
|
+
//com.inter = ptI
|
|
90
|
+
|
|
91
|
+
//console.log('com.t', com.t);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// adjust intersection
|
|
95
|
+
// comPrev.polyBottom[polyprev.length-1] = ptI
|
|
96
|
+
|
|
97
|
+
polyprev[polyprev.length - 1] = ptI
|
|
98
|
+
polyBottom[0] = ptI
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
/*
|
|
102
|
+
renderPoint(markers, pt1, 'purple')
|
|
103
|
+
renderPoint(markers, pt2, 'magenta')
|
|
104
|
+
renderPoint(markers, pt3, 'green')
|
|
105
|
+
renderPoint(markers, pt4, 'cyan')
|
|
106
|
+
renderPoint(markers, ptI, 'red')
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
//pathDataN.push(comN)
|
|
116
|
+
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
//console.log(pathData);
|
|
121
|
+
|
|
122
|
+
// convert pts to cpts
|
|
123
|
+
/*
|
|
124
|
+
pathDataN = [
|
|
125
|
+
{ type: 'M', values: [pathData[1].polyBottom[0].x, pathData[1].polyBottom[0].y] }
|
|
126
|
+
]
|
|
127
|
+
*/
|
|
128
|
+
|
|
129
|
+
pathDataN = [
|
|
130
|
+
{ type: 'M', values: [pathData[0].p0.x, pathData[0].p0.y] }
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
console.log(pathData);
|
|
135
|
+
|
|
136
|
+
for (let i = 1; i < l; i++) {
|
|
137
|
+
let com = pathData[i];
|
|
138
|
+
let comPrev = pathData[i - 1];
|
|
139
|
+
let comN = pathData[i+1] ? pathData[i+1] : null;
|
|
140
|
+
let { type, values, p0 = null, cp1 = null, cp2 = null, p, polyTop = null, polyBottom = null, segs = [], inter = null, t = 0 } = com;
|
|
141
|
+
|
|
142
|
+
// has intersection
|
|
143
|
+
if (inter) {
|
|
144
|
+
|
|
145
|
+
let s = t > 0.5 ? segs[0] : segs[1]
|
|
146
|
+
let s2 = t > 0.5 ? comN.segs[1] : comN.segs[0]
|
|
147
|
+
//let s2 =
|
|
148
|
+
|
|
149
|
+
console.log('segs', i, segs, s);
|
|
150
|
+
|
|
151
|
+
let pd1 = [
|
|
152
|
+
{ type: 'M', values: [s.p0.x, s.p0.y] },
|
|
153
|
+
{ type: 'C', values: [s.cp1.x, s.cp1.y, s.cp2.x, s.cp2.y, s.p.x, s.p.y] }
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
let pd2 = [
|
|
157
|
+
{ type: 'M', values: [s2.p0.x, s2.p0.y] },
|
|
158
|
+
{ type: 'C', values: [s2.cp1.x, s2.cp1.y, s2.cp2.x, s2.cp2.y, s2.p.x, s2.p.y] }
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
let d1 = pathDataToD(pd1);
|
|
163
|
+
renderPath(markers, d1, 'purple')
|
|
164
|
+
|
|
165
|
+
let d2 = pathDataToD(pd2);
|
|
166
|
+
renderPath(markers, d2, 'orange')
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
// find intersections
|
|
170
|
+
let intersections = findPathDataIntersections(pd1, pd2)
|
|
171
|
+
console.log('inter', intersections);
|
|
172
|
+
|
|
173
|
+
intersections.forEach(int=>{
|
|
174
|
+
let ptI1=pointAtT(int.cpts1, int.t1)
|
|
175
|
+
let ptI2=pointAtT(int.cpts2, int.t2)
|
|
176
|
+
|
|
177
|
+
renderPoint(markers, ptI1, 'orange', '2%')
|
|
178
|
+
renderPoint(markers, ptI2, 'red', '1%')
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
//renderPoint(markers, inter)
|
|
185
|
+
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
pathDataN.push(com)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
//let d = pathDataToD(pathDataN);
|
|
195
|
+
//renderPath(markers, d, 'red')
|
|
196
|
+
//console.log(d);
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
return pathDataN
|
|
201
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { getCombinedByDominant } from "./pathData_simplify_cubic_extrapolate";
|
|
2
|
-
import { getDistance, getSquareDistance, checkLineIntersection, pointAtT, interpolate, getDistManhattan } from "./svgii/geometry";
|
|
2
|
+
import { getDistance, getSquareDistance, checkLineIntersection, pointAtT, interpolate, getDistManhattan, getPerpendicularPoint } from "./svgii/geometry";
|
|
3
3
|
import { getBezierArea, getPolygonArea } from "./svgii/geometry_area";
|
|
4
|
-
import {
|
|
4
|
+
import { pathDataToD } from "./svgii/pathData_stringify";
|
|
5
|
+
import { renderPath, renderPoint, renderPoly } from "./svgii/visualize";
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
|
|
@@ -43,10 +44,13 @@ export function simplifyPathDataCubic(pathData, {
|
|
|
43
44
|
|
|
44
45
|
// try simplification
|
|
45
46
|
else {
|
|
47
|
+
|
|
48
|
+
|
|
46
49
|
//renderPoint(markers, p, 'magenta', '1%')
|
|
47
50
|
let combined = combineCubicPairs(com, comN, { tolerance })
|
|
48
51
|
let error = 0;
|
|
49
52
|
|
|
53
|
+
|
|
50
54
|
// combining successful! try next segment
|
|
51
55
|
if (combined.length === 1) {
|
|
52
56
|
com = combined[0]
|
|
@@ -56,7 +60,7 @@ export function simplifyPathDataCubic(pathData, {
|
|
|
56
60
|
error += com.error;
|
|
57
61
|
|
|
58
62
|
// find next candidates
|
|
59
|
-
for (let n = i +
|
|
63
|
+
for (let n = i + offset; error < tolerance && n < l; n++) {
|
|
60
64
|
let comN = pathData[n]
|
|
61
65
|
|
|
62
66
|
if (comN.type !== 'C' ||
|
|
@@ -66,6 +70,7 @@ export function simplifyPathDataCubic(pathData, {
|
|
|
66
70
|
(keepExtremes && com.extreme)
|
|
67
71
|
)
|
|
68
72
|
) {
|
|
73
|
+
|
|
69
74
|
break
|
|
70
75
|
}
|
|
71
76
|
|
|
@@ -73,6 +78,7 @@ export function simplifyPathDataCubic(pathData, {
|
|
|
73
78
|
|
|
74
79
|
// failure - could not be combined - exit loop
|
|
75
80
|
if (combined.length > 1) {
|
|
81
|
+
//renderPoint(markers, com.p, 'orange', '1%', '0.5')
|
|
76
82
|
break
|
|
77
83
|
}
|
|
78
84
|
|
|
@@ -86,6 +92,7 @@ export function simplifyPathDataCubic(pathData, {
|
|
|
86
92
|
|
|
87
93
|
// return combined
|
|
88
94
|
com = combined[0]
|
|
95
|
+
|
|
89
96
|
}
|
|
90
97
|
|
|
91
98
|
//console.log('tests', log, offset);
|
|
@@ -131,12 +138,13 @@ export function combineCubicPairs(com1, com2, {
|
|
|
131
138
|
// quit if t is start
|
|
132
139
|
if (!t) return commands;
|
|
133
140
|
|
|
141
|
+
// get averaged threshold
|
|
134
142
|
let distAv1 = getDistManhattan(com1.p0, com1.p);
|
|
135
143
|
let distAv2 = getDistManhattan(com2.p0, com2.p);
|
|
136
144
|
let distMin = Math.max(0, Math.min(distAv1, distAv2))
|
|
137
145
|
|
|
138
|
-
|
|
139
|
-
let distScale = 0.
|
|
146
|
+
//let distScale = 0.08
|
|
147
|
+
let distScale = 0.075
|
|
140
148
|
let maxDist = distMin * distScale * tolerance
|
|
141
149
|
|
|
142
150
|
// get hypothetical combined command
|
|
@@ -145,7 +153,6 @@ export function combineCubicPairs(com1, com2, {
|
|
|
145
153
|
// test new point-at-t against original mid segment starting point
|
|
146
154
|
let ptI = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t)
|
|
147
155
|
|
|
148
|
-
|
|
149
156
|
let dist0 = getDistManhattan(com1.p, ptI)
|
|
150
157
|
let dist1 = 0, dist2 = 0;
|
|
151
158
|
let close = dist0 < maxDist;
|
|
@@ -154,7 +161,6 @@ export function combineCubicPairs(com1, com2, {
|
|
|
154
161
|
// collect error data
|
|
155
162
|
let error = dist0;
|
|
156
163
|
|
|
157
|
-
|
|
158
164
|
if (close) {
|
|
159
165
|
|
|
160
166
|
/**
|
|
@@ -170,7 +176,6 @@ export function combineCubicPairs(com1, com2, {
|
|
|
170
176
|
let ptI_seg1 = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t2)
|
|
171
177
|
dist1 = getDistManhattan(ptM_seg1, ptI_seg1)
|
|
172
178
|
|
|
173
|
-
|
|
174
179
|
error += dist1;
|
|
175
180
|
|
|
176
181
|
if (dist1 < maxDist) {
|
|
@@ -187,17 +192,6 @@ export function combineCubicPairs(com1, com2, {
|
|
|
187
192
|
|
|
188
193
|
if (error < maxDist) success = true;
|
|
189
194
|
|
|
190
|
-
|
|
191
|
-
/*
|
|
192
|
-
renderPoint(markers, ptM_seg1, 'cyan')
|
|
193
|
-
renderPoint(markers, pt, 'orange', '1.5%', '1')
|
|
194
|
-
renderPoint(markers, ptM_seg2, 'orange')
|
|
195
|
-
|
|
196
|
-
renderPoint(markers, com1.p, 'green')
|
|
197
|
-
//renderPoint(markers, com2.p, 'green')
|
|
198
|
-
renderPoint(markers, ptI_seg1, 'purple')
|
|
199
|
-
*/
|
|
200
|
-
|
|
201
195
|
}
|
|
202
196
|
|
|
203
197
|
} // end 1st try
|
|
@@ -213,9 +207,9 @@ export function combineCubicPairs(com1, com2, {
|
|
|
213
207
|
|
|
214
208
|
comS.dimA = getDistManhattan(comS.p0, comS.p);
|
|
215
209
|
comS.type = 'C';
|
|
210
|
+
|
|
216
211
|
comS.extreme = com2.extreme;
|
|
217
212
|
comS.directionChange = com2.directionChange;
|
|
218
|
-
//comS.directionChange = com1.directionChange ? true : (com2.directionChange);
|
|
219
213
|
comS.corner = com2.corner;
|
|
220
214
|
|
|
221
215
|
comS.values = [comS.cp1.x, comS.cp1.y, comS.cp2.x, comS.cp2.y, comS.p.x, comS.p.y]
|
|
@@ -290,6 +284,7 @@ export function findSplitT(com1, com2) {
|
|
|
290
284
|
// distances between 1st and 2nd segment cpt to mid point
|
|
291
285
|
let l1 = getDistManhattan(com1.cp2, com1.p)
|
|
292
286
|
|
|
287
|
+
|
|
293
288
|
// exit for zero length control point vectors
|
|
294
289
|
if (l1 === 0) {
|
|
295
290
|
//console.log('!quit1');
|
|
@@ -313,5 +308,8 @@ export function findSplitT(com1, com2) {
|
|
|
313
308
|
}
|
|
314
309
|
*/
|
|
315
310
|
|
|
316
|
-
|
|
311
|
+
let t = l1 / l3;
|
|
312
|
+
//console.log('t', t);
|
|
313
|
+
|
|
314
|
+
return t;
|
|
317
315
|
}
|
|
@@ -1,10 +1,147 @@
|
|
|
1
|
-
import { checkLineIntersection, getAngle, getDistAv, getSquareDistance, interpolate, pointAtT, rotatePoint } from "./svgii/geometry";
|
|
1
|
+
import { checkLineIntersection, getAngle, getDistance, getDistAv, getDistManhattan, getSquareDistance, interpolate, pointAtT, rotatePoint } from "./svgii/geometry";
|
|
2
2
|
import { getPathArea } from "./svgii/geometry_area";
|
|
3
3
|
import { pathDataToD } from "./svgii/pathData_stringify";
|
|
4
4
|
import { renderPath, renderPoint } from "./svgii/visualize";
|
|
5
5
|
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
6
10
|
export function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, debug = false) {
|
|
7
11
|
|
|
12
|
+
// if combining fails return original commands
|
|
13
|
+
let commands = [com1, com2]
|
|
14
|
+
|
|
15
|
+
// detect dominant
|
|
16
|
+
let dist1 = getDistManhattan(com1.p0, com1.p)
|
|
17
|
+
let dist2 = getDistManhattan(com2.p0, com2.p)
|
|
18
|
+
let thresh = (dist1 + dist2) * 0.5 * 0.075 * tolerance
|
|
19
|
+
|
|
20
|
+
// take longer command
|
|
21
|
+
let reverse = dist1 < dist2;
|
|
22
|
+
//let reverse = dist1 > dist2;
|
|
23
|
+
|
|
24
|
+
// backup original commands
|
|
25
|
+
let com1_o = JSON.parse(JSON.stringify(com1))
|
|
26
|
+
let com2_o = JSON.parse(JSON.stringify(com2))
|
|
27
|
+
|
|
28
|
+
// intersection of control tangents
|
|
29
|
+
let ptI = checkLineIntersection(com1_o.p0, com1_o.cp1, com2_o.p, com2_o.cp2, false, true)
|
|
30
|
+
|
|
31
|
+
// no intersection - we can't combine
|
|
32
|
+
if (!ptI) {
|
|
33
|
+
return commands
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (reverse) {
|
|
37
|
+
let com2_R = {
|
|
38
|
+
p0: { x: com1.p.x, y: com1.p.y },
|
|
39
|
+
cp1: { x: com1.cp2.x, y: com1.cp2.y },
|
|
40
|
+
cp2: { x: com1.cp1.x, y: com1.cp1.y },
|
|
41
|
+
p: { x: com1.p0.x, y: com1.p0.y },
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let com1_R = {
|
|
45
|
+
p0: { x: com2.p.x, y: com2.p.y },
|
|
46
|
+
cp1: { x: com2.cp2.x, y: com2.cp2.y },
|
|
47
|
+
cp2: { x: com2.cp1.x, y: com2.cp1.y },
|
|
48
|
+
p: { x: com2.p0.x, y: com2.p0.y },
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
com1 = com1_R;
|
|
52
|
+
com2 = com2_R;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
// etsimate t for extrapolation
|
|
57
|
+
let PtI = checkLineIntersection(com1.cp2, com1.p, com2.p, com2.cp2, false, true)
|
|
58
|
+
let cp1_I = interpolate(com1.p, PtI, 0.666)
|
|
59
|
+
|
|
60
|
+
//renderPoint(markers, cp1_I)
|
|
61
|
+
//renderPoint(markers, com1.p0, 'green', '2%')
|
|
62
|
+
|
|
63
|
+
let dist1_2 = getDistManhattan(com1.cp2, com1.p)
|
|
64
|
+
let dist2_2 = getDistManhattan(com1.cp2, cp1_I)
|
|
65
|
+
let t = dist2_2 / dist1_2
|
|
66
|
+
|
|
67
|
+
// extrapolate
|
|
68
|
+
let segs = pointAtT([com1.p0, com1.cp1, com1.cp2, com1.p], t, false, true).segments
|
|
69
|
+
|
|
70
|
+
//console.log(segs);
|
|
71
|
+
let seg = segs[0]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
// if it worked - points should be nearby
|
|
75
|
+
let dist = getDistManhattan(seg.p, com2.p)
|
|
76
|
+
|
|
77
|
+
// if close enough adjust
|
|
78
|
+
if (dist < thresh) {
|
|
79
|
+
let angle = getAngle(seg.p, seg.cp2)
|
|
80
|
+
let angle2 = getAngle(com2.p, com2.cp2)
|
|
81
|
+
//let angleDiff = Math.abs(angle2 - angle)
|
|
82
|
+
let angleDiff = (angle2 - angle)
|
|
83
|
+
|
|
84
|
+
//let angleRat = angle / angle2;
|
|
85
|
+
//angleRat = (1-angleDiff) / angle2;
|
|
86
|
+
|
|
87
|
+
// adjust cp angle
|
|
88
|
+
seg.cp2 = rotatePoint(seg.cp2, seg.p.x, seg.p.y, angleDiff)
|
|
89
|
+
let dist1 = getDistManhattan(seg.p, seg.cp2)
|
|
90
|
+
|
|
91
|
+
// copy original final point coordinates
|
|
92
|
+
seg.p = com2.p
|
|
93
|
+
|
|
94
|
+
// after rotation
|
|
95
|
+
let dist2 = getDistManhattan(seg.p, seg.cp2)
|
|
96
|
+
let scale = dist2 / dist1
|
|
97
|
+
|
|
98
|
+
// adjust tangent length
|
|
99
|
+
seg.cp2 = interpolate(seg.p, seg.cp2, scale)
|
|
100
|
+
|
|
101
|
+
// reverse back
|
|
102
|
+
if (reverse) {
|
|
103
|
+
seg = {
|
|
104
|
+
p0: seg.p,
|
|
105
|
+
p: seg.p0,
|
|
106
|
+
cp1: seg.cp2,
|
|
107
|
+
cp2: seg.cp1,
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
commands = [
|
|
112
|
+
{
|
|
113
|
+
type: 'C',
|
|
114
|
+
values: [seg.cp1.x, seg.cp1.y, seg.cp2.x, seg.cp2.y, seg.p.x, seg.p.y],
|
|
115
|
+
p0: seg.p0,
|
|
116
|
+
cp1: seg.cp1,
|
|
117
|
+
cp2: seg.cp2,
|
|
118
|
+
p: seg.p,
|
|
119
|
+
extreme: com2_o.extreme,
|
|
120
|
+
corner: com2_o.corner,
|
|
121
|
+
directionChange: com2_o.directionChange,
|
|
122
|
+
dimA: getDistManhattan(seg.p0, seg.p),
|
|
123
|
+
error: dist
|
|
124
|
+
|
|
125
|
+
}
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
//console.log('commands', reverse, commands);
|
|
131
|
+
return commands
|
|
132
|
+
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
export function getCombinedByDominant_back(com1, com2, maxDist = 0, tolerance = 1, debug = false) {
|
|
142
|
+
|
|
143
|
+
//console.log('getCombinedByDominant');
|
|
144
|
+
|
|
8
145
|
// cubic Bézier derivative
|
|
9
146
|
const cubicDerivative = (p0, p1, p2, p3, t) => {
|
|
10
147
|
let mt = 1 - t;
|
|
@@ -25,8 +162,8 @@ export function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, de
|
|
|
25
162
|
let commands = [com1, com2]
|
|
26
163
|
|
|
27
164
|
// detect dominant
|
|
28
|
-
let dist1 =
|
|
29
|
-
let dist2 =
|
|
165
|
+
let dist1 = getDistManhattan(com1.p0, com1.p)
|
|
166
|
+
let dist2 = getDistManhattan(com2.p0, com2.p)
|
|
30
167
|
|
|
31
168
|
let reverse = dist1 > dist2;
|
|
32
169
|
|
|
@@ -91,10 +228,12 @@ export function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, de
|
|
|
91
228
|
let dP = cubicDerivative(com2.p0, com2.cp1, com2.cp2, com2.p, t0);
|
|
92
229
|
let r = sub(P, com1.p0);
|
|
93
230
|
|
|
94
|
-
//let t0_2 = t0 - dot(r, dP) / dot(dP, dP);
|
|
95
231
|
|
|
232
|
+
//let t0_2 = t0 - dot(r, dP) / dot(dP, dP);
|
|
96
233
|
t0 -= dot(r, dP) / dot(dP, dP);
|
|
97
234
|
|
|
235
|
+
//console.log(t0, t2);
|
|
236
|
+
|
|
98
237
|
// construct merged cubic over [t0, 1]
|
|
99
238
|
let Q0 = pointAtT([com2.p0, com2.cp1, com2.cp2, com2.p], t0);
|
|
100
239
|
let Q3 = com2.p;
|
|
@@ -145,8 +284,8 @@ export function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, de
|
|
|
145
284
|
|
|
146
285
|
//let tscale =(1 + t0)
|
|
147
286
|
//console.log('tscale', tscale);
|
|
148
|
-
let cp1_2 = interpolate(result.p0, ptI_1, 1.333
|
|
149
|
-
let cp2_2 = interpolate(result.p, ptI_2, 1.333
|
|
287
|
+
let cp1_2 = interpolate(result.p0, ptI_1, 1.333)
|
|
288
|
+
let cp2_2 = interpolate(result.p, ptI_2, 1.333)
|
|
150
289
|
|
|
151
290
|
// test self intersections and exit
|
|
152
291
|
let cp_intersection = checkLineIntersection(com1_o.p0, cp1_2, com2_o.p, cp2_2, true)
|
|
@@ -203,7 +342,7 @@ export function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, de
|
|
|
203
342
|
//console.log('distS', distS, maxDist );
|
|
204
343
|
|
|
205
344
|
// not close enough - exit
|
|
206
|
-
if (distSplit > maxDist * tolerance
|
|
345
|
+
if (distSplit > maxDist * tolerance) {
|
|
207
346
|
//renderPoint(markers, ptSplit, 'cyan', '1%')
|
|
208
347
|
//renderPoint(markers, com1.p, 'red', '0.5%')
|
|
209
348
|
return commands;
|
|
@@ -241,7 +380,7 @@ export function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, de
|
|
|
241
380
|
if (areaDiff < 0.05 * tolerance) {
|
|
242
381
|
commands = [result];
|
|
243
382
|
//console.log('areaDiff', areaDiff);
|
|
244
|
-
}
|
|
383
|
+
}
|
|
245
384
|
}
|
|
246
385
|
|
|
247
386
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { checkLineIntersection, getAngle, getDistManhattan, getDistance, rotatePoint } from "./svgii/geometry";
|
|
1
|
+
import { checkLineIntersection, getAngle, getAngleFromDelta, getDistManhattan, getDistance, rotatePoint } from "./svgii/geometry";
|
|
2
2
|
import { getPathArea, getPolygonArea, getRelativeAreaDiff } from "./svgii/geometry_area";
|
|
3
|
+
import { renderPoint } from "./svgii/visualize";
|
|
3
4
|
//import { cubicCommandToArc } from "./svgii/pathData_convert";
|
|
4
5
|
|
|
5
6
|
export function pathDataCubicsToArc(pathData, { areaThreshold = 2.5 } = {}) {
|
|
@@ -14,7 +15,7 @@ export function pathDataCubicsToArc(pathData, { areaThreshold = 2.5 } = {}) {
|
|
|
14
15
|
let comAN = cubicCommandToArc(comN.p0, comN.cp1, comN.cp2, comN.p, areaThreshold)
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
if (comA.isArc
|
|
18
|
+
if (comA.isArc && comAN.isArc) {
|
|
18
19
|
|
|
19
20
|
let dist = getDistManhattan(p0, comN.p);
|
|
20
21
|
let maxDist = dist * 0.01;
|
|
@@ -30,8 +31,18 @@ export function pathDataCubicsToArc(pathData, { areaThreshold = 2.5 } = {}) {
|
|
|
30
31
|
let sweep = area < 0 ? 0 : 1;
|
|
31
32
|
|
|
32
33
|
if (vertical || horizontal) {
|
|
34
|
+
|
|
33
35
|
rx = Math.min(rx, comAN.rx)
|
|
34
36
|
ry = Math.min(ry, comAN.ry)
|
|
37
|
+
|
|
38
|
+
let diffR = Math.abs(rx - ry) / rx;
|
|
39
|
+
let isSemiCircle = diffR < 0.025
|
|
40
|
+
//console.log('isSemiCircle', isSemiCircle, diffR);
|
|
41
|
+
if (isSemiCircle) {
|
|
42
|
+
rx = rx > 1 ? 1 : Math.min(rx, ry);
|
|
43
|
+
ry = rx;
|
|
44
|
+
}
|
|
45
|
+
|
|
35
46
|
pathData[c] = null;
|
|
36
47
|
pathData[c + 1].type = 'A';
|
|
37
48
|
pathData[c + 1].values = [rx, ry, 0, 0, sweep, comN.p.x, comN.p.y];
|
|
@@ -58,40 +69,74 @@ export function cubicCommandToArc(p0, cp1, cp2, p, tolerance = 7.5) {
|
|
|
58
69
|
let arcSegArea = 0, isArc = false
|
|
59
70
|
|
|
60
71
|
// check angles
|
|
61
|
-
let
|
|
62
|
-
let
|
|
72
|
+
let dx1 = (cp1.x - p0.x)
|
|
73
|
+
let dy1 = (cp1.y - p0.y)
|
|
74
|
+
let dx2 = (cp2.x - p.x)
|
|
75
|
+
let dy2 = (cp2.y - p.y)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
//let angle1 = getAngle(p0, cp1, true);
|
|
79
|
+
//let angle2 = getAngle(p, cp2, true);
|
|
80
|
+
|
|
81
|
+
let thresh = getDistManhattan(p0, p) * 0.001
|
|
82
|
+
|
|
83
|
+
let isVertical1 = dx1 < thresh
|
|
84
|
+
let isVertical2 = dx2 < thresh
|
|
85
|
+
|
|
86
|
+
let isHorizontal1 = dy1 < thresh
|
|
87
|
+
let isHorizontal2 = dy2 < thresh
|
|
88
|
+
|
|
89
|
+
let isRightAngle = (isVertical1 || isVertical2) && (isHorizontal1 || isHorizontal2);
|
|
90
|
+
|
|
91
|
+
/*
|
|
92
|
+
let angle1 = getAngleFromDelta(dx1, dy1, true);
|
|
93
|
+
let angle2 = getAngleFromDelta(dx2, dy2, true);
|
|
63
94
|
let deltaAngle = Math.abs(angle1 - angle2) * 180 / Math.PI;
|
|
64
95
|
|
|
65
96
|
|
|
66
97
|
let angleDiff = Math.abs((deltaAngle % 180) - 90);
|
|
67
98
|
let isRightAngle = angleDiff < 3;
|
|
99
|
+
*/
|
|
68
100
|
|
|
69
101
|
|
|
70
102
|
let rx = 0
|
|
71
103
|
let ry = 0
|
|
72
|
-
let ptC;
|
|
104
|
+
let ptC = p0;
|
|
105
|
+
let r1 = 0, r2 = 0
|
|
73
106
|
|
|
74
107
|
if (isRightAngle) {
|
|
75
|
-
// point between cps
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// center point
|
|
79
|
-
let cp1_r = rotatePoint(cp1, p0.x, p0.y, (Math.PI * -0.5))
|
|
80
|
-
let cp2_r = rotatePoint(cp2, p.x, p.y, (Math.PI * 0.5))
|
|
81
|
-
|
|
82
|
-
// assumed centroid
|
|
83
|
-
ptC = checkLineIntersection(p0, cp1_r, p, cp2_r, false)
|
|
84
|
-
//renderPoint(markers, ptC, 'red', '1%', '0.5' )
|
|
85
|
-
|
|
86
|
-
|
|
87
108
|
|
|
109
|
+
if (isHorizontal1 && isVertical2) {
|
|
110
|
+
ptC = { x: p0.x, y: p.y }
|
|
111
|
+
r1 = Math.abs(p.x-p0.x)
|
|
112
|
+
r2 = Math.abs(p.y-p0.y)
|
|
113
|
+
}
|
|
114
|
+
else if (isHorizontal2 && isVertical1) {
|
|
115
|
+
ptC = { x: p.x, y: p0.y }
|
|
116
|
+
r2 = Math.abs(p0.x-p.x)
|
|
117
|
+
r1 = Math.abs(p0.y-p.y)
|
|
118
|
+
}
|
|
88
119
|
|
|
89
|
-
|
|
120
|
+
/*
|
|
121
|
+
if (r1 && r2) {
|
|
122
|
+
//console.log('r right', r1, r2);
|
|
123
|
+
}
|
|
124
|
+
//renderPoint(markers, ptC, 'red', '1%', '0.5')
|
|
125
|
+
//let pI = checkLineIntersection(p0, cp1, p, cp2, false);
|
|
126
|
+
*/
|
|
90
127
|
|
|
91
|
-
if (
|
|
128
|
+
if (r1 && r2) {
|
|
92
129
|
|
|
130
|
+
/*
|
|
93
131
|
let r1 = getDistance(p0, pI);
|
|
94
132
|
let r2 = getDistance(p, pI);
|
|
133
|
+
*/
|
|
134
|
+
|
|
135
|
+
//r1 = getDistance(p0, pI);
|
|
136
|
+
//let r12 = isHorizontal1 Math.abs(dx1);
|
|
137
|
+
//r2 = getDistance(p, pI);
|
|
138
|
+
//console.log('rEx', r1, r2);
|
|
139
|
+
|
|
95
140
|
|
|
96
141
|
let rMax = +Math.max(r1, r2).toFixed(8);
|
|
97
142
|
let rMin = +Math.min(r1, r2).toFixed(8);
|
|
@@ -109,7 +154,6 @@ export function cubicCommandToArc(p0, cp1, cp2, p, tolerance = 7.5) {
|
|
|
109
154
|
let circular = (100 / rx * Math.abs(rx - ry)) < 5;
|
|
110
155
|
|
|
111
156
|
if (circular) {
|
|
112
|
-
//rx = (rx+ry)/2
|
|
113
157
|
rx = rMax
|
|
114
158
|
ry = rx;
|
|
115
159
|
}
|
|
@@ -136,7 +180,7 @@ export function cubicCommandToArc(p0, cp1, cp2, p, tolerance = 7.5) {
|
|
|
136
180
|
arcSegArea = (Math.PI * (rx * ry)) / 4
|
|
137
181
|
|
|
138
182
|
// subtract polygon between start, end and center point
|
|
139
|
-
arcSegArea -= Math.abs(getPolygonArea([p0, p,
|
|
183
|
+
arcSegArea -= Math.abs(getPolygonArea([p0, p, ptC]))
|
|
140
184
|
|
|
141
185
|
let areaDiff = getRelativeAreaDiff(comArea, arcSegArea);
|
|
142
186
|
|