Cafu Engine
Plane3.hpp
1 /*
2 Cafu Engine, http://www.cafu.de/
3 Copyright (c) Carsten Fuchs and other contributors.
4 This project is licensed under the terms of the MIT license.
5 */
6 
7 /**************/
8 /*** Plane3 ***/
9 /**************/
10 
11 #ifndef CAFU_MATH_PLANE_HPP_INCLUDED
12 #define CAFU_MATH_PLANE_HPP_INCLUDED
13 
14 #include "Vector3.hpp"
15 #include "Templates/Array.hpp"
16 
17 
18 /// This class represents a plane in three-dimensional space.
19 /// Eigenschaften der Ebene (Vereinbarungen):
20 /// 1. Die Ebenengleichung lautet VectorDot(UnitNormal,x)=Dist
21 /// 2. Der Normalenvektor muß stets der EINHEITSnormalenvektor sein (Länge 1)
22 /// 3. ==> Einen Stützvektor erhalten wir aus VectorScale(UnitNormal,Dist)
23 /// 4. Der vordere Halbraum / die Oberseite der Ebene liegt in Richtung des Normalenvektors
24 /// (Siehe auch: Mathematikheft Nr. 5, 24.01.96, "Die Bedeutung des Vorzeichens", Fälle 2+4)
25 template<class T>
26 class Plane3T
27 {
28  public:
29 
30  Vector3T<T> Normal; ///< Normal vector of this plane.
31  T Dist; ///< Distance to origin (0, 0, 0).
32 
33 
34 
35  /// The default constructor.
36  Plane3T() : Normal(0, 0, 0), Dist(0) { }
37 
38  /// An explicit constructor.
39  Plane3T(const Vector3T<T>& Normal_, T Dist_) : Normal(Normal_), Dist(Dist_) { }
40 
41  /// This constructor creates the plane that contains the points ABC in clockwise order.
42  /// The normal vector then points towards the observer (orientation!).
43  /// @param A First point.
44  /// @param B Second point.
45  /// @param C Third point.
46  /// @param Epsilon Tolerance value.
47  /// @throws DivisionByZeroE if cross(C-A, B-A) yields a vector shorter than Epsilon.
48  Plane3T(const Vector3T<T>& A, const Vector3T<T>& B, const Vector3T<T>& C, const T Epsilon)
49  {
50  // Gegenüber einer Herleitung einer solchen Ebene per Hand wurde das Vorzeichen von Dist umgedreht,
51  // da die Ebenengleichung und der Stützvektor sonst weniger schön aussähen (das entspräche der
52  // Ebenengleichung UnitNormal*x+Dist=0 anstatt UnitNormal*x-Dist=0).
53  Normal=normalize(cross(C-A, B-A), Epsilon);
54  Dist =dot(A, Normal);
55  }
56 
57 
58 
59  /// Returns true if the plane is valid, that is, if the normal vector is valid and (roughly) has length 1.
60  bool IsValid() const
61  {
62  if (!Normal.IsValid()) return false;
63 
64  const T len2=Normal.GetLengthSqr();
65 
66  return len2>0.9*0.9 && len2<1.1*1.1;
67  }
68 
69  /// Compute two orthogonal span vectors for this plane.
71  {
72  // Rundungsfehler sind ein echtes Problem:
73  // Zwei Planes mit SEHR ähnlichem Normalenvektor können trotzdem völlig verschiedene Spannvektoren bekommen.
74  // Betrachte z.B. Planes mit Normalenvektor (sqrt(2)/2, sqrt(2)/2, 0).
75  // Minimale Abweichungen können bereits entstehen, wenn der Compiler zur Optimierung floating-point
76  // Zwischenergebnisse in den Registern der FPU stehen läßt (OpenWatcom mit -otexan switches).
77  // Demgegenüber werden Zwischenergebnisse ohne Optimierung (unter Linux/g++ sogar immer) in eine 8-Byte
78  // Speicherzelle geschrieben. Weil FPU Register mehr Bits (d.h. Präzision) haben, kommt es im letzteren
79  // Fall zu einer Rundung, was wiederum dazu führen kann, daß in einem Fall die x-Komponente des obigen
80  // Beispielvektors größer ist als die y-Komponente, und im anderen Fall kleiner.
81  // Das Problem tritt also verschärft mit "45° Planes" auf. Es läßt sich nicht aus der Welt schaffen,
82  // aber wir können es auf andere, ganz selten vorkommende Winkel "verschieben", indem wir nach den
83  // ersten drei Nachkommastellen wie unten gezeigt selbst runden.
84  int max=-99999;
85 
86  int x=int(fabs(Normal.x*1000.0)); if (x>max) { max=x; U=Vector3T<T>(0, 1, 0); }
87  int y=int(fabs(Normal.y*1000.0)); if (y>max) { max=y; U=Vector3T<T>(1, 0, 0); }
88  int z=int(fabs(Normal.z*1000.0)); if (z>max) { max=z; U=Vector3T<T>(1, 0, 0); }
89 
90  // Projeziere U auf die durch den Ursprung verschobene Plane, um sicherzustellen, daß U in ihr liegt.
91  // VectorDot(Normal, U) entspricht PlaneDist(Plane3T(Normal, 0), U).
92  U=normalize(U-scale(Normal, dot(Normal, U)), T(0.0));
93 
94  // Finde V als Orthogonale zu Normal und U. (VectorCross zweier Einheitsvektoren ist wieder ein Einheitsvektor. (sicher?!?) )
95  V=cross(Normal, U);
96  }
97 
98  /// Compute two orthogonal span vectors for this plane, using a different method than GetSpanVectors().
99  /// @param U First span vector.
100  /// @param V Second span vector.
101  /// TODO: Is this method more robust than GetSpanVectors()? See the implementation of GetSpanVectors() to understand the problem.
102  /// That is, does this method *not* produce very different span vectors for very similar planes in some rare cases?
103  /// If I understand atan2() correctly, it's two parameters refer to the Gegenkathete and Ankathete of an orthogonal triangle.
104  /// Thus, atan2() should be able to operate smoothly even if one of its arguments is zero or near-zero.
105  /// This seems to imply the smoothness of its results, and thus the robustness of this method, which solves the problem.
106  /// But what if they are both zero? Does atan2() then yield an error? Or 0? Is its behaviour then consistent across platforms?
108  {
109  // The key idea here is as follows:
110  // Given the X-vector (1 0 0), figure out by which angle it must be rotated around the Y axis and
111  // then by which angle the result must be rotated around the Z axis in order to obtain the Normal vector.
112  // It's actually a simple idea, see my Tech-Archive sheet from 2005-10-20, on which I sketched a figure and derived the stuff below.
113 
114  // Determine the angles of rotation around the Y and Z axes that rotate (1 0 0) to the normal,
115  // that is, Vector3T<T>(1, 0, 0).GetRotY(RadToDeg(RotY)).GetRotZ(RadToDeg(RotZ)) == Normal.
116  // See http://en.wikipedia.org/wiki/Atan2 for an excellent article about the atan2() function.
117  const T RotY=-atan2(Normal.z, sqrt(Normal.y*Normal.y+Normal.x*Normal.x));
118  const T RotZ= atan2(Normal.y, Normal.x);
119 
120  // U=Vector3T<T>(0, 1, 0).GetRotY(RadToDeg(RotY)).GetRotZ(RadToDeg(RotZ));
121  U.x=-sin(RotZ);
122  U.y= cos(RotZ);
123  U.z=0;
124 
125  // V=Vector3T<T>(0, 0, -1).GetRotY(RadToDeg(RotY)).GetRotZ(RadToDeg(RotZ));
126  // We use the negative z-axis here, i.e. the V texture-coord axis is along -Z.
127  // WARNING: Don't change this! It must *always* be the *same* in all programs (CaWE, CaBSP, Cafu, ...).
128  // Removing the "-" signs below would also break the D3 map importer in CaWE.
129  V.x=-sin(RotY)*cos(RotZ);
130  V.y=-sin(RotY)*sin(RotZ);
131  V.z=-cos(RotY);
132  }
133 
134  /// Returns the same, but mirrored plane, i.e. the plane with the reversed orientation.
136  {
137  return Plane3T(-Normal, -Dist);
138  }
139 
140  /// Determines the distance from A to this plane.
141  T GetDistance(const Vector3T<T>& A) const
142  {
143  // Put A into the Hessesche Normalenform of this plane.
144  return dot(Normal, A)-Dist;
145  }
146 
147  /// Intersect the line through A and B with this plane.
148  /// @param A First point of the line.
149  /// @param B Second point of the line.
150  /// @param cosEpsilon If the angle between the planes normal vector and AB is too close to 90 degrees,
151  /// AB roughly parallels the plane. The cosine of this angle is then close to 0. This value defines
152  /// the minimally allowed cosine for which AB is considered not paralleling the plane.
153  /// @throws DivisionByZeroE if AB parallels the plane too much.
154  Vector3T<T> GetIntersection(const Vector3T<T>& A, const Vector3T<T>& B, const T cosEpsilon) const
155  {
156  // Die Geradengleichung x=a+r*(b-a) einfach in die Hessesche Normalenform der Ebene einsetzen und nach r auflösen.
157  // Das erhaltene r wird nun einfach wiederum in die Geradengleichung eingesetzt --> {S}.
158  const Vector3T<T> AB=B-A;
159  const T cosAngle=dot(Normal, AB);
160 
161  if (fabs(cosAngle)<=cosEpsilon) throw DivisionByZeroE();
162 
163  return A + AB*(Dist-dot(Normal, A))/cosAngle;
164  }
165 
166  /// Returns whether this plane and plane P are truly (bit-wise) identical.
167  /// Use this operator with care, as it comes *without* any epsilon threshold for taking rounding errors into account.
168  /// @param P Plane to compare to.
169  bool operator == (const Plane3T& P) const
170  {
171  return Normal==P.Normal && Dist==P.Dist;
172  }
173 
174  /// Returns whether this plane and plane P are not equal (bit-wise).
175  /// Use this operator with care, as it comes *without* any epsilon threshold for taking rounding errors into account.
176  /// @param P Plane to compare to.
177  bool operator != (const Plane3T& P) const
178  {
179  return Normal!=P.Normal || Dist!=P.Dist;
180  }
181 
182 
183  /// Builds the intersection of three planes.
184  /// @param P1 First plane.
185  /// @param P2 Second plane.
186  /// @param P3 Third plane.
187  /// @throws DivisionByZeroE if the intersection of the three planes has not exactly one solution.
188  Vector3T<T> static GetIntersection(const Plane3T& P1, const Plane3T& P2, const Plane3T& P3)
189  {
190  // Ein Gaußsches Gleichungssystem erhält man aus den Koordinatenformen der Ebenen. Hier wird der Nenner genau dann Null,
191  // wenn es keine, unendlich viele bzw. NICHT genau EINE Lösung gibt. Keine Lösung gibt es, wenn mindestens zwei Ebenen
192  // parallel sind und sie nicht ineinanderliegen. Unendlich viele Lösungen gibt es, wenn mindestens zwei Ebenen parallel
193  // sind und sie ineinanderliegen. Lösung nach dem Determinantenverfahren bzw. der Cramerschen Regel.
194  // Siehe Mathematikbuch Analytische Geometrie, Seiten 157 bis 163.
195  const Vector3T<T> A(P1.Normal.x, P2.Normal.x, P3.Normal.x);
196  const Vector3T<T> B(P1.Normal.y, P2.Normal.y, P3.Normal.y);
197  const Vector3T<T> C(P1.Normal.z, P2.Normal.z, P3.Normal.z);
198  const Vector3T<T> D(P1.Dist, P2.Dist, P3.Dist );
199 
200  const T Nenner=dot(cross(A, B), C);
201 
202  if (Nenner==0) throw DivisionByZeroE();
203 
204  return Vector3T<T>(dot(cross(D, B), C)/Nenner,
205  dot(cross(A, D), C)/Nenner,
206  dot(cross(A, B), D)/Nenner);
207  }
208 
209 
210  /// Computes the convex hull of a set of points.
211  /// @param Points The set of points for which the convex hull is computed.
212  /// @param HullPlanes The array in which the convex hull planes are returned.
213  /// @param HullPlanesPIs If non-NULL, in this array are triples of indices into Points returned, where the i-th triple indicates from which points the i-th hull plane was built.
214  /// @param Epsilon In order to fight rounding errors, the "thickness" of a hull plane is assumed to be 2*Epsilon.
215  static void ConvexHull(const ArrayT< Vector3T<T> >& Points, ArrayT<Plane3T>& HullPlanes, ArrayT<unsigned long>* HullPlanesPIs, const T Epsilon);
216 };
217 
218 
219 template<class T> inline std::string convertToString(const Plane3T<T>& P)
220 {
221  const int sigdigits=std::numeric_limits<T>::digits10;
222 
223  std::ostringstream out;
224 
225  out << std::setprecision(sigdigits) << convertToString(P.Normal) << ", " << P.Dist;
226 
227  return out.str();
228 }
229 
230 
231 template<class T> inline std::ostream& operator << (std::ostream& os, const Plane3T<T>& P)
232 {
233  return os << P.Normal << ", " << P.Dist;
234 }
235 
236 
237 /// Typedef for a Vector3T of floats.
238 typedef Plane3T<float> Plane3fT;
239 
240 /// Typedef for a Vector3T of doubles.
241 typedef Plane3T<double> Plane3dT;
242 
243 #endif
This class represents a plane in three-dimensional space.
Definition: Plane3.hpp:26
Vector3T< T > Normal
Normal vector of this plane.
Definition: Plane3.hpp:30
static void ConvexHull(const ArrayT< Vector3T< T > > &Points, ArrayT< Plane3T > &HullPlanes, ArrayT< unsigned long > *HullPlanesPIs, const T Epsilon)
Computes the convex hull of a set of points.
Definition: Plane3.cpp:14
bool operator==(const Plane3T &P) const
Returns whether this plane and plane P are truly (bit-wise) identical.
Definition: Plane3.hpp:169
Plane3T()
The default constructor.
Definition: Plane3.hpp:36
T y
The y-component of this vector.
Definition: Vector3.hpp:41
This class represents a polymorphic 3-dimensional vector.
Definition: Misc.hpp:11
bool IsValid() const
Returns true if the plane is valid, that is, if the normal vector is valid and (roughly) has length 1...
Definition: Plane3.hpp:60
bool operator!=(const Plane3T &P) const
Returns whether this plane and plane P are not equal (bit-wise).
Definition: Plane3.hpp:177
Plane3T(const Vector3T< T > &Normal_, T Dist_)
An explicit constructor.
Definition: Plane3.hpp:39
void GetSpanVectors(Vector3T< T > &U, Vector3T< T > &V) const
Compute two orthogonal span vectors for this plane.
Definition: Plane3.hpp:70
Vector3T< T > GetIntersection(const Vector3T< T > &A, const Vector3T< T > &B, const T cosEpsilon) const
Intersect the line through A and B with this plane.
Definition: Plane3.hpp:154
Plane3T(const Vector3T< T > &A, const Vector3T< T > &B, const Vector3T< T > &C, const T Epsilon)
This constructor creates the plane that contains the points ABC in clockwise order.
Definition: Plane3.hpp:48
T GetDistance(const Vector3T< T > &A) const
Determines the distance from A to this plane.
Definition: Plane3.hpp:141
Plane3T GetMirror() const
Returns the same, but mirrored plane, i.e. the plane with the reversed orientation.
Definition: Plane3.hpp:135
void GetSpanVectorsByRotation(Vector3T< T > &U, Vector3T< T > &V) const
Compute two orthogonal span vectors for this plane, using a different method than GetSpanVectors()...
Definition: Plane3.hpp:107
T z
The z-component of this vector.
Definition: Vector3.hpp:42
T x
The x-component of this vector.
Definition: Vector3.hpp:40
T Dist
Distance to origin (0, 0, 0).
Definition: Plane3.hpp:31
static Vector3T< T > GetIntersection(const Plane3T &P1, const Plane3T &P2, const Plane3T &P3)
Builds the intersection of three planes.
Definition: Plane3.hpp:188
Definition: Renderer.hpp:16
Division by zero error.
Definition: Errors.hpp:24