efront 3.25.16 → 3.26.1

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.
@@ -1,1722 +1 @@
1
- // rewrite by efront authors
2
-
3
- /*!
4
- Copyright (C) 2010-2013 Raymond Hill: https://github.com/gorhill/Javascript-Voronoi
5
- MIT License: See https://github.com/gorhill/Javascript-Voronoi/LICENSE.md
6
- */
7
- /*
8
- Author: Raymond Hill (rhill@raymondhill.net)
9
- Contributor: Jesse Morgan (morgajel@gmail.com)
10
- File: rhill-voronoi-core.js
11
- Version: 0.98
12
- Date: January 21, 2013
13
- Description: This is my personal Javascript implementation of
14
- Steven Fortune's algorithm to compute Voronoi diagrams.
15
-
16
- License: See https://github.com/gorhill/Javascript-Voronoi/LICENSE.md
17
- Credits: See https://github.com/gorhill/Javascript-Voronoi/CREDITS.md
18
- History: See https://github.com/gorhill/Javascript-Voronoi/CHANGELOG.md
19
-
20
- ## Usage:
21
-
22
- var sites = [{x:300,y:300}, {x:100,y:100}, {x:200,y:500}, {x:250,y:450}, {x:600,y:150}];
23
- // xl, xr means x left, x right
24
- // yt, yb means y top, y bottom
25
- var bbox = {xl:0, xr:800, yt:0, yb:600};
26
- var voronoi = new Voronoi();
27
- // pass an object which exhibits xl, xr, yt, yb properties. The bounding
28
- // box will be used to connect unbound edges, and to close open cells
29
- result = voronoi.compute(sites, bbox);
30
- // render, further analyze, etc.
31
-
32
- Return value:
33
- An object with the following properties:
34
-
35
- result.vertices = an array of unordered, unique Voronoi.Vertex objects making
36
- up the Voronoi diagram.
37
- result.edges = an array of unordered, unique Voronoi.Edge objects making up
38
- the Voronoi diagram.
39
- result.cells = an array of Voronoi.Cell object making up the Voronoi diagram.
40
- A Cell object might have an empty array of halfedges, meaning no Voronoi
41
- cell could be computed for a particular cell.
42
- result.execTime = the time it took to compute the Voronoi diagram, in
43
- milliseconds.
44
-
45
- Voronoi.Vertex object:
46
- x: The x position of the vertex.
47
- y: The y position of the vertex.
48
-
49
- Voronoi.Edge object:
50
- lSite: the Voronoi site object at the left of this Voronoi.Edge object.
51
- rSite: the Voronoi site object at the right of this Voronoi.Edge object (can
52
- be null).
53
- va: an object with an 'x' and a 'y' property defining the start point
54
- (relative to the Voronoi site on the left) of this Voronoi.Edge object.
55
- vb: an object with an 'x' and a 'y' property defining the end point
56
- (relative to Voronoi site on the left) of this Voronoi.Edge object.
57
-
58
- For edges which are used to close open cells (using the supplied bounding
59
- box), the rSite property will be null.
60
-
61
- Voronoi.Cell object:
62
- site: the Voronoi site object associated with the Voronoi cell.
63
- halfedges: an array of Voronoi.Halfedge objects, ordered counterclockwise,
64
- defining the polygon for this Voronoi cell.
65
-
66
- Voronoi.Halfedge object:
67
- site: the Voronoi site object owning this Voronoi.Halfedge object.
68
- edge: a reference to the unique Voronoi.Edge object underlying this
69
- Voronoi.Halfedge object.
70
- getStartpoint(): a method returning an object with an 'x' and a 'y' property
71
- for the start point of this halfedge. Keep in mind halfedges are always
72
- countercockwise.
73
- getEndpoint(): a method returning an object with an 'x' and a 'y' property
74
- for the end point of this halfedge. Keep in mind halfedges are always
75
- countercockwise.
76
-
77
- TODO: Identify opportunities for performance improvement.
78
-
79
- TODO: Let the user close the Voronoi cells, do not do it automatically. Not only let
80
- him close the cells, but also allow him to close more than once using a different
81
- bounding box for the same Voronoi diagram.
82
- */
83
-
84
- /*global Math */
85
-
86
- // ---------------------------------------------------------------------------
87
-
88
- function Voronoi() {
89
- this.vertices = null;
90
- this.edges = null;
91
- this.cells = null;
92
- this.toRecycle = null;
93
- this.beachsectionJunkyard = [];
94
- this.circleEventJunkyard = [];
95
- this.vertexJunkyard = [];
96
- this.edgeJunkyard = [];
97
- this.cellJunkyard = [];
98
- }
99
- Voronoi.ε = 1e-9;
100
- Voronoi.invε = 1.0 / Voronoi.ε;
101
-
102
- // ---------------------------------------------------------------------------
103
-
104
- // Red-Black tree code (based on C version of "rbtree" by Franck Bui-Huu
105
- // https://github.com/fbuihuu/libtree/blob/master/rb.c
106
- class RBTree {
107
- root = null;
108
- rbInsertSuccessor(node, successor) {
109
- var parent;
110
- if (node) {
111
- // >>> rhill 2011-05-27: Performance: cache previous/next nodes
112
- successor.rbPrevious = node;
113
- successor.rbNext = node.rbNext;
114
- if (node.rbNext) {
115
- node.rbNext.rbPrevious = successor;
116
- }
117
- node.rbNext = successor;
118
- // <<<
119
- if (node.rbRight) {
120
- // in-place expansion of node.rbRight.getFirst();
121
- node = node.rbRight;
122
- while (node.rbLeft) { node = node.rbLeft; }
123
- node.rbLeft = successor;
124
- }
125
- else {
126
- node.rbRight = successor;
127
- }
128
- parent = node;
129
- }
130
- // rhill 2011-06-07: if node is null, successor must be inserted
131
- // to the left-most part of the tree
132
- else if (this.root) {
133
- node = this.getFirst(this.root);
134
- // >>> Performance: cache previous/next nodes
135
- successor.rbPrevious = null;
136
- successor.rbNext = node;
137
- node.rbPrevious = successor;
138
- // <<<
139
- node.rbLeft = successor;
140
- parent = node;
141
- }
142
- else {
143
- // >>> Performance: cache previous/next nodes
144
- successor.rbPrevious = successor.rbNext = null;
145
- // <<<
146
- this.root = successor;
147
- parent = null;
148
- }
149
- successor.rbLeft = successor.rbRight = null;
150
- successor.rbParent = parent;
151
- successor.rbRed = true;
152
- // Fixup the modified tree by recoloring nodes and performing
153
- // rotations (2 at most) hence the red-black tree properties are
154
- // preserved.
155
- var grandpa, uncle;
156
- node = successor;
157
- while (parent && parent.rbRed) {
158
- grandpa = parent.rbParent;
159
- if (parent === grandpa.rbLeft) {
160
- uncle = grandpa.rbRight;
161
- if (uncle && uncle.rbRed) {
162
- parent.rbRed = uncle.rbRed = false;
163
- grandpa.rbRed = true;
164
- node = grandpa;
165
- }
166
- else {
167
- if (node === parent.rbRight) {
168
- this.rbRotateLeft(parent);
169
- node = parent;
170
- parent = node.rbParent;
171
- }
172
- parent.rbRed = false;
173
- grandpa.rbRed = true;
174
- this.rbRotateRight(grandpa);
175
- }
176
- }
177
- else {
178
- uncle = grandpa.rbLeft;
179
- if (uncle && uncle.rbRed) {
180
- parent.rbRed = uncle.rbRed = false;
181
- grandpa.rbRed = true;
182
- node = grandpa;
183
- }
184
- else {
185
- if (node === parent.rbLeft) {
186
- this.rbRotateRight(parent);
187
- node = parent;
188
- parent = node.rbParent;
189
- }
190
- parent.rbRed = false;
191
- grandpa.rbRed = true;
192
- this.rbRotateLeft(grandpa);
193
- }
194
- }
195
- parent = node.rbParent;
196
- }
197
- this.root.rbRed = false;
198
- }
199
- rbRemoveNode(node) {
200
- // >>> rhill 2011-05-27: Performance: cache previous/next nodes
201
- if (node.rbNext) {
202
- node.rbNext.rbPrevious = node.rbPrevious;
203
- }
204
- if (node.rbPrevious) {
205
- node.rbPrevious.rbNext = node.rbNext;
206
- }
207
- node.rbNext = node.rbPrevious = null;
208
- // <<<
209
- var parent = node.rbParent,
210
- left = node.rbLeft,
211
- right = node.rbRight,
212
- next;
213
- if (!left) {
214
- next = right;
215
- }
216
- else if (!right) {
217
- next = left;
218
- }
219
- else {
220
- next = this.getFirst(right);
221
- }
222
- if (parent) {
223
- if (parent.rbLeft === node) {
224
- parent.rbLeft = next;
225
- }
226
- else {
227
- parent.rbRight = next;
228
- }
229
- }
230
- else {
231
- this.root = next;
232
- }
233
- // enforce red-black rules
234
- var isRed;
235
- if (left && right) {
236
- isRed = next.rbRed;
237
- next.rbRed = node.rbRed;
238
- next.rbLeft = left;
239
- left.rbParent = next;
240
- if (next !== right) {
241
- parent = next.rbParent;
242
- next.rbParent = node.rbParent;
243
- node = next.rbRight;
244
- parent.rbLeft = node;
245
- next.rbRight = right;
246
- right.rbParent = next;
247
- }
248
- else {
249
- next.rbParent = parent;
250
- parent = next;
251
- node = next.rbRight;
252
- }
253
- }
254
- else {
255
- isRed = node.rbRed;
256
- node = next;
257
- }
258
- // 'node' is now the sole successor's child and 'parent' its
259
- // new parent (since the successor can have been moved)
260
- if (node) {
261
- node.rbParent = parent;
262
- }
263
- // the 'easy' cases
264
- if (isRed) { return; }
265
- if (node && node.rbRed) {
266
- node.rbRed = false;
267
- return;
268
- }
269
- // the other cases
270
- var sibling;
271
- do {
272
- if (node === this.root) {
273
- break;
274
- }
275
- if (node === parent.rbLeft) {
276
- sibling = parent.rbRight;
277
- if (sibling.rbRed) {
278
- sibling.rbRed = false;
279
- parent.rbRed = true;
280
- this.rbRotateLeft(parent);
281
- sibling = parent.rbRight;
282
- }
283
- if ((sibling.rbLeft && sibling.rbLeft.rbRed) || (sibling.rbRight && sibling.rbRight.rbRed)) {
284
- if (!sibling.rbRight || !sibling.rbRight.rbRed) {
285
- sibling.rbLeft.rbRed = false;
286
- sibling.rbRed = true;
287
- this.rbRotateRight(sibling);
288
- sibling = parent.rbRight;
289
- }
290
- sibling.rbRed = parent.rbRed;
291
- parent.rbRed = sibling.rbRight.rbRed = false;
292
- this.rbRotateLeft(parent);
293
- node = this.root;
294
- break;
295
- }
296
- }
297
- else {
298
- sibling = parent.rbLeft;
299
- if (sibling.rbRed) {
300
- sibling.rbRed = false;
301
- parent.rbRed = true;
302
- this.rbRotateRight(parent);
303
- sibling = parent.rbLeft;
304
- }
305
- if ((sibling.rbLeft && sibling.rbLeft.rbRed) || (sibling.rbRight && sibling.rbRight.rbRed)) {
306
- if (!sibling.rbLeft || !sibling.rbLeft.rbRed) {
307
- sibling.rbRight.rbRed = false;
308
- sibling.rbRed = true;
309
- this.rbRotateLeft(sibling);
310
- sibling = parent.rbLeft;
311
- }
312
- sibling.rbRed = parent.rbRed;
313
- parent.rbRed = sibling.rbLeft.rbRed = false;
314
- this.rbRotateRight(parent);
315
- node = this.root;
316
- break;
317
- }
318
- }
319
- sibling.rbRed = true;
320
- node = parent;
321
- parent = parent.rbParent;
322
- } while (!node.rbRed);
323
- if (node) { node.rbRed = false; }
324
- }
325
- rbRotateLeft(node) {
326
- var p = node,
327
- q = node.rbRight, // can't be null
328
- parent = p.rbParent;
329
- if (parent) {
330
- if (parent.rbLeft === p) {
331
- parent.rbLeft = q;
332
- }
333
- else {
334
- parent.rbRight = q;
335
- }
336
- }
337
- else {
338
- this.root = q;
339
- }
340
- q.rbParent = parent;
341
- p.rbParent = q;
342
- p.rbRight = q.rbLeft;
343
- if (p.rbRight) {
344
- p.rbRight.rbParent = p;
345
- }
346
- q.rbLeft = p;
347
- }
348
- rbRotateRight(node) {
349
- var p = node,
350
- q = node.rbLeft, // can't be null
351
- parent = p.rbParent;
352
- if (parent) {
353
- if (parent.rbLeft === p) {
354
- parent.rbLeft = q;
355
- }
356
- else {
357
- parent.rbRight = q;
358
- }
359
- }
360
- else {
361
- this.root = q;
362
- }
363
- q.rbParent = parent;
364
- p.rbParent = q;
365
- p.rbLeft = q.rbRight;
366
- if (p.rbLeft) {
367
- p.rbLeft.rbParent = p;
368
- }
369
- q.rbRight = p;
370
- }
371
- getFirst(node) {
372
- while (node.rbLeft) {
373
- node = node.rbLeft;
374
- }
375
- return node;
376
- }
377
- getLast(node) {
378
- while (node.rbRight) {
379
- node = node.rbRight;
380
- }
381
- return node;
382
- }
383
- }
384
-
385
- // ---------------------------------------------------------------------------
386
- // Diagram methods
387
- class Diagram {
388
- constructor(site) {
389
- this.site = site;
390
- }
391
- }
392
-
393
- // ---------------------------------------------------------------------------
394
- // Cell methods
395
- class Cell {
396
- constructor(site) {
397
- this.site = site;
398
- this.halfedges = [];
399
- this.closeMe = false;
400
- }
401
- init(site) {
402
- this.site = site;
403
- this.halfedges = [];
404
- this.closeMe = false;
405
- return this;
406
- }
407
- prepareHalfedges() {
408
- var halfedges = this.halfedges,
409
- iHalfedge = halfedges.length,
410
- edge;
411
- // get rid of unused halfedges
412
- // rhill 2011-05-27: Keep it simple, no point here in trying
413
- // to be fancy: dangling edges are a typically a minority.
414
- while (iHalfedge--) {
415
- edge = halfedges[iHalfedge].edge;
416
- if (!edge.vb || !edge.va) {
417
- halfedges.splice(iHalfedge, 1);
418
- }
419
- }
420
-
421
- // rhill 2011-05-26: I tried to use a binary search at insertion
422
- // time to keep the array sorted on-the-fly (in Cell.addHalfedge()).
423
- // There was no real benefits in doing so, performance on
424
- // Firefox 3.6 was improved marginally, while performance on
425
- // Opera 11 was penalized marginally.
426
- halfedges.sort(function (a, b) { return b.angle - a.angle; });
427
- return halfedges.length;
428
- }
429
- // Return a list of the neighbor Ids
430
-
431
- getNeighborIds() {
432
- var neighbors = [],
433
- iHalfedge = this.halfedges.length,
434
- edge;
435
- while (iHalfedge--) {
436
- edge = this.halfedges[iHalfedge].edge;
437
- if (edge.lSite !== null && edge.lSite.voronoiId != this.site.voronoiId) {
438
- neighbors.push(edge.lSite.voronoiId);
439
- }
440
- else if (edge.rSite !== null && edge.rSite.voronoiId != this.site.voronoiId) {
441
- neighbors.push(edge.rSite.voronoiId);
442
- }
443
- }
444
- return neighbors;
445
- }
446
-
447
- // Compute bounding box
448
- getBbox() {
449
- var halfedges = this.halfedges,
450
- iHalfedge = halfedges.length,
451
- xmin = Infinity,
452
- ymin = Infinity,
453
- xmax = -Infinity,
454
- ymax = -Infinity,
455
- v, vx, vy;
456
- while (iHalfedge--) {
457
- v = halfedges[iHalfedge].getStartpoint();
458
- vx = v.x;
459
- vy = v.y;
460
- if (vx < xmin) { xmin = vx; }
461
- if (vy < ymin) { ymin = vy; }
462
- if (vx > xmax) { xmax = vx; }
463
- if (vy > ymax) { ymax = vy; }
464
- // we dont need to take into account end point,
465
- // since each end point matches a start point
466
- }
467
- return {
468
- x: xmin,
469
- y: ymin,
470
- width: xmax - xmin,
471
- height: ymax - ymin
472
- };
473
- }
474
- // Return whether a point is inside, on, or outside the cell:
475
- // -1: point is outside the perimeter of the cell
476
- // 0: point is on the perimeter of the cell
477
- // 1: point is inside the perimeter of the cell
478
- //
479
- pointIntersection(x, y) {
480
- // Check if point in polygon. Since all polygons of a Voronoi
481
- // diagram are convex, then:
482
- // http://paulbourke.net/geometry/polygonmesh/
483
- // Solution 3 (2D):
484
- // "If the polygon is convex then one can consider the polygon
485
- // "as a 'path' from the first vertex. A point is on the interior
486
- // "of this polygons if it is always on the same side of all the
487
- // "line segments making up the path. ...
488
- // "(y - y0) (x1 - x0) - (x - x0) (y1 - y0)
489
- // "if it is less than 0 then P is to the right of the line segment,
490
- // "if greater than 0 it is to the left, if equal to 0 then it lies
491
- // "on the line segment"
492
- var halfedges = this.halfedges,
493
- iHalfedge = halfedges.length,
494
- halfedge,
495
- p0, p1, r;
496
- while (iHalfedge--) {
497
- halfedge = halfedges[iHalfedge];
498
- p0 = halfedge.getStartpoint();
499
- p1 = halfedge.getEndpoint();
500
- r = (y - p0.y) * (p1.x - p0.x) - (x - p0.x) * (p1.y - p0.y);
501
- if (!r) {
502
- return 0;
503
- }
504
- if (r > 0) {
505
- return -1;
506
- }
507
- }
508
- return 1;
509
- }
510
- }
511
-
512
- // ---------------------------------------------------------------------------
513
- // Edge methods
514
- //
515
- class Vertex {
516
- constructor(x, y) {
517
- this.x = x;
518
- this.y = y;
519
- }
520
- }
521
- class Edge {
522
- constructor(lSite, rSite) {
523
- this.lSite = lSite;
524
- this.rSite = rSite;
525
- this.va = this.vb = null;
526
- }
527
- }
528
- class Halfedge {
529
- constructor(edge, lSite, rSite) {
530
- this.site = lSite;
531
- this.edge = edge;
532
- // 'angle' is a value to be used for properly sorting the
533
- // halfsegments counterclockwise. By convention, we will
534
- // use the angle of the line defined by the 'site to the left'
535
- // to the 'site to the right'.
536
- // However, border edges have no 'site to the right': thus we
537
- // use the angle of line perpendicular to the halfsegment (the
538
- // edge should have both end points defined in such case.)
539
- if (rSite) {
540
- this.angle = Math.atan2(rSite.y - lSite.y, rSite.x - lSite.x);
541
- }
542
- else {
543
- var va = edge.va,
544
- vb = edge.vb;
545
- // rhill 2011-05-31: used to call getStartpoint()/getEndpoint(),
546
- // but for performance purpose, these are expanded in place here.
547
- this.angle = edge.lSite === lSite ?
548
- Math.atan2(vb.x - va.x, va.y - vb.y) :
549
- Math.atan2(va.x - vb.x, vb.y - va.y);
550
- }
551
- }
552
- getStartpoint() {
553
- return this.edge.lSite === this.site ? this.edge.va : this.edge.vb;
554
- }
555
- getEndpoint() {
556
- return this.edge.lSite === this.site ? this.edge.vb : this.edge.va;
557
- }
558
- }
559
-
560
-
561
- // ---------------------------------------------------------------------------
562
- // Beachline methods
563
-
564
- // rhill 2011-06-07: For some reasons, performance suffers significantly
565
- // when instanciating a literal object instead of an empty ctor
566
- class Beachsection {
567
- };
568
-
569
-
570
-
571
- // ---------------------------------------------------------------------------
572
- // Circle event methods
573
-
574
- // rhill 2011-06-07: For some reasons, performance suffers significantly
575
- // when instanciating a literal object instead of an empty ctor
576
- class CircleEvent {
577
- constructor() {
578
- // rhill 2013-10-12: it helps to state exactly what we are at ctor time.
579
- this.arc = null;
580
- this.rbLeft = null;
581
- this.rbNext = null;
582
- this.rbParent = null;
583
- this.rbPrevious = null;
584
- this.rbRed = false;
585
- this.rbRight = null;
586
- this.site = null;
587
- this.x = this.y = this.ycenter = 0;
588
- }
589
- };
590
-
591
- Voronoi.prototype = {
592
- reset() {
593
- if (!this.beachline) {
594
- this.beachline = new this.RBTree();
595
- }
596
- // Move leftover beachsections to the beachsection junkyard.
597
- if (this.beachline.root) {
598
- var beachsection = this.beachline.getFirst(this.beachline.root);
599
- while (beachsection) {
600
- this.beachsectionJunkyard.push(beachsection); // mark for reuse
601
- beachsection = beachsection.rbNext;
602
- }
603
- }
604
- this.beachline.root = null;
605
- if (!this.circleEvents) {
606
- this.circleEvents = new this.RBTree();
607
- }
608
- this.circleEvents.root = this.firstCircleEvent = null;
609
- this.vertices = [];
610
- this.edges = [];
611
- this.cells = [];
612
- },
613
- equalWithEpsilon(a, b) { return this.abs(a - b) < 1e-9; },
614
- greaterThanWithEpsilon(a, b) { return a - b > 1e-9; },
615
- greaterThanOrEqualWithEpsilon(a, b) { return b - a < 1e-9; },
616
- lessThanWithEpsilon(a, b) { return b - a > 1e-9; },
617
- lessThanOrEqualWithEpsilon(a, b) { return a - b < 1e-9; },
618
- RBTree: RBTree,
619
- Diagram: Diagram,
620
- Cell: Cell,
621
- Vertex: Vertex,
622
- Edge: Edge,
623
- Halfedge: Halfedge,
624
- Beachsection: Beachsection,
625
- CircleEvent: CircleEvent,
626
- sqrt: Math.sqrt,
627
- abs: Math.abs,
628
- ε: Voronoi.ε,
629
- invε: Voronoi.invε,
630
- createCell(site) {
631
- var cell = this.cellJunkyard.pop();
632
- if (cell) {
633
- return cell.init(site);
634
- }
635
- return new this.Cell(site);
636
- },
637
- createHalfedge(edge, lSite, rSite) {
638
- return new this.Halfedge(edge, lSite, rSite);
639
- },
640
- // this create and add a vertex to the internal collection
641
-
642
- createVertex(x, y) {
643
- var v = this.vertexJunkyard.pop();
644
- if (!v) {
645
- v = new this.Vertex(x, y);
646
- }
647
- else {
648
- v.x = x;
649
- v.y = y;
650
- }
651
- this.vertices.push(v);
652
- return v;
653
- },
654
-
655
- // this create and add an edge to internal collection, and also create
656
- // two halfedges which are added to each site's counterclockwise array
657
- // of halfedges.
658
-
659
- createEdge(lSite, rSite, va, vb) {
660
- var edge = this.edgeJunkyard.pop();
661
- if (!edge) {
662
- edge = new this.Edge(lSite, rSite);
663
- }
664
- else {
665
- edge.lSite = lSite;
666
- edge.rSite = rSite;
667
- edge.va = edge.vb = null;
668
- }
669
-
670
- this.edges.push(edge);
671
- if (va) {
672
- this.setEdgeStartpoint(edge, lSite, rSite, va);
673
- }
674
- if (vb) {
675
- this.setEdgeEndpoint(edge, lSite, rSite, vb);
676
- }
677
- this.cells[lSite.voronoiId].halfedges.push(this.createHalfedge(edge, lSite, rSite));
678
- this.cells[rSite.voronoiId].halfedges.push(this.createHalfedge(edge, rSite, lSite));
679
- return edge;
680
- },
681
-
682
- createBorderEdge(lSite, va, vb) {
683
- var edge = this.edgeJunkyard.pop();
684
- if (!edge) {
685
- edge = new this.Edge(lSite, null);
686
- }
687
- else {
688
- edge.lSite = lSite;
689
- edge.rSite = null;
690
- }
691
- edge.va = va;
692
- edge.vb = vb;
693
- this.edges.push(edge);
694
- return edge;
695
- },
696
-
697
- setEdgeStartpoint(edge, lSite, rSite, vertex) {
698
- if (!edge.va && !edge.vb) {
699
- edge.va = vertex;
700
- edge.lSite = lSite;
701
- edge.rSite = rSite;
702
- }
703
- else if (edge.lSite === rSite) {
704
- edge.vb = vertex;
705
- }
706
- else {
707
- edge.va = vertex;
708
- }
709
- },
710
-
711
- setEdgeEndpoint(edge, lSite, rSite, vertex) {
712
- this.setEdgeStartpoint(edge, rSite, lSite, vertex);
713
- },
714
- // rhill 2011-06-02: A lot of Beachsection instanciations
715
- // occur during the computation of the Voronoi diagram,
716
- // somewhere between the number of sites and twice the
717
- // number of sites, while the number of Beachsections on the
718
- // beachline at any given time is comparatively low. For this
719
- // reason, we reuse already created Beachsections, in order
720
- // to avoid new memory allocation. This resulted in a measurable
721
- // performance gain.
722
-
723
- createBeachsection(site) {
724
- var beachsection = this.beachsectionJunkyard.pop();
725
- if (!beachsection) {
726
- beachsection = new this.Beachsection();
727
- }
728
- beachsection.site = site;
729
- return beachsection;
730
- },
731
-
732
- // calculate the left break point of a particular beach section,
733
- // given a particular sweep line
734
- leftBreakPoint(arc, directrix) {
735
- // http://en.wikipedia.org/wiki/Parabola
736
- // http://en.wikipedia.org/wiki/Quadratic_equation
737
- // h1 = x1,
738
- // k1 = (y1+directrix)/2,
739
- // h2 = x2,
740
- // k2 = (y2+directrix)/2,
741
- // p1 = k1-directrix,
742
- // a1 = 1/(4*p1),
743
- // b1 = -h1/(2*p1),
744
- // c1 = h1*h1/(4*p1)+k1,
745
- // p2 = k2-directrix,
746
- // a2 = 1/(4*p2),
747
- // b2 = -h2/(2*p2),
748
- // c2 = h2*h2/(4*p2)+k2,
749
- // x = (-(b2-b1) + Math.sqrt((b2-b1)*(b2-b1) - 4*(a2-a1)*(c2-c1))) / (2*(a2-a1))
750
- // When x1 become the x-origin:
751
- // h1 = 0,
752
- // k1 = (y1+directrix)/2,
753
- // h2 = x2-x1,
754
- // k2 = (y2+directrix)/2,
755
- // p1 = k1-directrix,
756
- // a1 = 1/(4*p1),
757
- // b1 = 0,
758
- // c1 = k1,
759
- // p2 = k2-directrix,
760
- // a2 = 1/(4*p2),
761
- // b2 = -h2/(2*p2),
762
- // c2 = h2*h2/(4*p2)+k2,
763
- // x = (-b2 + Math.sqrt(b2*b2 - 4*(a2-a1)*(c2-k1))) / (2*(a2-a1)) + x1
764
-
765
- // change code below at your own risk: care has been taken to
766
- // reduce errors due to computers' finite arithmetic precision.
767
- // Maybe can still be improved, will see if any more of this
768
- // kind of errors pop up again.
769
- var site = arc.site,
770
- rfocx = site.x,
771
- rfocy = site.y,
772
- pby2 = rfocy - directrix;
773
- // parabola in degenerate case where focus is on directrix
774
- if (!pby2) {
775
- return rfocx;
776
- }
777
- var lArc = arc.rbPrevious;
778
- if (!lArc) {
779
- return -Infinity;
780
- }
781
- site = lArc.site;
782
- var lfocx = site.x,
783
- lfocy = site.y,
784
- plby2 = lfocy - directrix;
785
- // parabola in degenerate case where focus is on directrix
786
- if (!plby2) {
787
- return lfocx;
788
- }
789
- var hl = lfocx - rfocx,
790
- aby2 = 1 / pby2 - 1 / plby2,
791
- b = hl / plby2;
792
- if (aby2) {
793
- return (-b + this.sqrt(b * b - 2 * aby2 * (hl * hl / (-2 * plby2) - lfocy + plby2 / 2 + rfocy - pby2 / 2))) / aby2 + rfocx;
794
- }
795
- // both parabolas have same distance to directrix, thus break point is midway
796
- return (rfocx + lfocx) / 2;
797
- },
798
-
799
- // calculate the right break point of a particular beach section,
800
- // given a particular directrix
801
- rightBreakPoint(arc, directrix) {
802
- var rArc = arc.rbNext;
803
- if (rArc) {
804
- return this.leftBreakPoint(rArc, directrix);
805
- }
806
- var site = arc.site;
807
- return site.y === directrix ? site.x : Infinity;
808
- },
809
-
810
- detachBeachsection(beachsection) {
811
- this.detachCircleEvent(beachsection); // detach potentially attached circle event
812
- this.beachline.rbRemoveNode(beachsection); // remove from RB-tree
813
- this.beachsectionJunkyard.push(beachsection); // mark for reuse
814
- },
815
-
816
- removeBeachsection(beachsection) {
817
- var circle = beachsection.circleEvent,
818
- x = circle.x,
819
- y = circle.ycenter,
820
- vertex = this.createVertex(x, y),
821
- previous = beachsection.rbPrevious,
822
- next = beachsection.rbNext,
823
- disappearingTransitions = [beachsection],
824
- abs_fn = Math.abs;
825
-
826
- // remove collapsed beachsection from beachline
827
- this.detachBeachsection(beachsection);
828
-
829
- // there could be more than one empty arc at the deletion point, this
830
- // happens when more than two edges are linked by the same vertex,
831
- // so we will collect all those edges by looking up both sides of
832
- // the deletion point.
833
- // by the way, there is *always* a predecessor/successor to any collapsed
834
- // beach section, it's just impossible to have a collapsing first/last
835
- // beach sections on the beachline, since they obviously are unconstrained
836
- // on their left/right side.
837
-
838
- // look left
839
- var lArc = previous;
840
- while (lArc.circleEvent && abs_fn(x - lArc.circleEvent.x) < 1e-9 && abs_fn(y - lArc.circleEvent.ycenter) < 1e-9) {
841
- previous = lArc.rbPrevious;
842
- disappearingTransitions.unshift(lArc);
843
- this.detachBeachsection(lArc); // mark for reuse
844
- lArc = previous;
845
- }
846
- // even though it is not disappearing, I will also add the beach section
847
- // immediately to the left of the left-most collapsed beach section, for
848
- // convenience, since we need to refer to it later as this beach section
849
- // is the 'left' site of an edge for which a start point is set.
850
- disappearingTransitions.unshift(lArc);
851
- this.detachCircleEvent(lArc);
852
-
853
- // look right
854
- var rArc = next;
855
- while (rArc.circleEvent && abs_fn(x - rArc.circleEvent.x) < 1e-9 && abs_fn(y - rArc.circleEvent.ycenter) < 1e-9) {
856
- next = rArc.rbNext;
857
- disappearingTransitions.push(rArc);
858
- this.detachBeachsection(rArc); // mark for reuse
859
- rArc = next;
860
- }
861
- // we also have to add the beach section immediately to the right of the
862
- // right-most collapsed beach section, since there is also a disappearing
863
- // transition representing an edge's start point on its left.
864
- disappearingTransitions.push(rArc);
865
- this.detachCircleEvent(rArc);
866
-
867
- // walk through all the disappearing transitions between beach sections and
868
- // set the start point of their (implied) edge.
869
- var nArcs = disappearingTransitions.length,
870
- iArc;
871
- for (iArc = 1; iArc < nArcs; iArc++) {
872
- rArc = disappearingTransitions[iArc];
873
- lArc = disappearingTransitions[iArc - 1];
874
- this.setEdgeStartpoint(rArc.edge, lArc.site, rArc.site, vertex);
875
- }
876
-
877
- // create a new edge as we have now a new transition between
878
- // two beach sections which were previously not adjacent.
879
- // since this edge appears as a new vertex is defined, the vertex
880
- // actually define an end point of the edge (relative to the site
881
- // on the left)
882
- lArc = disappearingTransitions[0];
883
- rArc = disappearingTransitions[nArcs - 1];
884
- rArc.edge = this.createEdge(lArc.site, rArc.site, undefined, vertex);
885
-
886
- // create circle events if any for beach sections left in the beachline
887
- // adjacent to collapsed sections
888
- this.attachCircleEvent(lArc);
889
- this.attachCircleEvent(rArc);
890
- },
891
-
892
- addBeachsection(site) {
893
- var x = site.x,
894
- directrix = site.y;
895
-
896
- // find the left and right beach sections which will surround the newly
897
- // created beach section.
898
- // rhill 2011-06-01: This loop is one of the most often executed,
899
- // hence we expand in-place the comparison-against-epsilon calls.
900
- var lArc, rArc,
901
- dxl, dxr,
902
- node = this.beachline.root;
903
-
904
- while (node) {
905
- dxl = this.leftBreakPoint(node, directrix) - x;
906
- // x lessThanWithEpsilon xl => falls somewhere before the left edge of the beachsection
907
- if (dxl > 1e-9) {
908
- // this case should never happen
909
- // if (!node.rbLeft) {
910
- // rArc = node.rbLeft;
911
- // break;
912
- // }
913
- node = node.rbLeft;
914
- }
915
- else {
916
- dxr = x - this.rightBreakPoint(node, directrix);
917
- // x greaterThanWithEpsilon xr => falls somewhere after the right edge of the beachsection
918
- if (dxr > 1e-9) {
919
- if (!node.rbRight) {
920
- lArc = node;
921
- break;
922
- }
923
- node = node.rbRight;
924
- }
925
- else {
926
- // x equalWithEpsilon xl => falls exactly on the left edge of the beachsection
927
- if (dxl > -1e-9) {
928
- lArc = node.rbPrevious;
929
- rArc = node;
930
- }
931
- // x equalWithEpsilon xr => falls exactly on the right edge of the beachsection
932
- else if (dxr > -1e-9) {
933
- lArc = node;
934
- rArc = node.rbNext;
935
- }
936
- // falls exactly somewhere in the middle of the beachsection
937
- else {
938
- lArc = rArc = node;
939
- }
940
- break;
941
- }
942
- }
943
- }
944
- // at this point, keep in mind that lArc and/or rArc could be
945
- // undefined or null.
946
-
947
- // create a new beach section object for the site and add it to RB-tree
948
- var newArc = this.createBeachsection(site);
949
- this.beachline.rbInsertSuccessor(lArc, newArc);
950
-
951
- // cases:
952
- //
953
-
954
- // [null,null]
955
- // least likely case: new beach section is the first beach section on the
956
- // beachline.
957
- // This case means:
958
- // no new transition appears
959
- // no collapsing beach section
960
- // new beachsection become root of the RB-tree
961
- if (!lArc && !rArc) {
962
- return;
963
- }
964
-
965
- // [lArc,rArc] where lArc == rArc
966
- // most likely case: new beach section split an existing beach
967
- // section.
968
- // This case means:
969
- // one new transition appears
970
- // the left and right beach section might be collapsing as a result
971
- // two new nodes added to the RB-tree
972
- if (lArc === rArc) {
973
- // invalidate circle event of split beach section
974
- this.detachCircleEvent(lArc);
975
-
976
- // split the beach section into two separate beach sections
977
- rArc = this.createBeachsection(lArc.site);
978
- this.beachline.rbInsertSuccessor(newArc, rArc);
979
-
980
- // since we have a new transition between two beach sections,
981
- // a new edge is born
982
- newArc.edge = rArc.edge = this.createEdge(lArc.site, newArc.site);
983
-
984
- // check whether the left and right beach sections are collapsing
985
- // and if so create circle events, to be notified when the point of
986
- // collapse is reached.
987
- this.attachCircleEvent(lArc);
988
- this.attachCircleEvent(rArc);
989
- return;
990
- }
991
-
992
- // [lArc,null]
993
- // even less likely case: new beach section is the *last* beach section
994
- // on the beachline -- this can happen *only* if *all* the previous beach
995
- // sections currently on the beachline share the same y value as
996
- // the new beach section.
997
- // This case means:
998
- // one new transition appears
999
- // no collapsing beach section as a result
1000
- // new beach section become right-most node of the RB-tree
1001
- if (lArc && !rArc) {
1002
- newArc.edge = this.createEdge(lArc.site, newArc.site);
1003
- return;
1004
- }
1005
-
1006
- // [null,rArc]
1007
- // impossible case: because sites are strictly processed from top to bottom,
1008
- // and left to right, which guarantees that there will always be a beach section
1009
- // on the left -- except of course when there are no beach section at all on
1010
- // the beach line, which case was handled above.
1011
- // rhill 2011-06-02: No point testing in non-debug version
1012
- //if (!lArc && rArc) {
1013
- // throw "Voronoi.addBeachsection(): What is this I don't even";
1014
- // }
1015
-
1016
- // [lArc,rArc] where lArc != rArc
1017
- // somewhat less likely case: new beach section falls *exactly* in between two
1018
- // existing beach sections
1019
- // This case means:
1020
- // one transition disappears
1021
- // two new transitions appear
1022
- // the left and right beach section might be collapsing as a result
1023
- // only one new node added to the RB-tree
1024
- if (lArc !== rArc) {
1025
- // invalidate circle events of left and right sites
1026
- this.detachCircleEvent(lArc);
1027
- this.detachCircleEvent(rArc);
1028
-
1029
- // an existing transition disappears, meaning a vertex is defined at
1030
- // the disappearance point.
1031
- // since the disappearance is caused by the new beachsection, the
1032
- // vertex is at the center of the circumscribed circle of the left,
1033
- // new and right beachsections.
1034
- // http://mathforum.org/library/drmath/view/55002.html
1035
- // Except that I bring the origin at A to simplify
1036
- // calculation
1037
- var lSite = lArc.site,
1038
- ax = lSite.x,
1039
- ay = lSite.y,
1040
- bx = site.x - ax,
1041
- by = site.y - ay,
1042
- rSite = rArc.site,
1043
- cx = rSite.x - ax,
1044
- cy = rSite.y - ay,
1045
- d = 2 * (bx * cy - by * cx),
1046
- hb = bx * bx + by * by,
1047
- hc = cx * cx + cy * cy,
1048
- vertex = this.createVertex((cy * hb - by * hc) / d + ax, (bx * hc - cx * hb) / d + ay);
1049
-
1050
- // one transition disappear
1051
- this.setEdgeStartpoint(rArc.edge, lSite, rSite, vertex);
1052
-
1053
- // two new transitions appear at the new vertex location
1054
- newArc.edge = this.createEdge(lSite, site, undefined, vertex);
1055
- rArc.edge = this.createEdge(site, rSite, undefined, vertex);
1056
-
1057
- // check whether the left and right beach sections are collapsing
1058
- // and if so create circle events, to handle the point of collapse.
1059
- this.attachCircleEvent(lArc);
1060
- this.attachCircleEvent(rArc);
1061
- return;
1062
- }
1063
- },
1064
- attachCircleEvent(arc) {
1065
- var lArc = arc.rbPrevious,
1066
- rArc = arc.rbNext;
1067
- if (!lArc || !rArc) { return; } // does that ever happen?
1068
- var lSite = lArc.site,
1069
- cSite = arc.site,
1070
- rSite = rArc.site;
1071
-
1072
- // If site of left beachsection is same as site of
1073
- // right beachsection, there can't be convergence
1074
- if (lSite === rSite) { return; }
1075
-
1076
- // Find the circumscribed circle for the three sites associated
1077
- // with the beachsection triplet.
1078
- // rhill 2011-05-26: It is more efficient to calculate in-place
1079
- // rather than getting the resulting circumscribed circle from an
1080
- // object returned by calling Voronoi.circumcircle()
1081
- // http://mathforum.org/library/drmath/view/55002.html
1082
- // Except that I bring the origin at cSite to simplify calculations.
1083
- // The bottom-most part of the circumcircle is our Fortune 'circle
1084
- // event', and its center is a vertex potentially part of the final
1085
- // Voronoi diagram.
1086
- var bx = cSite.x,
1087
- by = cSite.y,
1088
- ax = lSite.x - bx,
1089
- ay = lSite.y - by,
1090
- cx = rSite.x - bx,
1091
- cy = rSite.y - by;
1092
-
1093
- // If points l->c->r are clockwise, then center beach section does not
1094
- // collapse, hence it can't end up as a vertex (we reuse 'd' here, which
1095
- // sign is reverse of the orientation, hence we reverse the test.
1096
- // http://en.wikipedia.org/wiki/Curve_orientation#Orientation_of_a_simple_polygon
1097
- // rhill 2011-05-21: Nasty finite precision error which caused circumcircle() to
1098
- // return infinites: 1e-12 seems to fix the problem.
1099
- var d = 2 * (ax * cy - ay * cx);
1100
- if (d >= -2e-12) { return; }
1101
-
1102
- var ha = ax * ax + ay * ay,
1103
- hc = cx * cx + cy * cy,
1104
- x = (cy * ha - ay * hc) / d,
1105
- y = (ax * hc - cx * ha) / d,
1106
- ycenter = y + by;
1107
-
1108
- // Important: ybottom should always be under or at sweep, so no need
1109
- // to waste CPU cycles by checking
1110
-
1111
- // recycle circle event object if possible
1112
- var circleEvent = this.circleEventJunkyard.pop();
1113
- if (!circleEvent) {
1114
- circleEvent = new this.CircleEvent();
1115
- }
1116
- circleEvent.arc = arc;
1117
- circleEvent.site = cSite;
1118
- circleEvent.x = x + bx;
1119
- circleEvent.y = ycenter + this.sqrt(x * x + y * y); // y bottom
1120
- circleEvent.ycenter = ycenter;
1121
- arc.circleEvent = circleEvent;
1122
-
1123
- // find insertion point in RB-tree: circle events are ordered from
1124
- // smallest to largest
1125
- var predecessor = null,
1126
- node = this.circleEvents.root;
1127
- while (node) {
1128
- if (circleEvent.y < node.y || (circleEvent.y === node.y && circleEvent.x <= node.x)) {
1129
- if (node.rbLeft) {
1130
- node = node.rbLeft;
1131
- }
1132
- else {
1133
- predecessor = node.rbPrevious;
1134
- break;
1135
- }
1136
- }
1137
- else {
1138
- if (node.rbRight) {
1139
- node = node.rbRight;
1140
- }
1141
- else {
1142
- predecessor = node;
1143
- break;
1144
- }
1145
- }
1146
- }
1147
- this.circleEvents.rbInsertSuccessor(predecessor, circleEvent);
1148
- if (!predecessor) {
1149
- this.firstCircleEvent = circleEvent;
1150
- }
1151
- },
1152
-
1153
- detachCircleEvent(arc) {
1154
- var circleEvent = arc.circleEvent;
1155
- if (circleEvent) {
1156
- if (!circleEvent.rbPrevious) {
1157
- this.firstCircleEvent = circleEvent.rbNext;
1158
- }
1159
- this.circleEvents.rbRemoveNode(circleEvent); // remove from RB-tree
1160
- this.circleEventJunkyard.push(circleEvent);
1161
- arc.circleEvent = null;
1162
- }
1163
- },
1164
-
1165
-
1166
- // ---------------------------------------------------------------------------
1167
- // Diagram completion methods
1168
-
1169
- // connect dangling edges (not if a cursory test tells us
1170
- // it is not going to be visible.
1171
- // return value:
1172
- // false: the dangling endpoint couldn't be connected
1173
- // true: the dangling endpoint could be connected
1174
- connectEdge(edge, bbox) {
1175
- // skip if end point already connected
1176
- var vb = edge.vb;
1177
- if (!!vb) { return true; }
1178
-
1179
- // make local copy for performance purpose
1180
- var va = edge.va,
1181
- xl = bbox.xl,
1182
- xr = bbox.xr,
1183
- yt = bbox.yt,
1184
- yb = bbox.yb,
1185
- lSite = edge.lSite,
1186
- rSite = edge.rSite,
1187
- lx = lSite.x,
1188
- ly = lSite.y,
1189
- rx = rSite.x,
1190
- ry = rSite.y,
1191
- fx = (lx + rx) / 2,
1192
- fy = (ly + ry) / 2,
1193
- fm, fb;
1194
-
1195
- // if we reach here, this means cells which use this edge will need
1196
- // to be closed, whether because the edge was removed, or because it
1197
- // was connected to the bounding box.
1198
- this.cells[lSite.voronoiId].closeMe = true;
1199
- this.cells[rSite.voronoiId].closeMe = true;
1200
-
1201
- // get the line equation of the bisector if line is not vertical
1202
- if (ry !== ly) {
1203
- fm = (lx - rx) / (ry - ly);
1204
- fb = fy - fm * fx;
1205
- }
1206
-
1207
- // remember, direction of line (relative to left site):
1208
- // upward: left.x < right.x
1209
- // downward: left.x > right.x
1210
- // horizontal: left.x == right.x
1211
- // upward: left.x < right.x
1212
- // rightward: left.y < right.y
1213
- // leftward: left.y > right.y
1214
- // vertical: left.y == right.y
1215
-
1216
- // depending on the direction, find the best side of the
1217
- // bounding box to use to determine a reasonable start point
1218
-
1219
- // rhill 2013-12-02:
1220
- // While at it, since we have the values which define the line,
1221
- // clip the end of va if it is outside the bbox.
1222
- // https://github.com/gorhill/Javascript-Voronoi/issues/15
1223
- // TODO: Do all the clipping here rather than rely on Liang-Barsky
1224
- // which does not do well sometimes due to loss of arithmetic
1225
- // precision. The code here doesn't degrade if one of the vertex is
1226
- // at a huge distance.
1227
-
1228
- // special case: vertical line
1229
- if (fm === undefined) {
1230
- // doesn't intersect with viewport
1231
- if (fx < xl || fx >= xr) { return false; }
1232
- // downward
1233
- if (lx > rx) {
1234
- if (!va || va.y < yt) {
1235
- va = this.createVertex(fx, yt);
1236
- }
1237
- else if (va.y >= yb) {
1238
- return false;
1239
- }
1240
- vb = this.createVertex(fx, yb);
1241
- }
1242
- // upward
1243
- else {
1244
- if (!va || va.y > yb) {
1245
- va = this.createVertex(fx, yb);
1246
- }
1247
- else if (va.y < yt) {
1248
- return false;
1249
- }
1250
- vb = this.createVertex(fx, yt);
1251
- }
1252
- }
1253
- // closer to vertical than horizontal, connect start point to the
1254
- // top or bottom side of the bounding box
1255
- else if (fm < -1 || fm > 1) {
1256
- // downward
1257
- if (lx > rx) {
1258
- if (!va || va.y < yt) {
1259
- va = this.createVertex((yt - fb) / fm, yt);
1260
- }
1261
- else if (va.y >= yb) {
1262
- return false;
1263
- }
1264
- vb = this.createVertex((yb - fb) / fm, yb);
1265
- }
1266
- // upward
1267
- else {
1268
- if (!va || va.y > yb) {
1269
- va = this.createVertex((yb - fb) / fm, yb);
1270
- }
1271
- else if (va.y < yt) {
1272
- return false;
1273
- }
1274
- vb = this.createVertex((yt - fb) / fm, yt);
1275
- }
1276
- }
1277
- // closer to horizontal than vertical, connect start point to the
1278
- // left or right side of the bounding box
1279
- else {
1280
- // rightward
1281
- if (ly < ry) {
1282
- if (!va || va.x < xl) {
1283
- va = this.createVertex(xl, fm * xl + fb);
1284
- }
1285
- else if (va.x >= xr) {
1286
- return false;
1287
- }
1288
- vb = this.createVertex(xr, fm * xr + fb);
1289
- }
1290
- // leftward
1291
- else {
1292
- if (!va || va.x > xr) {
1293
- va = this.createVertex(xr, fm * xr + fb);
1294
- }
1295
- else if (va.x < xl) {
1296
- return false;
1297
- }
1298
- vb = this.createVertex(xl, fm * xl + fb);
1299
- }
1300
- }
1301
- edge.va = va;
1302
- edge.vb = vb;
1303
-
1304
- return true;
1305
- },
1306
-
1307
- // line-clipping code taken from:
1308
- // Liang-Barsky function by Daniel White
1309
- // http://www.skytopia.com/project/articles/compsci/clipping.html
1310
- // Thanks!
1311
- // A bit modified to minimize code paths
1312
- clipEdge(edge, bbox) {
1313
- var ax = edge.va.x,
1314
- ay = edge.va.y,
1315
- bx = edge.vb.x,
1316
- by = edge.vb.y,
1317
- t0 = 0,
1318
- t1 = 1,
1319
- dx = bx - ax,
1320
- dy = by - ay;
1321
- // left
1322
- var q = ax - bbox.xl;
1323
- if (dx === 0 && q < 0) { return false; }
1324
- var r = -q / dx;
1325
- if (dx < 0) {
1326
- if (r < t0) { return false; }
1327
- if (r < t1) { t1 = r; }
1328
- }
1329
- else if (dx > 0) {
1330
- if (r > t1) { return false; }
1331
- if (r > t0) { t0 = r; }
1332
- }
1333
- // right
1334
- q = bbox.xr - ax;
1335
- if (dx === 0 && q < 0) { return false; }
1336
- r = q / dx;
1337
- if (dx < 0) {
1338
- if (r > t1) { return false; }
1339
- if (r > t0) { t0 = r; }
1340
- }
1341
- else if (dx > 0) {
1342
- if (r < t0) { return false; }
1343
- if (r < t1) { t1 = r; }
1344
- }
1345
- // top
1346
- q = ay - bbox.yt;
1347
- if (dy === 0 && q < 0) { return false; }
1348
- r = -q / dy;
1349
- if (dy < 0) {
1350
- if (r < t0) { return false; }
1351
- if (r < t1) { t1 = r; }
1352
- }
1353
- else if (dy > 0) {
1354
- if (r > t1) { return false; }
1355
- if (r > t0) { t0 = r; }
1356
- }
1357
- // bottom
1358
- q = bbox.yb - ay;
1359
- if (dy === 0 && q < 0) { return false; }
1360
- r = q / dy;
1361
- if (dy < 0) {
1362
- if (r > t1) { return false; }
1363
- if (r > t0) { t0 = r; }
1364
- }
1365
- else if (dy > 0) {
1366
- if (r < t0) { return false; }
1367
- if (r < t1) { t1 = r; }
1368
- }
1369
-
1370
- // if we reach this point, Voronoi edge is within bbox
1371
-
1372
- // if t0 > 0, va needs to change
1373
- // rhill 2011-06-03: we need to create a new vertex rather
1374
- // than modifying the existing one, since the existing
1375
- // one is likely shared with at least another edge
1376
- if (t0 > 0) {
1377
- edge.va = this.createVertex(ax + t0 * dx, ay + t0 * dy);
1378
- }
1379
-
1380
- // if t1 < 1, vb needs to change
1381
- // rhill 2011-06-03: we need to create a new vertex rather
1382
- // than modifying the existing one, since the existing
1383
- // one is likely shared with at least another edge
1384
- if (t1 < 1) {
1385
- edge.vb = this.createVertex(ax + t1 * dx, ay + t1 * dy);
1386
- }
1387
-
1388
- // va and/or vb were clipped, thus we will need to close
1389
- // cells which use this edge.
1390
- if (t0 > 0 || t1 < 1) {
1391
- this.cells[edge.lSite.voronoiId].closeMe = true;
1392
- this.cells[edge.rSite.voronoiId].closeMe = true;
1393
- }
1394
-
1395
- return true;
1396
- },
1397
-
1398
- // Connect/cut edges at bounding box
1399
- clipEdges(bbox) {
1400
- // connect all dangling edges to bounding box
1401
- // or get rid of them if it can't be done
1402
- var edges = this.edges,
1403
- iEdge = edges.length,
1404
- edge,
1405
- abs_fn = Math.abs;
1406
-
1407
- // iterate backward so we can splice safely
1408
- while (iEdge--) {
1409
- edge = edges[iEdge];
1410
- // edge is removed if:
1411
- // it is wholly outside the bounding box
1412
- // it is looking more like a point than a line
1413
- if (!this.connectEdge(edge, bbox) ||
1414
- !this.clipEdge(edge, bbox) ||
1415
- (abs_fn(edge.va.x - edge.vb.x) < 1e-9 && abs_fn(edge.va.y - edge.vb.y) < 1e-9)) {
1416
- edge.va = edge.vb = null;
1417
- edges.splice(iEdge, 1);
1418
- }
1419
- }
1420
- },
1421
-
1422
- // Close the cells.
1423
- // The cells are bound by the supplied bounding box.
1424
- // Each cell refers to its associated site, and a list
1425
- // of halfedges ordered counterclockwise.
1426
- closeCells(bbox) {
1427
- var xl = bbox.xl,
1428
- xr = bbox.xr,
1429
- yt = bbox.yt,
1430
- yb = bbox.yb,
1431
- cells = this.cells,
1432
- iCell = cells.length,
1433
- cell,
1434
- iLeft,
1435
- halfedges, nHalfedges,
1436
- edge,
1437
- va, vb, vz,
1438
- lastBorderSegment,
1439
- abs_fn = Math.abs;
1440
-
1441
- while (iCell--) {
1442
- cell = cells[iCell];
1443
- // prune, order halfedges counterclockwise, then add missing ones
1444
- // required to close cells
1445
- if (!cell.prepareHalfedges()) {
1446
- continue;
1447
- }
1448
- if (!cell.closeMe) {
1449
- continue;
1450
- }
1451
- // find first 'unclosed' point.
1452
- // an 'unclosed' point will be the end point of a halfedge which
1453
- // does not match the start point of the following halfedge
1454
- halfedges = cell.halfedges;
1455
- nHalfedges = halfedges.length;
1456
- // special case: only one site, in which case, the viewport is the cell
1457
- // ...
1458
-
1459
- // all other cases
1460
- iLeft = 0;
1461
- while (iLeft < nHalfedges) {
1462
- va = halfedges[iLeft].getEndpoint();
1463
- vz = halfedges[(iLeft + 1) % nHalfedges].getStartpoint();
1464
- // if end point is not equal to start point, we need to add the missing
1465
- // halfedge(s) up to vz
1466
- if (abs_fn(va.x - vz.x) >= 1e-9 || abs_fn(va.y - vz.y) >= 1e-9) {
1467
-
1468
- // rhill 2013-12-02:
1469
- // "Holes" in the halfedges are not necessarily always adjacent.
1470
- // https://github.com/gorhill/Javascript-Voronoi/issues/16
1471
-
1472
- // find entry point:
1473
- switch (true) {
1474
-
1475
- // walk downward along left side
1476
- case this.equalWithEpsilon(va.x, xl) && this.lessThanWithEpsilon(va.y, yb):
1477
- lastBorderSegment = this.equalWithEpsilon(vz.x, xl);
1478
- vb = this.createVertex(xl, lastBorderSegment ? vz.y : yb);
1479
- edge = this.createBorderEdge(cell.site, va, vb);
1480
- iLeft++;
1481
- halfedges.splice(iLeft, 0, this.createHalfedge(edge, cell.site, null));
1482
- nHalfedges++;
1483
- if (lastBorderSegment) { break; }
1484
- va = vb;
1485
- // fall through
1486
-
1487
- // walk rightward along bottom side
1488
- case this.equalWithEpsilon(va.y, yb) && this.lessThanWithEpsilon(va.x, xr):
1489
- lastBorderSegment = this.equalWithEpsilon(vz.y, yb);
1490
- vb = this.createVertex(lastBorderSegment ? vz.x : xr, yb);
1491
- edge = this.createBorderEdge(cell.site, va, vb);
1492
- iLeft++;
1493
- halfedges.splice(iLeft, 0, this.createHalfedge(edge, cell.site, null));
1494
- nHalfedges++;
1495
- if (lastBorderSegment) { break; }
1496
- va = vb;
1497
- // fall through
1498
-
1499
- // walk upward along right side
1500
- case this.equalWithEpsilon(va.x, xr) && this.greaterThanWithEpsilon(va.y, yt):
1501
- lastBorderSegment = this.equalWithEpsilon(vz.x, xr);
1502
- vb = this.createVertex(xr, lastBorderSegment ? vz.y : yt);
1503
- edge = this.createBorderEdge(cell.site, va, vb);
1504
- iLeft++;
1505
- halfedges.splice(iLeft, 0, this.createHalfedge(edge, cell.site, null));
1506
- nHalfedges++;
1507
- if (lastBorderSegment) { break; }
1508
- va = vb;
1509
- // fall through
1510
-
1511
- // walk leftward along top side
1512
- case this.equalWithEpsilon(va.y, yt) && this.greaterThanWithEpsilon(va.x, xl):
1513
- lastBorderSegment = this.equalWithEpsilon(vz.y, yt);
1514
- vb = this.createVertex(lastBorderSegment ? vz.x : xl, yt);
1515
- edge = this.createBorderEdge(cell.site, va, vb);
1516
- iLeft++;
1517
- halfedges.splice(iLeft, 0, this.createHalfedge(edge, cell.site, null));
1518
- nHalfedges++;
1519
- if (lastBorderSegment) { break; }
1520
- va = vb;
1521
- // fall through
1522
-
1523
- // walk downward along left side
1524
- lastBorderSegment = this.equalWithEpsilon(vz.x, xl);
1525
- vb = this.createVertex(xl, lastBorderSegment ? vz.y : yb);
1526
- edge = this.createBorderEdge(cell.site, va, vb);
1527
- iLeft++;
1528
- halfedges.splice(iLeft, 0, this.createHalfedge(edge, cell.site, null));
1529
- nHalfedges++;
1530
- if (lastBorderSegment) { break; }
1531
- va = vb;
1532
- // fall through
1533
-
1534
- // walk rightward along bottom side
1535
- lastBorderSegment = this.equalWithEpsilon(vz.y, yb);
1536
- vb = this.createVertex(lastBorderSegment ? vz.x : xr, yb);
1537
- edge = this.createBorderEdge(cell.site, va, vb);
1538
- iLeft++;
1539
- halfedges.splice(iLeft, 0, this.createHalfedge(edge, cell.site, null));
1540
- nHalfedges++;
1541
- if (lastBorderSegment) { break; }
1542
- va = vb;
1543
- // fall through
1544
-
1545
- // walk upward along right side
1546
- lastBorderSegment = this.equalWithEpsilon(vz.x, xr);
1547
- vb = this.createVertex(xr, lastBorderSegment ? vz.y : yt);
1548
- edge = this.createBorderEdge(cell.site, va, vb);
1549
- iLeft++;
1550
- halfedges.splice(iLeft, 0, this.createHalfedge(edge, cell.site, null));
1551
- nHalfedges++;
1552
- if (lastBorderSegment) { break; }
1553
- // fall through
1554
-
1555
- default:
1556
- throw "Voronoi.closeCells() > this makes no sense!";
1557
- }
1558
- }
1559
- iLeft++;
1560
- }
1561
- cell.closeMe = false;
1562
- }
1563
- },
1564
-
1565
- // ---------------------------------------------------------------------------
1566
- // Debugging helper
1567
- /*
1568
- Voronoi.prototype.dumpBeachline = function(y) {
1569
- console.log('Voronoi.dumpBeachline(%f) > Beachsections, from left to right:', y);
1570
- if ( !this.beachline ) {
1571
- console.log(' None');
1572
- }
1573
- else {
1574
- var bs = this.beachline.getFirst(this.beachline.root);
1575
- while ( bs ) {
1576
- console.log(' site %d: xl: %f, xr: %f', bs.site.voronoiId, this.leftBreakPoint(bs, y), this.rightBreakPoint(bs, y));
1577
- bs = bs.rbNext;
1578
- }
1579
- }
1580
- };
1581
- */
1582
-
1583
- // ---------------------------------------------------------------------------
1584
- // Helper: Quantize sites
1585
-
1586
- // rhill 2013-10-12:
1587
- // This is to solve https://github.com/gorhill/Javascript-Voronoi/issues/15
1588
- // Since not all users will end up using the kind of coord values which would
1589
- // cause the issue to arise, I chose to let the user decide whether or not
1590
- // he should sanitize his coord values through this helper. This way, for
1591
- // those users who uses coord values which are known to be fine, no overhead is
1592
- // added.
1593
-
1594
- quantizeSites(sites) {
1595
- var ε = this.ε,
1596
- n = sites.length,
1597
- site;
1598
- while (n--) {
1599
- site = sites[n];
1600
- site.x = Math.floor(site.x / ε) * ε;
1601
- site.y = Math.floor(site.y / ε) * ε;
1602
- }
1603
- },
1604
-
1605
- // ---------------------------------------------------------------------------
1606
- // Helper: Recycle diagram: all vertex, edge and cell objects are
1607
- // "surrendered" to the Voronoi object for reuse.
1608
- // TODO: rhill-voronoi-core v2: more performance to be gained
1609
- // when I change the semantic of what is returned.
1610
-
1611
- recycle(diagram) {
1612
- if (diagram) {
1613
- if (diagram instanceof this.Diagram) {
1614
- this.toRecycle = diagram;
1615
- }
1616
- else {
1617
- throw 'Voronoi.recycleDiagram() > Need a Diagram object.';
1618
- }
1619
- }
1620
- },
1621
-
1622
- // ---------------------------------------------------------------------------
1623
- // Top-level Fortune loop
1624
-
1625
- // rhill 2011-05-19:
1626
- // Voronoi sites are kept client-side now, to allow
1627
- // user to freely modify content. At compute time,
1628
- // *references* to sites are copied locally.
1629
-
1630
- compute(sites, bbox) {
1631
- // to measure execution time
1632
- var startTime = new Date();
1633
-
1634
- // init internal state
1635
- this.reset();
1636
-
1637
- // any diagram data available for recycling?
1638
- // I do that here so that this is included in execution time
1639
- if (this.toRecycle) {
1640
- this.vertexJunkyard = this.vertexJunkyard.concat(this.toRecycle.vertices);
1641
- this.edgeJunkyard = this.edgeJunkyard.concat(this.toRecycle.edges);
1642
- this.cellJunkyard = this.cellJunkyard.concat(this.toRecycle.cells);
1643
- this.toRecycle = null;
1644
- }
1645
-
1646
- // Initialize site event queue
1647
- var siteEvents = sites.slice(0);
1648
- siteEvents.sort(function (a, b) {
1649
- var r = b.y - a.y;
1650
- if (r) { return r; }
1651
- return b.x - a.x;
1652
- });
1653
-
1654
- // process queue
1655
- var site = siteEvents.pop(),
1656
- siteid = 0,
1657
- xsitex, // to avoid duplicate sites
1658
- xsitey,
1659
- cells = this.cells,
1660
- circle;
1661
-
1662
- // main loop
1663
- for (; ;) {
1664
- // we need to figure whether we handle a site or circle event
1665
- // for this we find out if there is a site event and it is
1666
- // 'earlier' than the circle event
1667
- circle = this.firstCircleEvent;
1668
-
1669
- // add beach section
1670
- if (site && (!circle || site.y < circle.y || (site.y === circle.y && site.x < circle.x))) {
1671
- // only if site is not a duplicate
1672
- if (site.x !== xsitex || site.y !== xsitey) {
1673
- // first create cell for new site
1674
- cells[siteid] = this.createCell(site);
1675
- site.voronoiId = siteid++;
1676
- // then create a beachsection for that site
1677
- this.addBeachsection(site);
1678
- // remember last site coords to detect duplicate
1679
- xsitey = site.y;
1680
- xsitex = site.x;
1681
- }
1682
- site = siteEvents.pop();
1683
- }
1684
-
1685
- // remove beach section
1686
- else if (circle) {
1687
- this.removeBeachsection(circle.arc);
1688
- }
1689
-
1690
- // all done, quit
1691
- else {
1692
- break;
1693
- }
1694
- }
1695
-
1696
- // wrapping-up:
1697
- // connect dangling edges to bounding box
1698
- // cut edges as per bounding box
1699
- // discard edges completely outside bounding box
1700
- // discard edges which are point-like
1701
- this.clipEdges(bbox);
1702
-
1703
- // add missing edges in order to close opened cells
1704
- this.closeCells(bbox);
1705
-
1706
- // to measure execution time
1707
- var stopTime = new Date();
1708
-
1709
- // prepare return values
1710
- var diagram = new this.Diagram();
1711
- diagram.cells = this.cells;
1712
- diagram.edges = this.edges;
1713
- diagram.vertices = this.vertices;
1714
- diagram.execTime = stopTime.getTime() - startTime.getTime();
1715
-
1716
- // clean up
1717
- this.reset();
1718
-
1719
- return diagram;
1720
- },
1721
-
1722
- };
1
+ thirdParty$Voronoi