]> git.xonotic.org Git - voretournament/voretournament.git/blob - misc/source/netradiant-src/libs/math/frustum.h
Rename mediasource to source
[voretournament/voretournament.git] / misc / source / netradiant-src / libs / math / frustum.h
1 /*
2 Copyright (C) 2001-2006, William Joseph.
3 All Rights Reserved.
4
5 This file is part of GtkRadiant.
6
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 */
21
22 #if !defined(INCLUDED_MATH_FRUSTUM_H)
23 #define INCLUDED_MATH_FRUSTUM_H
24
25 /// \file
26 /// \brief View-frustum data types and related operations.
27
28 #include "generic/enumeration.h"
29 #include "math/matrix.h"
30 #include "math/plane.h"
31 #include "math/aabb.h"
32 #include "math/line.h"
33
34 inline Matrix4 matrix4_frustum(float left, float right, float bottom, float top, float nearval, float farval)
35 {
36   return Matrix4(
37     static_cast<float>( (2*nearval) / (right-left) ),
38     0,
39     0,
40     0,
41     0,
42     static_cast<float>( (2*nearval) / (top-bottom) ),
43     0,
44     0,
45     static_cast<float>( (right+left) / (right-left) ),
46     static_cast<float>( (top+bottom) / (top-bottom) ),
47     static_cast<float>( -(farval+nearval) / (farval-nearval) ),
48     -1,
49     0,
50     0,
51     static_cast<float>( -(2*farval*nearval) / (farval-nearval) ),
52     0
53   );
54 }
55
56
57
58 typedef unsigned char ClipResult;
59 const ClipResult c_CLIP_PASS = 0x00; // 000000
60 const ClipResult c_CLIP_LT_X = 0x01; // 000001
61 const ClipResult c_CLIP_GT_X = 0x02; // 000010
62 const ClipResult c_CLIP_LT_Y = 0x04; // 000100
63 const ClipResult c_CLIP_GT_Y = 0x08; // 001000
64 const ClipResult c_CLIP_LT_Z = 0x10; // 010000
65 const ClipResult c_CLIP_GT_Z = 0x20; // 100000
66 const ClipResult c_CLIP_FAIL = 0x3F; // 111111
67
68 template<typename Index>
69 class Vector4ClipLT
70 {
71 public:
72   static bool compare(const Vector4& self)
73   {
74     return self[Index::VALUE] < self[3];
75   }
76   static double scale(const Vector4& self, const Vector4& other)
77   {
78     return (self[Index::VALUE] - self[3]) / (other[3] - other[Index::VALUE]);
79   }
80 };
81
82 template<typename Index>
83 class Vector4ClipGT
84 {
85 public:
86   static bool compare(const Vector4& self)
87   {
88     return self[Index::VALUE] > -self[3];
89   }
90   static double scale(const Vector4& self, const Vector4& other)
91   {
92     return (self[Index::VALUE] + self[3]) / (-other[3] - other[Index::VALUE]);
93   }
94 };
95
96 template<typename ClipPlane>
97 class Vector4ClipPolygon
98 {
99 public:
100   typedef Vector4* iterator;
101   typedef const Vector4* const_iterator;
102
103   static std::size_t apply(const_iterator first, const_iterator last, iterator out)
104   {
105     const_iterator next = first, i = last - 1;
106     iterator tmp(out);
107     bool b0 = ClipPlane::compare(*i);
108     while(next != last)
109     {
110       bool b1 = ClipPlane::compare(*next);
111       if(b0 ^ b1)
112       {
113         *out = vector4_subtracted(*next, *i);
114
115         double scale = ClipPlane::scale(*i, *out);
116
117         (*out)[0] = static_cast<float>((*i)[0] + scale*((*out)[0]));
118         (*out)[1] = static_cast<float>((*i)[1] + scale*((*out)[1]));
119         (*out)[2] = static_cast<float>((*i)[2] + scale*((*out)[2]));
120         (*out)[3] = static_cast<float>((*i)[3] + scale*((*out)[3]));
121
122         ++out;
123       }
124
125       if(b1)
126       {
127         *out = *next;
128         ++out;
129       }
130
131       i = next;
132       ++next;
133       b0 = b1;
134     }
135
136     return out - tmp;
137   }
138 };
139
140 #define CLIP_X_LT_W(p) (Vector4ClipLT< IntegralConstant<0> >::compare(p))
141 #define CLIP_X_GT_W(p) (Vector4ClipGT< IntegralConstant<0> >::compare(p))
142 #define CLIP_Y_LT_W(p) (Vector4ClipLT< IntegralConstant<1> >::compare(p))
143 #define CLIP_Y_GT_W(p) (Vector4ClipGT< IntegralConstant<1> >::compare(p))
144 #define CLIP_Z_LT_W(p) (Vector4ClipLT< IntegralConstant<2> >::compare(p))
145 #define CLIP_Z_GT_W(p) (Vector4ClipGT< IntegralConstant<2> >::compare(p))
146
147 inline ClipResult homogenous_clip_point(const Vector4& clipped)
148 {
149   ClipResult result = c_CLIP_FAIL;
150   if(CLIP_X_LT_W(clipped)) result &= ~c_CLIP_LT_X; // X < W
151   if(CLIP_X_GT_W(clipped)) result &= ~c_CLIP_GT_X; // X > -W
152   if(CLIP_Y_LT_W(clipped)) result &= ~c_CLIP_LT_Y; // Y < W
153   if(CLIP_Y_GT_W(clipped)) result &= ~c_CLIP_GT_Y; // Y > -W
154   if(CLIP_Z_LT_W(clipped)) result &= ~c_CLIP_LT_Z; // Z < W
155   if(CLIP_Z_GT_W(clipped)) result &= ~c_CLIP_GT_Z; // Z > -W
156   return result;
157 }
158
159 /// \brief Clips \p point by canonical matrix \p self.
160 /// Stores the result in \p clipped.
161 /// Returns a bitmask indicating which clip-planes the point was outside.
162 inline ClipResult matrix4_clip_point(const Matrix4& self, const Vector3& point, Vector4& clipped)
163 {
164   clipped[0] = point[0];
165   clipped[1] = point[1];
166   clipped[2] = point[2];
167   clipped[3] = 1;
168   matrix4_transform_vector4(self, clipped);
169   return homogenous_clip_point(clipped);
170 }
171
172
173 inline std::size_t homogenous_clip_triangle(Vector4 clipped[9])
174 {
175   Vector4 buffer[9];
176   std::size_t count = 3;
177   count = Vector4ClipPolygon< Vector4ClipLT< IntegralConstant<0> > >::apply(clipped, clipped + count, buffer);
178   count = Vector4ClipPolygon< Vector4ClipGT< IntegralConstant<0> > >::apply(buffer, buffer + count, clipped);
179   count = Vector4ClipPolygon< Vector4ClipLT< IntegralConstant<1> > >::apply(clipped, clipped + count, buffer);
180   count = Vector4ClipPolygon< Vector4ClipGT< IntegralConstant<1> > >::apply(buffer, buffer + count, clipped);
181   count = Vector4ClipPolygon< Vector4ClipLT< IntegralConstant<2> > >::apply(clipped, clipped + count, buffer);
182   return Vector4ClipPolygon< Vector4ClipGT< IntegralConstant<2> > >::apply(buffer, buffer + count, clipped);
183 }
184
185 /// \brief Transforms and clips the triangle formed by \p p0, \p p1, \p p2 by the canonical matrix \p self.
186 /// Stores the resulting polygon in \p clipped.
187 /// Returns the number of points in the resulting polygon.
188 inline std::size_t matrix4_clip_triangle(const Matrix4& self, const Vector3& p0, const Vector3& p1, const Vector3& p2, Vector4 clipped[9])
189 {
190   clipped[0][0] = p0[0];
191   clipped[0][1] = p0[1];
192   clipped[0][2] = p0[2];
193   clipped[0][3] = 1;
194   clipped[1][0] = p1[0];
195   clipped[1][1] = p1[1];
196   clipped[1][2] = p1[2];
197   clipped[1][3] = 1;
198   clipped[2][0] = p2[0];
199   clipped[2][1] = p2[1];
200   clipped[2][2] = p2[2];
201   clipped[2][3] = 1;
202
203   matrix4_transform_vector4(self, clipped[0]);
204   matrix4_transform_vector4(self, clipped[1]);
205   matrix4_transform_vector4(self, clipped[2]);
206
207   return homogenous_clip_triangle(clipped);
208 }
209
210 inline std::size_t homogenous_clip_line(Vector4 clipped[2])
211 {
212   const Vector4& p0 = clipped[0];
213   const Vector4& p1 = clipped[1];
214
215   // early out
216   {
217     ClipResult mask0 = homogenous_clip_point(clipped[0]);
218     ClipResult mask1 = homogenous_clip_point(clipped[1]);
219
220     if((mask0 | mask1) == c_CLIP_PASS) // both points passed all planes
221       return 2;
222
223     if(mask0 & mask1) // both points failed any one plane
224       return 0;
225   }
226
227   {
228     const bool index = CLIP_X_LT_W(p0);
229     if(index ^ CLIP_X_LT_W(p1))
230     {
231       Vector4 clip(vector4_subtracted(p1, p0));
232
233       double scale = (p0[0] - p0[3]) / (clip[3] - clip[0]);
234
235       clip[0] = static_cast<float>(p0[0] + scale * clip[0]);
236       clip[1] = static_cast<float>(p0[1] + scale * clip[1]);
237       clip[2] = static_cast<float>(p0[2] + scale * clip[2]);
238       clip[3] = static_cast<float>(p0[3] + scale * clip[3]);
239
240       clipped[index] = clip;
241     }
242     else if(index == 0)
243       return 0;
244   }
245
246   {
247     const bool index = CLIP_X_GT_W(p0);
248     if(index ^ CLIP_X_GT_W(p1))
249     {
250       Vector4 clip(vector4_subtracted(p1, p0));
251
252       double scale = (p0[0] + p0[3]) / (-clip[3] - clip[0]);
253
254       clip[0] = static_cast<float>(p0[0] + scale * clip[0]);
255       clip[1] = static_cast<float>(p0[1] + scale * clip[1]);
256       clip[2] = static_cast<float>(p0[2] + scale * clip[2]);
257       clip[3] = static_cast<float>(p0[3] + scale * clip[3]);
258
259       clipped[index] = clip;
260     }
261     else if(index == 0)
262       return 0;
263   }
264
265   {
266     const bool index = CLIP_Y_LT_W(p0);
267     if(index ^ CLIP_Y_LT_W(p1))
268     {
269       Vector4 clip(vector4_subtracted(p1, p0));
270
271       double scale = (p0[1] - p0[3]) / (clip[3] - clip[1]);
272
273       clip[0] = static_cast<float>(p0[0] + scale * clip[0]);
274       clip[1] = static_cast<float>(p0[1] + scale * clip[1]);
275       clip[2] = static_cast<float>(p0[2] + scale * clip[2]);
276       clip[3] = static_cast<float>(p0[3] + scale * clip[3]);
277
278       clipped[index] = clip;
279     }
280     else if(index == 0)
281       return 0;
282   }
283
284   {
285     const bool index = CLIP_Y_GT_W(p0);
286     if(index ^ CLIP_Y_GT_W(p1))
287     {
288       Vector4 clip(vector4_subtracted(p1, p0));
289
290       double scale = (p0[1] + p0[3]) / (-clip[3] - clip[1]);
291
292       clip[0] = static_cast<float>(p0[0] + scale * clip[0]);
293       clip[1] = static_cast<float>(p0[1] + scale * clip[1]);
294       clip[2] = static_cast<float>(p0[2] + scale * clip[2]);
295       clip[3] = static_cast<float>(p0[3] + scale * clip[3]);
296
297       clipped[index] = clip;
298     }
299     else if(index == 0)
300       return 0;
301   }
302
303   {
304     const bool index = CLIP_Z_LT_W(p0);
305     if(index ^ CLIP_Z_LT_W(p1))
306     {
307       Vector4 clip(vector4_subtracted(p1, p0));
308
309       double scale = (p0[2] - p0[3]) / (clip[3] - clip[2]);
310
311       clip[0] = static_cast<float>(p0[0] + scale * clip[0]);
312       clip[1] = static_cast<float>(p0[1] + scale * clip[1]);
313       clip[2] = static_cast<float>(p0[2] + scale * clip[2]);
314       clip[3] = static_cast<float>(p0[3] + scale * clip[3]);
315
316       clipped[index] = clip;
317     }
318     else if(index == 0)
319       return 0;
320   }
321
322   {
323     const bool index = CLIP_Z_GT_W(p0);
324     if(index ^ CLIP_Z_GT_W(p1))
325     {
326       Vector4 clip(vector4_subtracted(p1, p0));
327
328       double scale = (p0[2] + p0[3]) / (-clip[3] - clip[2]);
329
330       clip[0] = static_cast<float>(p0[0] + scale * clip[0]);
331       clip[1] = static_cast<float>(p0[1] + scale * clip[1]);
332       clip[2] = static_cast<float>(p0[2] + scale * clip[2]);
333       clip[3] = static_cast<float>(p0[3] + scale * clip[3]);
334
335       clipped[index] = clip;
336     }
337     else if(index == 0)
338       return 0;
339   }
340
341   return 2;
342 }
343
344 /// \brief Transforms and clips the line formed by \p p0, \p p1 by the canonical matrix \p self.
345 /// Stores the resulting line in \p clipped.
346 /// Returns the number of points in the resulting line.
347 inline std::size_t matrix4_clip_line(const Matrix4& self, const Vector3& p0, const Vector3& p1, Vector4 clipped[2])
348 {
349   clipped[0][0] = p0[0];
350   clipped[0][1] = p0[1];
351   clipped[0][2] = p0[2];
352   clipped[0][3] = 1;
353   clipped[1][0] = p1[0];
354   clipped[1][1] = p1[1];
355   clipped[1][2] = p1[2];
356   clipped[1][3] = 1;
357
358   matrix4_transform_vector4(self, clipped[0]);
359   matrix4_transform_vector4(self, clipped[1]);
360
361   return homogenous_clip_line(clipped);
362 }
363
364
365
366
367 struct Frustum
368 {
369   Plane3 right, left, bottom, top, back, front;
370
371   Frustum()
372   {
373   }
374   Frustum(const Plane3& _right,
375     const Plane3& _left,
376     const Plane3& _bottom,
377     const Plane3& _top,
378     const Plane3& _back,
379     const Plane3& _front)
380     : right(_right), left(_left), bottom(_bottom), top(_top), back(_back), front(_front)
381   {
382   }
383 };
384
385 inline Frustum frustum_transformed(const Frustum& frustum, const Matrix4& transform)
386 {
387   return Frustum(
388     plane3_transformed(frustum.right, transform),
389     plane3_transformed(frustum.left, transform),
390     plane3_transformed(frustum.bottom, transform),
391     plane3_transformed(frustum.top, transform),
392     plane3_transformed(frustum.back, transform),
393     plane3_transformed(frustum.front, transform)
394   );
395 }
396
397 inline Frustum frustum_inverse_transformed(const Frustum& frustum, const Matrix4& transform)
398 {
399   return Frustum(
400     plane3_inverse_transformed(frustum.right, transform),
401     plane3_inverse_transformed(frustum.left, transform),
402     plane3_inverse_transformed(frustum.bottom, transform),
403     plane3_inverse_transformed(frustum.top, transform),
404     plane3_inverse_transformed(frustum.back, transform),
405     plane3_inverse_transformed(frustum.front, transform)
406   );
407 }
408
409 inline bool viewproj_test_point(const Matrix4& viewproj, const Vector3& point)
410 {
411   Vector4 hpoint(matrix4_transformed_vector4(viewproj, Vector4(point, 1.0f)));
412   if(fabs(hpoint[0]) < fabs(hpoint[3])
413     && fabs(hpoint[1]) < fabs(hpoint[3])
414     && fabs(hpoint[2]) < fabs(hpoint[3]))
415     return true;
416   return false;
417 }
418
419 inline bool viewproj_test_transformed_point(const Matrix4& viewproj, const Vector3& point, const Matrix4& localToWorld)
420 {
421   return viewproj_test_point(viewproj, matrix4_transformed_point(localToWorld, point));
422 }
423
424 inline Frustum frustum_from_viewproj(const Matrix4& viewproj)
425 {
426   return Frustum
427   (
428     plane3_normalised(Plane3(viewproj[ 3] - viewproj[ 0], viewproj[ 7] - viewproj[ 4], viewproj[11] - viewproj[ 8], viewproj[15] - viewproj[12])),
429     plane3_normalised(Plane3(viewproj[ 3] + viewproj[ 0], viewproj[ 7] + viewproj[ 4], viewproj[11] + viewproj[ 8], viewproj[15] + viewproj[12])),
430     plane3_normalised(Plane3(viewproj[ 3] + viewproj[ 1], viewproj[ 7] + viewproj[ 5], viewproj[11] + viewproj[ 9], viewproj[15] + viewproj[13])),
431     plane3_normalised(Plane3(viewproj[ 3] - viewproj[ 1], viewproj[ 7] - viewproj[ 5], viewproj[11] - viewproj[ 9], viewproj[15] - viewproj[13])),
432     plane3_normalised(Plane3(viewproj[ 3] - viewproj[ 2], viewproj[ 7] - viewproj[ 6], viewproj[11] - viewproj[10], viewproj[15] - viewproj[14])),
433     plane3_normalised(Plane3(viewproj[ 3] + viewproj[ 2], viewproj[ 7] + viewproj[ 6], viewproj[11] + viewproj[10], viewproj[15] + viewproj[14]))
434   );
435 }
436
437 struct VolumeIntersection
438 {
439   enum Value
440   {
441     OUTSIDE,
442     INSIDE,
443     PARTIAL
444   };
445 };
446
447 typedef EnumeratedValue<VolumeIntersection> VolumeIntersectionValue;
448
449 const VolumeIntersectionValue c_volumeOutside(VolumeIntersectionValue::OUTSIDE);
450 const VolumeIntersectionValue c_volumeInside(VolumeIntersectionValue::INSIDE);
451 const VolumeIntersectionValue c_volumePartial(VolumeIntersectionValue::PARTIAL);
452
453 inline VolumeIntersectionValue frustum_test_aabb(const Frustum& frustum, const AABB& aabb)
454 {
455   VolumeIntersectionValue result = c_volumeInside;
456
457   switch(aabb_classify_plane(aabb, frustum.right))
458   {
459   case 2:
460     return c_volumeOutside;
461   case 1:
462     result = c_volumePartial;
463   }
464
465   switch(aabb_classify_plane(aabb, frustum.left))
466   {
467   case 2:
468     return c_volumeOutside;
469   case 1:
470     result = c_volumePartial;
471   }
472
473   switch(aabb_classify_plane(aabb, frustum.bottom))
474   {
475   case 2:
476     return c_volumeOutside;
477   case 1:
478     result = c_volumePartial;
479   }
480
481   switch(aabb_classify_plane(aabb, frustum.top))
482   {
483   case 2:
484     return c_volumeOutside;
485   case 1:
486     result = c_volumePartial;
487   }
488
489   switch(aabb_classify_plane(aabb, frustum.back))
490   {
491   case 2:
492     return c_volumeOutside;
493   case 1:
494     result = c_volumePartial;
495   }
496
497   switch(aabb_classify_plane(aabb, frustum.front))
498   {
499   case 2:
500     return c_volumeOutside;
501   case 1:
502     result = c_volumePartial;
503   }
504
505   return result;
506 }
507
508 inline double plane_distance_to_point(const Plane3& plane, const Vector3& point)
509 {
510   return vector3_dot(plane.normal(), point) + plane.d;
511 }
512
513 inline double plane_distance_to_oriented_extents(const Plane3& plane, const Vector3& extents, const Matrix4& orientation)
514 {
515   return fabs(extents[0] * vector3_dot(plane.normal(), vector4_to_vector3(orientation.x())))
516     + fabs(extents[1] * vector3_dot(plane.normal(), vector4_to_vector3(orientation.y())))
517     + fabs(extents[2] * vector3_dot(plane.normal(), vector4_to_vector3(orientation.z())));
518 }
519
520 /// \brief Return false if \p aabb with \p orientation is partially or completely outside \p plane.
521 inline bool plane_contains_oriented_aabb(const Plane3& plane, const AABB& aabb, const Matrix4& orientation)
522 {
523   double dot = plane_distance_to_point(plane, aabb.origin);
524   return !(dot > 0 || -dot < plane_distance_to_oriented_extents(plane, aabb.extents, orientation));
525 }
526
527 inline VolumeIntersectionValue frustum_intersects_transformed_aabb(const Frustum& frustum, const AABB& aabb, const Matrix4& localToWorld)
528 {
529   AABB aabb_world(aabb);
530   matrix4_transform_point(localToWorld, aabb_world.origin);
531
532   if(plane_contains_oriented_aabb(frustum.right, aabb_world, localToWorld)
533     || plane_contains_oriented_aabb(frustum.left, aabb_world, localToWorld)
534     || plane_contains_oriented_aabb(frustum.bottom, aabb_world, localToWorld)
535     || plane_contains_oriented_aabb(frustum.top, aabb_world, localToWorld)
536     || plane_contains_oriented_aabb(frustum.back, aabb_world, localToWorld)
537     || plane_contains_oriented_aabb(frustum.front, aabb_world, localToWorld))
538     return c_volumeOutside;
539   return c_volumeInside;
540 }
541
542 inline bool plane3_test_point(const Plane3& plane, const Vector3& point)
543 {
544   return vector3_dot(point, plane.normal()) + plane.dist() <= 0;
545 }
546
547 inline bool plane3_test_line(const Plane3& plane, const Segment& segment)
548 {
549   return segment_classify_plane(segment, plane) == 2;
550 }
551
552 inline bool frustum_test_point(const Frustum& frustum, const Vector3& point)
553 {
554   return !plane3_test_point(frustum.right, point)
555     && !plane3_test_point(frustum.left, point)
556     && !plane3_test_point(frustum.bottom, point)
557     && !plane3_test_point(frustum.top, point)
558     && !plane3_test_point(frustum.back, point)
559     && !plane3_test_point(frustum.front, point);
560 }
561
562 inline bool frustum_test_line(const Frustum& frustum, const Segment& segment)
563 {
564   return !plane3_test_line(frustum.right, segment)
565     && !plane3_test_line(frustum.left, segment)
566     && !plane3_test_line(frustum.bottom, segment)
567     && !plane3_test_line(frustum.top, segment)
568     && !plane3_test_line(frustum.back, segment)
569     && !plane3_test_line(frustum.front, segment);
570 }
571
572 inline bool viewer_test_plane(const Vector4& viewer, const Plane3& plane)
573 {
574   return ((plane.a * viewer[0])
575     + (plane.b * viewer[1])
576     + (plane.c * viewer[2])
577     + (plane.d * viewer[3])) > 0;
578 }
579
580 inline Vector3 triangle_cross(const Vector3& p0, const Vector3& p1, const Vector3& p2)
581 {
582   return vector3_cross(vector3_subtracted(p1, p0), vector3_subtracted(p1, p2));
583 }
584
585 inline bool viewer_test_triangle(const Vector4& viewer, const Vector3& p0, const Vector3& p1, const Vector3& p2)
586 {
587   Vector3 cross(triangle_cross(p0, p1, p2));
588   return ((viewer[0] * cross[0])
589     + (viewer[1] * cross[1])
590     + (viewer[2] * cross[2])
591     + (viewer[3] * 0)) > 0;
592 }
593
594 inline Vector4 viewer_from_transformed_viewer(const Vector4& viewer, const Matrix4& transform)
595 {
596   if(viewer[3] == 0)
597   {
598     return Vector4(matrix4_transformed_direction(transform, vector4_to_vector3(viewer)), 0);
599   }
600   else
601   {
602     return Vector4(matrix4_transformed_point(transform, vector4_to_vector3(viewer)), viewer[3]);
603   }
604 }
605
606 inline bool viewer_test_transformed_plane(const Vector4& viewer, const Plane3& plane, const Matrix4& localToWorld)
607 {
608 #if 0
609   return viewer_test_plane(viewer_from_transformed_viewer(viewer, matrix4_affine_inverse(localToWorld)), plane);
610 #else
611   return viewer_test_plane(viewer, plane3_transformed(plane, localToWorld));
612 #endif
613 }
614
615 inline Vector4 viewer_from_viewproj(const Matrix4& viewproj)
616 {
617   // get viewer pos in object coords
618   Vector4 viewer(matrix4_transformed_vector4(matrix4_full_inverse(viewproj), Vector4(0, 0, -1, 0)));
619   if(viewer[3] != 0) // non-affine matrix
620   {
621     viewer[0] /= viewer[3];
622     viewer[1] /= viewer[3];
623     viewer[2] /= viewer[3];
624     viewer[3] /= viewer[3];
625   }
626   return viewer;
627 }
628
629 #endif