@d3plus/math 3.0.0-alpha.0
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/README.md +216 -0
- package/es/index.js +15 -0
- package/es/src/ckmeans.js +204 -0
- package/es/src/closest.js +19 -0
- package/es/src/largestRect.js +324 -0
- package/es/src/lineIntersection.js +22 -0
- package/es/src/path2polygon.js +23 -0
- package/es/src/pointDistance.js +10 -0
- package/es/src/pointDistanceSquared.js +10 -0
- package/es/src/pointRotate.js +18 -0
- package/es/src/polygonInside.js +26 -0
- package/es/src/polygonRayCast.js +101 -0
- package/es/src/polygonRotate.js +17 -0
- package/es/src/segmentBoxContains.js +57 -0
- package/es/src/segmentsIntersect.js +15 -0
- package/es/src/shapeEdgePoint.js +45 -0
- package/es/src/simplify.js +96 -0
- package/package.json +40 -0
- package/umd/d3plus-math.full.js +1024 -0
- package/umd/d3plus-math.full.js.map +1 -0
- package/umd/d3plus-math.full.min.js +289 -0
- package/umd/d3plus-math.js +939 -0
- package/umd/d3plus-math.js.map +1 -0
- package/umd/d3plus-math.min.js +289 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
function _array_like_to_array(arr, len) {
|
|
2
|
+
if (len == null || len > arr.length) len = arr.length;
|
|
3
|
+
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
4
|
+
return arr2;
|
|
5
|
+
}
|
|
6
|
+
function _array_with_holes(arr) {
|
|
7
|
+
if (Array.isArray(arr)) return arr;
|
|
8
|
+
}
|
|
9
|
+
function _instanceof(left, right) {
|
|
10
|
+
if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
|
|
11
|
+
return !!right[Symbol.hasInstance](left);
|
|
12
|
+
} else {
|
|
13
|
+
return left instanceof right;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function _iterable_to_array_limit(arr, i) {
|
|
17
|
+
var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
|
|
18
|
+
if (_i == null) return;
|
|
19
|
+
var _arr = [];
|
|
20
|
+
var _n = true;
|
|
21
|
+
var _d = false;
|
|
22
|
+
var _s, _e;
|
|
23
|
+
try {
|
|
24
|
+
for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){
|
|
25
|
+
_arr.push(_s.value);
|
|
26
|
+
if (i && _arr.length === i) break;
|
|
27
|
+
}
|
|
28
|
+
} catch (err) {
|
|
29
|
+
_d = true;
|
|
30
|
+
_e = err;
|
|
31
|
+
} finally{
|
|
32
|
+
try {
|
|
33
|
+
if (!_n && _i["return"] != null) _i["return"]();
|
|
34
|
+
} finally{
|
|
35
|
+
if (_d) throw _e;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return _arr;
|
|
39
|
+
}
|
|
40
|
+
function _non_iterable_rest() {
|
|
41
|
+
throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
42
|
+
}
|
|
43
|
+
function _sliced_to_array(arr, i) {
|
|
44
|
+
return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest();
|
|
45
|
+
}
|
|
46
|
+
function _unsupported_iterable_to_array(o, minLen) {
|
|
47
|
+
if (!o) return;
|
|
48
|
+
if (typeof o === "string") return _array_like_to_array(o, minLen);
|
|
49
|
+
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
50
|
+
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
51
|
+
if (n === "Map" || n === "Set") return Array.from(n);
|
|
52
|
+
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
|
|
53
|
+
}
|
|
54
|
+
import { extent, merge, range } from "d3-array";
|
|
55
|
+
import { polygonArea, polygonCentroid, polygonContains } from "d3-polygon";
|
|
56
|
+
import polygonInside from "./polygonInside.js";
|
|
57
|
+
import polygonRayCast from "./polygonRayCast.js";
|
|
58
|
+
import polygonRotate from "./polygonRotate.js";
|
|
59
|
+
import simplify from "./simplify.js";
|
|
60
|
+
import pointDistanceSquared from "./pointDistanceSquared.js";
|
|
61
|
+
// Algorithm constants
|
|
62
|
+
var aspectRatioStep = 0.5; // step size for the aspect ratio
|
|
63
|
+
var angleStep = 5; // step size for angles (in degrees); has linear impact on running time
|
|
64
|
+
var polyCache = {};
|
|
65
|
+
/**
|
|
66
|
+
@typedef {Object} LargestRect
|
|
67
|
+
@desc The returned Object of the largestRect function.
|
|
68
|
+
@property {Number} width The width of the rectangle
|
|
69
|
+
@property {Number} height The height of the rectangle
|
|
70
|
+
@property {Number} cx The x coordinate of the rectangle's center
|
|
71
|
+
@property {Number} cy The y coordinate of the rectangle's center
|
|
72
|
+
@property {Number} angle The rotation angle of the rectangle in degrees. The anchor of rotation is the center point.
|
|
73
|
+
@property {Number} area The area of the largest rectangle.
|
|
74
|
+
@property {Array} points An array of x/y coordinates for each point in the rectangle, useful for rendering paths.
|
|
75
|
+
*/ /**
|
|
76
|
+
@function largestRect
|
|
77
|
+
@author Daniel Smilkov [dsmilkov@gmail.com]
|
|
78
|
+
@desc An angle of zero means that the longer side of the polygon (the width) will be aligned with the x axis. An angle of 90 and/or -90 means that the longer side of the polygon (the width) will be aligned with the y axis. The value can be a number between -90 and 90 specifying the angle of rotation of the polygon, a string which is parsed to a number, or an array of numbers specifying the possible rotations of the polygon.
|
|
79
|
+
@param {Array} poly An Array of points that represent a polygon.
|
|
80
|
+
@param {Object} [options] An Object that allows for overriding various parameters of the algorithm.
|
|
81
|
+
@param {Number|String|Array} [options.angle = d3.range(-90, 95, 5)] The allowed rotations of the final rectangle.
|
|
82
|
+
@param {Number|String|Array} [options.aspectRatio] The ratio between the width and height of the rectangle. The value can be a number, a string which is parsed to a number, or an array of numbers specifying the possible aspect ratios of the final rectangle.
|
|
83
|
+
@param {Number} [options.maxAspectRatio = 15] The maximum aspect ratio (width/height) allowed for the rectangle. This property should only be used if the aspectRatio is not provided.
|
|
84
|
+
@param {Number} [options.minAspectRatio = 1] The minimum aspect ratio (width/height) allowed for the rectangle. This property should only be used if the aspectRatio is not provided.
|
|
85
|
+
@param {Number} [options.nTries = 20] The number of randomly drawn points inside the polygon which the algorithm explores as possible center points of the maximal rectangle.
|
|
86
|
+
@param {Number} [options.minHeight = 0] The minimum height of the rectangle.
|
|
87
|
+
@param {Number} [options.minWidth = 0] The minimum width of the rectangle.
|
|
88
|
+
@param {Number} [options.tolerance = 0.02] The simplification tolerance factor, between 0 and 1. A larger tolerance corresponds to more extensive simplification.
|
|
89
|
+
@param {Array} [options.origin] The center point of the rectangle. If specified, the rectangle will be fixed at that point, otherwise the algorithm optimizes across all possible points. The given value can be either a two dimensional array specifying the x and y coordinate of the origin or an array of two dimensional points specifying multiple possible center points of the rectangle.
|
|
90
|
+
@param {Boolean} [options.cache] Whether or not to cache the result, which would be used in subsequent calculations to preserve consistency and speed up calculation time.
|
|
91
|
+
@return {LargestRect}
|
|
92
|
+
*/ export default function(poly) {
|
|
93
|
+
var options = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
|
|
94
|
+
if (poly.length < 3) {
|
|
95
|
+
if (options.verbose) console.error("polygon has to have at least 3 points", poly);
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
// For visualization debugging purposes
|
|
99
|
+
var events = [];
|
|
100
|
+
// User's input normalization
|
|
101
|
+
options = Object.assign({
|
|
102
|
+
angle: range(-90, 90 + angleStep, angleStep),
|
|
103
|
+
cache: true,
|
|
104
|
+
maxAspectRatio: 15,
|
|
105
|
+
minAspectRatio: 1,
|
|
106
|
+
minHeight: 0,
|
|
107
|
+
minWidth: 0,
|
|
108
|
+
nTries: 20,
|
|
109
|
+
tolerance: 0.02,
|
|
110
|
+
verbose: false
|
|
111
|
+
}, options);
|
|
112
|
+
var angles = _instanceof(options.angle, Array) ? options.angle : typeof options.angle === "number" ? [
|
|
113
|
+
options.angle
|
|
114
|
+
] : typeof options.angle === "string" && !isNaN(options.angle) ? [
|
|
115
|
+
Number(options.angle)
|
|
116
|
+
] : [];
|
|
117
|
+
var aspectRatios = _instanceof(options.aspectRatio, Array) ? options.aspectRatio : typeof options.aspectRatio === "number" ? [
|
|
118
|
+
options.aspectRatio
|
|
119
|
+
] : typeof options.aspectRatio === "string" && !isNaN(options.aspectRatio) ? [
|
|
120
|
+
Number(options.aspectRatio)
|
|
121
|
+
] : [];
|
|
122
|
+
var origins = options.origin && _instanceof(options.origin, Array) ? _instanceof(options.origin[0], Array) ? options.origin : [
|
|
123
|
+
options.origin
|
|
124
|
+
] : [];
|
|
125
|
+
var cacheString;
|
|
126
|
+
if (options.cache) {
|
|
127
|
+
cacheString = merge(poly).join(",");
|
|
128
|
+
cacheString += "-".concat(options.minAspectRatio);
|
|
129
|
+
cacheString += "-".concat(options.maxAspectRatio);
|
|
130
|
+
cacheString += "-".concat(options.minHeight);
|
|
131
|
+
cacheString += "-".concat(options.minWidth);
|
|
132
|
+
cacheString += "-".concat(angles.join(","));
|
|
133
|
+
cacheString += "-".concat(origins.join(","));
|
|
134
|
+
if (polyCache[cacheString]) return polyCache[cacheString];
|
|
135
|
+
}
|
|
136
|
+
var area = Math.abs(polygonArea(poly)); // take absolute value of the signed area
|
|
137
|
+
if (area === 0) {
|
|
138
|
+
if (options.verbose) console.error("polygon has 0 area", poly);
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
// get the width of the bounding box of the original polygon to determine tolerance
|
|
142
|
+
var _extent = _sliced_to_array(extent(poly, function(d) {
|
|
143
|
+
return d[0];
|
|
144
|
+
}), 2), minx = _extent[0], maxx = _extent[1];
|
|
145
|
+
var _extent1 = _sliced_to_array(extent(poly, function(d) {
|
|
146
|
+
return d[1];
|
|
147
|
+
}), 2), miny = _extent1[0], maxy = _extent1[1];
|
|
148
|
+
// simplify polygon
|
|
149
|
+
var tolerance = Math.min(maxx - minx, maxy - miny) * options.tolerance;
|
|
150
|
+
if (tolerance > 0) poly = simplify(poly, tolerance);
|
|
151
|
+
if (options.events) events.push({
|
|
152
|
+
type: "simplify",
|
|
153
|
+
poly: poly
|
|
154
|
+
});
|
|
155
|
+
var ref;
|
|
156
|
+
// get the width of the bounding box of the simplified polygon
|
|
157
|
+
ref = _sliced_to_array(extent(poly, function(d) {
|
|
158
|
+
return d[0];
|
|
159
|
+
}), 2), minx = ref[0], maxx = ref[1], ref;
|
|
160
|
+
var ref1;
|
|
161
|
+
ref1 = _sliced_to_array(extent(poly, function(d) {
|
|
162
|
+
return d[1];
|
|
163
|
+
}), 2), miny = ref1[0], maxy = ref1[1], ref1;
|
|
164
|
+
var _ref = [
|
|
165
|
+
maxx - minx,
|
|
166
|
+
maxy - miny
|
|
167
|
+
], boxWidth = _ref[0], boxHeight = _ref[1];
|
|
168
|
+
// discretize the binary search for optimal width to a resolution of this times the polygon width
|
|
169
|
+
var widthStep = Math.min(boxWidth, boxHeight) / 50;
|
|
170
|
+
// populate possible center points with random points inside the polygon
|
|
171
|
+
if (!origins.length) {
|
|
172
|
+
// get the centroid of the polygon
|
|
173
|
+
var centroid = polygonCentroid(poly);
|
|
174
|
+
if (!isFinite(centroid[0])) {
|
|
175
|
+
if (options.verbose) console.error("cannot find centroid", poly);
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
if (polygonContains(poly, centroid)) origins.push(centroid);
|
|
179
|
+
var nTries = options.nTries;
|
|
180
|
+
// get few more points inside the polygon
|
|
181
|
+
while(nTries){
|
|
182
|
+
var rndX = Math.random() * boxWidth + minx;
|
|
183
|
+
var rndY = Math.random() * boxHeight + miny;
|
|
184
|
+
var rndPoint = [
|
|
185
|
+
rndX,
|
|
186
|
+
rndY
|
|
187
|
+
];
|
|
188
|
+
if (polygonContains(poly, rndPoint)) {
|
|
189
|
+
origins.push(rndPoint);
|
|
190
|
+
}
|
|
191
|
+
nTries--;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (options.events) events.push({
|
|
195
|
+
type: "origins",
|
|
196
|
+
points: origins
|
|
197
|
+
});
|
|
198
|
+
var maxArea = 0;
|
|
199
|
+
var maxRect = null;
|
|
200
|
+
for(var ai = 0; ai < angles.length; ai++){
|
|
201
|
+
var angle = angles[ai];
|
|
202
|
+
var angleRad = -angle * Math.PI / 180;
|
|
203
|
+
if (options.events) events.push({
|
|
204
|
+
type: "angle",
|
|
205
|
+
angle: angle
|
|
206
|
+
});
|
|
207
|
+
for(var i = 0; i < origins.length; i++){
|
|
208
|
+
var origOrigin = origins[i];
|
|
209
|
+
// generate improved origins
|
|
210
|
+
var _polygonRayCast = _sliced_to_array(polygonRayCast(poly, origOrigin, angleRad), 2), p1W = _polygonRayCast[0], p2W = _polygonRayCast[1];
|
|
211
|
+
var _polygonRayCast1 = _sliced_to_array(polygonRayCast(poly, origOrigin, angleRad + Math.PI / 2), 2), p1H = _polygonRayCast1[0], p2H = _polygonRayCast1[1];
|
|
212
|
+
var modifOrigins = [];
|
|
213
|
+
if (p1W && p2W) modifOrigins.push([
|
|
214
|
+
(p1W[0] + p2W[0]) / 2,
|
|
215
|
+
(p1W[1] + p2W[1]) / 2
|
|
216
|
+
]); // average along with width axis
|
|
217
|
+
if (p1H && p2H) modifOrigins.push([
|
|
218
|
+
(p1H[0] + p2H[0]) / 2,
|
|
219
|
+
(p1H[1] + p2H[1]) / 2
|
|
220
|
+
]); // average along with height axis
|
|
221
|
+
if (options.events) events.push({
|
|
222
|
+
type: "modifOrigin",
|
|
223
|
+
idx: i,
|
|
224
|
+
p1W: p1W,
|
|
225
|
+
p2W: p2W,
|
|
226
|
+
p1H: p1H,
|
|
227
|
+
p2H: p2H,
|
|
228
|
+
modifOrigins: modifOrigins
|
|
229
|
+
});
|
|
230
|
+
for(var i1 = 0; i1 < modifOrigins.length; i1++){
|
|
231
|
+
var origin = modifOrigins[i1];
|
|
232
|
+
if (options.events) events.push({
|
|
233
|
+
type: "origin",
|
|
234
|
+
cx: origin[0],
|
|
235
|
+
cy: origin[1]
|
|
236
|
+
});
|
|
237
|
+
var _polygonRayCast2 = _sliced_to_array(polygonRayCast(poly, origin, angleRad), 2), p1W1 = _polygonRayCast2[0], p2W1 = _polygonRayCast2[1];
|
|
238
|
+
if (p1W1 === null || p2W1 === null) continue;
|
|
239
|
+
var minSqDistW = Math.min(pointDistanceSquared(origin, p1W1), pointDistanceSquared(origin, p2W1));
|
|
240
|
+
var maxWidth = 2 * Math.sqrt(minSqDistW);
|
|
241
|
+
var _polygonRayCast3 = _sliced_to_array(polygonRayCast(poly, origin, angleRad + Math.PI / 2), 2), p1H1 = _polygonRayCast3[0], p2H1 = _polygonRayCast3[1];
|
|
242
|
+
if (p1H1 === null || p2H1 === null) continue;
|
|
243
|
+
var minSqDistH = Math.min(pointDistanceSquared(origin, p1H1), pointDistanceSquared(origin, p2H1));
|
|
244
|
+
var maxHeight = 2 * Math.sqrt(minSqDistH);
|
|
245
|
+
if (maxWidth * maxHeight < maxArea) continue;
|
|
246
|
+
var aRatios = aspectRatios;
|
|
247
|
+
if (!aRatios.length) {
|
|
248
|
+
var minAspectRatio = Math.max(options.minAspectRatio, options.minWidth / maxHeight, maxArea / (maxHeight * maxHeight));
|
|
249
|
+
var maxAspectRatio = Math.min(options.maxAspectRatio, maxWidth / options.minHeight, maxWidth * maxWidth / maxArea);
|
|
250
|
+
aRatios = range(minAspectRatio, maxAspectRatio + aspectRatioStep, aspectRatioStep);
|
|
251
|
+
}
|
|
252
|
+
for(var a = 0; a < aRatios.length; a++){
|
|
253
|
+
var aRatio = aRatios[a];
|
|
254
|
+
// do a binary search to find the max width that works
|
|
255
|
+
var left = Math.max(options.minWidth, Math.sqrt(maxArea * aRatio));
|
|
256
|
+
var right = Math.min(maxWidth, maxHeight * aRatio);
|
|
257
|
+
if (right * maxHeight < maxArea) continue;
|
|
258
|
+
if (options.events && right - left >= widthStep) events.push({
|
|
259
|
+
type: "aRatio",
|
|
260
|
+
aRatio: aRatio
|
|
261
|
+
});
|
|
262
|
+
while(right - left >= widthStep){
|
|
263
|
+
var width = (left + right) / 2;
|
|
264
|
+
var height = width / aRatio;
|
|
265
|
+
var _origin = _sliced_to_array(origin, 2), cx = _origin[0], cy = _origin[1];
|
|
266
|
+
var rectPoly = [
|
|
267
|
+
[
|
|
268
|
+
cx - width / 2,
|
|
269
|
+
cy - height / 2
|
|
270
|
+
],
|
|
271
|
+
[
|
|
272
|
+
cx + width / 2,
|
|
273
|
+
cy - height / 2
|
|
274
|
+
],
|
|
275
|
+
[
|
|
276
|
+
cx + width / 2,
|
|
277
|
+
cy + height / 2
|
|
278
|
+
],
|
|
279
|
+
[
|
|
280
|
+
cx - width / 2,
|
|
281
|
+
cy + height / 2
|
|
282
|
+
]
|
|
283
|
+
];
|
|
284
|
+
rectPoly = polygonRotate(rectPoly, angleRad, origin);
|
|
285
|
+
var insidePoly = polygonInside(rectPoly, poly);
|
|
286
|
+
if (insidePoly) {
|
|
287
|
+
// we know that the area is already greater than the maxArea found so far
|
|
288
|
+
maxArea = width * height;
|
|
289
|
+
rectPoly.push(rectPoly[0]);
|
|
290
|
+
maxRect = {
|
|
291
|
+
area: maxArea,
|
|
292
|
+
cx: cx,
|
|
293
|
+
cy: cy,
|
|
294
|
+
width: width,
|
|
295
|
+
height: height,
|
|
296
|
+
angle: -angle,
|
|
297
|
+
points: rectPoly
|
|
298
|
+
};
|
|
299
|
+
left = width; // increase the width in the binary search
|
|
300
|
+
} else {
|
|
301
|
+
right = width; // decrease the width in the binary search
|
|
302
|
+
}
|
|
303
|
+
if (options.events) events.push({
|
|
304
|
+
type: "rectangle",
|
|
305
|
+
areaFraction: width * height / area,
|
|
306
|
+
cx: cx,
|
|
307
|
+
cy: cy,
|
|
308
|
+
width: width,
|
|
309
|
+
height: height,
|
|
310
|
+
angle: angle,
|
|
311
|
+
insidePoly: insidePoly
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (options.cache) {
|
|
319
|
+
polyCache[cacheString] = maxRect;
|
|
320
|
+
}
|
|
321
|
+
return options.events ? Object.assign(maxRect || {}, {
|
|
322
|
+
events: events
|
|
323
|
+
}) : maxRect;
|
|
324
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@function lineIntersection
|
|
3
|
+
@desc Finds the intersection point (if there is one) of the lines p1q1 and p2q2.
|
|
4
|
+
@param {Array} p1 The first point of the first line segment, which should always be an `[x, y]` formatted Array.
|
|
5
|
+
@param {Array} q1 The second point of the first line segment, which should always be an `[x, y]` formatted Array.
|
|
6
|
+
@param {Array} p2 The first point of the second line segment, which should always be an `[x, y]` formatted Array.
|
|
7
|
+
@param {Array} q2 The second point of the second line segment, which should always be an `[x, y]` formatted Array.
|
|
8
|
+
@returns {Boolean}
|
|
9
|
+
*/ export default function(p1, q1, p2, q2) {
|
|
10
|
+
// allow for some margins due to numerical errors
|
|
11
|
+
var eps = 1e-9;
|
|
12
|
+
// find the intersection point between the two infinite lines
|
|
13
|
+
var dx1 = p1[0] - q1[0], dx2 = p2[0] - q2[0], dy1 = p1[1] - q1[1], dy2 = p2[1] - q2[1];
|
|
14
|
+
var denom = dx1 * dy2 - dy1 * dx2;
|
|
15
|
+
if (Math.abs(denom) < eps) return null;
|
|
16
|
+
var cross1 = p1[0] * q1[1] - p1[1] * q1[0], cross2 = p2[0] * q2[1] - p2[1] * q2[0];
|
|
17
|
+
var px = (cross1 * dx2 - cross2 * dx1) / denom, py = (cross1 * dy2 - cross2 * dy1) / denom;
|
|
18
|
+
return [
|
|
19
|
+
px,
|
|
20
|
+
py
|
|
21
|
+
];
|
|
22
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@function path2polygon
|
|
3
|
+
@desc Transforms a path string into an Array of points.
|
|
4
|
+
@param {String} path An SVG string path, commonly the "d" property of a <path> element.
|
|
5
|
+
@param {Number} [segmentLength = 50] The length of line segments when converting curves line segments. Higher values lower computation time, but will result in curves that are more rigid.
|
|
6
|
+
@returns {Array}
|
|
7
|
+
*/ export default function(path) {
|
|
8
|
+
var segmentLength = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : 50;
|
|
9
|
+
if (typeof document === "undefined") return [];
|
|
10
|
+
var svgPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
11
|
+
svgPath.setAttribute("d", path);
|
|
12
|
+
var len = svgPath.getTotalLength();
|
|
13
|
+
var NUM_POINTS = len / segmentLength < 10 ? len / 10 : len / segmentLength;
|
|
14
|
+
var points = [];
|
|
15
|
+
for(var i = 0; i < NUM_POINTS; i++){
|
|
16
|
+
var pt = svgPath.getPointAtLength(i * len / (NUM_POINTS - 1));
|
|
17
|
+
points.push([
|
|
18
|
+
pt.x,
|
|
19
|
+
pt.y
|
|
20
|
+
]);
|
|
21
|
+
}
|
|
22
|
+
return points;
|
|
23
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import pointDistanceSquared from "./pointDistanceSquared.js";
|
|
2
|
+
/**
|
|
3
|
+
@function pointDistance
|
|
4
|
+
@desc Calculates the pixel distance between two points.
|
|
5
|
+
@param {Array} p1 The first point, which should always be an `[x, y]` formatted Array.
|
|
6
|
+
@param {Array} p2 The second point, which should always be an `[x, y]` formatted Array.
|
|
7
|
+
@returns {Number}
|
|
8
|
+
*/ export default function(p1, p2) {
|
|
9
|
+
return Math.sqrt(pointDistanceSquared(p1, p2));
|
|
10
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@function pointDistanceSquared
|
|
3
|
+
@desc Returns the squared euclidean distance between two points.
|
|
4
|
+
@param {Array} p1 The first point, which should always be an `[x, y]` formatted Array.
|
|
5
|
+
@param {Array} p2 The second point, which should always be an `[x, y]` formatted Array.
|
|
6
|
+
@returns {Number}
|
|
7
|
+
*/ export default function(p1, p2) {
|
|
8
|
+
var dx = p2[0] - p1[0], dy = p2[1] - p1[1];
|
|
9
|
+
return dx * dx + dy * dy;
|
|
10
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@function pointRotate
|
|
3
|
+
@desc Rotates a point around a given origin.
|
|
4
|
+
@param {Array} p The point to be rotated, which should always be an `[x, y]` formatted Array.
|
|
5
|
+
@param {Number} alpha The angle in radians to rotate.
|
|
6
|
+
@param {Array} [origin = [0, 0]] The origin point of the rotation, which should always be an `[x, y]` formatted Array.
|
|
7
|
+
@returns {Boolean}
|
|
8
|
+
*/ export default function(p, alpha) {
|
|
9
|
+
var origin = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : [
|
|
10
|
+
0,
|
|
11
|
+
0
|
|
12
|
+
];
|
|
13
|
+
var cosAlpha = Math.cos(alpha), sinAlpha = Math.sin(alpha), xshifted = p[0] - origin[0], yshifted = p[1] - origin[1];
|
|
14
|
+
return [
|
|
15
|
+
cosAlpha * xshifted - sinAlpha * yshifted + origin[0],
|
|
16
|
+
sinAlpha * xshifted + cosAlpha * yshifted + origin[1]
|
|
17
|
+
];
|
|
18
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { polygonContains } from "d3-polygon";
|
|
2
|
+
import segmentsIntersect from "./segmentsIntersect.js";
|
|
3
|
+
/**
|
|
4
|
+
@function polygonInside
|
|
5
|
+
@desc Checks if one polygon is inside another polygon.
|
|
6
|
+
@param {Array} polyA An Array of `[x, y]` points to be used as the inner polygon, checking if it is inside polyA.
|
|
7
|
+
@param {Array} polyB An Array of `[x, y]` points to be used as the containing polygon.
|
|
8
|
+
@returns {Boolean}
|
|
9
|
+
*/ export default function(polyA, polyB) {
|
|
10
|
+
var iA = -1;
|
|
11
|
+
var nA = polyA.length;
|
|
12
|
+
var nB = polyB.length;
|
|
13
|
+
var bA = polyA[nA - 1];
|
|
14
|
+
while(++iA < nA){
|
|
15
|
+
var aA = bA;
|
|
16
|
+
bA = polyA[iA];
|
|
17
|
+
var iB = -1;
|
|
18
|
+
var bB = polyB[nB - 1];
|
|
19
|
+
while(++iB < nB){
|
|
20
|
+
var aB = bB;
|
|
21
|
+
bB = polyB[iB];
|
|
22
|
+
if (segmentsIntersect(aA, bA, aB, bB)) return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return polygonContains(polyB, polyA[0]);
|
|
26
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
function _array_like_to_array(arr, len) {
|
|
2
|
+
if (len == null || len > arr.length) len = arr.length;
|
|
3
|
+
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
4
|
+
return arr2;
|
|
5
|
+
}
|
|
6
|
+
function _array_with_holes(arr) {
|
|
7
|
+
if (Array.isArray(arr)) return arr;
|
|
8
|
+
}
|
|
9
|
+
function _iterable_to_array_limit(arr, i) {
|
|
10
|
+
var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
|
|
11
|
+
if (_i == null) return;
|
|
12
|
+
var _arr = [];
|
|
13
|
+
var _n = true;
|
|
14
|
+
var _d = false;
|
|
15
|
+
var _s, _e;
|
|
16
|
+
try {
|
|
17
|
+
for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){
|
|
18
|
+
_arr.push(_s.value);
|
|
19
|
+
if (i && _arr.length === i) break;
|
|
20
|
+
}
|
|
21
|
+
} catch (err) {
|
|
22
|
+
_d = true;
|
|
23
|
+
_e = err;
|
|
24
|
+
} finally{
|
|
25
|
+
try {
|
|
26
|
+
if (!_n && _i["return"] != null) _i["return"]();
|
|
27
|
+
} finally{
|
|
28
|
+
if (_d) throw _e;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return _arr;
|
|
32
|
+
}
|
|
33
|
+
function _non_iterable_rest() {
|
|
34
|
+
throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
35
|
+
}
|
|
36
|
+
function _sliced_to_array(arr, i) {
|
|
37
|
+
return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest();
|
|
38
|
+
}
|
|
39
|
+
function _unsupported_iterable_to_array(o, minLen) {
|
|
40
|
+
if (!o) return;
|
|
41
|
+
if (typeof o === "string") return _array_like_to_array(o, minLen);
|
|
42
|
+
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
43
|
+
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
44
|
+
if (n === "Map" || n === "Set") return Array.from(n);
|
|
45
|
+
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
|
|
46
|
+
}
|
|
47
|
+
import lineIntersection from "./lineIntersection.js";
|
|
48
|
+
import segmentBoxContains from "./segmentBoxContains.js";
|
|
49
|
+
import pointDistanceSquared from "./pointDistanceSquared.js";
|
|
50
|
+
/**
|
|
51
|
+
@function polygonRayCast
|
|
52
|
+
@desc Gives the two closest intersection points between a ray cast from a point inside a polygon. The two points should lie on opposite sides of the origin.
|
|
53
|
+
@param {Array} poly The polygon to test against, which should be an `[x, y]` formatted Array.
|
|
54
|
+
@param {Array} origin The origin point of the ray to be cast, which should be an `[x, y]` formatted Array.
|
|
55
|
+
@param {Number} [alpha = 0] The angle in radians of the ray.
|
|
56
|
+
@returns {Array} An array containing two values, the closest point on the left and the closest point on the right. If either point cannot be found, that value will be `null`.
|
|
57
|
+
*/ export default function(poly, origin) {
|
|
58
|
+
var alpha = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : 0;
|
|
59
|
+
var eps = 1e-9;
|
|
60
|
+
origin = [
|
|
61
|
+
origin[0] + eps * Math.cos(alpha),
|
|
62
|
+
origin[1] + eps * Math.sin(alpha)
|
|
63
|
+
];
|
|
64
|
+
var _origin = _sliced_to_array(origin, 2), x0 = _origin[0], y0 = _origin[1];
|
|
65
|
+
var shiftedOrigin = [
|
|
66
|
+
x0 + Math.cos(alpha),
|
|
67
|
+
y0 + Math.sin(alpha)
|
|
68
|
+
];
|
|
69
|
+
var idx = 0;
|
|
70
|
+
if (Math.abs(shiftedOrigin[0] - x0) < eps) idx = 1;
|
|
71
|
+
var i = -1;
|
|
72
|
+
var n = poly.length;
|
|
73
|
+
var b = poly[n - 1];
|
|
74
|
+
var minSqDistLeft = Number.MAX_VALUE;
|
|
75
|
+
var minSqDistRight = Number.MAX_VALUE;
|
|
76
|
+
var closestPointLeft = null;
|
|
77
|
+
var closestPointRight = null;
|
|
78
|
+
while(++i < n){
|
|
79
|
+
var a = b;
|
|
80
|
+
b = poly[i];
|
|
81
|
+
var p = lineIntersection(origin, shiftedOrigin, a, b);
|
|
82
|
+
if (p && segmentBoxContains(a, b, p)) {
|
|
83
|
+
var sqDist = pointDistanceSquared(origin, p);
|
|
84
|
+
if (p[idx] < origin[idx]) {
|
|
85
|
+
if (sqDist < minSqDistLeft) {
|
|
86
|
+
minSqDistLeft = sqDist;
|
|
87
|
+
closestPointLeft = p;
|
|
88
|
+
}
|
|
89
|
+
} else if (p[idx] > origin[idx]) {
|
|
90
|
+
if (sqDist < minSqDistRight) {
|
|
91
|
+
minSqDistRight = sqDist;
|
|
92
|
+
closestPointRight = p;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return [
|
|
98
|
+
closestPointLeft,
|
|
99
|
+
closestPointRight
|
|
100
|
+
];
|
|
101
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import pointRotate from "./pointRotate.js";
|
|
2
|
+
/**
|
|
3
|
+
@function polygonRotate
|
|
4
|
+
@desc Rotates a point around a given origin.
|
|
5
|
+
@param {Array} poly The polygon to be rotated, which should be an Array of `[x, y]` values.
|
|
6
|
+
@param {Number} alpha The angle in radians to rotate.
|
|
7
|
+
@param {Array} [origin = [0, 0]] The origin point of the rotation, which should be an `[x, y]` formatted Array.
|
|
8
|
+
@returns {Boolean}
|
|
9
|
+
*/ export default function(poly, alpha) {
|
|
10
|
+
var origin = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : [
|
|
11
|
+
0,
|
|
12
|
+
0
|
|
13
|
+
];
|
|
14
|
+
return poly.map(function(p) {
|
|
15
|
+
return pointRotate(p, alpha, origin);
|
|
16
|
+
});
|
|
17
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@function segmentBoxContains
|
|
3
|
+
@desc Checks whether a point is inside the bounding box of a line segment.
|
|
4
|
+
@param {Array} s1 The first point of the line segment to be used for the bounding box, which should always be an `[x, y]` formatted Array.
|
|
5
|
+
@param {Array} s2 The second point of the line segment to be used for the bounding box, which should always be an `[x, y]` formatted Array.
|
|
6
|
+
@param {Array} p The point to be checked, which should always be an `[x, y]` formatted Array.
|
|
7
|
+
@returns {Boolean}
|
|
8
|
+
*/ function _array_like_to_array(arr, len) {
|
|
9
|
+
if (len == null || len > arr.length) len = arr.length;
|
|
10
|
+
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
11
|
+
return arr2;
|
|
12
|
+
}
|
|
13
|
+
function _array_with_holes(arr) {
|
|
14
|
+
if (Array.isArray(arr)) return arr;
|
|
15
|
+
}
|
|
16
|
+
function _iterable_to_array_limit(arr, i) {
|
|
17
|
+
var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
|
|
18
|
+
if (_i == null) return;
|
|
19
|
+
var _arr = [];
|
|
20
|
+
var _n = true;
|
|
21
|
+
var _d = false;
|
|
22
|
+
var _s, _e;
|
|
23
|
+
try {
|
|
24
|
+
for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){
|
|
25
|
+
_arr.push(_s.value);
|
|
26
|
+
if (i && _arr.length === i) break;
|
|
27
|
+
}
|
|
28
|
+
} catch (err) {
|
|
29
|
+
_d = true;
|
|
30
|
+
_e = err;
|
|
31
|
+
} finally{
|
|
32
|
+
try {
|
|
33
|
+
if (!_n && _i["return"] != null) _i["return"]();
|
|
34
|
+
} finally{
|
|
35
|
+
if (_d) throw _e;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return _arr;
|
|
39
|
+
}
|
|
40
|
+
function _non_iterable_rest() {
|
|
41
|
+
throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
42
|
+
}
|
|
43
|
+
function _sliced_to_array(arr, i) {
|
|
44
|
+
return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest();
|
|
45
|
+
}
|
|
46
|
+
function _unsupported_iterable_to_array(o, minLen) {
|
|
47
|
+
if (!o) return;
|
|
48
|
+
if (typeof o === "string") return _array_like_to_array(o, minLen);
|
|
49
|
+
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
50
|
+
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
51
|
+
if (n === "Map" || n === "Set") return Array.from(n);
|
|
52
|
+
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
|
|
53
|
+
}
|
|
54
|
+
export default function(s1, s2, p) {
|
|
55
|
+
var eps = 1e-9, _p = _sliced_to_array(p, 2), px = _p[0], py = _p[1];
|
|
56
|
+
return !(px < Math.min(s1[0], s2[0]) - eps || px > Math.max(s1[0], s2[0]) + eps || py < Math.min(s1[1], s2[1]) - eps || py > Math.max(s1[1], s2[1]) + eps);
|
|
57
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import lineIntersection from "./lineIntersection.js";
|
|
2
|
+
import segmentBoxContains from "./segmentBoxContains.js";
|
|
3
|
+
/**
|
|
4
|
+
@function segmentsIntersect
|
|
5
|
+
@desc Checks whether the line segments p1q1 && p2q2 intersect.
|
|
6
|
+
@param {Array} p1 The first point of the first line segment, which should always be an `[x, y]` formatted Array.
|
|
7
|
+
@param {Array} q1 The second point of the first line segment, which should always be an `[x, y]` formatted Array.
|
|
8
|
+
@param {Array} p2 The first point of the second line segment, which should always be an `[x, y]` formatted Array.
|
|
9
|
+
@param {Array} q2 The second point of the second line segment, which should always be an `[x, y]` formatted Array.
|
|
10
|
+
@returns {Boolean}
|
|
11
|
+
*/ export default function(p1, q1, p2, q2) {
|
|
12
|
+
var p = lineIntersection(p1, q1, p2, q2);
|
|
13
|
+
if (!p) return false;
|
|
14
|
+
return segmentBoxContains(p1, q1, p) && segmentBoxContains(p2, q2, p);
|
|
15
|
+
}
|