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