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, Tf
31
32def err( msg ):
33    return "ERROR: " + msg + " failed"
34
35class TestGfPlane(unittest.TestCase):
36
37    def test_Constructors(self):
38        self.assertIsInstance(Gf.Plane(), Gf.Plane, err( "constructor" ))
39        self.assertIsInstance(Gf.Plane(Gf.Vec3d(1,1,1), 1), Gf.Plane, err( "constructor" ))
40        self.assertIsInstance(Gf.Plane(Gf.Vec3d(1,1,1), Gf.Vec3d(1,1,1)), Gf.Plane, err( "constructor" ))
41        self.assertIsInstance(Gf.Plane(Gf.Vec3d(0,0,1), Gf.Vec3d(0,1,0), Gf.Vec3d(1,0,0)), Gf.Plane,
42            err( "constructor" ))
43        self.assertIsInstance(Gf.Plane(Gf.Vec4d(3,4,0,5)), Gf.Plane, err("constructor" ))
44
45    def test_Properties(self):
46        p = Gf.Plane()
47        self.assertEqual(p.Set(Gf.Vec3d(1,1,1), 1), Gf.Plane(Gf.Vec3d(1,1,1), 1), err("Set"))
48        self.assertEqual(p.Set(Gf.Vec3d(1,1,1), Gf.Vec3d(1,1,1)), Gf.Plane(Gf.Vec3d(1,1,1), Gf.Vec3d(1,1,1)),
49            err("Set"))
50        self.assertEqual(p.Set(Gf.Vec3d(0,0,1), Gf.Vec3d(0,1,0), Gf.Vec3d(1,0,0)),
51            Gf.Plane(Gf.Vec3d(0,0,1), Gf.Vec3d(0,1,0), Gf.Vec3d(1,0,0)), err("Set"))
52
53        p = Gf.Plane(Gf.Vec3d(1,1,1), 1)
54        self.assertEqual(p.normal, Gf.Vec3d(1,1,1).GetNormalized(), err("normal"))
55
56        p = Gf.Plane(Gf.Vec3d(1,1,1), 10)
57        self.assertEqual(p.distanceFromOrigin, 10, err("distanceFromOrigin"))
58
59    def test_vec4d(self):
60        p = Gf.Plane(Gf.Vec4d(3,4,0,5))
61        self.assertEqual(p.normal, Gf.Vec3d(3, 4, 0) / 5, err("normal"))
62        self.assertEqual(p.distanceFromOrigin, -1, err("distanceFromOrigin"))
63
64        pt0 = Gf.Vec3d(2,3,1)
65        pt1 = Gf.Vec3d(5,1,2)
66        pt2 = Gf.Vec3d(6,0,7)
67
68        p = Gf.Plane(pt0, pt1, pt2)
69        eqn = p.GetEquation()
70
71        for pt in [pt0, pt1, pt2]:
72            v = eqn[0] * pt[0] + eqn[1] * pt[1] + eqn[2] * pt[2] + eqn[3]
73            self.assertTrue(Gf.IsClose(v, 0, 1e-12))
74
75    def test_Operators(self):
76        p1 = Gf.Plane(Gf.Vec3d(1,1,1), 10)
77        p2 = Gf.Plane(Gf.Vec3d(1,1,1), 20)
78        self.assertEqual(p1, Gf.Plane(Gf.Vec3d(1,1,1), 10), err("equality"))
79        self.assertTrue(not p1 == p2, err("equality"))
80        self.assertTrue(not p1 != Gf.Plane(Gf.Vec3d(1,1,1), 10), err("inequality"))
81        self.assertTrue(p1 != p2, err("inequality"))
82
83    def test_Methods(self):
84        p = Gf.Plane(Gf.Vec3d(1,1,1), 1)
85        self.assertTrue(Gf.IsClose(p.GetDistance(Gf.Vec3d(2,2,2)), 2.4641016151377553, 0.00001),
86            err("GetDistance"))
87
88        p = Gf.Plane(Gf.Vec3d(1,1,1), 0)
89        self.assertTrue(Gf.IsClose(p.GetDistance(Gf.Vec3d(0,0,0)), 0, 0.00001), \
90            err("GetDistance"))
91
92
93        p = Gf.Plane(Gf.Vec3d(0,1,0), 0)
94        self.assertEqual(p.Project(Gf.Vec3d(3, 3, 3)), Gf.Vec3d(3, 0, 3), err("Project"))
95
96        p = Gf.Plane(Gf.Vec3d(1,1,1), 0)
97        p.Transform(Gf.Matrix4d().SetRotate(Gf.Rotation(Gf.Vec3d(1,0,0), 30)))
98        self.assertTrue(Gf.IsClose(p.normal, Gf.Vec3d(0.57735, 0.211325, 0.788675), 0.0001))
99
100        p = Gf.Plane(Gf.Vec3d(1,1,1), 1)
101        self.assertEqual(p.normal * -1, p.Reorient(Gf.Vec3d()).normal, err("Reorient"))
102
103        b = Gf.Range3d()
104        self.assertFalse(Gf.Plane(Gf.Vec3d(1,1,1), 1).IntersectsPositiveHalfSpace(b),
105                err("IntersectsPositiveHalfSpace"))
106
107        b = Gf.Range3d(Gf.Vec3d(-1,-1,-1), Gf.Vec3d(1,1,1))
108        self.assertTrue(Gf.Plane(Gf.Vec3d(1,1,1), 1).IntersectsPositiveHalfSpace(b),
109                err("IntersectsPositiveHalfSpace"))
110        self.assertTrue(Gf.Plane(Gf.Vec3d(1,1,-1), 1).IntersectsPositiveHalfSpace(b),
111                err("IntersectsPositiveHalfSpace"))
112        self.assertTrue(Gf.Plane(Gf.Vec3d(1,-1,1), 1).IntersectsPositiveHalfSpace(b),
113                err("IntersectsPositiveHalfSpace"))
114        self.assertTrue(Gf.Plane(Gf.Vec3d(1,-1,-1), 1).IntersectsPositiveHalfSpace(b),
115                err("IntersectsPositiveHalfSpace"))
116        self.assertTrue(Gf.Plane(Gf.Vec3d(-1,1,1), 1).IntersectsPositiveHalfSpace(b),
117                err("IntersectsPositiveHalfSpace"))
118        self.assertTrue(Gf.Plane(Gf.Vec3d(-1,1,-1), 1).IntersectsPositiveHalfSpace(b),
119                err("IntersectsPositiveHalfSpace"))
120        self.assertTrue(Gf.Plane(Gf.Vec3d(-1,-1,1), 1).IntersectsPositiveHalfSpace(b),
121            err("IntersectsPositiveHalfSpace"))
122        self.assertTrue(Gf.Plane(Gf.Vec3d(-1,-1,-1), 1).IntersectsPositiveHalfSpace(b),
123            err("IntersectsPositiveHalfSpace"))
124
125        p = Gf.Plane(Gf.Vec3d(1,1,1), 10)
126        self.assertFalse(p.IntersectsPositiveHalfSpace(Gf.Range3d(Gf.Vec3d(), Gf.Vec3d(1,1,1))),
127            err("IntersectsPositiveHalfSpace"))
128
129        p = Gf.Plane(Gf.Vec3d(1,1,1), 1)
130        self.assertTrue(p.IntersectsPositiveHalfSpace(Gf.Vec3d(1,1,1)),
131            err("IntersectsPositiveHalfSpace"))
132
133        p = Gf.Plane(Gf.Vec3d(1,1,1), 10)
134        self.assertFalse(p.IntersectsPositiveHalfSpace(Gf.Vec3d()),
135            err("IntersectsPositiveHalfSpace"))
136
137        self.assertEqual(p, eval(repr(p)), err("repr"))
138
139        self.assertTrue(len(str(Gf.Plane())), err("str"))
140
141    def test_Fitting(self):
142        # Collinear points should not define a plane.
143        a = Gf.Vec3d(0, 0, 0)
144        b = Gf.Vec3d(1, 0, 0)
145        c = Gf.Vec3d(2, 0, 0)
146        self.assertIsNone(Gf.FitPlaneToPoints([a, b, c]), err("collinear"))
147
148        # Cannot fit plane to 2 or fewer points.
149        with self.assertRaises(Tf.ErrorException):
150            Gf.FitPlaneToPoints([a, b])
151
152        # Perfect fit (normal should be parallel to Z-axis, but OK if opposite
153        # direction).
154        c = Gf.Vec3d(0, 1, 0)
155        p = Gf.FitPlaneToPoints([a, b, c])
156        self.assertAlmostEqual(Gf.Dot(p.GetNormal(), Gf.Vec3d.ZAxis()), 1.0,
157                msg=err("normal1"))
158        self.assertAlmostEqual(p.GetDistanceFromOrigin(), 0.0,
159                msg=err("distance1"))
160
161        # Try the same plane but with non-unit vectors.
162        b = Gf.Vec3d(1.5, 0, 0)
163        c = Gf.Vec3d(0, 3.2, 0)
164        p = Gf.FitPlaneToPoints([a, b, c])
165        self.assertAlmostEqual(Gf.Dot(p.GetNormal(), Gf.Vec3d.ZAxis()), 1.0,
166                msg=err("normal2"))
167        self.assertAlmostEqual(p.GetDistanceFromOrigin(), 0.0,
168                msg=err("distance2"))
169
170        # Try a more complicated plane.
171        p1 = Gf.Plane(Gf.Vec4d(3, 4, 0, 5)) # equation constructor
172        a = p1.Project(Gf.Vec3d(2, 3, 6))
173        b = p1.Project(Gf.Vec3d(34, -2, 2))
174        c = p1.Project(Gf.Vec3d(-3, 7, -8))
175        d = p1.Project(Gf.Vec3d(4, 1, 1))
176        e = p1.Project(Gf.Vec3d(87, 67, 92))
177        p2 = Gf.FitPlaneToPoints([a, b, c])
178        self.assertAlmostEqual(
179                Gf.Dot(p1.GetNormal(), p2.GetNormal()), 1.0,
180                msg=err("p2 normal parallel to p1"))
181        self.assertAlmostEqual(
182                p1.GetDistanceFromOrigin(),
183                p2.GetDistanceFromOrigin(),
184                msg=err("p2 distance equals p1"))
185        p3 = Gf.FitPlaneToPoints([a, b, c, d, e])
186        self.assertAlmostEqual(
187                Gf.Dot(p1.GetNormal(), p3.GetNormal()), 1.0,
188                msg=err("p3 normal parallel to p1"))
189        self.assertAlmostEqual(
190                p1.GetDistanceFromOrigin(),
191                p3.GetDistanceFromOrigin(),
192                msg=err("p3 distance equals p1"))
193
194        # Try fitting points that don't form a perfect plane.
195        # This roughly forms the plane with normal (1, -1, 0) passing through
196        # the origin.
197        # Decrease the number of places of accuracy since these points are
198        # fudged and don't form an exact plane.
199        a = Gf.Vec3d(1.1, 1, 5)
200        b = Gf.Vec3d(1, 1.1, 2)
201        c = Gf.Vec3d(2, 2.1, -4)
202        d = Gf.Vec3d(2.1, 2, 1)
203        e = Gf.Vec3d(25.3, 25.2, 3)
204        f = Gf.Vec3d(25.1, 25.4, 6)
205        p = Gf.FitPlaneToPoints([a, b, c, d, e, f])
206        expectedNormal = Gf.Vec3d(1, -1, 0).GetNormalized()
207        self.assertAlmostEqual(
208                Gf.Dot(p.GetNormal(), expectedNormal), 1.0,
209                places=2,
210                msg=err("normal3"))
211        self.assertAlmostEqual(
212                p.GetDistanceFromOrigin(), 0.0,
213                places=2,
214                msg=err("distance3"))
215
216if __name__ == '__main__':
217    unittest.main()
218