1 module frustum_ray_builder;
2 
3 import Math = math;
4 
5 import matrix_4d;
6 
7 import vector_3d;
8 
9 /*
10  * The MIT License
11  *
12  * Copyright (c) 2015-2021 Kai Burjack
13  *
14  * Permission is hereby granted, free of charge, to any person obtaining a copy
15  * of this software and associated documentation files (the "Software"), to deal
16  * in the Software without restriction, including without limitation the rights
17  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18  * copies of the Software, and to permit persons to whom the Software is
19  * furnished to do so, subject to the following conditions:
20  *
21  * The above copyright notice and this permission notice shall be included in
22  * all copies or substantial portions of the Software.
23  *
24  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
30  * THE SOFTWARE.
31  */
32 
33 /**
34  * Provides methods to compute rays through an arbitrary perspective transformation defined by a {@link Matrix4d}.
35  * <p>
36  * This can be used to compute the eye-rays in simple software-based raycasting/raytracing.
37  * <p>
38  * To obtain the origin of the rays call {@link #origin(Vector3d)}.
39  * Then to compute the directions of subsequent rays use {@link #dir(float, float, Vector3d)}.
40  * 
41  * @author Kai Burjack
42  */
43 struct FrustumRayBuilder {
44 
45     private float nxnyX, nxnyY, nxnyZ;
46     private float pxnyX, pxnyY, pxnyZ;
47     private float pxpyX, pxpyY, pxpyZ;
48     private float nxpyX, nxpyY, nxpyZ;
49     private float cx, cy, cz;
50 
51     /**
52      * Create a new {@link FrustumRayBuilder} from the given {@link Matrix4d matrix} by extracing the matrix's frustum.
53      * 
54      * @param m
55      *          the {@link Matrix4d} to create the frustum from
56      */
57     this(Matrix4d m) {
58         set(m);
59     }
60 
61     /**
62      * Update the stored frustum corner rays and origin of <code>this</code> {@link FrustumRayBuilder} with the given {@link Matrix4d matrix}.
63      * <p>
64      * Reference: <a href="http://gamedevs.org/uploads/fast-extraction-viewing-frustum-planes-from-world-view-projection-matrix.pdf">
65      * Fast Extraction of Viewing Frustum Planes from the World-View-Projection Matrix</a>
66      * <p>
67      * Reference: <a href="http://geomalgorithms.com/a05-_intersect-1.html">http://geomalgorithms.com</a>
68      * 
69      * @param m
70      *          the {@link Matrix4d matrix} to update the frustum corner rays and origin with
71      * @return this
72      */
73     ref public FrustumRayBuilder set(Matrix4d m) return {
74         float nxX = m.m03 + m.m00, nxY = m.m13 + m.m10, nxZ = m.m23 + m.m20, d1 = m.m33 + m.m30;
75         float pxX = m.m03 - m.m00, pxY = m.m13 - m.m10, pxZ = m.m23 - m.m20, d2 = m.m33 - m.m30;
76         float nyX = m.m03 + m.m01, nyY = m.m13 + m.m11, nyZ = m.m23 + m.m21;
77         float pyX = m.m03 - m.m01, pyY = m.m13 - m.m11, pyZ = m.m23 - m.m21, d3 = m.m33 - m.m31;
78         // bottom left
79         nxnyX = nyY * nxZ - nyZ * nxY;
80         nxnyY = nyZ * nxX - nyX * nxZ;
81         nxnyZ = nyX * nxY - nyY * nxX;
82         // bottom right
83         pxnyX = pxY * nyZ - pxZ * nyY;
84         pxnyY = pxZ * nyX - pxX * nyZ;
85         pxnyZ = pxX * nyY - pxY * nyX;
86         // top left
87         nxpyX = nxY * pyZ - nxZ * pyY;
88         nxpyY = nxZ * pyX - nxX * pyZ;
89         nxpyZ = nxX * pyY - nxY * pyX;
90         // top right
91         pxpyX = pyY * pxZ - pyZ * pxY;
92         pxpyY = pyZ * pxX - pyX * pxZ;
93         pxpyZ = pyX * pxY - pyY * pxX;
94         // compute origin
95         float pxnxX, pxnxY, pxnxZ;
96         pxnxX = pxY * nxZ - pxZ * nxY;
97         pxnxY = pxZ * nxX - pxX * nxZ;
98         pxnxZ = pxX * nxY - pxY * nxX;
99         float invDot = 1.0f / (nxX * pxpyX + nxY * pxpyY + nxZ * pxpyZ);
100         cx = (-pxpyX * d1 - nxpyX * d2 - pxnxX * d3) * invDot;
101         cy = (-pxpyY * d1 - nxpyY * d2 - pxnxY * d3) * invDot;
102         cz = (-pxpyZ * d1 - nxpyZ * d2 - pxnxZ * d3) * invDot;
103         return this;
104     }
105 
106     /**
107      * Store the eye/origin of the perspective frustum in the given <code>origin</code>.
108      * 
109      * @param origin
110      *          will hold the perspective origin
111      * @return the <code>origin</code> vector
112      */
113     public Vector3d origin(Vector3d origin) {
114         origin.x = cx;
115         origin.y = cy;
116         origin.z = cz;
117         return origin;
118     }
119 
120     /**
121      * Obtain the normalized direction of a ray starting at the center of the coordinate system and going 
122      * through the near frustum plane.
123      * <p>
124      * The parameters <code>x</code> and <code>y</code> are used to interpolate the generated ray direction
125      * from the bottom-left to the top-right frustum corners.
126      * 
127      * @param x
128      *          the interpolation factor along the left-to-right frustum planes, within <code>[0..1]</code>
129      * @param y
130      *          the interpolation factor along the bottom-to-top frustum planes, within <code>[0..1]</code>
131      * @param dir
132      *          will hold the normalized ray direction
133      * @return the <code>dir</code> vector
134      */
135     public Vector3d dir(float x, float y, Vector3d dir) {
136         float y1x = nxnyX + (nxpyX - nxnyX) * y;
137         float y1y = nxnyY + (nxpyY - nxnyY) * y;
138         float y1z = nxnyZ + (nxpyZ - nxnyZ) * y;
139         float y2x = pxnyX + (pxpyX - pxnyX) * y;
140         float y2y = pxnyY + (pxpyY - pxnyY) * y;
141         float y2z = pxnyZ + (pxpyZ - pxnyZ) * y;
142         float dx = y1x + (y2x - y1x) * x;
143         float dy = y1y + (y2y - y1y) * x;
144         float dz = y1z + (y2z - y1z) * x;
145         // normalize the vector
146         float invLen = Math.invsqrt(dx * dx + dy * dy + dz * dz);
147         dir.x = dx * invLen;
148         dir.y = dy * invLen;
149         dir.z = dz * invLen;
150         return dir;
151     }
152 
153 }