1#!/pxrpythonsubst
2#
3# Copyright 2016 Pixar
4#
5# Licensed under the Apache License, Version 2.0 (the "Apache License")
6# with the following modification; you may not use this file except in
7# compliance with the Apache License and the following modification to it:
8# Section 6. Trademarks. is deleted and replaced with:
9#
10# 6. Trademarks. This License does not grant permission to use the trade
11#    names, trademarks, service marks, or product names of the Licensor
12#    and its affiliates, except as required to comply with Section 4(c) of
13#    the License and to reproduce the content of the NOTICE file.
14#
15# You may obtain a copy of the Apache License at
16#
17#     http://www.apache.org/licenses/LICENSE-2.0
18#
19# Unless required by applicable law or agreed to in writing, software
20# distributed under the Apache License with the above modification is
21# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
22# KIND, either express or implied. See the Apache License for the specific
23# language governing permissions and limitations under the Apache License.
24#
25from __future__ import division
26
27import sys
28import unittest
29import math
30from pxr import Gf
31
32class TestGfFrustum(unittest.TestCase):
33
34    def test_Constructors(self):
35        self.assertIsInstance(Gf.Frustum(), Gf.Frustum)
36        self.assertIsInstance(Gf.Frustum(Gf.Frustum()), Gf.Frustum)
37
38        # code coverage wonderfulness.
39        f = Gf.Frustum()
40        # force instantiation of the frustum planes
41        f.Intersects(Gf.Vec3d())
42        f2 = Gf.Frustum(f)
43
44    def test_Operators(self):
45        f1 = Gf.Frustum()
46        f2 = Gf.Frustum(f1)
47        self.assertEqual(f1, f2)
48
49    def test_PlaneIntersection(self):
50        f1 = Gf.Frustum()
51        f2 = Gf.Frustum()
52        # force plane instantiation.
53        f1.Intersects(Gf.Vec3d())
54        f2.Intersects(Gf.Vec3d())
55        self.assertEqual(f1, f2)
56
57    def test_Position(self):
58        f1 = Gf.Frustum()
59        f2 = Gf.Frustum()
60        f1.position = Gf.Vec3d(1, 0, 0)
61        f2.position = Gf.Vec3d(0, 1, 0)
62        self.assertNotEqual(f1, f2)
63
64    def test_Properties(self):
65        f = Gf.Frustum()
66        f.position = Gf.Vec3d(1, 2, 3)
67        self.assertEqual(f.position, Gf.Vec3d(1, 2, 3))
68
69        f.rotation = Gf.Rotation(Gf.Vec3d(1, 1, 1), 30)
70        self.assertEqual(f.rotation, Gf.Rotation(Gf.Vec3d(1, 1, 1), 30))
71
72        f.window = Gf.Range2d( Gf.Vec2d(0, 1), Gf.Vec2d(2, 3))
73        self.assertEqual(f.window, Gf.Range2d( Gf.Vec2d(0, 1), Gf.Vec2d(2, 3)))
74
75        f.nearFar = Gf.Range1d(1, 2)
76        self.assertEqual(f.nearFar, Gf.Range1d(1, 2))
77
78        f.viewDistance = 10
79        self.assertEqual(f.viewDistance, 10)
80
81        f.projectionType = Gf.Frustum.Orthographic
82        self.assertEqual(f.projectionType, Gf.Frustum.Orthographic)
83        f.projectionType = Gf.Frustum.Perspective
84        self.assertEqual(f.projectionType, Gf.Frustum.Perspective)
85
86    def test_Projection(self):
87        f = Gf.Frustum()
88        f.SetPerspective( 10, True, 20, 30, 40 )
89        self.assertEqual(f.GetPerspective(True), (10, 20, 30, 40))
90        self.assertEqual(f.GetFOV(True), 10)
91        f.SetPerspective( 10, False, 20, 30, 40 )
92        self.assertEqual(f.GetPerspective(False), (10, 20, 30, 40))
93        self.assertEqual(f.GetFOV(False), 10)
94        self.assertEqual(f.GetFOV(), 10)
95        f = Gf.Frustum()
96        f.projectionType = f.Orthographic
97        self.assertIsNone(f.GetPerspective(True))
98        self.assertEqual(f.GetFOV(True), 0.0)
99        self.assertEqual(f.GetFOV(False), 0.0)
100
101        self.assertEqual(Gf.Frustum.GetReferencePlaneDepth(), 1.0)
102
103        f.SetOrthographic( 10, 20, 30, 40, 50, 60 )
104        self.assertEqual(f.GetOrthographic(), (10, 20, 30, 40, 50, 60))
105        self.assertEqual(f.GetFOV(), 0.0)
106        f = Gf.Frustum()
107        f.projectionType = f.Perspective
108        self.assertEqual(len(f.GetOrthographic()), 0)
109
110    def test_Intersection(self):
111        f = Gf.Frustum()
112        f.projectionType = f.Orthographic
113        f.FitToSphere( Gf.Vec3d(0, 0, 0), 1 )
114        self.assertTrue(f.Intersects( Gf.Vec3d(0, 0, 0) ))
115        self.assertTrue(f.Intersects( Gf.Vec3d(0.9, 0, 0) ))
116        self.assertTrue(f.Intersects( Gf.Vec3d(0, 0.9, 0) ))
117        self.assertTrue(f.Intersects( Gf.Vec3d(0, 0, 0.9) ))
118        self.assertTrue(f.Intersects( Gf.Vec3d(-0.9, 0, 0) ))
119        self.assertTrue(f.Intersects( Gf.Vec3d(0, -0.9, 0) ))
120        self.assertTrue(f.Intersects( Gf.Vec3d(0, 0, -0.9) ))
121        f = Gf.Frustum()
122        f.projectionType = f.Perspective
123        f.FitToSphere( Gf.Vec3d(1, 1, 1), 1, 0.2 )
124        self.assertTrue(f.Intersects( Gf.Vec3d(1, 1, 1) ))
125        self.assertTrue(f.Intersects( Gf.Vec3d(2.1, 1, 1) ))
126        self.assertTrue(f.Intersects( Gf.Vec3d(1, 2.1, 1) ))
127        self.assertTrue(f.Intersects( Gf.Vec3d(1, 1, 2.1) ))
128        self.assertTrue(f.Intersects( Gf.Vec3d(-0.1, 1, 1) ))
129        self.assertTrue(f.Intersects( Gf.Vec3d(1, -0.1, 1) ))
130        self.assertTrue(f.Intersects( Gf.Vec3d(1, 1, -0.1) ))
131
132    def test_CodeCoverage(self):
133        f = Gf.Frustum()
134        f.projectionType = f.Perspective
135        f.window = Gf.Range2d(Gf.Vec2d(10,20), Gf.Vec2d(30,40))
136        f.FitToSphere(Gf.Vec3d(), 1)
137        f.window = Gf.Range2d(Gf.Vec2d(30,40), Gf.Vec2d(10,20))
138        f.FitToSphere(Gf.Vec3d(), 1)
139        f.window = Gf.Range2d(Gf.Vec2d(), Gf.Vec2d())
140        f.FitToSphere(Gf.Vec3d(), 1)
141        f = Gf.Frustum()
142        f.projectionType = f.Perspective
143        f.window = Gf.Range2d(Gf.Vec2d(-30,-40), Gf.Vec2d(10,20))
144        f.FitToSphere(Gf.Vec3d(), 1)
145
146    def test_Transform(self):
147        f = Gf.Frustum()
148        f.Transform( Gf.Matrix4d(2) )
149        self.assertEqual(f.nearFar, 2 * Gf.Frustum().nearFar)
150        f.Transform( Gf.Matrix4d(-2) )
151        f.window = Gf.Range2d(Gf.Vec2d(1,1), Gf.Vec2d(-1,-1))
152        f.Transform(Gf.Matrix4d(1))
153
154    def test_SetRotate(self):
155        f = Gf.Frustum()
156        self.assertEqual(f.ComputeViewDirection(), Gf.Vec3d(0,0,-1))
157        m = Gf.Matrix4d().SetRotate(Gf.Rotation(Gf.Vec3d(1, 1, 1), 30))
158        self.assertTrue(Gf.IsClose(Gf.Frustum().Transform(m).ComputeViewDirection(), \
159            Gf.Vec3d(-0.333333, 0.244017, -0.910684), 0.0001))
160
161        f = Gf.Frustum()
162        self.assertEqual(f.ComputeUpVector(), Gf.Vec3d(0,1,0))
163        m = Gf.Matrix4d().SetRotate(Gf.Rotation(Gf.Vec3d(1, 1, 1), 30))
164        self.assertTrue(Gf.IsClose(Gf.Frustum().Transform(m).ComputeUpVector(), \
165            Gf.Vec3d(-0.244017, 0.910684, 0.333333), 0.0001))
166
167    def test_ComputeViewFrame(self):
168        f = Gf.Frustum()
169        (side, up, view) = f.ComputeViewFrame()
170        self.assertEqual(side, Gf.Vec3d(1, 0, 0))
171        self.assertEqual(up, Gf.Vec3d(0, 1, 0))
172        self.assertEqual(view, Gf.Vec3d(0, 0, -1))
173
174    def test_ComputeLookAtPoint(self):
175        f = Gf.Frustum()
176        self.assertEqual(f.ComputeLookAtPoint(), Gf.Vec3d(0, 0, -5))
177        f.projectionType = f.Orthographic
178        self.assertEqual(f.ComputeLookAtPoint(), Gf.Vec3d(0, 0, -5))
179
180    def test_ComputeViewInverse(self):
181        f = Gf.Frustum()
182        f.Transform(Gf.Matrix4d().SetRotate(Gf.Rotation(Gf.Vec3d(1,1,1),60)))
183        r1 = f.ComputeViewMatrix() * Gf.Vec4d(1, 2, 3, 4)
184        r2 = Gf.Matrix4d().SetRotate(Gf.Rotation(Gf.Vec3d(1,1,1),60)).GetInverse() * Gf.Vec4d(1, 2, 3, 4)
185        self.assertTrue(Gf.IsClose(r1, r2, 0.0001))
186        r1 = f.ComputeViewInverse() * Gf.Vec4d(1, 2, 3, 4)
187        r2 = Gf.Matrix4d().SetRotate(Gf.Rotation(Gf.Vec3d(1,1,1),60)) * Gf.Vec4d(1, 2, 3, 4)
188        self.assertTrue(Gf.IsClose(r1, r2, 0.0001))
189
190    def test_ComputeProjectionMatrix(self):
191        # FIXME how to test ComputeProjectionMatrix?
192        f = Gf.Frustum()
193        f.projectionType = f.Orthographic
194        f.ComputeProjectionMatrix()
195        f.projectionType = f.Perspective
196        f.ComputeProjectionMatrix()
197
198    def test_ComputeAspectRatio(self):
199        f = Gf.Frustum().Transform(Gf.Matrix4d(Gf.Vec4d(3,2,1,1)))
200        self.assertEqual(f.ComputeAspectRatio(), 1.5)
201        corners = f.ComputeCorners()
202        self.assertEqual(corners[0], Gf.Vec3d(-3, -2, -1))
203        self.assertEqual(corners[1], Gf.Vec3d(3, -2, -1))
204        self.assertEqual(corners[2], Gf.Vec3d(-3, 2, -1))
205        self.assertEqual(corners[3], Gf.Vec3d(3, 2, -1))
206        self.assertEqual(corners[4], Gf.Vec3d(-30, -20, -10))
207        self.assertEqual(corners[5], Gf.Vec3d(30, -20, -10))
208        self.assertEqual(corners[6], Gf.Vec3d(-30, 20, -10))
209        self.assertEqual(corners[7], Gf.Vec3d(30, 20, -10))
210
211    def test_ComputeCorners(self):
212        f = Gf.Frustum()
213        f.projectionType = f.Orthographic
214        f.Transform(Gf.Matrix4d(Gf.Vec4d(3,2,1,1)))
215        corners = f.ComputeCorners()
216        self.assertEqual(corners[0], Gf.Vec3d(-3, -2, -1))
217        self.assertEqual(corners[1], Gf.Vec3d(3, -2, -1))
218        self.assertEqual(corners[2], Gf.Vec3d(-3, 2, -1))
219        self.assertEqual(corners[3], Gf.Vec3d(3, 2, -1))
220        self.assertEqual(corners[4], Gf.Vec3d(-3, -2, -10))
221        self.assertEqual(corners[5], Gf.Vec3d(3, -2, -10))
222        self.assertEqual(corners[6], Gf.Vec3d(-3, 2, -10))
223        self.assertEqual(corners[7], Gf.Vec3d(3, 2, -10))
224
225    def test_ComputeNarrowedFrustum(self):
226        f = Gf.Frustum()
227        f.projectionType = f.Orthographic
228        f.Transform(Gf.Matrix4d(Gf.Vec4d(3,2,1,1)))
229        narrowF = f.ComputeNarrowedFrustum(Gf.Vec2d(0, 0), Gf.Vec2d(0.1, 0.1))
230        self.assertTrue(Gf.IsClose(narrowF.window.min, Gf.Vec2d(-0.3, -0.2), 0.0001))
231        self.assertTrue(Gf.IsClose(narrowF.window.max, Gf.Vec2d(0.3, 0.2), 0.0001))
232
233        narrowF = f.ComputeNarrowedFrustum(Gf.Vec3d(0, 0, -1), Gf.Vec2d(0.1, 0.1))
234        self.assertTrue(Gf.IsClose(narrowF.window.min, Gf.Vec2d(-0.3, -0.2), 0.0001))
235        self.assertTrue(Gf.IsClose(narrowF.window.max, Gf.Vec2d(0.3, 0.2), 0.0001))
236
237        # Given a point behind the eye should get the same frustum back
238        narrowF = f.ComputeNarrowedFrustum(Gf.Vec3d(0, 0, 1), Gf.Vec2d(0.1, 0.1))
239        self.assertTrue(Gf.IsClose(narrowF.window.min, Gf.Vec2d(-3.0,-2.0), 0.0001))
240        self.assertTrue(Gf.IsClose(narrowF.window.max, Gf.Vec2d(3.0,2.0), 0.0001))
241
242    def test_ComputePickRay(self):
243        f = Gf.Frustum()
244        f.window = Gf.Range2d(Gf.Vec2d(3,3),Gf.Vec2d(4,4))
245        f.ComputeNarrowedFrustum(Gf.Vec2d(3,3), Gf.Vec2d(100,100))
246
247        r = Gf.Frustum().ComputePickRay(Gf.Vec2d(2, 2))
248        self.assertTrue(Gf.IsClose( r.startPoint, Gf.Vec3d(2./3, 2./3, -1./3), 0.00001 ))
249        self.assertTrue(Gf.IsClose( r.direction, Gf.Vec3d(2./3, 2./3, -1./3), 0.00001 ))
250
251        f = Gf.Frustum()
252        f.projectionType = f.Orthographic
253        r = f.ComputePickRay(Gf.Vec2d(2, 2))
254        self.assertTrue(Gf.IsClose( r.startPoint, Gf.Vec3d(2, 2, -1), 0.00001 ))
255        self.assertTrue(Gf.IsClose( r.direction, Gf.Vec3d(0, 0, -1), 0.00001 ))
256
257        r = Gf.Frustum().ComputePickRay(Gf.Vec3d(0, 0, -2))
258        self.assertTrue(Gf.IsClose( r.startPoint, Gf.Vec3d(0, 0, -1), 0.00001 ))
259        self.assertTrue(Gf.IsClose( r.direction, Gf.Vec3d(0, 0, -1), 0.00001 ))
260
261        r = Gf.Frustum().ComputePickRay(Gf.Vec3d(2, 2, -1))
262        self.assertTrue(Gf.IsClose( r.startPoint, Gf.Vec3d(2./3, 2./3, -1./3), 0.00001 ))
263        self.assertTrue(Gf.IsClose( r.direction, Gf.Vec3d(2./3, 2./3, -1./3), 0.00001 ))
264
265        f = Gf.Frustum()
266        f.projectionType = f.Orthographic
267        r = f.ComputePickRay(Gf.Vec3d(2, 2, -2))
268        self.assertTrue(Gf.IsClose( r.startPoint, Gf.Vec3d(2, 2, -1), 0.00001 ))
269        self.assertTrue(Gf.IsClose( r.direction, Gf.Vec3d(0, 0, -1), 0.00001 ))
270
271    def test_EmptyFrustumIntersection(self):
272        self.assertFalse(Gf.Frustum().Intersects(Gf.BBox3d()))
273        self.assertTrue(Gf.Frustum().Intersects(Gf.BBox3d(Gf.Range3d(Gf.Vec3d(-1,-1,-1),
274                                                Gf.Vec3d(1,1,1)))))
275        self.assertTrue(Gf.Frustum().Intersects(Gf.Vec3d(0,0,-1)))
276        self.assertFalse(Gf.Frustum().Intersects(Gf.Vec3d(0,0,0)))
277
278        self.assertFalse(Gf.Frustum().Intersects(Gf.Vec3d(), Gf.Vec3d(1,1,1), Gf.Vec3d(0,0,1)))
279        self.assertTrue(Gf.Frustum().Intersects(Gf.Vec3d(), Gf.Vec3d(-1,-1,-1), Gf.Vec3d(0,0,1)))
280
281        self.assertFalse(Gf.Frustum().Intersects(Gf.Vec3d(0, 100, -100), \
282                                        Gf.Vec3d(-100,-100,100), \
283                                        Gf.Vec3d(100,-100,100)))
284        self.assertTrue(Gf.Frustum().Intersects(Gf.Vec3d(0, 10, 100), \
285                                    Gf.Vec3d(-100,-10,-10), \
286                                    Gf.Vec3d(100,-10,-10)))
287        self.assertTrue(Gf.Frustum().Intersects(Gf.Vec3d(0, 1, 1), \
288                                    Gf.Vec3d(50,0,-50), \
289                                    Gf.Vec3d(-50,0,-50)))
290
291        self.assertTrue(Gf.Frustum().Intersects(Gf.Vec3d(), Gf.Vec3d(-1,-1,-1), Gf.Vec3d(0,0,1)))
292
293        self.assertTrue(Gf.Frustum().Intersects(Gf.Vec3d(0,0,0), Gf.Vec3d(1,1,-1)))
294        self.assertTrue(Gf.Frustum().Intersects(Gf.Vec3d(-100,0,-1), Gf.Vec3d(100,0,-1)))
295        self.assertTrue(Gf.Frustum().Intersects(Gf.Vec3d(0,100,-1), Gf.Vec3d(0,-100,-1)))
296        self.assertFalse(Gf.Frustum().Intersects(Gf.Vec3d(-100,0,1), Gf.Vec3d(100,0,-1)))
297        self.assertFalse(Gf.Frustum().Intersects(Gf.Vec3d(0,0,0), Gf.Vec3d(1,1,1)))
298
299    def test_Str(self):
300        f = Gf.Frustum()
301        f.projectionType = f.Perspective
302        self.assertTrue(len(str(f)))
303        f.projectionType = f.Orthographic
304        self.assertTrue(len(str(f)))
305
306    def test_IntersectionViewVolume(self):
307
308        # a viewProjMat corresponding to a persp cam looking down the Y axis,
309        # aimed at the origin.
310        viewMat = Gf.Matrix4d(1.0, 0.0, 0.0, 0.0,
311                            0.0, 0.0, -1.0, 0.0,
312                            0.0, 1.0, 0.0, 0.0,
313                            0.0, 0.0, -20, 1.0)
314        projMat = Gf.Matrix4d(4.241894005673533, 0.0, 0.0, 0.0,
315                            0.0, 4.2418940586972074, 0.0, 0.0,
316                            0.0, 0.0, -1, -1.0,
317                            0.0, 0.0, -20, 0.0)
318        viewProjMat = viewMat * projMat
319
320        # a typical box entirely in the view
321        b = Gf.BBox3d( Gf.Range3d( Gf.Vec3d( 0, 0, 0 ), Gf.Vec3d( 1, 1, 1 ) ) )
322        self.assertTrue(Gf.Frustum.IntersectsViewVolume(b,viewProjMat))
323
324        # a typical box entirely out of the view
325        b = Gf.BBox3d( Gf.Range3d( Gf.Vec3d( 100, 0, 0 ), Gf.Vec3d( 101, 1, 1 ) ) )
326        self.assertFalse(Gf.Frustum.IntersectsViewVolume(b,viewProjMat))
327
328        # a large box entirely enclosing the view
329        b = Gf.BBox3d( Gf.Range3d( Gf.Vec3d( -1e9, -1e9, -1e9 ),
330                    Gf.Vec3d( 1e9, 1e9, 1e9 ) ) )
331        self.assertTrue(Gf.Frustum.IntersectsViewVolume(b,viewProjMat))
332
333    def test_Serialization(self):
334        f = Gf.Frustum(Gf.Vec3d(3,4,5), Gf.Rotation((1,2,3),40),
335                    Gf.Range2d(Gf.Vec2d(-0.2,-0.3), Gf.Vec2d(0.2, 0.33)),
336                    Gf.Range1d(10, 100),
337                    Gf.Frustum.Perspective,
338                    viewDistance = 20)
339
340        f2 = eval(repr(f))
341
342        f3 = Gf.Frustum(rotation = Gf.Rotation((1,2,3),40),
343                    nearFar = Gf.Range1d(10, 100),
344                    projectionType = Gf.Frustum.Perspective,
345                    position = Gf.Vec3d(3,4,5),
346                    viewDistance = 20,
347                    window = Gf.Range2d(Gf.Vec2d(-0.2,-0.3), Gf.Vec2d(0.2, 0.33)))
348
349        self.assertAlmostEqual(f, f2)
350        self.assertAlmostEqual(f, f3)
351        self.assertAlmostEqual(f.viewDistance, 20.0)
352        self.assertAlmostEqual(f2.viewDistance, 20.0)
353
354    def test_ConstructFromMatrix(self):
355        m = Gf.Matrix4d(0.9987016645043332, -0.035803686178599, -0.036236464677155, 0.0,
356                        0.0362364646771555,  0.999278702502407,  0.011357524061459, 0.0,
357                        0.0358036861785999, -0.012655859557126,  0.999278702502407, 0.0,
358                        3.0               , -6.0              ,  5.0              , 1.0)
359
360        f = Gf.Frustum(m,
361                    Gf.Range2d(Gf.Vec2d(-0.22,-0.2), Gf.Vec2d(0.2, 0.33)),
362                    Gf.Range1d(20, 90),
363                    Gf.Frustum.Perspective)
364
365        f = Gf.Frustum(m,
366                    Gf.Range2d(Gf.Vec2d(-0.22,-0.2), Gf.Vec2d(0.2, 0.33)),
367                    Gf.Range1d(20, 90),
368                    Gf.Frustum.Perspective)
369
370        corners = f.ComputeCorners()
371        results = (Gf.Vec3d( -2.255306906099,  -9.58646139968125, -14.8715637017144),
372                   Gf.Vec3d(  6.133787075736,  -9.88721236358150, -15.1759500050026),
373                   Gf.Vec3d( -1.871200380521,   1.00589284684426, -14.7511739466630),
374                   Gf.Vec3d(  6.517893601314,   0.70514188294401, -15.0555602499511),
375                   Gf.Vec3d(-20.648881077448, -22.13907629856565, -84.4220366577152),
376                   Gf.Vec3d( 17.102041840815, -23.49245563611677, -85.7917750225117),
377                   Gf.Vec3d(-18.920401712348,  25.52651781079917, -83.8802827599836),
378                   Gf.Vec3d( 18.830521205915,  24.17313847324806, -85.2500211247801))
379
380        self.assertEqual(len(corners), len(results))
381        for i in range(len(results)):
382            self.assertTrue(Gf.IsClose(corners[i], results[i], 0.0001))
383
384        corners = f.ComputeCornersAtDistance(20)
385        for i in range(len(corners)):
386            self.assertTrue(Gf.IsClose(corners[i], results[i], 0.0001))
387
388        corners = f.ComputeCornersAtDistance(90)
389        for i in range(len(corners)):
390            self.assertTrue(Gf.IsClose(corners[i], results[i+4], 0.0001))
391
392        corners = f.ComputeCornersAtDistance((20 + 90) / 2.0)
393        for i in range(len(corners)):
394            self.assertTrue(
395                Gf.IsClose(corners[i], (results[i] + results[i+4]) / 2.0,
396                           0.0001))
397
398
399if __name__ == '__main__':
400    unittest.main()
401