Cogs.Core
TexAtlasLodTree.cpp
1#include <algorithm>
2
3#include "Foundation/Logging/Logger.h"
4
5#include "Systems/Core/CameraSystem.h"
6#include "Systems/Core/TransformSystem.h"
7
8#include "Context.h"
9
10#include "TexAtlasSystem.h"
11
12namespace {
13 using namespace Cogs::Core;
14 using namespace Cogs::Core::TexAtlas;
15
17
18 bool mightBeVisible(const glm::mat4& M, const glm::mat4& V, const glm::vec4* corners, bool restrictBetweenNearAndFar)
19 {
20 if (restrictBetweenNearAndFar) {
21 uint32_t anyInside = 0;
22 for (size_t i = 0; i < 8; i++) {
23 const glm::vec4 p = M * corners[i];
24 if (-p.w < p.x) anyInside |= 1;
25 if (p.x < p.w) anyInside |= 2;
26 if (-p.w < p.y) anyInside |= 4;
27 if (p.y < p.w) anyInside |= 8;
28 if (-p.w < p.z) anyInside |= 16;
29 if (p.z < p.w) anyInside |= 32;
30 }
31 return anyInside == 0b111111u;
32 }
33 else {
34 uint32_t anyInside = 0;
35 for (size_t i = 0; i < 8; i++) {
36 const glm::vec4 p = M * corners[i];
37 if (-p.w < p.x) anyInside |= 1;
38 if (p.x < p.w) anyInside |= 2;
39 if (-p.w < p.y) anyInside |= 4;
40 if (p.y < p.w) anyInside |= 8;
41
42 const glm::vec4 q = V * corners[i];
43 if (q.z < 0.f) anyInside |= 16;
44 }
45 return anyInside == 0b011111u;
46 }
47 }
48
49 float getScaledError(const glm::mat4& /*P*/, const glm::mat4& V, glm::vec4* corners, float texelSizeView, float tolerance, uint32_t level)
50 {
51 float near = std::numeric_limits<float>::max();
52 for (size_t i = 0; i < 8; i++) {
53 near = std::min(near, -(V * corners[i]).z);
54 }
55 return texelSizeView / (2.f * tolerance * (1 << level) * std::max(1.0f, near));
56 }
57
58 // Convenience func to wrap lookups
59 SlotIx getOrAllocCacheSlotWrap(Context* context, Cache& cache, Fetcher& fetcher, const LodTree::Tile& tile)
60 {
61 const uint32_t mask = (1u << tile.level) - 1u;
62 return cache.getOrAllocCacheSlot(context, fetcher, glm::uvec3(uint32_t(tile.x) & mask,
63 uint32_t(tile.y) & mask,
64 tile.level));
65 }
66
67}
68
69void Cogs::Core::TexAtlas::Geometry::calcTileCorners(glm::vec4* corners, const LodTree::Tile& tile) const
70{
71 const float w = 1.f / float(1 << tile.level);
72 glm::vec2 u0 = w * glm::vec2(tile.x, tile.y);
73 glm::vec2 u1 = w * glm::vec2(tile.x + 1, tile.y + 1);
74
75 glm::vec2 d0 = glm::clamp(invDomainMapScale * (u0 + invDomainMapShift), glm::vec2(0.0), glm::vec2(1.0));
76 glm::vec2 d1 = glm::clamp(invDomainMapScale * (u1 + invDomainMapShift), glm::vec2(0.0), glm::vec2(1.0));
77
78 glm::vec2 U[4] = {
79 glm::vec2(d0.x, d0.y),
80 glm::vec2(d1.x, d0.y),
81 glm::vec2(d1.x, d1.y),
82 glm::vec2(d0.x, d1.y)
83 };
84
85 for (size_t i = 0; i < 4; i++) {
86 const glm::vec2& u = U[i];
87 glm::vec2 p = (coefficients[0] * (1.f - u.x) * (1.f - u.y) +
88 coefficients[1] * u.x * (1.f - u.y) +
89 coefficients[2] * (1.f - u.x) * u.y +
90 coefficients[3] * u.x * u.y);
91
92 corners[i + 0] = glm::vec4(p, elevationMin, 1.f);
93 corners[i + 4] = glm::vec4(p, elevationMax, 1.f);
94 }
95}
96
97namespace {
98
99 bool compareTile(const LodTree::Tile& a, const LodTree::Tile& b)
100 {
101 return a.error < b.error;
102 }
103
104}
105
106void Cogs::Core::TexAtlas::LodTree::update(Context* context, const Geometry& geo, Cache& cache, Fetcher& fetcher, const Layout& layout, float tolerance, bool restrictBetweenNearAndFar, bool lodFreeze)
107{
108 // Upper two bits control the meaning of the lower bits.
109 constexpr uint16_t TileHasData = 0x0000u; // Tile is loaded, lower bits contains atlas slot of tile.
110 constexpr uint16_t TileIsRefined = 0x4000u; // Tile is refined, lower bits contain tree offset to first of four consecutive elemnts containing child nodes.
111 constexpr uint16_t TileHadDataAndIsDebugFlagged = 0x8000u; // Tile is loaded, but flagged for debugging purposes.
112 constexpr uint16_t TileNotYetLoaded = 0xc000u; // Tile is not yet loaded, lower bits contain atlas slot of parent tile.
113
114 (void)TileHadDataAndIsDebugFlagged; // Don't complain on this being used when it is not in use.
115
116 if (!lodFreeze) {
117
118 const CameraData& camData = context->cameraSystem->getMainCameraData();
119 const glm::mat4 P = camData.rawProjectionMatrix;
120 const glm::mat4 V = camData.viewMatrix;
121
122 const glm::mat4 PV = camData.rawViewProjection;
123
124 float baseLevelPixels = std::max(1u, fetcher.tileWidth) * glm::length(glm::vec2(geo.domain[1] - geo.domain[0]));
125 float baseLevelExtent = static_cast<float>(std::sqrt(std::abs(glm::cross(glm::dvec3(geo.coefficients[2] - geo.coefficients[0], 0.f), glm::dvec3(geo.coefficients[3] - geo.coefficients[0], 0.f)).z +
126 glm::cross(glm::dvec3(geo.coefficients[3] - geo.coefficients[0], 0.f), glm::dvec3(geo.coefficients[1] - geo.coefficients[0], 0.f)).z)));
127 float texelSizeWorld = baseLevelExtent / baseLevelPixels;
128 float texelSizeView = glm::determinant(glm::mat3(V)) * texelSizeWorld;
129
130 // screen x-axis in world space
131 glm::vec3 x_w = glm::normalize(glm::vec3(camData.inverseViewMatrix * glm::vec4(1.f, 0.f, 0.f, 0.f)));
132
133 // Calculate the max allowed size. This is the size of a tile at the near plane.
134 /*float maxAllowedSize = 0.f;
135 {
136 const glm::mat4 PVinv = glm::inverse(PV);
137 const glm::vec4 o = PVinv * glm::vec4(0.f, 0.f, -1.f, 1.f);
138 glm::vec4 a = PV * o;
139 glm::vec4 b = PV * (o + o.w * glm::vec4(x_w, 0.f));
140 maxAllowedSize = b.x / b.w - a.x / a.w;
141 }*/
142
143
144 encoded.resize(std::max(1u, layout.size.x * layout.size.y));
145
146 glm::vec4 corners[8];
147
148 candidates.clear();
149 for (uint32_t j = 0; j < layout.size.y; j++) {
150 for (uint32_t i = 0; i < layout.size.x; i++) {
151
152 Tile tile{
153 .error = 0.f,
154 .x = layout.offset.x + int32_t(i),
155 .y = layout.offset.y + int32_t(j),
156 .level = static_cast<uint8_t>(layout.level),
157 .encodedIx = static_cast<uint16_t>(j * layout.size.x + i),
158 .slotIx = 0
159 };
160
161 // We probe cache of tile regardless of it being visible or not, as we
162 // try to keep the most coarse version always in cache.
163 tile.slotIx = getOrAllocCacheSlotWrap(context, cache, fetcher, tile);
164 if (tile.slotIx != NoSlotIx) {
165 encoded[tile.encodedIx] = tile.slotIx;
166 geo.calcTileCorners(corners, tile);
167 if (mightBeVisible(PV, V, corners, restrictBetweenNearAndFar)) {
168 tile.error = getScaledError(P, V, corners, texelSizeView, tolerance, tile.level);
169 if (1.f < tile.error) {
170 candidates.push_back(tile);
171 }
172 }
173 }
174
175 else {
176 encoded[tile.encodedIx] = TileNotYetLoaded;
177 }
178 }
179 }
180
181
182 std::make_heap(candidates.begin(), candidates.end(), compareTile);
183
184 // We process tiles as the group of four children so we have the four children
185 // adjacent in the encoded array (simplifies traversal in shader).
186
187 tiles.clear();
188 while (!candidates.empty()) {
189
190 // Pop visible parent
191 std::pop_heap(candidates.begin(), candidates.end(), compareTile);
192 tiles.push_back(candidates.back());
193 candidates.pop_back();
194
195 // Check if parent tile should be refined
196 const Tile& parentTile = tiles.back();
197 if (parentTile.level < layout.maxLevel) {
198
199 bool anyValid = false;
200 uint32_t firstSlot = static_cast<uint32_t>(encoded.size());
201
202 for (int32_t j = 0; j < 2; j++) {
203 for (int32_t i = 0; i < 2; i++) {
204
205 Tile childTile{
206 .error = 0.f,
207 .x = 2 * parentTile.x + i,
208 .y = 2 * parentTile.y + j,
209 .level = static_cast<uint8_t>(parentTile.level + 1),
210 .encodedIx = static_cast<uint16_t>(encoded.size()),
211 .slotIx = NoSlotIx
212 };
213
214 geo.calcTileCorners(corners, childTile);
215 if (mightBeVisible(PV, V, corners, restrictBetweenNearAndFar)) {
216 childTile.error = getScaledError(P, V, corners, texelSizeView, tolerance, childTile.level);
217 if (1.f < childTile.error) {
218 childTile.slotIx = getOrAllocCacheSlotWrap(context, cache, fetcher, childTile);
219 if (childTile.slotIx != NoSlotIx) {
220
221 candidates.push_back(childTile);
222 std::push_heap(candidates.begin(), candidates.end(), compareTile);
223
224 anyValid = true;
225 }
226 }
227 }
228
229 if (childTile.slotIx != NoSlotIx) {
230 encoded.push_back(childTile.slotIx + TileHasData);
231 }
232 else {
233 // If this tile is refined, we do not yet have data here, so we use the parent's data.
234 encoded.push_back(TileNotYetLoaded + parentTile.slotIx);
235 }
236 }
237 }
238
239
240 if (anyValid) {
241 // Update parent to point at that the first of the four slots
242 encoded[parentTile.encodedIx] = TileIsRefined + static_cast<uint16_t>(firstSlot);
243 }
244 else {
245 // Otherwise, discard the slots allocated in this iteration
246 encoded.resize(firstSlot);
247 }
248
249 }
250 }
251 }
252
253 // Make sure texture has at least one element.
254 if (encoded.empty()) {
255 encoded.push_back(0xc000u);
256 }
257}
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
Log implementation class.
Definition: LogManager.h:140
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:181
Contains data describing a Camera instance and its derived data structured such as matrix data and vi...
Definition: CameraSystem.h:67
glm::mat4 rawProjectionMatrix
Projection matrix that has not been adjusted by the renderer, and is thus appropriate for direct scre...
Definition: CameraSystem.h:130
glm::vec2 coefficients[4]
Coefficients wrt engine origin.
uint32_t level
Base level.
uint32_t maxLevel
Maximum level.
glm::uvec2 size
Size of grid.
glm::ivec2 offset
Indices of the grid cell with smallest indices.