Cogs.Core
TessellationPredicates.cpp
1#include "Utilities/TessellationPredicates.h"
2
3#define _USE_MATH_DEFINES
4#include <cmath>
5#include <algorithm>
6
7glm::vec2 Cogs::Core::localSphereRadiusInScreenSpace(const glm::mat4& /*localToClip*/,
8 const glm::mat4& clipToLocal,
9 const glm::vec4& clipPosition,
10 const float localRadius)
11{
12 glm::vec4 locPos4 = clipToLocal*clipPosition;
13 glm::vec3 locPos3 = (1.f / locPos4.w)*glm::vec3(locPos4);
14
15 glm::vec4 locPosDispX4 = clipToLocal*(clipPosition + glm::vec4(clipPosition.w, 0.f, 0.f, 0.f));
16 glm::vec3 locPosDispX3 = (1.f / locPosDispX4.w)*glm::vec3(locPosDispX4);
17
18 glm::vec4 locPosDispY4 = clipToLocal*(clipPosition + glm::vec4(0.f, clipPosition.w, 0.f, 0.f));
19 glm::vec3 locPosDispY3 = (1.f / locPosDispY4.w)*glm::vec3(locPosDispY4);
20
21 // radius in x and y pixels
22 return localRadius*glm::vec2(1.f / glm::distance(locPos3, locPosDispX3),
23 1.f / glm::distance(locPos3, locPosDispY3));
24#if 0
25 float screenSpaceRadius = 0.f;
26
27 glm::vec4 locPos4 = clipToLocal*screenPosition;
28 glm::vec3 locPos3 = (1.f / locPos4.w)*glm::vec3(locPos4);
29
30 {
31 glm::vec4 xp4 = localToClip*glm::vec4(locPos3 + glm::vec3(localRadius, 0.f, 0.f), 1.f);
32 glm::vec4 xm4 = localToClip*glm::vec4(locPos3 - glm::vec3(localRadius, 0.f, 0.f), 1.f);
33 if ((xp4.z > -xp4.w) && (xm4.z > -xm4.w)) {
34 glm::vec2 p = (0.5f / xp4.w)*viewportSize*glm::vec2(xp4.x, xp4.y);
35 glm::vec2 m = (0.5f / xm4.w)*viewportSize*glm::vec2(xm4.x, xm4.y);
36 screenSpaceRadius = glm::max(screenSpaceRadius, glm::distance(p, m));
37 }
38 }
39 {
40 glm::vec4 yp4 = localToClip*glm::vec4(locPos3 + glm::vec3(0.f, localRadius, 0.f), 1.f);
41 glm::vec4 ym4 = localToClip*glm::vec4(locPos3 - glm::vec3(0.f, localRadius, 0.f), 1.f);
42 if ((yp4.z > -yp4.w) && (ym4.z > -ym4.w)) {
43 glm::vec2 p = (0.5f / yp4.w)*viewportSize*glm::vec2(yp4.x, yp4.y);
44 glm::vec2 m = (0.5f / ym4.w)*viewportSize*glm::vec2(ym4.x, ym4.y);
45 screenSpaceRadius = glm::max(screenSpaceRadius, glm::distance(p, m));
46 }
47 }
48 {
49 glm::vec4 zp4 = localToClip*glm::vec4(locPos3 + glm::vec3(0.f, 0.f, localRadius), 1.f);
50 glm::vec4 zm4 = localToClip*glm::vec4(locPos3 - glm::vec3(0.f, 0.f, localRadius), 1.f);
51 if ((zp4.z > -zp4.w) && (zm4.z > -zm4.w)) {
52 glm::vec2 p = (0.5f / zp4.w)*viewportSize*glm::vec2(zp4.x, zp4.y);
53 glm::vec2 m = (0.5f / zm4.w)*viewportSize*glm::vec2(zm4.x, zm4.y);
54 screenSpaceRadius = glm::max(screenSpaceRadius, glm::distance(p, m));
55 }
56 }
57 return 0.5f*screenSpaceRadius;
58#endif
59}
60
61
62float Cogs::Core::sphereSectorGeometricDistancePredicate(const glm::vec2 screenSpaceRadii,
63 const glm::vec2 viewportSize,
64 const float distancePixelTolerance,
65 const float minSectors,
66 const float maxSectors)
67{
68 glm::vec2 pixelRadii = 0.5f*viewportSize*screenSpaceRadii;
69 float radius = glm::max(pixelRadii.x, pixelRadii.y);
70
71 // __________
72 // _//\ \_
73 // / /c \ \
74 // | \__q \ r |
75 // | /i \__\ |
76 // |/_____phi\ |
77 // |a b |
78 // | |
79 // | |
80 // \_ _/
81 // \___________/
82 //
83 // Let phi be the angle of the sector, and r is the radius of the circle, and
84 // let c be the coord of the sector. We want to limit the distance between the
85 // circular arc of the sector and the coord c.
86 //
87 // By bisecting the sector, we get q, which is orthogonal to c. If i is the
88 // intersection between c and q, we get a triangle [a,b,i] where the corner
89 // between [a,i] and [i,b] is pi/2, and thus the triangle is right. Hence,
90 // the length of [i,b] is r cos(phi/2),
91 //
92 // r - r cos (phi/2) <= tolerance
93 // 1 - cos(phi/2) <= tolerance/r
94 // 2 acos( 1 - tolerance/r) >= phi
95 //
96 // The number of sectors is n = 2pi/phi,
97 //
98 // 2 acos( 1 - tolerance/r) = 2pi/n
99 // n = pi/acos( 1 - tolerance/r).
100 //
101 static const float pi = float(M_PI);
102 float a = 1.f - distancePixelTolerance / radius;
103
104 // Make sure that we're inside acos' defined range
105 a = std::max(a, std::cos(pi / minSectors));
106 a = std::min(a, std::cos(pi / maxSectors));
107
108 float sectors = float(M_PI) / glm::max(std::numeric_limits<float>::epsilon(), std::acos(a));
109
110 return glm::clamp(sectors, minSectors, maxSectors);
111}
112
113float Cogs::Core::sphereSectorGeometricAreaPredicate(const glm::vec2 screenSpaceRadii,
114 const glm::vec2 viewportSize,
115 const float areaPixelTolerance,
116 const float minSectors,
117 const float maxSectors)
118{
119 glm::vec2 pixelRadii = 0.5f*viewportSize*screenSpaceRadii;
120 float radius = glm::max(pixelRadii.x, pixelRadii.y);
121
122 // __________
123 // _//\ \_
124 // /C/ \ \
125 // | / T \ r |
126 // | / \ |
127 // |/_____phi\ |
128 // | |
129 // | |
130 // | |
131 // \_ _/
132 // \___________/
133 //
134 // Given an angle phi, the area of the triangle T is (1/2) r^2 sin(phi).
135 // The area of the circular sector C is (pi r^2)/(phi/(2 pi)).
136 // The approximation error for phi is area(C)-area(T),
137 //
138 // (pi r^2)*(phi/(2 pi)) - (1/2) r^2 sin(phi) <= tolerance
139 // pi*(phi/(2 pi)) - (1/2) sin(phi) <= tolerance/r^2
140 // phi - sin(phi) <= 2 tolerance/r^2
141 //
142 // The Maclaurin series for sin(x) is
143 //
144 // sum_{n=0}^{\intfy} \frac{(-1)^n}{(2n+1)!}x^{2n+1} = x - x^3/3! + x^5/5! ...
145 //
146 // This is an alternating series (-1)^n with a_n = x^{2n+1}/(2n+1)!. If a_n+1 < a_n,
147 // we can use Leibnitz alternating series theorem to cap the error of a truncated series.
148 // Thus,
149 //
150 // a_{n+1} = x^{2(n+1)+1}/(2(n+1)+1)!
151 // = x^{2n+3}/(2n+3)!
152 // = (x^2/((2n+3)(2n+2))) x^{2n+1}/(2n+1)!
153 // = (x^2/((2n+3)(2n+2))) a_n
154 //
155 // and therefore if x^2 < (2n+3)(2n+2) the series converge. If we want truncate from
156 // n=2, we get x^2 < (4+3)(4+2) = 7*6 = 36, and we have convergence for all sane values
157 // of x. Further, the error is then bounded by x^{2*3+1}/(2*3+1)! = x^7/5040. In our
158 // case, maximum sane phi represents three slices, i.e. (2/3)pi, which bounds the
159 // error within 0.0004156.
160 //
161 // Then, back to the inequality. We replace sin(phi) with the two first terms
162 // of the Maclaurin series
163 //
164 // phi - phi + phi^3/3! + R <= 2 tolerance/r^2, |R| < (((2/3)pi)^7)/(7!).
165 // phi^3 <= 6*(2 tolerance/ r^2 + R)
166 //
167 // phi = cbrt( 16 tolerance/ r^2) )
168 //
169
170 float phi = std::cbrt(16.f*areaPixelTolerance / (radius*radius));
171
172 float sectors = 2.f*float(M_PI)/phi;
173
174 return glm::clamp(sectors, minSectors, maxSectors);
175}
176
COGSCORE_DLL_API glm::vec2 localSphereRadiusInScreenSpace(const glm::mat4 &localToClip, const glm::mat4 &clipToLocal, const glm::vec4 &clipPosition, const float localRadius)
Given a screen space reference position and a local space radius, find the corresponding screen space...