The open source OpenXR runtime
1// Copyright 2019, Collabora, Ltd.
2// SPDX-License-Identifier: BSL-1.0
3/*!
4 * @file
5 * @brief Functions related to field-of-view.
6 * @author Rylie Pavlik <rylie.pavlik@collabora.com>
7 * @ingroup aux_math
8 */
9
10#include "math/m_mathinclude.h"
11#include "math/m_api.h"
12#include "util/u_debug.h"
13
14#include <math.h>
15#include <stdio.h>
16#include <assert.h>
17
18
19DEBUG_GET_ONCE_BOOL_OPTION(views, "MATH_DEBUG_VIEWS", false)
20
21/*!
22 * Perform some of the computations from
23 * "Computing Half-Fields-Of-View from Simpler Display Models",
24 * to solve for the half-angles for a triangle where we know the center and
25 * total angle but not the "distance".
26 *
27 * In the diagram below, the top angle is theta_total, the length of the bottom
28 * is w_total, and the distance between the vertical line and the left corner is
29 * w_1.
30 * out_theta_1 is the angle at the top of the left-most right triangle,
31 * out_theta_2 is the angle at the top of the right-most right triangle,
32 * and out_d is the length of that center vertical line, a logical "distance".
33 *
34 * Any outparams that are NULL will simply not be set.
35 *
36 * The triangle need not be symmetrical, despite how the diagram looks.
37 *
38 * ```
39 * theta_total
40 * *
41 * theta_1 -> / | \ <- theta_2
42 * / | \
43 * / |d \
44 * / | \
45 * -------------
46 * [ w_1 ][ w_2 ]
47 *
48 * [ --- w --- ]
49 * ```
50 *
51 * Distances are in arbitrary but consistent units. Angles are in radians.
52 *
53 * @return true if successful.
54 */
55static bool
56math_solve_triangle(
57 double w_total, double w_1, double theta_total, double *out_theta_1, double *out_theta_2, double *out_d)
58{
59 /* should have at least one out-variable */
60 assert(out_theta_1 || out_theta_2 || out_d);
61 const double w_2 = w_total - w_1;
62
63 const double u = w_2 / w_1;
64 const double v = tan(theta_total);
65
66 /* Parts of the quadratic formula solution */
67 const double b = u + 1.0;
68 const double root = sqrt(b * b + 4 * u * v * v);
69 const double two_a = 2 * v;
70
71 /* The two possible solutions. */
72 const double tan_theta_2_plus = (-b + root) / two_a;
73 const double tan_theta_2_minus = (-b - root) / two_a;
74 const double theta_2_plus = atan(tan_theta_2_plus);
75 const double theta_2_minus = atan(tan_theta_2_minus);
76
77 /* Pick the solution that is in the right range. */
78 double tan_theta_2 = 0;
79 double theta_2 = 0;
80 if (theta_2_plus > 0.f && theta_2_plus < theta_total) {
81 // OH_DEBUG(ohd, "Using the + solution to the quadratic.");
82 tan_theta_2 = tan_theta_2_plus;
83 theta_2 = theta_2_plus;
84 } else if (theta_2_minus > 0.f && theta_2_minus < theta_total) {
85 // OH_DEBUG(ohd, "Using the - solution to the quadratic.");
86 tan_theta_2 = tan_theta_2_minus;
87 theta_2 = theta_2_minus;
88 } else {
89 // OH_ERROR(ohd, "NEITHER QUADRATIC SOLUTION APPLIES!");
90 return false;
91 }
92#define METERS_FORMAT "%0.4fm"
93#define DEG_FORMAT "%0.1f deg"
94 if (debug_get_bool_option_views()) {
95 const double rad_to_deg = M_1_PI * 180.0;
96 // comments are to force wrapping
97 U_LOG_D("w=" METERS_FORMAT " theta=" DEG_FORMAT " w1=" METERS_FORMAT " theta1=" DEG_FORMAT
98 " w2=" METERS_FORMAT " theta2=" DEG_FORMAT " d=" METERS_FORMAT,
99 w_total, theta_total * rad_to_deg, //
100 w_1, (theta_total - theta_2) * rad_to_deg, //
101 w_2, theta_2 * rad_to_deg, //
102 w_2 / tan_theta_2);
103 }
104 if (out_theta_2) {
105 *out_theta_2 = theta_2;
106 }
107
108 if (out_theta_1) {
109 *out_theta_1 = theta_total - theta_2;
110 }
111 if (out_d) {
112 *out_d = w_2 / tan_theta_2;
113 }
114 return true;
115}
116
117bool
118math_compute_fovs(double w_total,
119 double w_1,
120 double horizfov_total,
121 double h_total,
122 double h_1,
123 double vertfov_total,
124 struct xrt_fov *fov)
125{
126 double d = 0;
127 double theta_1 = 0;
128 double theta_2 = 0;
129 if (!math_solve_triangle(w_total, w_1, horizfov_total, &theta_1, &theta_2, &d)) {
130 /* failure is contagious */
131 return false;
132 }
133
134 fov->angle_left = (float)-theta_1;
135 fov->angle_right = (float)theta_2;
136
137 double phi_1 = 0;
138 double phi_2 = 0;
139 if (vertfov_total == 0) {
140 phi_1 = atan(h_1 / d);
141
142 /* h_2 is "up".
143 * so the corresponding phi_2 is naturally positive.
144 */
145 const double h_2 = h_total - h_1;
146 phi_2 = atan(h_2 / d);
147 } else {
148 /* Run the same algorithm again for vertical. */
149 if (!math_solve_triangle(h_total, h_1, vertfov_total, &phi_1, &phi_2, NULL)) {
150 /* failure is contagious */
151 return false;
152 }
153 }
154
155 /* phi_1 is "down" so we record this as negative. */
156 fov->angle_down = (float)(-phi_1);
157 fov->angle_up = (float)phi_2;
158
159 return true;
160}