JavaCAD
Face.java
Go to the documentation of this file.
1 /*
2  * Copyright John E. Lloyd, 2003. All rights reserved. Permission
3  * to use, copy, and modify, without fee, is granted for non-commercial
4  * and research purposes, provided that this copyright notice appears
5  * in all copies.
6  *
7  * This software is distributed "as is", without any warranty, including
8  * any implied warranty of merchantability or fitness for a particular
9  * use. The authors assume no responsibility for, and shall not be liable
10  * for, any special, indirect, or consequential damages, or any damages
11  * whatsoever, arising out of or in connection with the use of this
12  * software.
13  */
14 
15 package eu.mihosoft.vrl.v3d.ext.quickhull3d;
16 
17 import java.util.*;
18 
19 // TODO: Auto-generated Javadoc
29 class Face
30 {
31 
33  HalfEdge he0;
34 
36  private Vector3d normal;
37 
39  double area;
40 
42  private Point3d centroid;
43 
45  double planeOffset;
46 
48  int index;
49 
51  int numVerts;
52 
54  Face next;
55 
57  static final int VISIBLE = 1;
58 
60  static final int NON_CONVEX = 2;
61 
63  static final int DELETED = 3;
64 
66  int mark = VISIBLE;
67 
69  Vertex outside;
70 
76  public void computeCentroid (Point3d centroid)
77  {
78  centroid.setZero();
79  HalfEdge he = he0;
80  do
81  { centroid.add (he.head().pnt);
82  he = he.next;
83  }
84  while (he != he0);
85  centroid.scale (1/(double)numVerts);
86  }
87 
94  public void computeNormal (Vector3d normal, double minArea)
95  {
96  computeNormal(normal);
97 
98  if (area < minArea)
99  {
100  // make the normal more robust by removing
101  // components parallel to the longest edge
102 
103  HalfEdge hedgeMax = null;
104  double lenSqrMax = 0;
105  HalfEdge hedge = he0;
106  do
107  { double lenSqr = hedge.lengthSquared();
108  if (lenSqr > lenSqrMax)
109  { hedgeMax = hedge;
110  lenSqrMax = lenSqr;
111  }
112  hedge = hedge.next;
113  }
114  while (hedge != he0);
115 
116  Point3d p2 = hedgeMax.head().pnt;
117  Point3d p1 = hedgeMax.tail().pnt;
118  double lenMax = Math.sqrt(lenSqrMax);
119  double ux = (p2.x - p1.x)/lenMax;
120  double uy = (p2.y - p1.y)/lenMax;
121  double uz = (p2.z - p1.z)/lenMax;
122  double dot = normal.x*ux + normal.y*uy + normal.z*uz;
123  normal.x -= dot*ux;
124  normal.y -= dot*uy;
125  normal.z -= dot*uz;
126 
127  normal.normalize();
128  }
129  }
130 
136  public void computeNormal (Vector3d normal)
137  {
138  HalfEdge he1 = he0.next;
139  HalfEdge he2 = he1.next;
140 
141  Point3d p0 = he0.head().pnt;
142  Point3d p2 = he1.head().pnt;
143 
144  double d2x = p2.x - p0.x;
145  double d2y = p2.y - p0.y;
146  double d2z = p2.z - p0.z;
147 
148  normal.setZero();
149 
150  numVerts = 2;
151 
152  while (he2 != he0)
153  {
154  double d1x = d2x;
155  double d1y = d2y;
156  double d1z = d2z;
157 
158  p2 = he2.head().pnt;
159  d2x = p2.x - p0.x;
160  d2y = p2.y - p0.y;
161  d2z = p2.z - p0.z;
162 
163  normal.x += d1y*d2z - d1z*d2y;
164  normal.y += d1z*d2x - d1x*d2z;
165  normal.z += d1x*d2y - d1y*d2x;
166 
167  he1 = he2;
168  he2 = he2.next;
169  numVerts++;
170  }
171  area = normal.norm();
172  normal.scale (1/area);
173  }
174 
178  private void computeNormalAndCentroid()
179  {
180  computeNormal (normal);
181  computeCentroid (centroid);
182  planeOffset = normal.dot(centroid);
183  int numv = 0;
184  HalfEdge he = he0;
185  do
186  { numv++;
187  he = he.next;
188  }
189  while (he != he0);
190  if (numv != numVerts)
191  { throw new InternalErrorException (
192 "face " + getVertexString() + " numVerts=" + numVerts + " should be " + numv);
193  }
194  }
195 
201  private void computeNormalAndCentroid(double minArea)
202  {
203  computeNormal (normal, minArea);
204  computeCentroid (centroid);
205  planeOffset = normal.dot(centroid);
206  }
207 
216  public static Face createTriangle (Vertex v0, Vertex v1, Vertex v2)
217  {
218  return createTriangle (v0, v1, v2, 0);
219  }
220 
230  public static Face createTriangle (Vertex v0, Vertex v1, Vertex v2,
231  double minArea)
232  {
233  Face face = new Face();
234  HalfEdge he0 = new HalfEdge (v0, face);
235  HalfEdge he1 = new HalfEdge (v1, face);
236  HalfEdge he2 = new HalfEdge (v2, face);
237 
238  he0.prev = he2;
239  he0.next = he1;
240  he1.prev = he0;
241  he1.next = he2;
242  he2.prev = he1;
243  he2.next = he0;
244 
245  face.he0 = he0;
246 
247  // compute the normal and offset
248  face.computeNormalAndCentroid(minArea);
249  return face;
250  }
251 
259  public static Face create (Vertex[] vtxArray, int[] indices)
260  {
261  Face face = new Face();
262  HalfEdge hePrev = null;
263  for (int i=0; i<indices.length; i++)
264  { HalfEdge he = new HalfEdge (vtxArray[indices[i]], face);
265  if (hePrev != null)
266  { he.setPrev (hePrev);
267  hePrev.setNext (he);
268  }
269  else
270  { face.he0 = he;
271  }
272  hePrev = he;
273  }
274  face.he0.setPrev (hePrev);
275  hePrev.setNext (face.he0);
276 
277  // compute the normal and offset
278  face.computeNormalAndCentroid();
279  return face;
280  }
281 
285  public Face ()
286  {
287  normal = new Vector3d();
288  centroid = new Point3d();
289  mark = VISIBLE;
290  }
291 
298  public HalfEdge getEdge(int i)
299  {
300  HalfEdge he = he0;
301  while (i > 0)
302  { he = he.next;
303  i--;
304  }
305  while (i < 0)
306  { he = he.prev;
307  i++;
308  }
309  return he;
310  }
311 
317  public HalfEdge getFirstEdge()
318  { return he0;
319  }
320 
329  public HalfEdge findEdge (Vertex vt, Vertex vh)
330  {
331  HalfEdge he = he0;
332  do
333  { if (he.head() == vh && he.tail() == vt)
334  { return he;
335  }
336  he = he.next;
337  }
338  while (he != he0);
339  return null;
340  }
341 
349  public double distanceToPlane (Point3d p)
350  {
351  return normal.x*p.x + normal.y*p.y + normal.z*p.z - planeOffset;
352  }
353 
359  public Vector3d getNormal ()
360  {
361  return normal;
362  }
363 
369  public Point3d getCentroid ()
370  {
371  return centroid;
372  }
373 
379  public int numVertices()
380  {
381  return numVerts;
382  }
383 
389  public String getVertexString ()
390  {
391  String s = null;
392  HalfEdge he = he0;
393  do
394  { if (s == null)
395  { s = "" + he.head().index;
396  }
397  else
398  { s += " " + he.head().index;
399  }
400  he = he.next;
401  }
402  while (he != he0);
403  return s;
404  }
405 
411  public void getVertexIndices (int[] idxs)
412  {
413  HalfEdge he = he0;
414  int i = 0;
415  do
416  { idxs[i++] = he.head().index;
417  he = he.next;
418  }
419  while (he != he0);
420  }
421 
429  private Face connectHalfEdges (
430  HalfEdge hedgePrev, HalfEdge hedge)
431  {
432  Face discardedFace = null;
433 
434  if (hedgePrev.oppositeFace() == hedge.oppositeFace())
435  { // then there is a redundant edge that we can get rid off
436 
437  Face oppFace = hedge.oppositeFace();
438  HalfEdge hedgeOpp;
439 
440  if (hedgePrev == he0)
441  { he0 = hedge;
442  }
443  if (oppFace.numVertices() == 3)
444  { // then we can get rid of the opposite face altogether
445  hedgeOpp = hedge.getOpposite().prev.getOpposite();
446 
447  oppFace.mark = DELETED;
448  discardedFace = oppFace;
449  }
450  else
451  { hedgeOpp = hedge.getOpposite().next;
452 
453  if (oppFace.he0 == hedgeOpp.prev)
454  { oppFace.he0 = hedgeOpp;
455  }
456  hedgeOpp.prev = hedgeOpp.prev.prev;
457  hedgeOpp.prev.next = hedgeOpp;
458  }
459  hedge.prev = hedgePrev.prev;
460  hedge.prev.next = hedge;
461 
462  hedge.opposite = hedgeOpp;
463  hedgeOpp.opposite = hedge;
464 
465  // oppFace was modified, so need to recompute
466  oppFace.computeNormalAndCentroid();
467  }
468  else
469  { hedgePrev.next = hedge;
470  hedge.prev = hedgePrev;
471  }
472  return discardedFace;
473  }
474 
478  void checkConsistency()
479  {
480  // do a sanity check on the face
481  HalfEdge hedge = he0;
482  double maxd = 0;
483  int numv = 0;
484 
485  if (numVerts < 3)
486  { throw new InternalErrorException (
487  "degenerate face: " + getVertexString());
488  }
489  do
490  { HalfEdge hedgeOpp = hedge.getOpposite();
491  if (hedgeOpp == null)
492  { throw new InternalErrorException (
493  "face " + getVertexString() + ": " +
494  "unreflected half edge " + hedge.getVertexString());
495  }
496  else if (hedgeOpp.getOpposite() != hedge)
497  { throw new InternalErrorException (
498  "face " + getVertexString() + ": " +
499  "opposite half edge " + hedgeOpp.getVertexString() +
500  " has opposite " +
501  hedgeOpp.getOpposite().getVertexString());
502  }
503  if (hedgeOpp.head() != hedge.tail() ||
504  hedge.head() != hedgeOpp.tail())
505  { throw new InternalErrorException (
506  "face " + getVertexString() + ": " +
507  "half edge " + hedge.getVertexString() +
508  " reflected by " + hedgeOpp.getVertexString());
509  }
510  Face oppFace = hedgeOpp.face;
511  if (oppFace == null)
512  { throw new InternalErrorException (
513  "face " + getVertexString() + ": " +
514  "no face on half edge " + hedgeOpp.getVertexString());
515  }
516  else if (oppFace.mark == DELETED)
517  { throw new InternalErrorException (
518  "face " + getVertexString() + ": " +
519  "opposite face " + oppFace.getVertexString() +
520  " not on hull");
521  }
522  double d = Math.abs(distanceToPlane(hedge.head().pnt));
523  if (d > maxd)
524  { maxd = d;
525  }
526  numv++;
527  hedge = hedge.next;
528  }
529  while (hedge != he0);
530 
531  if (numv != numVerts)
532  { throw new InternalErrorException (
533 "face " + getVertexString() + " numVerts=" + numVerts + " should be " + numv);
534  }
535 
536  }
537 
545  public int mergeAdjacentFace (HalfEdge hedgeAdj,
546  Face[] discarded)
547  {
548  Face oppFace = hedgeAdj.oppositeFace();
549  int numDiscarded = 0;
550 
551  discarded[numDiscarded++] = oppFace;
552  oppFace.mark = DELETED;
553 
554  HalfEdge hedgeOpp = hedgeAdj.getOpposite();
555 
556  HalfEdge hedgeAdjPrev = hedgeAdj.prev;
557  HalfEdge hedgeAdjNext = hedgeAdj.next;
558  HalfEdge hedgeOppPrev = hedgeOpp.prev;
559  HalfEdge hedgeOppNext = hedgeOpp.next;
560 
561  while (hedgeAdjPrev.oppositeFace() == oppFace)
562  { hedgeAdjPrev = hedgeAdjPrev.prev;
563  hedgeOppNext = hedgeOppNext.next;
564  }
565 
566  while (hedgeAdjNext.oppositeFace() == oppFace)
567  { hedgeOppPrev = hedgeOppPrev.prev;
568  hedgeAdjNext = hedgeAdjNext.next;
569  }
570 
571  HalfEdge hedge;
572 
573  for (hedge=hedgeOppNext; hedge!=hedgeOppPrev.next; hedge=hedge.next)
574  { hedge.face = this;
575  }
576 
577  if (hedgeAdj == he0)
578  { he0 = hedgeAdjNext;
579  }
580 
581  // handle the half edges at the head
582  Face discardedFace;
583 
584  discardedFace = connectHalfEdges (hedgeOppPrev, hedgeAdjNext);
585  if (discardedFace != null)
586  { discarded[numDiscarded++] = discardedFace;
587  }
588 
589  // handle the half edges at the tail
590  discardedFace = connectHalfEdges (hedgeAdjPrev, hedgeOppNext);
591  if (discardedFace != null)
592  { discarded[numDiscarded++] = discardedFace;
593  }
594 
595  computeNormalAndCentroid ();
596  checkConsistency();
597 
598  return numDiscarded;
599  }
600 
608  private double areaSquared (HalfEdge hedge0, HalfEdge hedge1)
609  {
610  // return the squared area of the triangle defined
611  // by the half edge hedge0 and the point at the
612  // head of hedge1.
613 
614  Point3d p0 = hedge0.tail().pnt;
615  Point3d p1 = hedge0.head().pnt;
616  Point3d p2 = hedge1.head().pnt;
617 
618  double dx1 = p1.x - p0.x;
619  double dy1 = p1.y - p0.y;
620  double dz1 = p1.z - p0.z;
621 
622  double dx2 = p2.x - p0.x;
623  double dy2 = p2.y - p0.y;
624  double dz2 = p2.z - p0.z;
625 
626  double x = dy1*dz2 - dz1*dy2;
627  double y = dz1*dx2 - dx1*dz2;
628  double z = dx1*dy2 - dy1*dx2;
629 
630  return x*x + y*y + z*z;
631  }
632 
639  public void triangulate (FaceList newFaces, double minArea)
640  {
641  HalfEdge hedge;
642 
643  if (numVertices() < 4)
644  { return;
645  }
646 
647  Vertex v0 = he0.head();
648  Face prevFace = null;
649 
650  hedge = he0.next;
651  HalfEdge oppPrev = hedge.opposite;
652  Face face0 = null;
653 
654  for (hedge=hedge.next; hedge!=he0.prev; hedge=hedge.next)
655  { Face face =
656  createTriangle (v0, hedge.prev.head(), hedge.head(), minArea);
657  face.he0.next.setOpposite (oppPrev);
658  face.he0.prev.setOpposite (hedge.opposite);
659  oppPrev = face.he0;
660  newFaces.add (face);
661  if (face0 == null)
662  { face0 = face;
663  }
664  }
665  hedge = new HalfEdge (he0.prev.prev.head(), this);
666  hedge.setOpposite (oppPrev);
667 
668  hedge.prev = he0;
669  hedge.prev.next = hedge;
670 
671  hedge.next = he0.prev;
672  hedge.next.prev = hedge;
673 
674  computeNormalAndCentroid (minArea);
675  checkConsistency();
676 
677  for (Face face=face0; face!=null; face=face.next)
678  { face.checkConsistency();
679  }
680 
681  }
682 }
683 
684 
685