EOS 2  1.1.0
Einfache Objektbasierte Sprache
BorderWalkTesselation.java
gehe zur Dokumentation dieser Datei
1 package de.lathanda.eos.game.geom.tesselation;
2 
3 import java.text.MessageFormat;
4 import java.util.ArrayList;
5 import java.util.Collection;
6 import java.util.Iterator;
7 import java.util.LinkedList;
8 import java.util.TreeSet;
9 
10 import de.lathanda.eos.base.math.Point;
11 import de.lathanda.eos.base.math.Vector;
12 import de.lathanda.eos.base.util.IntervalTree;
13 import de.lathanda.eos.game.geom.BoundingBox;
14 import de.lathanda.eos.game.geom.Triangle;
15 import de.lathanda.eos.game.geom.Tesselation;
30 public class BorderWalkTesselation implements Tesselation {
31  //original data
32  private LinkedList<Vertice> v = new LinkedList<>() ;
33  //simple concave polygon representing outer boarder
34  private LinkedList<Vertice> p = new LinkedList<>();
35  //final triangulation
36  private LinkedList<Triangle> t = new LinkedList<>();
37  //array of all incoming edges
38  private ArrayList<Edge> edges;
39  //dirty?
40  private boolean triangleReady = false;
41  private boolean borderReady = false;
43  }
44  public BorderWalkTesselation(double[] x, double[] y) {
45  if (x.length != y.length) throw new ArrayIndexOutOfBoundsException();
46  for(int i = 0; i < x.length; i++) {
47  v.add(new Vertice(x[i], y[i]));
48  }
49  }
50  public BorderWalkTesselation(Collection<? extends Point> points) {
51  for(Point p : points) {
52  v.add(new Vertice(p.getX(), p.getY()));
53  }
54  }
55  public BorderWalkTesselation(Collection<? extends Point> points, double raster) {
56  for(Point p : points) {
57  v.add(new Vertice(((int)(raster*p.getX()))/raster, ((int)(raster*p.getY()))/raster));
58  }
59  }
60  public void addVertice(double x, double y) {
61  v.add(new Vertice(x, y));
62  triangleReady = false;
63  borderReady = false;
64  }
65  public void tesselate() throws TesselationFailedException {
66  p.clear();
67  t.clear();
68  switch (v.size()) {
69  case 0:
70  break;
71  case 1:
72  case 2:
73  p.addAll(v);
74  break;
75  default:
76  Edge start = createEdges(); //v -> edges
77  convertToSimplePolygon(start); //edges -> p
78  earCut(); //p -> t
79  }
80  triangleReady = true;
81  borderReady = true;
82  }
84  p.clear();
85  t.clear();
86  switch (v.size()) {
87  case 0:
88  break;
89  case 1:
90  case 2:
91  p.addAll(v);
92  break;
93  default:
94  Edge start = createEdges(); //v -> edges
95  convertToSimplePolygon(start); //edges -> p
96 
97  }
98  borderReady = true;
99  }
100  public Collection<Triangle> getTriangles() throws TesselationFailedException {
101  if (!triangleReady && ! borderReady ) {
102  tesselate();
103  } else if (!triangleReady && borderReady) {
104  earCut();
105  }
106  return t;
107  }
108 
109  @Override
110  public LinkedList<? extends Point> getOuterBorder() throws TesselationFailedException {
111  if (!borderReady) {
112  calculateBorder();
113  }
114  return p;
115  }
116 
117  public LinkedList<? extends Point> getPolygon() {
118  return v;
119  }
120  private void convertToSimplePolygon(Edge start) throws TesselationFailedException {
121  //prepare search structure
122  IntervalTree<BoundingBox<Edge>.xInterval> xRange = new IntervalTree<>();
123  IntervalTree<BoundingBox<Edge>.yInterval> yRange = new IntervalTree<>();
124  for (Edge e : edges) {
125  xRange.insert(e.b.new xInterval());
126  yRange.insert(e.b.new yInterval());
127  }
128  // --- run clockwise searching intersections changing edge ---
129  Edge act = start;
130  int failed = v.size() * 4; //counter prevents unlimited loop caused by numerical error
131  do {
132  if (failed -- == 0) { // calculation failed
133  throw new TesselationFailedException();
134  }
135  // check for potential crossing edges using bounding boxes
136  TreeSet<BoundingBox<Edge>.xInterval> xHits = new TreeSet<>();
137  TreeSet<BoundingBox<Edge>.yInterval> yHits = new TreeSet<>();
138  xRange.seek(act.b.new xInterval(), xHits); //the result is sorted by natural edge order!
139  yRange.seek(act.b.new yInterval(), yHits);
140  // as the sets are sorted, we can easily check for items that are in both sets
141  TreeSet<Edge> hits = new TreeSet<>();
142  BoundingBox<Edge>.xInterval xInt = xHits.pollFirst();
143  BoundingBox<Edge>.yInterval yInt = yHits.pollFirst();
144  while (xInt != null && yInt != null) {
145  if (xInt.getSource() == yInt.getSource()) {
146  //bounding boxes overlap
147  hits.add(xInt.getSource());
148  xInt = xHits.pollFirst();
149  yInt = yHits.pollFirst();
150  } else if ( xInt.getSource().compareTo(yInt.getSource()) < 0) {
151  xInt = xHits.pollFirst();
152  } else {
153  yInt = yHits.pollFirst();
154  }
155  }
156  // check all potential crossing edges, if they actually do intersect
157  CrossingPoint best = null;
158  for (Edge e : hits) {
159  if (e.id == act.id) {
160  continue; //ignore intersection with identical edge
161  }
162  CrossingPoint inter = act.getIntersection(e, edges);
163  //edges not chosen in the previous step are ignored (same starting point)
164  if (inter != null && (inter.edge.x1 != act.x1 || inter.edge.y1 != act.y1)) {
165  if (best != null) {
166  //remark: mathematically inter.angle > 2 is equal to act.v.crossproduct(inter.edge.v) > 0
167  if (inter.lambda < best.lambda && act.v.crossproduct(inter.edge.v) > 0) {
168  //better lambda
169  best = inter;
170  } else {
171  if (inter.lambda == best.lambda) {
172  //same crossing point determine better
173  if (best.angle < inter.angle) {
174  //better angle
175  best = inter;
176  } else if (best.angle == inter.angle) {
177  //same angle choose longer
178  if (best.edge.v.getSquareLength() < inter.edge.v.getSquareLength()) {
179  best = inter;
180  }
181  }
182  }
183  }
184  } else {
185  if (inter.lambda == 1.0 || act.v.crossproduct(inter.edge.v) > 0) {
186  best = inter;
187  }
188  }
189  }
190  }
191 
192  act = best.edge;
193  p.add(new Vertice(act.x1, act.y1));
194  } while (act.id != start.id);
195  p.removeLast();
196  }
197  private void earCut() throws TesselationFailedException {
198  //determine concave points and link points
199  Vertice prev = p.getLast();
200  Vertice[] last = new Vertice[] {prev}; //call by ref parameter
201  for(Vertice a : p) {
202  a.linkPredecessor(prev);
203  prev = a;
204  }
205  LinkedList<Vertice> concaves = new LinkedList<>();
206  for(Vertice a: p) {
207  if (a.isConcave()) {
208  concaves.add(a);
209  }
210  }
211  boolean ok = true;
212  while (!concaves.isEmpty() && ok) {
213  ok = false;
214  for(Iterator<Vertice> i = concaves.iterator(); i.hasNext(); ) {
215  Vertice v = i.next();
216  if (v.suc == null || !v.isConcave()) {
217  i.remove();
218  ok = true;
219  } else {
220  if (v.pre.cutEmpty(last)) {
221  ok = true;
222  } else if (v.suc.cutEmpty(last)) {
223  ok = true;
224  } else if (v.pre.isEar(concaves)) {
225  t.add(v.pre.cutEar(last));
226  ok = true;
227  } else if (v.suc.isEar(concaves)) {
228  t.add(v.suc.cutEar(last));
229  ok = true;
230  }
231  }
232  }
233  }
234  if (!ok) {
235  throw new TesselationFailedException();
236  }
237  while (last[0].pre.pre != last[0]) { //no triangle
238  t.add(last[0].cutEar(last));
239  }
240  }
246  private Edge createEdges() {
247  edges = new ArrayList<>();
248  // fill edges
249  Vertice v1 = v.get(v.size() - 1);
250  double minX = Double.POSITIVE_INFINITY;
251  double minY = Double.POSITIVE_INFINITY;
252  for(Vertice v2 : v) {
253  if (v1.isDifferent(v2)) {
254  edges.add(new Edge(v1, v2, edges.size()));
255  v1 = v2;
256  if (v2.getX() < minX) {
257  minX = v2.getX();
258  minY = v2.getY();
259  }
260  }
261  }
262  Edge start = new Edge(minX, minY, edges.size());
263  edges.add(start);
264  return start;
265  }
266  private static class Vertice extends Point {
267  private Vector v;
268  private Vertice pre;
269  private Vertice suc;
270  public Vertice(double x, double y) {
271  super(x,y);
272  }
273  public boolean isDifferent(Vertice v2) {
274  return x != v2.x || y != v2.y;
275  }
276  public void linkPredecessor(Vertice predecessor) {
277  this.pre = predecessor;
278  predecessor.suc = this;
279  v = new Vector(pre, this);
280  }
281  public Triangle cutEar(Vertice[] last) {
282  if (this == last[0]) {
283  last[0] = pre;
284  }
285  Triangle t = new Triangle(pre.x, pre.y, x, y, suc.x, suc.y);
286  suc.linkPredecessor(pre);
287  pre = null;
288  suc = null;
289  return t;
290  }
291  public boolean cutEmpty(Vertice[] last) {
292  if (v.crossproduct(suc.v) != 0) {
293  if (this == last[0]) {
294  last[0] = pre;
295  }
296  suc.linkPredecessor(pre);
297  pre = null;
298  suc = null;
299  return true;
300  } else {
301  return false;
302  }
303  }
304  public boolean isEar(Vertice p) {
305  Vector cv = new Vector(pre.x - suc.x, pre.y - suc.y);
306  Vector a = new Vector(this, p);
307  Vector b = new Vector(suc, p);
308  Vector c = new Vector(pre, p);
309  return suc.v.crossproduct(a) >= 0 || cv.crossproduct(b) >= 0 || v.crossproduct(c) >= 0;
310  }
311  public boolean isEar(Collection<Vertice> c) {
312  if (isConcave()) return false;
313  for(Vertice v : c) {
314  if (!isEar(v)) {
315  return false;
316  }
317  }
318  return true;
319  }
320  public boolean isConcave() {
321  return v.crossproduct(suc.v) > 0;
322  }
323  @Override
324  public String toString() {
325  return super.toString();
326  }
327  }
328  private static class Edge implements Comparable<Edge> {
330  private final double x1, y1;
332  private final double x2, y2;
334  private final int id;
336  private final Vector v;
338  private final BoundingBox<Edge> b;
340  private final double angle;
347  public Edge(double x, double y, int id) {
348  this.x1 = Double.NEGATIVE_INFINITY;
349  this.y1 = round(y);
350  this.x2 = round(x);
351  this.y2 = round(y);
352  this.b = new BoundingBox<>(x2, y2, x2, y2, this);
353  this.id = id;
354  this.v = new Vector(1, 0);
355  this.angle = v.getAngleOrdernumber();
356  }
357  public Edge(double x1, double y1, double x2, double y2, int id) {
358  this.x1 = round(x1);
359  this.y1 = round(y1);
360  this.x2 = round(x2);
361  this.y2 = round(y2);
362  this.b = new BoundingBox<>(this.x1, this.y1, this.x2, this.y2, this);
363  this.id = id;
364  this.v = new Vector(this.x2 - this.x1, this.y2 - this.y1);
365  this.angle = v.getAngleOrdernumber();
366  }
367  public Edge(Vertice v1, Vertice v2, int id) {
368  this(v1.getX(), v1.getY(), v2.getX(), v2.getY(), id);
369  }
370  private final static double HALF = Double.longBitsToDouble(0x0000000000080000l);
371  private static double round(double a) {
372  //the following line will floor 20 bits to 0
373  //this is approximately a loose of precision of 6 digits
374  return Double.longBitsToDouble(Double.doubleToRawLongBits(a + HALF) & 0xFFFFFFFFFFF00000l);
375  }
376 
381  public Edge(Edge e) {
382  this.x1 = e.x2;
383  this.y1 = e.y2;
384  this.x2 = e.x1;
385  this.y2 = e.y1;
386  this.b = e.b;
387  this.id = e.id;
388  this.v = e.v.negative();
389  this.angle = (e.angle >= 2)?e.angle - 2:e.angle + 2;
390  }
391 
392  public CrossingPoint getIntersection(Edge b, ArrayList<Edge> edges) {
393  /* preface:
394  * In order to calculate crossing points, we do calculate lambda or my.
395  * We can do this using x or y.
396  * In algebra the choice doesn't matter.
397  * In numerical algebra it does.
398  * Depending on our choice the results will differ.
399  * We need to keep divisors as big as possible.
400  * In order to avoid accumulated errors
401  * we will use the original master edges as far as possible.
402  * The shortened edge will only be used in the last step.
403  */
404  //check identical point first, this avoids numerical errors
405  if (x2 == b.x1 && y2 == b.y1) {
406  // continuous polygon
407  return new CrossingPoint(this, b, false, true);
408  }
409  if (x2 == b.x2 && y2 == b.y2) {
410  // inverted continuous polygon
411  return new CrossingPoint(this, b, true, true);
412  }
413  if (x1 == b.x1 && y1 == b.y1) {
414  // alternative edge (will be ignored later)
415  return new CrossingPoint(this, b, false, false);
416  }
417  if (x1 == b.x2 && y1 == b.y2) {
418  // inverted alternative edge (will be ignored later)
419  return new CrossingPoint(this, b, true, false);
420  }
421  //Lookup original master edges
422  Edge am, bm;
423  if (id < b.id) {
424  am = edges.get(id);
425  bm = edges.get(b.id);
426  } else {
427  am = edges.get(b.id);
428  bm = edges.get(id);
429  }
430  //Cramer's rule
431  Vector base = new Vector(bm.x1 - am.x1, bm.y1 - am.y1);
432  double n = am.v.crossproduct(bm.v);
433  double za = base.crossproduct(bm.v);
434  if (n == 0) { //parallel
435  if (za == 0) {
436  //same line
437  return CrossingPoint.createCrossingPoint(this, b, x2, y2);
438  } else {
439  // no intersection
440  return null;
441  }
442  } else {
443  double lambdaM = za / n; //lambda original edge
444  double lambdaM2 = 1 - lambdaM;
445  double px = am.x1 * lambdaM2 + lambdaM * am.x2;
446  double py = am.y1 * lambdaM2 + lambdaM * am.y2;
447  return CrossingPoint.createCrossingPoint(this, b, round(px), round(py));
448  }
449  }
450  public Edge invert() {
451  return new Edge(this);
452  }
453  @Override
454  public int compareTo(Edge o) {
455  //return b.getSource().compareTo(o.b.getSource());
456  return o.id - id;
457  }
458  @Override
459  public String toString() {
460  return MessageFormat.format("id {4,number,#} ({0,number,#.##}/{1,number,#.##}) -({5,number,#.###})-> ({2,number,#.##}/{3,number,#.##})", x1, y1, x2, y2, id, angle);
461  }
462 
463  }
464  private static class CrossingPoint {
465  private final double lambda;
466  private final double angle;
467  private final Edge edge;
468  private CrossingPoint(Edge edge, double lambda, double angle) {
469  this.edge = edge;
470  this.angle = angle;
471  this.lambda = lambda;
472  }
480  public CrossingPoint(Edge a, Edge b, boolean inverted, boolean continues) {
481  if (continues) {
482  lambda = 1;
483  } else {
484  lambda = 0;
485  }
486  if (inverted) {
487  edge = b.invert();
488  } else {
489  edge = b;
490  }
491  double diffA = 2 + edge.angle - a.angle;
492  if (diffA < 0) {
493  this.angle = diffA + 4;
494  } else if (diffA >= 4) {
495  this.angle = diffA - 4;
496  } else {
497  this.angle = diffA;
498  }
499  }
500 
501  private static CrossingPoint createCrossingPoint(Edge a, Edge b, double px, double py) {
502  double lambda, my;
503  //use the bigger delta,
504  //this avoids dividing by zero and is less vulnerable to rounding problems
505  if (Math.abs(a.x2 - a.x1) > Math.abs(a.y2 - a.y1)) {
506  lambda = (px - a.x1) / (a.x2 - a.x1);
507  } else {
508  lambda = (py - a.y1) / (a.y2 - a.y1);
509  }
510  if (Math.abs(b.x2 - b.x1) > Math.abs(b.y2 - b.y1)) {
511  my = (px - b.x1) / (b.x2 - b.x1);
512  } else {
513  my = (py - b.y1) / (b.y2 - b.y1);
514  }
515  //detected values that are near the corners, and move them
516  if (lambda < 0.0000001 && lambda > -0.0000001) {
517  lambda = 0;
518  px = a.x1;
519  py = a.y1;
520  } else if (lambda > 0.9999999 && lambda < 1.0000001) {
521  lambda = 1;
522  px = a.x2;
523  py = a.y2;
524  }
525  if (my < 0.0000001 && my > -0.0000001) {
526  my = 0;
527  } else if (my > 0.9999999 && my < 1.0000001) {
528  my = 1;
529  }
530  //check if crossing point is within the edge
531  if (lambda >= 0 && my >= 0 && lambda <= 1 && my <= 1) {
532  //determine the orientation
533  Edge edge;
534  if (my == 0) {
535  //edge starts at crossing point
536  edge = b;
537  } else if (my == 1) {
538  //edge ends at crossing point
539  edge = b.invert();
540  } else {
541  //edge is on both sides ...
542  double c = a.v.crossproduct(b.v);
543  //...choose the edge half that turns left
544  if (c > 0) {
545  edge = new Edge(px, py, b.x2, b.y2, b.id);
546  } else if (c < 0) {
547  edge = new Edge(px, py, b.x1, b.y1, b.id);
548  } else {
549  //...overlapping edges => choose the edge direction that continues
550  if (b.v.dotProduct(b.v) > 0) {
551  edge = new Edge(px, py, b.x2, b.y2, b.id);
552  } else {
553  edge = new Edge(px, py, b.x1, b.y1, b.id);
554  }
555  }
556  }
557  //determine the crossing angle order
558  double diffA = 2 + edge.angle - a.angle;
559  double angle;
560  if (diffA < 0) {
561  angle = diffA + 4;
562  } else if (diffA >= 4) {
563  angle = diffA - 4;
564  } else {
565  angle = diffA;
566  }
567  return new CrossingPoint(edge, lambda, angle);
568  } else {
569  return null;
570  }
571  }
572  @Override
573  public String toString() {
574  return edge + " " + MessageFormat.format("lambda = {0,number,#.##}, angle = {1,number,#.###}", lambda, angle);
575  }
576 
577  }
578 }
double y
y Koordinate
Definition: Point.java:21
double x
x Koordinate
Definition: Point.java:17
BorderWalkTesselation(Collection<? extends Point > points, double raster)
Impressum