1 /* Copyright 2011 Jukka Jyl�nki
2
3    Licensed under the Apache License, Version 2.0 (the "License");
4    you may not use this file except in compliance with the License.
5    You may obtain a copy of the License at
6
7        http://www.apache.org/licenses/LICENSE-2.0
8
9    Unless required by applicable law or agreed to in writing, software
10    distributed under the License is distributed on an "AS IS" BASIS,
11    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12    See the License for the specific language governing permissions and
13    limitations under the License. */
14
15 /** @file Plane.cpp
16         @author Jukka Jyl�nki
17         @brief Implementation for the Plane geometry object. */
18 #include "Math/MathFunc.h"
19 #include "Math/Polynomial.h"
20 #include "Geometry/AABB.h"
21 #include "Geometry/Circle.h"
22 #include "Geometry/Plane.h"
23 #include "Geometry/Line.h"
24 #include "Geometry/OBB.h"
25 #include "Geometry/Polygon.h"
26 #include "Geometry/Polyhedron.h"
27 #include "Geometry/Ray.h"
28 #include "Geometry/Capsule.h"
29 #include "Geometry/Sphere.h"
30 #include "Geometry/Triangle.h"
31 #include "Geometry/LineSegment.h"
32 #include "Math/float3x3.h"
33 #include "Math/float3x4.h"
34 #include "Math/float4.h"
35 #include "Math/Quat.h"
36 #include "Geometry/Frustum.h"
37
38 MATH_BEGIN_NAMESPACE
39
40 Plane::Plane(const float3 &normal_, float d_)
41 :normal(normal_), d(d_)
42 {
43         assume(normal.IsNormalized());
44 }
45
46 Plane::Plane(const float3 &v1, const float3 &v2, const float3 &v3)
47 {
48         Set(v1, v2, v3);
49 }
50
51 Plane::Plane(const float3 &point, const float3 &normal_)
52 {
53         Set(point, normal_);
54 }
55
56 Plane::Plane(const Ray &ray, const float3 &normal)
57 {
58         float3 perpNormal = normal - normal.ProjectToNorm(ray.dir);
59         Set(ray.pos, perpNormal.Normalized());
60 }
61
62 Plane::Plane(const Line &line, const float3 &normal)
63 {
64         float3 perpNormal = normal - normal.ProjectToNorm(line.dir);
65         Set(line.pos, perpNormal.Normalized());
66 }
67
68 Plane::Plane(const LineSegment &lineSegment, const float3 &normal)
69 {
70         float3 perpNormal = normal - normal.ProjectTo(lineSegment.b - lineSegment.a);
71         Set(lineSegment.a, perpNormal.Normalized());
72 }
73
74 void Plane::Set(const float3 &v1, const float3 &v2, const float3 &v3)
75 {
76         normal = (v2-v1).Cross(v3-v1);
77         assume(!normal.IsZero());
78         normal.Normalize();
79         d = Dot(v1, normal);
80
81         assume(EqualAbs(SignedDistance(v1), 0.f));
82         assume(EqualAbs(SignedDistance(v2), 0.f));
83         assume(EqualAbs(SignedDistance(v3), 0.f));
84         assume(EqualAbs(SignedDistance(v3 + normal), 1.f));
85 }
86
87 void Plane::Set(const float3 &point, const float3 &normal_)
88 {
89         normal = normal_;
90         assume(normal.IsNormalized());
91         d = Dot(point, normal);
92
93         assume(EqualAbs(SignedDistance(point), 0.f));
94         assume(EqualAbs(SignedDistance(point + normal_), 1.f));
95 }
96
97 void Plane::ReverseNormal()
98 {
99         normal = -normal;
100         d = -d;
101 }
102
103 float3 Plane::PointOnPlane() const
104 {
105         return normal * d;
106 }
107
108 void Plane::Transform(const float3x3 &transform)
109 {
110         float3x3 it = transform.InverseTransposed(); ///@todo Could optimize the inverse here by assuming orthogonality or orthonormality.
111         normal = it * normal;
112 }
113
114 /// For Plane-float3x4 transform code, see Eric Lengyel's Mathematics for 3D Game Programming And Computer Graphics 2nd ed., p.110, chapter 4.2.3. [groupSyntax]
115 void Plane::Transform(const float3x4 &transform)
116 {
117         ///@todo Could optimize this function by switching to plane convention ax+by+cz+d=0 instead of ax+by+cz=d.
118         float3x3 r = transform.Float3x3Part();
119         bool success = r.Inverse(); ///@todo Can optimize the inverse here by assuming orthogonality or orthonormality.
120         assume(success);
121         d = d + Dot(normal, r * transform.TranslatePart());
122         normal = normal * r;
123 }
124
125 void Plane::Transform(const float4x4 &transform)
126 {
127         assume(transform.Row(3).Equals(float4(0,0,0,1)));
128         Transform(transform.Float3x4Part());
129 }
130
131 void Plane::Transform(const Quat &transform)
132 {
133         float3x3 r = transform.ToFloat3x3();
134         Transform(r);
135 }
136
137 bool Plane::IsInPositiveDirection(const float3 &directionVector) const
138 {
139         return normal.Dot(directionVector) >= 0.f;
140 }
141
142 bool Plane::IsOnPositiveSide(const float3 &point) const
143 {
144         return SignedDistance(point) >= 0.f;
145 }
146
147 int Plane::ExamineSide(const Triangle &triangle) const
148 {
149         float a = SignedDistance(triangle.a);
150         float b = SignedDistance(triangle.b);
151         float c = SignedDistance(triangle.c);
152         const float epsilon = 1e-4f; // Allow a small epsilon amount for tests for floating point inaccuracies.
153         if (a >= -epsilon && b >= -epsilon && c >= -epsilon)
154                 return 1;
155         if (a <= epsilon && b <= epsilon && c <= epsilon)
156                 return -1;
157         return 0;
158 }
159
160 bool Plane::AreOnSameSide(const float3 &p1, const float3 &p2) const
161 {
162         return SignedDistance(p1) * SignedDistance(p2) >= 0.f;
163 }
164
165 float Plane::Distance(const float3 &point) const
166 {
167         return Abs(SignedDistance(point));
168 }
169
170 float Plane::Distance(const LineSegment &lineSegment) const
171 {
172         return lineSegment.Distance(*this);
173 }
174
175 float Plane::Distance(const Sphere &sphere) const
176 {
177         return Max(0.f, Distance(sphere.pos) - sphere.r);
178 }
179
180 float Plane::Distance(const Capsule &capsule) const
181 {
182         return Max(0.f, Distance(capsule.l) - capsule.r);
183 }
184
185 float Plane::SignedDistance(const float3 &point) const
186 {
187         return normal.Dot(point) - d;
188 }
189
190 float3x4 Plane::OrthoProjection() const
191 {
192         return float3x4::OrthographicProjection(*this);
193 }
194
195 float3x4 Plane::ObliqueProjection(const float3 &obliqueProjectionDir) const
196 {
197         assume(false && "Not implemented!"); /// @todo Implement.
198         return float3x4();
199 }
200
201 float3x4 Plane::MirrorMatrix() const
202 {
203         return float3x4::Mirror(*this);
204 }
205
206 float3 Plane::Mirror(const float3 &point) const
207 {
208         assume(normal.IsNormalized());
209         float3 reflected = point - 2.f * (Dot(point, normal) + d) * normal;
210         assume(reflected.Equals(MirrorMatrix().MulPos(point)));
211         return reflected;
212 }
213
214 float3 Plane::Refract(const float3 &vec, float negativeSideRefractionIndex, float positiveSideRefractionIndex) const
215 {
216         return float3(vec).Refract(normal, negativeSideRefractionIndex, positiveSideRefractionIndex);
217 }
218
219 float3 Plane::Project(const float3 &point) const
220 {
221         float3 projected = point - (Dot(normal, point) - d) * normal;
222         assume(projected.Equals(OrthoProjection().MulPos(point)));
223         return projected;
224 }
225
226 LineSegment Plane::Project(const LineSegment &lineSegment) const
227 {
228         return LineSegment(Project(lineSegment.a), Project(lineSegment.b));
229 }
230
231 Line Plane::Project(const Line &line, bool *nonDegenerate) const
232 {
233         Line l;
234         l.pos = Project(line.pos);
235         l.dir = l.dir - l.dir.ProjectToNorm(normal);
236         float len = l.dir.Normalize();
237         if (nonDegenerate)
238                 *nonDegenerate = (len > 0.f);
239         return l;
240 }
241
242 Ray Plane::Project(const Ray &ray, bool *nonDegenerate) const
243 {
244         Ray r;
245         r.pos = Project(ray.pos);
246         r.dir = r.dir - r.dir.ProjectToNorm(normal);
247         float len = r.dir.Normalize();
248         if (nonDegenerate)
249                 *nonDegenerate = (len > 0.f);
250         return r;
251 }
252
253 Triangle Plane::Project(const Triangle &triangle) const
254 {
255         Triangle t;
256         t.a = Project(triangle.a);
257         t.b = Project(triangle.b);
258         t.c = Project(triangle.c);
259         return t;
260 }
261
262 Polygon Plane::Project(const Polygon &polygon) const
263 {
264         Polygon p;
265         for(size_t i = 0; i < polygon.p.size(); ++i)
266                 p.p.push_back(Project(polygon.p[i]));
267
268         return p;
269 }
270
271 float3 Plane::ClosestPoint(const Ray &ray) const
272 {
273         ///@todo Output parametric d as well.
274         float d;
275         if (ray.Intersects(*this, &d))
276                 return ray.GetPoint(d);
277         else
278                 return Project(ray.pos);
279 }
280
281 float3 Plane::ClosestPoint(const LineSegment &lineSegment) const
282 {
283         ///@todo Output parametric d as well.
284         float d;
285         if (lineSegment.Intersects(*this, &d))
286                 return lineSegment.GetPoint(d);
287         else
288                 if (Distance(lineSegment.a) < Distance(lineSegment.b))
289                         return Project(lineSegment.a);
290                 else
291                         return Project(lineSegment.b);
292 }
293
294 float3 Plane::ObliqueProject(const float3 &point, const float3 &obliqueProjectionDir) const
295 {
296         assume(false && "Not implemented!"); /// @todo Implement.
297         return float3();
298 }
299
300 bool Plane::Contains(const float3 &point, float distanceThreshold) const
301 {
302         return Distance(point) <= distanceThreshold;
303 }
304
305 bool Plane::Contains(const Line &line, float epsilon) const
306 {
307         return Contains(line.pos) && line.dir.IsPerpendicular(normal, epsilon);
308 }
309
310 bool Plane::Contains(const Ray &ray, float epsilon) const
311 {
312         return Contains(ray.pos) && ray.dir.IsPerpendicular(normal, epsilon);
313 }
314
315 bool Plane::Contains(const LineSegment &lineSegment, float epsilon) const
316 {
317         return Contains(lineSegment.a, epsilon) && Contains(lineSegment.b, epsilon);
318 }
319
320 bool Plane::Contains(const Triangle &triangle, float epsilon) const
321 {
322         return Contains(triangle.a, epsilon) && Contains(triangle.b, epsilon) && Contains(triangle.c, epsilon);
323 }
324
325 bool Plane::Contains(const Circle &circle, float epsilon) const
326 {
327         return Contains(circle.pos, epsilon) && (EqualAbs(Abs(Dot(normal, circle.normal)), 1.f) || circle.r <= epsilon);
328 }
329
330 bool Plane::Contains(const Polygon &polygon, float epsilon) const
331 {
332         switch(polygon.NumVertices())
333         {
334         case 0: assume(false && "Plane::Contains(Polygon) called with a degenerate polygon of 0 vertices!"); return false;
335         case 1: return Contains(polygon.Vertex(0), epsilon);
336         case 2: return Contains(polygon.Vertex(0), epsilon) && Contains(polygon.Vertex(1), epsilon);
337         default:
338                 return SetEquals(polygon.PlaneCCW(), epsilon);
339           }
340 }
341
342 bool Plane::SetEquals(const Plane &plane, float epsilon) const
343 {
344         return (normal.Equals(plane.normal) && EqualAbs(d, plane.d, epsilon)) ||
345                 (normal.Equals(-plane.normal) && EqualAbs(-d, plane.d, epsilon));
346 }
347
348 bool Plane::Equals(const Plane &other, float epsilon) const
349 {
350         return IsParallel(other, epsilon) && EqualAbs(d, other.d, epsilon);
351 }
352
353 bool Plane::Intersects(const Plane &plane, Line *outLine) const
354 {
355         float3 perp = Cross(normal, plane.normal);
356
357         float3x3 m;
358         m.SetRow(0, normal);
359         m.SetRow(1, plane.normal);
360         m.SetRow(2, perp); // This is arbitrarily chosen, to produce m invertible.
361         bool success = m.Inverse();
362         if (!success) // Inverse failed, so the planes must be parallel.
363         {
364                 if (EqualAbs(d, plane.d)) // The planes are equal?
365                 {
366                         if (outLine)
367                                 *outLine = Line(plane.PointOnPlane(), plane.normal.Perpendicular());
368                         return true;
369                 }
370                 else
371                         return false;
372         }
373         if (outLine)
374                 *outLine = Line(m * float3(d, plane.d, 0.f), perp.Normalized());
375         return true;
376 }
377
378 bool Plane::Intersects(const Plane &plane, const Plane &plane2, Line *outLine, float3 *outPoint) const
379 {
380         Line dummy;
381         if (!outLine)
382                 outLine = &dummy;
383
384         // First check all planes for parallel pairs.
385         if (this->IsParallel(plane) || this->IsParallel(plane2))
386         {
387                 if (EqualAbs(d, plane.d) || EqualAbs(d, plane2.d))
388                 {
389                         bool intersect = plane.Intersects(plane2, outLine);
390                         if (intersect && outPoint)
391                                 *outPoint = outLine->GetPoint(0);
392                         return intersect;
393                 }
394                 else
395                         return false;
396         }
397         if (plane.IsParallel(plane2))
398         {
399                 if (EqualAbs(plane.d, plane2.d))
400                 {
401                         bool intersect = this->Intersects(plane, outLine);
402                         if (intersect && outPoint)
403                                 *outPoint = outLine->GetPoint(0);
404                         return intersect;
405                 }
406                 else
407                         return false;
408         }
409
410         // All planes point to different directions.
411         float3x3 m;
412         m.SetRow(0, normal);
413         m.SetRow(1, plane.normal);
414         m.SetRow(2, plane2.normal);
415         bool success = m.Inverse();
416         if (!success)
417                 return false;
418         if (outPoint)
419                 *outPoint = m * float3(d, plane.d, plane2.d);
420         return true;
421 }
422
423 bool Plane::Intersects(const Polygon &polygon) const
424 {
425         return polygon.Intersects(*this);
426 }
427
428 /// Computes the intersection of a line and a plane.
429 /// @param ptOnPlane An arbitrary point on the plane.
430 /// @param planeNormal The plane normal direction vector, which must be normalized.
431 /// @param lineStart The starting point of the line.
432 /// @param lineDir The line direction vector. This vector does not need to be normalized.
433 /// @param t [out] If this function returns true, this parameter will receive the distance along the line where intersection occurs.
434 ///                             That is, the point lineStart + t * lineDir will be the intersection point.
435 /// @return If an intersection occurs, this function returns true.
436 bool IntersectLinePlane(const float3 &ptOnPlane, const float3 &planeNormal, const float3 &lineStart, const float3 &lineDir, float *t)
437 {
438         float denom = Dot(lineDir, planeNormal);
439         if (EqualAbs(denom, 0.f))
440                 return false// Either we have no intersection, or the whole line is on the plane. @todo distinguish these cases.
441         if (t)
442                 *t = Dot(ptOnPlane - lineStart, planeNormal);
443         return true;
444 }
445
446 bool Plane::Intersects(const Ray &ray, float *d) const
447 {
448         float t;
449         bool success = IntersectLinePlane(PointOnPlane(), normal, ray.pos, ray.dir, &t);
450         if (d)
451                 *d = t;
452         return success && t >= 0.f;
453 }
454
455 bool Plane::Intersects(const Line &line, float *d) const
456 {
457         return IntersectLinePlane(PointOnPlane(), normal, line.pos, line.dir, d);
458 }
459
460 bool Plane::Intersects(const LineSegment &lineSegment, float *d) const
461 {
462         float t;
463         bool success = IntersectLinePlane(PointOnPlane(), normal, lineSegment.a, lineSegment.Dir(), &t);
464         const float lineSegmentLength = lineSegment.Length();
465         if (d)
466                 *d = t / lineSegmentLength;
467         return success && t >= 0.f && t <= lineSegmentLength;
468 }
469
470 bool Plane::Intersects(const Sphere &sphere) const
471 {
472         return Distance(sphere.pos) <= sphere.r;
473 }
474
475 bool Plane::Intersects(const Capsule &capsule) const
476 {
477         return capsule.Intersects(*this);
478 }
479
480 /// The Plane-AABB intersection is implemented according to Christer Ericson's Real-Time Collision Detection, p.164. [groupSyntax]
481 bool Plane::Intersects(const AABB &aabb) const
482 {
483         float3 c = aabb.CenterPoint();
484         float3 e = aabb.HalfDiagonal();
485
486         // Compute the projection interval radius of the AABB onto L(t) = aabb.center + t * plane.normal;
487         float r = e[0]*Abs(normal[0]) + e[1]*Abs(normal[1]) + e[2]*Abs(normal[2]);
488         // Compute the distance of the box center from plane.
489         float s = Dot(normal, c) - d;
490         return Abs(s) <= r;
491 }
492
493 bool Plane::Intersects(const OBB &obb) const
494 {
495         return obb.Intersects(*this);
496 }
497
498 bool Plane::Intersects(const Triangle &triangle) const
499 {
500         float a = SignedDistance(triangle.a);
501         float b = SignedDistance(triangle.b);
502         float c = SignedDistance(triangle.c);
503         return (a*b <= 0.f || a*c <= 0.f);
504 }
505
506 bool Plane::Intersects(const Frustum &frustum) const
507 {
508         bool sign = IsOnPositiveSide(frustum.CornerPoint(0));
509         for(int i = 1; i < 8; ++i)
510                 if (sign != IsOnPositiveSide(frustum.CornerPoint(i)))
511                         return true;
512         return false;
513 }
514
515 bool Plane::Intersects(const Polyhedron &polyhedron) const
516 {
517         if (polyhedron.NumVertices() == 0)
518                 return false;
519         bool sign = IsOnPositiveSide(polyhedron.Vertex(0));
520         for(int i = 1; i < polyhedron.NumVertices(); ++i)
521                 if (sign != IsOnPositiveSide(polyhedron.Vertex(i)))
522                         return true;
523         return false;
524 }
525
526 int Plane::Intersects(const Circle &circle, float3 *pt1, float3 *pt2) const
527 {
528         Line line;
529         bool planeIntersects = Intersects(circle.ContainingPlane(), &line);
530         if (!planeIntersects)
531                 return false;
532
533         // Offset both line and circle position so the circle origin is at center.
534         line.pos -= circle.pos;
535
536         float a = 1.f;
537         float b = 2.f * Dot(line.pos, line.dir);
538         float c = line.pos.LengthSq() - circle.r * circle.r;
539         float r1, r2;
540         int numRoots = Polynomial::SolveQuadratic(a, b, c, r1, r2);
541         if (numRoots >= 1 && pt1)
542                 *pt1 = circle.pos + line.GetPoint(r1);
543         if (numRoots >= 2 && pt2)
544                 *pt2 = circle.pos + line.GetPoint(r2);
545         return numRoots;
546 }
547
548 int Plane::Intersects(const Circle &circle) const
549 {
550         return Intersects(circle, 0, 0);
551 }
552
553 bool Plane::Clip(float3 &a, float3 &b) const
554 {
555         float t;
556         bool intersects = IntersectLinePlane(PointOnPlane(), normal, a, b-a, &t);
557         if (!intersects || t <= 0.f || t >= 1.f)
558         {
559                 if (SignedDistance(a) <= 0.f)
560                         return false// Discard the whole line segment, it's completely behind the plane.
561                 else
562                         return true// The whole line segment is in the positive halfspace. Keep all of it.
563         }
564         float3 pt = a + (b-a) * t; // The intersection point.
565         // We are either interested in the line segment [a, pt] or the segment [pt, b]. Which one is in the positive side?
566         if (IsOnPositiveSide(a))
567                 b = pt;
568         else
569                 a = pt;
570
571         return true;
572 }
573
574 bool Plane::Clip(LineSegment &line) const
575 {
576         return Clip(line.a, line.b);
577 }
578
579 int Plane::Clip(const Line &line, Ray &outRay) const
580 {
581         float t;
582         bool intersects = IntersectLinePlane(PointOnPlane(), normal, line.pos, line.dir, &t);
583         if (!intersects)
584         {
585                 if (SignedDistance(line.pos) <= 0.f)
586                         return 0; // Discard the whole line, it's completely behind the plane.
587                 else
588                         return 2; // The whole line is in the positive halfspace. Keep all of it.
589         }
590
591         outRay.pos = line.pos + line.dir * t; // The intersection point
592         if (Dot(line.dir, normal) >= 0.f)
593                 outRay.dir = line.dir;
594         else
595                 outRay.dir = -line.dir;
596
597         return 1; // Clipping resulted in a ray being generated.
598 }
599
600 int Plane::Clip(const Triangle &triangle, Triangle &t1, Triangle &t2) const
601 {
602         bool side[3];
603         side[0] = IsOnPositiveSide(triangle.a);
604         side[1] = IsOnPositiveSide(triangle.b);
605         side[2] = IsOnPositiveSide(triangle.c);
606         int nPos = (side[0] ? 1 : 0) + (side[1] ? 1 : 0) + (side[2] ? 1 : 0);
607         if (nPos == 0) // Everything should be clipped?
608                 return 0;
609         // We will output at least one triangle, so copy the input to t1 for processing.
610         t1 = triangle;
611
612         if (nPos == 3) // All vertices of the triangle are in positive side?
613                 return 1;
614
615         if (nPos == 1)
616         {
617                 if (side[1])
618                 {
619                         float3 tmp = t1.a;
620                         t1.a = t1.b;
621                         t1.b = t1.c;
622                         t1.c = tmp;
623                 }
624                 else if (side[2])
625                 {
626                         float3 tmp = t1.a;
627                         t1.a = t1.c;
628                         t1.c = t1.b;
629                         t1.b = tmp;
630                 }
631
632                 // After the above cycling, t1.a is the triangle on the positive side.
633                 float t;
634                 Intersects(LineSegment(t1.a, t1.b), &t);
635                 t1.b = t1.a + (t1.b-t1.a)*t;
636                 Intersects(LineSegment(t1.a, t1.c), &t);
637                 t1.c = t1.a + (t1.c-t1.a)*t;
638                 return 1;
639         }
640         // Must be nPos == 2.
641         if (!side[1])
642         {
643                 float3 tmp = t1.a;
644                 t1.a = t1.b;
645                 t1.b = t1.c;
646                 t1.c = tmp;
647         }
648         else if (!side[2])
649         {
650                 float3 tmp = t1.a;
651                 t1.a = t1.c;
652                 t1.c = t1.b;
653                 t1.b = tmp;
654         }
655         // After the above cycling, t1.a is the triangle on the negative side.
656
657         float t, r;
658         Intersects(LineSegment(t1.a, t1.b), &t);
659         float3 ab = t1.a + (t1.b-t1.a)*t;
660         Intersects(LineSegment(t1.a, t1.c), &r);
661         float3 ac = t1.a + (t1.c-t1.a)*t;
662         t1.a = ab;
663
664         t2.a = t1.c;
665         t2.b = ac;
666         t2.c = ab;
667
668         return 2;
669 }
670
671 bool Plane::IsParallel(const Plane &plane, float epsilon) const
672 {
673         return normal.Equals(plane.normal, epsilon);
674 }
675
676 bool Plane::PassesThroughOrigin(float epsilon) const
677 {
678         return fabs(d) <= epsilon;
679 }
680
681 float Plane::DihedralAngle(const Plane &plane) const
682 {
683         assume(false && "Not implemented!"); /// @todo Implement.
684         return false;
685 }
686
687 Circle Plane::GenerateCircle(const float3 &circleCenter, float radius) const
688 {
689         assume(false && "Not implemented!"); /// @todo Implement.
690         return Circle();
691 }
692
693 Plane operator *(const float3x3 &transform, const Plane &plane)
694 {
695         Plane p(plane);
696         p.Transform(transform);
697         return p;
698 }
699
700 Plane operator *(const float3x4 &transform, const Plane &plane)
701 {
702         Plane p(plane);
703         p.Transform(transform);
704         return p;
705 }
706
707 Plane operator *(const float4x4 &transform, const Plane &plane)
708 {
709         Plane p(plane);
710         p.Transform(transform);
711         return p;
712 }
713
714 Plane operator *(const Quat &transform, const Plane &plane)
715 {
716         Plane p(plane);
717         p.Transform(transform);
718         return p;
719 }
720
721 #ifdef MATH_ENABLE_STL_SUPPORT
722 std::string Plane::ToString() const
723 {
724         char str[256];
725         sprintf(str, "Plane(Normal:(%.2f, %.2f, %.2f) d:%.2f)", normal.x, normal.y, normal.z, d);
726         return str;
727 }
728 #endif
729
730 MATH_END_NAMESPACE

Go back to previous page