Cogs.Core
DynamicLodTree.cpp
1#include "Context.h"
2#include "Utilities/Math.h"
3#include "Systems/Core/TransformSystem.h"
4#include "Systems/Core/CameraSystem.h"
5
6#include "Image360System.h"
7
8namespace {
9 using namespace Cogs::Core;
10 using namespace Cogs::Core::Image360;
11
12 bool isQuadVisible(const glm::vec3(&planes)[5],
13 const glm::vec3& o,
14 const glm::vec3& u,
15 const glm::vec3& v)
16 {
17 const glm::vec3 corners[4] = {
18 o - u - v,
19 o + u - v,
20 o + u + v,
21 o - u + v,
22 };
23 for (const glm::vec3& plane : planes) {
24 bool anyIn = false;
25 for (size_t i = 0; i < 4; i++) {
26 anyIn = anyIn || (0.f <= glm::dot(plane, corners[i]));
27 }
28 if (!anyIn) {
29 return false;
30 }
31 }
32 return true;
33 }
34
35 bool initializeQuads(Context* context,
36 DynamicLodTree& tree,
37 Cache& cache,
38 Fetcher& fetcher,
39 const Image360Component& im360Comp,
40 const Config& config,
41 const uint32_t currentFrame,
42 std::vector<DynamicLodTree::Quad>& quads)
43 {
44 static const struct Face {
45 glm::vec3 o;
46 glm::vec3 u;
47 glm::vec3 v;
48 } faces[6] = {
49 {.o = {+1 ,0, 0}, .u = {0, 0, -1}, .v = {0, +1, 0}},
50 {.o = {-1, 0, 0}, .u = {0, 0, +1}, .v = {0, +1, 0}},
51 {.o = {0, +1, 0}, .u = {+1, 0, 0}, .v = {0, 0, -1}},
52 {.o = {0, -1, 0}, .u = {+1, 0, 0}, .v = {0, 0, +1}},
53 {.o = {0, 0, +1}, .u = {+1, 0, 0}, .v = {0, +1, 0}},
54 {.o = {0, 0, -1}, .u = {-1, 0, 0}, .v = {0, +1, 0}}
55 };
56
57 bool allBaseLevelsPresent = true;
58 quads.resize(6);
59 tree.data.resize(6);
60 for (size_t i = 0; i < 6; i++) {
61 quads[i].o = faces[i].o;
62 quads[i].u = faces[i].u;
63 quads[i].v = faces[i].v;
64 quads[i].slot = cache.isQuadInCache(context, fetcher, im360Comp, config, currentFrame, 0, i);
65 quads[i].cacheLevelIndex = i;
66 quads[i].encodedTreeIndex = i;
67 quads[i].level = 0;
68 if (0 <= quads[i].slot) {
69 tree.data[i] = quads[i].slot << 3;
70 }
71 else {
72 tree.data[i] = -1;
73 allBaseLevelsPresent = false;
74 }
75 }
76 return allBaseLevelsPresent;
77 }
78
79 bool quadRefineIteration(Context* context,
80 DynamicLodTree& tree,
81 Cache& cache,
82 Fetcher& fetcher,
83 const Image360Component& im360Comp,
84 const Config& config,
85 const glm::vec3(&planes)[5],
86 const uint32_t currentFrame,
87 const float cosinePerTile,
88 std::vector<DynamicLodTree::Quad>& quads,
89 std::vector<DynamicLodTree::Quad>& quadsNext)
90 {
91 quadsNext.clear();
92
93 for (const DynamicLodTree::Quad& quad : quads) {
94
95 // Cannot descend beyond treedepth
96 if (config.treeDepth <= quad.level + 1) continue;
97
98 // Do not descend if FOV covered by quad is sufficently small.
99 // Do both U and V on both sides since the tiles are not necessarily symmetric
100 glm::vec3 m00 = glm::normalize(quad.o - quad.u - quad.v);
101 glm::vec3 m10 = glm::normalize(quad.o + quad.u - quad.v);
102 glm::vec3 m01 = glm::normalize(quad.o - quad.u + quad.v);
103 glm::vec3 m11 = glm::normalize(quad.o + quad.u + quad.v);
104
105 // We want to make sure the largest angle is less than the limit. Since cosine
106 // decreases with increasing angle in [0, pi], ordering is switched (we find the
107 // min cosine and see if this is larger than the limit.
108 float angleMinCosine = glm::min(glm::min(glm::dot(m00, m10),
109 glm::dot(m01, m11)),
110 glm::min(glm::dot(m00, m01),
111 glm::dot(m10, m11)));
112
113 if (cosinePerTile <= angleMinCosine) {
114 continue;
115 }
116
117 size_t leavesBase = tree.data.size();
118 if (config.treeMaxSize < leavesBase + 4) {
119 break; // no more space in tree, cannot refine any more
120 }
121
122 DynamicLodTree::Quad childQuads[4];
123 size_t childQuadCount = 0;
124 EncodedSlotIx leaves[4] = { -1, -1, -1, -1 };
125
126 for (size_t k = 0; k < 4; k++) {
127
128 // Check if child quad is inside frustum
129 glm::vec3 u = 0.5f * quad.u;
130 glm::vec3 v = 0.5f * quad.v;
131 glm::vec3 o = quad.o + ((k & 1) == 0 ? -u : u) + ((k & 2) == 0 ? -v : v);
132
133
134 EncodedSlotIx encodedCacheSlot = (quad.slot << 3) + 4 + (EncodedSlotIx)k;
135 if (isQuadVisible(planes, o, u, v)) {
136
137 // Check if quad data is present in cache (and if not, a fetch might get triggered)
138 size_t childLevel = quad.level + 1;
139 size_t childCacheLevelIndex = 4 * quad.cacheLevelIndex + k;
140 SlotIx cacheSlot = cache.isQuadInCache(context, fetcher, im360Comp, config, currentFrame, childLevel, childCacheLevelIndex);
141 if (0 <= cacheSlot) {
142 childQuads[childQuadCount++] = DynamicLodTree::Quad{
143 .o = o,
144 .u = u,
145 .v = v,
146 .slot = cacheSlot,
147 .cacheLevelIndex = childCacheLevelIndex,
148 .encodedTreeIndex = leavesBase + k,
149 .level = childLevel
150 };
151 encodedCacheSlot = cacheSlot << 3;
152 }
153 }
154
155 leaves[k] = encodedCacheSlot;
156 }
157
158 // If we have any children and they are all ready, commit refinement
159 if (childQuadCount) {
160
161 // Push visible children onto queue for further refinement
162 for (size_t k = 0; k < childQuadCount; k++) {
163 quadsNext.emplace_back(childQuads[k]);
164 }
165
166 // Let all children be represented in the tree
167 for (size_t k = 0; k < 4; k++) {
168 tree.data.push_back(leaves[k]);
169 }
170
171 // Convert parent leaf into a branch with these leaves
172 tree.data[quad.encodedTreeIndex] = -Image360::SlotIx(leavesBase);
173 }
174 }
175 quads.swap(quadsNext);
176
177 return !quads.empty();
178 }
179
180
181}
182
183
184void Cogs::Core::Image360::DynamicLodTree::update(Context* context,
185 RendererExtensionData& rendererData,
186 Fetcher& fetcher,
187 Cache& cache,
188 const Image360Component& im360Comp,
189 const Config& config,
190 const uint32_t currentFrame,
191 const Image360Component& comp)
192{
194 if (trComp == nullptr) return;
195
196 quads.clear();
197 bool allBaseLevelsPresent = initializeQuads(context, *this, cache, fetcher, im360Comp, config, currentFrame, quads);
198
199 rendererData.worldFromLocal = context->transformSystem->getLocalToWorld(trComp);
200 rendererData.localFromWorld = glm::inverse(rendererData.worldFromLocal);
201
202 const CameraData& camData = context->cameraSystem->getMainCameraData();
203 const float clipSpaceNearPlane = context->variables->get("renderer.reverseDepth", false) ? 1.f : -1.f;
204
205 // Discard translation, eye should be at origin when combined with localFromWorld
206 const glm::mat3 localFromView = rendererData.localFromWorld * glm::mat3(camData.inverseViewMatrix);
207 const glm::vec3 fwd = localFromView * glm::vec3(0, 0, -1);
208
209 const glm::mat4 localFromClip = glm::mat4(localFromView) * camData.inverseProjectionMatrix;
210 const glm::vec3 c00 = euclidean(localFromClip * glm::vec4(-1.0, -1.0, clipSpaceNearPlane, 1));
211 const glm::vec3 c10 = euclidean(localFromClip * glm::vec4(+1.0, -1.0, clipSpaceNearPlane, 1));
212 const glm::vec3 c01 = euclidean(localFromClip * glm::vec4(-1.0, +1.0, clipSpaceNearPlane, 1));
213 const glm::vec3 c11 = euclidean(localFromClip * glm::vec4(+1.0, +1.0, clipSpaceNearPlane, 1));
214 glm::vec3 planes[5] = {
215 glm::normalize(glm::cross(c01, c11)),
216 -glm::normalize(glm::cross(c00, c10)),
217 glm::normalize(glm::cross(c00, c01)),
218 -glm::normalize(glm::cross(c10, c11)),
219 glm::normalize(fwd)
220 };
221
222 // Find largest fov-angle a tile can cover without needing to be refined. This is found by
223 // calculating the angle-per-pixel for width or height (use the largest).
224 float hAngle = glm::acos(glm::dot(glm::normalize(c00), glm::normalize(c10)));
225 float vAngle = glm::acos(glm::dot(glm::normalize(c00), glm::normalize(c01)));
226 float anglePerPixel;
227 if (vAngle <= hAngle) {
228 anglePerPixel = hAngle / camData.viewportSize.x;
229 }
230 else {
231 anglePerPixel = vAngle / camData.viewportSize.y;
232 }
233
234 // The angle per pixel is then scaled by the subsampling factor and then multiplied by the
235 // number of pixels in in the tile.
236 //
237 // It might be more correct to use steradians to avoid the min/max of horizontal/vert etc,
238 // but this simpler approach seems to work OK
239 float anglePerTile = std::min(glm::pi<float>(), anglePerPixel * comp.subsampling * config.baseSize);
240
241 // We clamp this angle to [0, pi] where cos is monotonically decreasing, so we can just
242 // use the cosine in the test without finding the actual angle with acos.
243 float cosinePerTile = std::cos(std::min(glm::pi<float>(), anglePerTile));
244
245
246 // Just give up if something degenerate.
247 if (!std::isfinite(cosinePerTile)) return;
248
249 if (allBaseLevelsPresent) {
250 while (quadRefineIteration(context, *this, cache, fetcher, im360Comp, config, planes, currentFrame, cosinePerTile, quads, quadsNext)) {}
251 }
252}
ComponentType * getComponent() const
Definition: Component.h:159
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
std::unique_ptr< class Variables > variables
Variables service instance.
Definition: Context.h:180
Defines a 4x4 transformation matrix for the entity and a global offset for root entities.
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
Contains data describing a Camera instance and its derived data structured such as matrix data and vi...
Definition: CameraSystem.h:67
uint32_t baseSize
Base image size of a cached tile. From json.
Definition: Image360.h:46
uint32_t treeDepth
Depth of tile hierarchy. From json.
Definition: Image360.h:45
std::vector< Quad > quadsNext
Scratch buffers kept here to avoid excessive reallocations.
Definition: Image360.h:207