1///////////////////////////////////////////////////////////////////////////
2//
3// Copyright (c) 2002, Industrial Light & Magic, a division of Lucas
4// Digital Ltd. LLC
5//
6// All rights reserved.
7//
8// Redistribution and use in source and binary forms, with or without
9// modification, are permitted provided that the following conditions are
10// met:
11// *       Redistributions of source code must retain the above copyright
12// notice, this list of conditions and the following disclaimer.
13// *       Redistributions in binary form must reproduce the above
14// copyright notice, this list of conditions and the following disclaimer
15// in the documentation and/or other materials provided with the
16// distribution.
17// *       Neither the name of Industrial Light & Magic nor the names of
18// its contributors may be used to endorse or promote products derived
19// from this software without specific prior written permission.
20//
21// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32//
33///////////////////////////////////////////////////////////////////////////
34
35
36
37#ifndef INCLUDED_IMATHFRUSTUM_H
38#define INCLUDED_IMATHFRUSTUM_H
39
40
41#include "ImathVec.h"
42#include "ImathPlane.h"
43#include "ImathLine.h"
44#include "ImathMatrix.h"
45#include "ImathLimits.h"
46#include "ImathFun.h"
47#include "IexMathExc.h"
48
49#if defined _WIN32 || defined _WIN64
50    #ifdef near
51        #undef near
52    #endif
53    #ifdef far
54        #undef far
55    #endif
56#endif
57
58namespace Imath {
59
60//
61//	template class Frustum<T>
62//
63//	The frustum is always located with the eye point at the
64//	origin facing down -Z. This makes the Frustum class
65//	compatable with OpenGL (or anything that assumes a camera
66//	looks down -Z, hence with a right-handed coordinate system)
67//	but not with RenderMan which assumes the camera looks down
68//	+Z. Additional functions are provided for conversion from
69//	and from various camera coordinate spaces.
70//
71
72
73template<class T>
74class Frustum
75{
76  public:
77    Frustum();
78    Frustum(const Frustum &);
79    Frustum(T near, T far, T left, T right, T top, T bottom, bool ortho=false);
80    Frustum(T near, T far, T fovx, T fovy, T aspect);
81    virtual ~Frustum();
82
83    //--------------------
84    // Assignment operator
85    //--------------------
86
87    const Frustum &operator	= (const Frustum &);
88
89    //--------------------
90    //  Operators:  ==, !=
91    //--------------------
92
93    bool                        operator == (const Frustum<T> &src) const;
94    bool                        operator != (const Frustum<T> &src) const;
95
96    //--------------------------------------------------------
97    //  Set functions change the entire state of the Frustum
98    //--------------------------------------------------------
99
100    void		set(T near, T far,
101			    T left, T right,
102			    T top, T bottom,
103			    bool ortho=false);
104
105    void		set(T near, T far, T fovx, T fovy, T aspect);
106
107    //------------------------------------------------------
108    //	These functions modify an already valid frustum state
109    //------------------------------------------------------
110
111    void		modifyNearAndFar(T near, T far);
112    void		setOrthographic(bool);
113
114    //--------------
115    //  Access
116    //--------------
117
118    bool		orthographic() const	{ return _orthographic; }
119    T			near() const		{ return _near;		}
120    T			far() const		{ return _far;		}
121    T			left() const		{ return _left;		}
122    T			right() const		{ return _right;	}
123    T			bottom() const		{ return _bottom;	}
124    T			top() const		{ return _top;		}
125
126    //-----------------------------------------------------------------------
127    //  Sets the planes in p to be the six bounding planes of the frustum, in
128    //  the following order: top, right, bottom, left, near, far.
129    //  Note that the planes have normals that point out of the frustum.
130    //  The version of this routine that takes a matrix applies that matrix
131    //  to transform the frustum before setting the planes.
132    //-----------------------------------------------------------------------
133
134    void		planes(Plane3<T> p[6]);
135    void		planes(Plane3<T> p[6], const Matrix44<T> &M);
136
137    //----------------------
138    //  Derived Quantities
139    //----------------------
140
141    T			fovx() const;
142    T			fovy() const;
143    T			aspect() const;
144    Matrix44<T>		projectionMatrix() const;
145
146    //-----------------------------------------------------------------------
147    //  Takes a rectangle in the screen space (i.e., -1 <= left <= right <= 1
148    //  and -1 <= bottom <= top <= 1) of this Frustum, and returns a new
149    //  Frustum whose near clipping-plane window is that rectangle in local
150    //  space.
151    //-----------------------------------------------------------------------
152
153    Frustum<T>		window(T left, T right, T top, T bottom) const;
154
155    //----------------------------------------------------------
156    // Projection is in screen space / Conversion from Z-Buffer
157    //----------------------------------------------------------
158
159    Line3<T>		projectScreenToRay( const Vec2<T> & ) const;
160    Vec2<T>		projectPointToScreen( const Vec3<T> & ) const;
161
162    T			ZToDepth(long zval, long min, long max) const;
163    T			normalizedZToDepth(T zval) const;
164    long		DepthToZ(T depth, long zmin, long zmax) const;
165
166    T			worldRadius(const Vec3<T> &p, T radius) const;
167    T			screenRadius(const Vec3<T> &p, T radius) const;
168
169
170  protected:
171
172    Vec2<T>		screenToLocal( const Vec2<T> & ) const;
173    Vec2<T>		localToScreen( const Vec2<T> & ) const;
174
175  protected:
176    T			_near;
177    T			_far;
178    T			_left;
179    T			_right;
180    T			_top;
181    T			_bottom;
182    bool		_orthographic;
183};
184
185
186template<class T>
187inline Frustum<T>::Frustum()
188{
189    set(T (0.1),
190	T (1000.0),
191	T (-1.0),
192	T (1.0),
193	T (1.0),
194	T (-1.0),
195	false);
196}
197
198template<class T>
199inline Frustum<T>::Frustum(const Frustum &f)
200{
201    *this = f;
202}
203
204template<class T>
205inline Frustum<T>::Frustum(T n, T f, T l, T r, T t, T b, bool o)
206{
207    set(n,f,l,r,t,b,o);
208}
209
210template<class T>
211inline Frustum<T>::Frustum(T near, T far, T fovx, T fovy, T aspect)
212{
213    set(near,far,fovx,fovy,aspect);
214}
215
216template<class T>
217Frustum<T>::~Frustum()
218{
219}
220
221template<class T>
222const Frustum<T> &
223Frustum<T>::operator = (const Frustum &f)
224{
225    _near         = f._near;
226    _far          = f._far;
227    _left         = f._left;
228    _right        = f._right;
229    _top          = f._top;
230    _bottom       = f._bottom;
231    _orthographic = f._orthographic;
232
233    return *this;
234}
235
236template <class T>
237bool
238Frustum<T>::operator == (const Frustum<T> &src) const
239{
240    return
241        _near         == src._near   &&
242        _far          == src._far    &&
243        _left         == src._left   &&
244        _right        == src._right  &&
245        _top          == src._top    &&
246        _bottom       == src._bottom &&
247        _orthographic == src._orthographic;
248}
249
250template <class T>
251inline bool
252Frustum<T>::operator != (const Frustum<T> &src) const
253{
254    return !operator== (src);
255}
256
257template<class T>
258void Frustum<T>::set(T n, T f, T l, T r, T t, T b, bool o)
259{
260    _near	    = n;
261    _far	    = f;
262    _left	    = l;
263    _right	    = r;
264    _bottom	    = b;
265    _top	    = t;
266    _orthographic   = o;
267}
268
269template<class T>
270void Frustum<T>::modifyNearAndFar(T n, T f)
271{
272    if ( _orthographic )
273    {
274	_near = n;
275    }
276    else
277    {
278	Line3<T>  lowerLeft( Vec3<T>(0,0,0), Vec3<T>(_left,_bottom,-_near) );
279	Line3<T> upperRight( Vec3<T>(0,0,0), Vec3<T>(_right,_top,-_near) );
280	Plane3<T> nearPlane( Vec3<T>(0,0,-1), n );
281
282	Vec3<T> ll,ur;
283	nearPlane.intersect(lowerLeft,ll);
284	nearPlane.intersect(upperRight,ur);
285
286	_left	= ll.x;
287	_right	= ur.x;
288	_top	= ur.y;
289	_bottom	= ll.y;
290	_near	= n;
291	_far	= f;
292    }
293
294    _far = f;
295}
296
297template<class T>
298void Frustum<T>::setOrthographic(bool ortho)
299{
300    _orthographic   = ortho;
301}
302
303template<class T>
304void Frustum<T>::set(T near, T far, T fovx, T fovy, T aspect)
305{
306    if (fovx != 0 && fovy != 0)
307	throw Iex::ArgExc ("fovx and fovy cannot both be non-zero.");
308
309    if (fovx != 0)
310    {
311	_right	    = near * Math<T>::tan(fovx/2.0);
312	_left	    = -_right;
313	_top	    = ((_right - _left)/aspect)/2.0;
314	_bottom	    = -_top;
315    }
316    else
317    {
318	_top	    = near * Math<T>::tan(fovy/2.0);
319	_bottom	    = -_top;
320	_right	    = (_top - _bottom) * aspect / 2.0;
321	_left	    = -_right;
322    }
323    _near	    = near;
324    _far	    = far;
325    _orthographic   = false;
326}
327
328template<class T>
329T Frustum<T>::fovx() const
330{
331    return Math<T>::atan2(_right,_near) - Math<T>::atan2(_left,_near);
332}
333
334template<class T>
335T Frustum<T>::fovy() const
336{
337    return Math<T>::atan2(_top,_near) - Math<T>::atan2(_bottom,_near);
338}
339
340template<class T>
341T Frustum<T>::aspect() const
342{
343    T rightMinusLeft = _right-_left;
344    T topMinusBottom = _top-_bottom;
345
346    if (abs(topMinusBottom) < 1 &&
347	abs(rightMinusLeft) > limits<T>::max() * abs(topMinusBottom))
348    {
349	throw Iex::DivzeroExc ("Bad viewing frustum: "
350			       "aspect ratio cannot be computed.");
351    }
352
353    return rightMinusLeft / topMinusBottom;
354}
355
356template<class T>
357Matrix44<T> Frustum<T>::projectionMatrix() const
358{
359    T rightPlusLeft  = _right+_left;
360    T rightMinusLeft = _right-_left;
361
362    T topPlusBottom  = _top+_bottom;
363    T topMinusBottom = _top-_bottom;
364
365    T farPlusNear    = _far+_near;
366    T farMinusNear   = _far-_near;
367
368    if ((abs(rightMinusLeft) < 1 &&
369	 abs(rightPlusLeft) > limits<T>::max() * abs(rightMinusLeft)) ||
370	(abs(topMinusBottom) < 1 &&
371	 abs(topPlusBottom) > limits<T>::max() * abs(topMinusBottom)) ||
372	(abs(farMinusNear) < 1 &&
373	 abs(farPlusNear) > limits<T>::max() * abs(farMinusNear)))
374    {
375	throw Iex::DivzeroExc ("Bad viewing frustum: "
376			       "projection matrix cannot be computed.");
377    }
378
379    if ( _orthographic )
380    {
381	T tx = -rightPlusLeft / rightMinusLeft;
382	T ty = -topPlusBottom / topMinusBottom;
383	T tz = -farPlusNear   / farMinusNear;
384
385	if ((abs(rightMinusLeft) < 1 &&
386	     2 > limits<T>::max() * abs(rightMinusLeft)) ||
387	    (abs(topMinusBottom) < 1 &&
388	     2 > limits<T>::max() * abs(topMinusBottom)) ||
389	    (abs(farMinusNear) < 1 &&
390	     2 > limits<T>::max() * abs(farMinusNear)))
391	{
392	    throw Iex::DivzeroExc ("Bad viewing frustum: "
393				   "projection matrix cannot be computed.");
394	}
395
396	T A  =  2 / rightMinusLeft;
397	T B  =  2 / topMinusBottom;
398	T C  = -2 / farMinusNear;
399
400	return Matrix44<T>( A,  0,  0,  0,
401			    0,  B,  0,  0,
402			    0,  0,  C,  0,
403			    tx, ty, tz, 1.f );
404    }
405    else
406    {
407	T A =  rightPlusLeft / rightMinusLeft;
408	T B =  topPlusBottom / topMinusBottom;
409	T C = -farPlusNear   / farMinusNear;
410
411	T farTimesNear = -2 * _far * _near;
412	if (abs(farMinusNear) < 1 &&
413	    abs(farTimesNear) > limits<T>::max() * abs(farMinusNear))
414	{
415	    throw Iex::DivzeroExc ("Bad viewing frustum: "
416				   "projection matrix cannot be computed.");
417	}
418
419	T D = farTimesNear / farMinusNear;
420
421	T twoTimesNear = 2 * _near;
422
423	if ((abs(rightMinusLeft) < 1 &&
424	     abs(twoTimesNear) > limits<T>::max() * abs(rightMinusLeft)) ||
425	    (abs(topMinusBottom) < 1 &&
426	     abs(twoTimesNear) > limits<T>::max() * abs(topMinusBottom)))
427	{
428	    throw Iex::DivzeroExc ("Bad viewing frustum: "
429				   "projection matrix cannot be computed.");
430	}
431
432	T E = twoTimesNear / rightMinusLeft;
433	T F = twoTimesNear / topMinusBottom;
434
435	return Matrix44<T>( E,  0,  0,  0,
436			    0,  F,  0,  0,
437			    A,  B,  C, -1,
438			    0,  0,  D,  0 );
439    }
440}
441
442template<class T>
443Frustum<T> Frustum<T>::window(T l, T r, T t, T b) const
444{
445    // move it to 0->1 space
446
447    Vec2<T> bl = screenToLocal( Vec2<T>(l,b) );
448    Vec2<T> tr = screenToLocal( Vec2<T>(r,t) );
449
450    return Frustum<T>(_near, _far, bl.x, tr.x, tr.y, bl.y, _orthographic);
451}
452
453
454template<class T>
455Vec2<T> Frustum<T>::screenToLocal(const Vec2<T> &s) const
456{
457    return Vec2<T>( _left + (_right-_left) * (1.f+s.x) / 2.f,
458		    _bottom + (_top-_bottom) * (1.f+s.y) / 2.f );
459}
460
461template<class T>
462Vec2<T> Frustum<T>::localToScreen(const Vec2<T> &p) const
463{
464    T leftPlusRight  = _left - 2 * p.x + _right;
465    T leftMinusRight = _left-_right;
466    T bottomPlusTop  = _bottom - 2 * p.y + _top;
467    T bottomMinusTop = _bottom-_top;
468
469    if ((abs(leftMinusRight) < 1 &&
470	 abs(leftPlusRight) > limits<T>::max() * abs(leftMinusRight)) ||
471	(abs(bottomMinusTop) < 1 &&
472	 abs(bottomPlusTop) > limits<T>::max() * abs(bottomMinusTop)))
473    {
474	throw Iex::DivzeroExc
475	    ("Bad viewing frustum: "
476	     "local-to-screen transformation cannot be computed");
477    }
478
479    return Vec2<T>( leftPlusRight / leftMinusRight,
480		    bottomPlusTop / bottomMinusTop );
481}
482
483template<class T>
484Line3<T> Frustum<T>::projectScreenToRay(const Vec2<T> &p) const
485{
486    Vec2<T> point = screenToLocal(p);
487    if (orthographic())
488	return Line3<T>( Vec3<T>(point.x,point.y, 0.0),
489			 Vec3<T>(point.x,point.y,-_near));
490    else
491	return Line3<T>( Vec3<T>(0, 0, 0), Vec3<T>(point.x,point.y,-_near));
492}
493
494template<class T>
495Vec2<T> Frustum<T>::projectPointToScreen(const Vec3<T> &point) const
496{
497    if (orthographic() || point.z == 0)
498	return localToScreen( Vec2<T>( point.x, point.y ) );
499    else
500	return localToScreen( Vec2<T>( point.x * _near / -point.z,
501				       point.y * _near / -point.z ) );
502}
503
504template<class T>
505T Frustum<T>::ZToDepth(long zval,long zmin,long zmax) const
506{
507    int zdiff = zmax - zmin;
508
509    if (zdiff == 0)
510    {
511	throw Iex::DivzeroExc
512	    ("Bad call to Frustum::ZToDepth: zmax == zmin");
513    }
514
515    if ( zval > zmax+1 ) zval -= zdiff;
516
517    T fzval = (T(zval) - T(zmin)) / T(zdiff);
518    return normalizedZToDepth(fzval);
519}
520
521template<class T>
522T Frustum<T>::normalizedZToDepth(T zval) const
523{
524    T Zp = zval * 2.0 - 1;
525
526    if ( _orthographic )
527    {
528        return   -(Zp*(_far-_near) + (_far+_near))/2;
529    }
530    else
531    {
532	T farTimesNear = 2 * _far * _near;
533	T farMinusNear = Zp * (_far - _near) - _far - _near;
534
535	if (abs(farMinusNear) < 1 &&
536	    abs(farTimesNear) > limits<T>::max() * abs(farMinusNear))
537	{
538	    throw Iex::DivzeroExc
539		("Frustum::normalizedZToDepth cannot be computed.  The "
540		 "near and far clipping planes of the viewing frustum "
541		 "may be too close to each other");
542	}
543
544	return farTimesNear / farMinusNear;
545    }
546}
547
548template<class T>
549long Frustum<T>::DepthToZ(T depth,long zmin,long zmax) const
550{
551    long zdiff     = zmax - zmin;
552    T farMinusNear = _far-_near;
553
554    if ( _orthographic )
555    {
556	T farPlusNear = 2*depth + _far + _near;
557
558	if (abs(farMinusNear) < 1 &&
559	    abs(farPlusNear) > limits<T>::max() * abs(farMinusNear))
560	{
561	    throw Iex::DivzeroExc
562		("Bad viewing frustum: near and far clipping planes "
563		 "are too close to each other");
564	}
565
566	T Zp = -farPlusNear/farMinusNear;
567	return long(0.5*(Zp+1)*zdiff) + zmin;
568    }
569    else
570    {
571	// Perspective
572
573	T farTimesNear = 2*_far*_near;
574	if (abs(depth) < 1 &&
575	    abs(farTimesNear) > limits<T>::max() * abs(depth))
576	{
577	    throw Iex::DivzeroExc
578		("Bad call to DepthToZ function: value of `depth' "
579		 "is too small");
580	}
581
582	T farPlusNear = farTimesNear/depth + _far + _near;
583	if (abs(farMinusNear) < 1 &&
584	    abs(farPlusNear) > limits<T>::max() * abs(farMinusNear))
585	{
586	    throw Iex::DivzeroExc
587		("Bad viewing frustum: near and far clipping planes "
588		 "are too close to each other");
589	}
590
591	T Zp = farPlusNear/farMinusNear;
592	return long(0.5*(Zp+1)*zdiff) + zmin;
593    }
594}
595
596template<class T>
597T Frustum<T>::screenRadius(const Vec3<T> &p, T radius) const
598{
599    // Derivation:
600    // Consider X-Z plane.
601    // X coord of projection of p = xp = p.x * (-_near / p.z)
602    // Let q be p + (radius, 0, 0).
603    // X coord of projection of q = xq = (p.x - radius)  * (-_near / p.z)
604    // X coord of projection of segment from p to q = r = xp - xq
605    //         = radius * (-_near / p.z)
606    // A similar analysis holds in the Y-Z plane.
607    // So r is the quantity we want to return.
608
609    if (abs(p.z) > 1 || abs(-_near) < limits<T>::max() * abs(p.z))
610    {
611	return radius * (-_near / p.z);
612    }
613    else
614    {
615	throw Iex::DivzeroExc
616	    ("Bad call to Frustum::screenRadius: the magnitude of `p' "
617	     "is too small");
618    }
619
620    return radius * (-_near / p.z);
621}
622
623template<class T>
624T Frustum<T>::worldRadius(const Vec3<T> &p, T radius) const
625{
626    if (abs(-_near) > 1 || abs(p.z) < limits<T>::max() * abs(-_near))
627    {
628	return radius * (p.z / -_near);
629    }
630    else
631    {
632	throw Iex::DivzeroExc
633	    ("Bad viewing frustum: the near clipping plane is too "
634	     "close to zero");
635    }
636}
637
638template<class T>
639void Frustum<T>::planes(Plane3<T> p[6])
640{
641    //
642    //	Plane order: Top, Right, Bottom, Left, Near, Far.
643    //  Normals point outwards.
644    //
645
646    if (! _orthographic)
647    {
648        Vec3<T> a( _left,  _bottom, -_near);
649        Vec3<T> b( _left,  _top,    -_near);
650        Vec3<T> c( _right, _top,    -_near);
651        Vec3<T> d( _right, _bottom, -_near);
652        Vec3<T> o(0,0,0);
653
654        p[0].set( o, c, b );
655        p[1].set( o, d, c );
656        p[2].set( o, a, d );
657        p[3].set( o, b, a );
658    }
659    else
660    {
661        p[0].set( Vec3<T>( 0, 1, 0), _top );
662        p[1].set( Vec3<T>( 1, 0, 0), _right );
663        p[2].set( Vec3<T>( 0,-1, 0),-_bottom );
664        p[3].set( Vec3<T>(-1, 0, 0),-_left );
665    }
666    p[4].set( Vec3<T>(0, 0, 1), -_near );
667    p[5].set( Vec3<T>(0, 0,-1), _far );
668}
669
670
671template<class T>
672void Frustum<T>::planes(Plane3<T> p[6], const Matrix44<T> &M)
673{
674    //
675    //	Plane order: Top, Right, Bottom, Left, Near, Far.
676    //  Normals point outwards.
677    //
678
679    Vec3<T> a   = Vec3<T>( _left,  _bottom, -_near) * M;
680    Vec3<T> b   = Vec3<T>( _left,  _top,    -_near) * M;
681    Vec3<T> c   = Vec3<T>( _right, _top,    -_near) * M;
682    Vec3<T> d   = Vec3<T>( _right, _bottom, -_near) * M;
683    if (! _orthographic)
684    {
685        double s    = _far / double(_near);
686        T farLeft   = (T) (s * _left);
687        T farRight  = (T) (s * _right);
688        T farTop    = (T) (s * _top);
689        T farBottom = (T) (s * _bottom);
690        Vec3<T> e   = Vec3<T>( farLeft,  farBottom, -_far) * M;
691        Vec3<T> f   = Vec3<T>( farLeft,  farTop,    -_far) * M;
692        Vec3<T> g   = Vec3<T>( farRight, farTop,    -_far) * M;
693        Vec3<T> o   = Vec3<T>(0,0,0) * M;
694        p[0].set( o, c, b );
695        p[1].set( o, d, c );
696        p[2].set( o, a, d );
697        p[3].set( o, b, a );
698        p[4].set( a, d, c );
699        p[5].set( e, f, g );
700     }
701    else
702    {
703        Vec3<T> e   = Vec3<T>( _left,  _bottom, -_far) * M;
704        Vec3<T> f   = Vec3<T>( _left,  _top,    -_far) * M;
705        Vec3<T> g   = Vec3<T>( _right, _top,    -_far) * M;
706        Vec3<T> h   = Vec3<T>( _right, _bottom, -_far) * M;
707        p[0].set( c, g, f );
708        p[1].set( d, h, g );
709        p[2].set( a, e, h );
710        p[3].set( b, f, e );
711        p[4].set( a, d, c );
712        p[5].set( e, f, g );
713    }
714}
715
716typedef Frustum<float>	Frustumf;
717typedef Frustum<double> Frustumd;
718
719
720} // namespace Imath
721
722#endif
723