summaryrefslogtreecommitdiffstats
path: root/tests/fcpw/bounding-volumes.slang
blob: 39094f228a710cce7e03b4b196a6cdd080ddbe5a (plain)
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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
implementing fcpw;
__include ray;
__include math_constants;

public struct BoundingSphere
{
    public float3 c; // sphere center
    public float r2; // sphere squared radius

    // constructor
    public __init(float3 c_, float r2_)
    {
        c = c_;
        r2 = r2_;
    }
};

public struct BoundingBox
{
    public float3 pMin; // aabb min position
    public float3 pMax; // aabb max position

    // constructor
    public __init(float3 pMin_, float3 pMax_)
    {
        pMin = pMin_;
        pMax = pMax_;
    }

    // checks whether box is valid
    public bool isValid()
    {
        return all(pMin <= pMax);
    }

    // returns box centroid
    public float3 getCentroid()
    {
        return 0.5 * (pMin + pMax);
    }

    // checks for overlap with sphere
    public bool overlap(BoundingSphere s, out float d2Min, out float d2Max)
    {
        float3 u = pMin - s.c;
        float3 v = s.c - pMax;
        float3 a = max(max(u, v), float3(0.0, 0.0, 0.0));
        float3 b = min(u, v);
        d2Min = dot(a, a);
        d2Max = dot(b, b);

        return d2Min <= s.r2;
    }

    // checks for overlap with sphere
    public bool overlap(BoundingSphere s, out float d2Min)
    {
        float3 u = pMin - s.c;
        float3 v = s.c - pMax;
        float3 a = max(max(u, v), float3(0.0, 0.0, 0.0));
        d2Min = dot(a, a);

        return d2Min <= s.r2;
    }

    // intersects box with ray
    public bool intersect(Ray r, out float tMin, out float tMax)
    {
        // slab test for ray box intersection
        // source: http://www.jcgt.org/published/0007/03/04/paper-lowres.pdf
        float3 t0 = (pMin - r.o) * r.dInv;
        float3 t1 = (pMax - r.o) * r.dInv;
        float3 tNear = min(t0, t1);
        float3 tFar = max(t0, t1);

        float tNearMax = max(0.0, max(tNear.x, max(tNear.y, tNear.z)));
        float tFarMin = min(r.tMax, min(tFar.x, min(tFar.y, tFar.z)));
        if (tNearMax > tFarMin)
        {
            tMin = FLT_MAX;
            tMax = FLT_MAX;
            return false;
        }

        tMin = tNearMax;
        tMax = tFarMin;
        return true;
    }
};

public BoundingBox mergeBoundingBoxes(BoundingBox boxA, BoundingBox boxB)
{
    BoundingBox box;
    box.pMin = min(boxA.pMin, boxB.pMin);
    box.pMax = max(boxA.pMax, boxB.pMax);

    return box;
}

public bool inRange(float val, float low, float high)
{
    return val >= low && val <= high;
}

public void computeOrthonormalBasis(float3 n, out float3 b1, out float3 b2)
{
    // source: https://graphics.pixar.com/library/OrthonormalB/paper.pdf
    float sign = n.z >= 0.0 ? 1.0 : -1.0;
    float a = -1.0 / (sign + n.z);
    float b = n.x * n.y * a;

    b1 = float3(1.0 + sign * n.x * n.x * a, sign * b, -sign * n.x);
    b2 = float3(b, sign + n.y * n.y * a, -n.y);
}

public float projectToPlane(float3 n, float3 e)
{
    // compute orthonormal basis
    float3 b1, b2;
    computeOrthonormalBasis(n, b1, b2);

    // compute maximal projection radius
    float r1 = dot(e, abs(b1));
    float r2 = dot(e, abs(b2));
    return sqrt(r1 * r1 + r2 * r2);
}

public struct BoundingCone
{
    public float3 axis;     // cone axis
    public float halfAngle; // cone half angle
    public float radius;    // cone radius

    // constructors
    public __init()
    {
        axis = float3(0.0, 0.0, 0.0);
        halfAngle = M_PI;
        radius = 0.0;
    }
    public __init(float3 axis_, float halfAngle_, float radius_)
    {
        axis = axis_;
        halfAngle = halfAngle_;
        radius = radius_;
    }

    // checks whether cone is valid
    public bool isValid()
    {
        return halfAngle >= 0.0;
    }

    // check for overlap between this cone and the "view" cone defined by the given
    // point o and bounding box b; the two cones overlap when there exist two vectors,
    // one in each cone, that are orthogonal to each other.
    public bool overlap(float3 o, BoundingBox b, float distToBox,
                        out float minAngleRange, out float maxAngleRange)
    {
        // initialize angle bounds
        minAngleRange = 0.0f;
        maxAngleRange = M_PI_2;

        // there's overlap if this cone's halfAngle is greater than 90 degrees, or
        // if the box contains the view cone origin (since the view cone is invalid)
        if (halfAngle >= M_PI_2 || distToBox < FLT_EPSILON)
        {
            return true;
        }

        // compute the view cone axis
        float3 c = b.getCentroid();
        float3 viewConeAxis = c - o;
        float l = length(viewConeAxis);
        viewConeAxis /= l;

        // check for overlap between the view cone axis and this cone
        float dAxisAngle = acos(max(-1.0, min(1.0, dot(axis, viewConeAxis)))); // [0, 180]
        if (inRange(M_PI_2, dAxisAngle - halfAngle, dAxisAngle + halfAngle))
        {
            return true;
        }

        // check if the view cone origin lies outside this cone's bounding sphere;
        // if it does, compute the view cone halfAngle and check for overlap
        if (l > radius)
        {
            float viewConeHalfAngle = asin(radius / l);
            float halfAngleSum = halfAngle + viewConeHalfAngle;
            minAngleRange = dAxisAngle - halfAngleSum;
            maxAngleRange = dAxisAngle + halfAngleSum;
            return halfAngleSum >= M_PI_2 ? true : inRange(M_PI_2, minAngleRange, maxAngleRange);
        }

        // the view cone origin lies inside the box's bounding sphere, so check if
        // the plane defined by the view cone axis intersects the box; if it does, then
        // there's overlap since the view cone has a halfAngle greater than 90 degrees
        float3 e = b.pMax - c;
        float d = dot(e, abs(viewConeAxis)); // max projection length onto axis
        float s = l - d;
        if (s <= 0.0)
        {
            return true;
        }

        // compute the view cone halfAngle by projecting the max extents of the box
        // onto the plane, and check for overlap
        d = projectToPlane(viewConeAxis, e);
        float viewConeHalfAngle = atan2(d, s);
        float halfAngleSum = halfAngle + viewConeHalfAngle;
        minAngleRange = dAxisAngle - halfAngleSum;
        maxAngleRange = dAxisAngle + halfAngleSum;
        return halfAngleSum >= M_PI_2 ? true : inRange(M_PI_2, minAngleRange, maxAngleRange);
    }
};

public float3 rotate(float3 u, float3 v, float theta)
{
    float cosTheta = cos(theta);
    float sinTheta = sin(theta);
    float3 w = length(cross(u, v));
    float3 oneMinusCosThetaW = (1.0 - cosTheta) * w;

    float3x3 R;
    R[0][0] = cosTheta + oneMinusCosThetaW[0] * w[0];
    R[0][1] = oneMinusCosThetaW[1] * w[0] - sinTheta * w[2];
    R[0][2] = oneMinusCosThetaW[2] * w[0] + sinTheta * w[1];
    R[1][0] = oneMinusCosThetaW[0] * w[1] + sinTheta * w[2];
    R[1][1] = cosTheta + oneMinusCosThetaW[1] * w[1];
    R[1][2] = oneMinusCosThetaW[2] * w[1] - sinTheta * w[0];
    R[2][0] = oneMinusCosThetaW[0] * w[2] - sinTheta * w[1];
    R[2][1] = oneMinusCosThetaW[1] * w[2] + sinTheta * w[0];
    R[2][2] = cosTheta + oneMinusCosThetaW[2] * w[2];

    return mul(R, u);
}

public BoundingCone mergeBoundingCones(BoundingCone coneA, BoundingCone coneB,
                                       float3 originA, float3 originB,
                                       float3 newOrigin)
{
    BoundingCone cone;
    if (coneA.isValid() && coneB.isValid())
    {
        float3 axisA = coneA.axis;
        float3 axisB = coneB.axis;
        float halfAngleA = coneA.halfAngle;
        float halfAngleB = coneB.halfAngle;
        float3 dOriginA = newOrigin - originA;
        float3 dOriginB = newOrigin - originB;
        cone.radius = sqrt(max(coneA.radius * coneA.radius + dot(dOriginA, dOriginA),
                               coneB.radius * coneB.radius + dot(dOriginB, dOriginB)));

        if (halfAngleB > halfAngleA)
        {
            float3 tmpAxis = axisA;
            axisA = axisB;
            axisB = tmpAxis;

            float tmpHalfAngle = halfAngleA;
            halfAngleA = halfAngleB;
            halfAngleB = tmpHalfAngle;
        }

        float theta = acos(max(-1.0, min(1.0, dot(axisA, axisB))));
        if (min(theta + halfAngleB, M_PI) <= halfAngleA)
        {
            // right cone is completely inside left cone
            cone.axis = axisA;
            cone.halfAngle = halfAngleA;
            return cone;
        }

        // merge cones by first computing the spread angle of the cone to cover both cones
        float oTheta = (halfAngleA + theta + halfAngleB) / 2.0;
        if (oTheta >= M_PI)
        {
            cone.axis = axisA;
            return cone;
        }

        float rTheta = oTheta - halfAngleA;
        cone.axis = rotate(axisA, axisB, rTheta);
        cone.halfAngle = oTheta;
    }
    else if (coneA.isValid())
    {
        cone = coneA;
    }
    else if (coneB.isValid())
    {
        cone = coneB;
    }
    else
    {
        cone.halfAngle = -M_PI;
    }

    return cone;
}