File: AxisAlignedBox.py

package info (click to toggle)
uranium 5.0.0-7
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 5,304 kB
  • sloc: python: 31,765; sh: 132; makefile: 12
file content (178 lines) | stat: -rw-r--r-- 6,126 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# Copyright (c) 2018 Ultimaker B.V.
# Uranium is released under the terms of the LGPLv3 or higher.

from UM.Math.Float import Float
from UM.Math.Ray import Ray #For typing.
from UM.Math.Vector import Vector

import numpy

from typing import Optional, Tuple, Union


class AxisAlignedBox:
    """Axis aligned bounding box."""

    class IntersectionResult:
        NoIntersection = 1
        PartialIntersection = 2
        FullIntersection = 3

    def __init__(self, minimum: Vector = Vector.Null, maximum: Vector = Vector.Null) -> None:
        if minimum.x > maximum.x or minimum.y > maximum.y or minimum.z > maximum.z:
            swapped_minimum = Vector(min(minimum.x, maximum.x), min(minimum.y, maximum.y), min(minimum.z, maximum.z))
            swapped_maximum = Vector(max(minimum.x, maximum.x), max(minimum.y, maximum.y), max(minimum.z, maximum.z))
            minimum = swapped_minimum
            maximum = swapped_maximum
        minimum.setRoundDigits(3)
        maximum.setRoundDigits(3)
        self._min = minimum #type: Vector
        self._max = maximum #type: Vector

    def set(self, minimum: Optional[Vector] = None, maximum: Optional[Vector] = None, left: Optional[float] = None,
            right: Optional[float] = None, top: Optional[float] = None, bottom: Optional[float] = None,
            front: Optional[float] = None, back: Optional[float] = None) -> "AxisAlignedBox":
        if minimum is None:
            minimum = self._min

        if maximum is None:
            maximum = self._max

        if left is not None or bottom is not None or back is not None:
            left = minimum.x if left is None else left
            bottom = minimum.y if bottom is None else bottom
            back = minimum.z if back is None else back
            minimum = Vector(left, bottom, back)

        if right is not None or top is not None or front is not None:
            right = maximum.x if right is None else right
            top = maximum.y if top is None else top
            front = maximum.z if front is None else front
            maximum = Vector(right, top, front)

        return AxisAlignedBox(minimum, maximum)

    def __add__(self, other: object) -> "AxisAlignedBox":
        if other is None or not isinstance(other, AxisAlignedBox) or not other.isValid():
            return self

        new_min = Vector(min(self._min.x, other.left), min(self._min.y, other.bottom),
                         min(self._min.z, other.back))
        new_max = Vector(max(self._max.x, other.right), max(self._max.y, other.top),
                         max(self._max.z, other.front))
        return AxisAlignedBox(minimum=new_min, maximum=new_max)

    def __iadd__(self, other: object) -> "AxisAlignedBox":
        raise NotImplementedError()

    @property
    def width(self) -> float:
        return self._max.x - self._min.x

    @property
    def height(self) -> float:
        return self._max.y - self._min.y

    @property
    def depth(self) -> float:
        return self._max.z - self._min.z

    @property
    def center(self) -> Vector:
        return self._min + ((self._max - self._min) / 2.0)

    @property
    def left(self) -> float:
        return self._min.x

    @property
    def right(self) -> float:
        return self._max.x

    @property
    def bottom(self) -> float:
        return self._min.y

    @property
    def top(self) -> float:
        return self._max.y

    @property
    def back(self) -> float:
        return self._min.z

    @property
    def front(self) -> float:
        return self._max.z

    @property
    def minimum(self) -> Vector:
        return self._min

    @property
    def maximum(self) -> Vector:
        return self._max

    def isValid(self) -> bool:
        """Check if the bounding box is valid.
        Uses fuzzycompare to validate.
        :sa Float::fuzzyCompare()
        """

        return not(Float.fuzzyCompare(self._min.x, self._max.x) or
                   Float.fuzzyCompare(self._min.y, self._max.y) or
                   Float.fuzzyCompare(self._min.z, self._max.z))

    def intersectsRay(self, ray: Ray) -> Union[Tuple[float, float], bool]:
        """Intersect the bounding box with a ray
        :sa Ray
        """

        inv = ray.inverseDirection

        t = numpy.empty((2,3), dtype=numpy.float32)
        t[0, 0] = inv.x * (self._min.x - ray.origin.x)
        t[0, 1] = inv.y * (self._min.y - ray.origin.y)
        t[0, 2] = inv.z * (self._min.z - ray.origin.z)
        t[1, 0] = inv.x * (self._max.x - ray.origin.x)
        t[1, 1] = inv.y * (self._max.y - ray.origin.y)
        t[1, 2] = inv.z * (self._max.z - ray.origin.z)

        tmin = numpy.min(t, axis=0)
        tmax = numpy.max(t, axis=0)

        largest_min = numpy.max(tmin)
        smallest_max = numpy.min(tmax)

        if smallest_max > largest_min:
            return (largest_min, smallest_max)
        else:
            return False

    def intersectsBox(self, box: "AxisAlignedBox") -> int:
        """Check to see if this box intersects another box.

        :param box: The box to check for intersection.
        :return: NoIntersection when no intersection occurs, PartialIntersection when partially intersected, FullIntersection when box is fully contained inside this box.
        """

        if self._min.x > box._max.x or box._min.x > self._max.x:
            return self.IntersectionResult.NoIntersection

        if self._min.y > box._max.y or box._min.y > self._max.y:
            return self.IntersectionResult.NoIntersection

        if self._min.z > box._max.z or box._min.z > self._max.z:
            return self.IntersectionResult.NoIntersection

        if box._min >= self._min and box._max <= self._max:
            return self.IntersectionResult.FullIntersection

        return self.IntersectionResult.PartialIntersection

    def __repr__(self) -> str:
        return "AxisAlignedBox(min = {0}, max = {1})".format(self._min, self._max)

    # This field is filled in below. This is needed to help static analysis tools (read: PyCharm)
    Null = None  # type: AxisAlignedBox
AxisAlignedBox.Null = AxisAlignedBox()