Cogs.Core
OctSystem.cpp
1#include "Context.h"
2#include "ExtensionRegistry.h"
3#include "Systems/Core/CameraSystem.h"
4#include "Systems/Core/TransformSystem.h"
5#include "Resources/MaterialManager.h"
6
7#include "../OctBounds.h"
8#include "OctSystem.h"
9#include "../Renderers/OctRenderer.h"
10
11#include "Rendering/ICapabilities.h"
12#include "Rendering/IBuffers.h"
13
14#include "Foundation/BitTwiddling/MortonCode.h"
15#include "Foundation/BitTwiddling/PowerOfTwo.h"
16#include "Foundation/Geometry/Glm.hpp"
17#include "Foundation/Logging/Logger.h"
18#include "Foundation/HashSequence.h"
19
20
21namespace {
22 using namespace Cogs::Core;
23
24 Cogs::Logging::Log logger = Cogs::Logging::getLogger("OctSystem");
25
26 void updateMaterialVariant(Volumetric::OctData& octData)
27 {
28 switch (octData.source)
29 {
30 case Volumetric::OctSource::Value:
31 octData.materialInstance->setVariant("Source", "Value");
32 break;
33 case Volumetric::OctSource::ValueAge:
34 octData.materialInstance->setVariant("Source", "ValueAge");
35 break;
36 }
37 octData.materialInstance->setVariant("AlphaTest", "Discard");
38 octData.materialInstance->setFloatProperty(octData.materialInstance->material->getFloatKey("alphaThreshold"), 0.0f);
39 }
40
41
42
43 bool forgetRegion(Volumetric::OctData& octData, const uint64_t regionKey)
44 {
45 auto jt = octData.knownRegions.find(regionKey);
46 if (jt != octData.knownRegions.end()) {
47 auto & regData = jt->second;
48 for (const auto blockKey : regData->baseBlocks) {
49
50 // Find block and erase.
51 auto lt = octData.baseBlocks.find(blockKey);
52 lt->second->regionKeys.erase(regData->regionKey);
53
54 // If block has no regions left, delete it.
55 if (lt->second->regionKeys.empty()) {
56 octData.baseBlockPool.destroy(lt->second);
57 octData.baseBlocks.erase(lt);
58 }
59 }
60
61 octData.knownRegionPool.destroy(jt->second);
62 octData.knownRegions.erase(jt);
63 return true;
64 }
65 return false;
66 }
67
68 bool removeRegions(Volumetric::OctComponent& octComp, Volumetric::OctData& octData)
69 {
70 for (const auto regionKey : octComp.regionsToRemove) {
71 forgetRegion(octData, regionKey);
72 }
73 auto rv = !octComp.regionsToRemove.empty();
74 octComp.regionsToRemove.clear();
75 return rv;
76 }
77
78 bool addRegions(Volumetric::OctComponent& octComp, Volumetric::OctData& octData, std::vector<Volumetric::Region>& regionsToAdd)
79 {
80 const glm::vec3 blockScale(1.f / octComp.blockExtent.x,
81 1.f / octComp.blockExtent.y,
82 1.f / octComp.blockExtent.z);
83
84 const auto skirtExpand = float(Volumetric::OctSystem::skirtSize) / float(octComp.tileSize - Volumetric::OctSystem::skirtSize);
85
86 const glm::vec3 skirtExtent = octComp.blockExtent * skirtExpand;
87
88 const auto M = glm::scale(blockScale) * glm::translate(-octComp.blockShift);
89
90
91 for (auto & region : regionsToAdd) {
92
93 Volumetric::OctRegionData* regData = octData.knownRegionPool.create();
94 regData->regionKey = region.regionKey;
95 regData->min = region.min;
96 regData->max = region.max;
97
98 // Determine range of base blocks intersecting the region.
99 glm::ivec3 i;
100 const auto A4 = M * glm::vec4(region.min - skirtExtent, 1.f);
101 const auto A = glm::ivec3(glm::floor((1.f / A4.w)*glm::vec3(A4)));
102
103 const auto B4 = M * glm::vec4(region.max + skirtExtent, 1.f);
104 const auto B = glm::ivec3(glm::floor((1.f / B4.w)*glm::vec3(B4)));
105 regData->baseBlocks.reserve((B.x - A.x + 1)*(B.y - A.y + 1)*(B.z - A.z + 1));
106 for (i.z = A.z; i.z <= B.z; i.z++) {
107 for (i.y = A.y; i.y <= B.y; i.y++) {
108 for (i.x = A.x; i.x <= B.x; i.x++) {
109 const auto baseBlockKey = Volumetric::createBaseBlockKey(i.x, i.y, i.z);
110
111 Volumetric::OctBaseBlock* baseBlock;
112 auto blockIt = octData.baseBlocks.find(baseBlockKey);
113 if (blockIt == octData.baseBlocks.end()) { // New base block
114 baseBlock = octData.baseBlockPool.create();
115 baseBlock->ix3 = i;
116 octData.baseBlocks[baseBlockKey] = baseBlock;
117 }
118 else {
119 baseBlock = blockIt->second;
120 }
121
122 baseBlock->timestamp = octData.currentTimestamp;
123 baseBlock->regionKeys.insert(region.regionKey);
124 regData->baseBlocks.push_back(baseBlockKey);
125 }
126 }
127 }
128
129 if (forgetRegion(octData, region.regionKey)) {
130 LOG_DEBUG(logger, "Region %PRIu64 overwritten", region.regionKey);
131 }
132 octData.knownRegions[region.regionKey] = std::move(regData);
133 }
134 auto rv = !regionsToAdd.empty();
135 regionsToAdd.clear();
136 return rv;
137 }
138
139 void wipe(Volumetric::OctData* octData)
140 {
141 LOG_DEBUG(logger, "Wiped %d baseblocks and %d regions.", unsigned(octData->baseBlocks.size()), unsigned(octData->knownRegions.size()));
142 for (auto it : octData->baseBlocks) {
143 octData->baseBlockPool.destroy(it.second);
144 }
145 octData->baseBlocks.clear();
146
147 for (auto it : octData->knownRegions) {
148 octData->knownRegionPool.destroy(it.second);
149 }
150 octData->knownRegions.clear();
151 }
152
153}
154
155
156Cogs::Core::Volumetric::OctData::~OctData()
157{
158 // Make sure that destructors for pool contents get called.
159 wipe(this);
160}
161
163{
165
166 renderer = new OctRenderer();
167 context->renderer->registerExtension(renderer);
168
169 material = context->materialManager->loadMaterial("OcttreeRaycastMaterial.material");
170 context->materialManager->processLoading();
171 transferTexKey = material->getTextureKey("transferTexture");
172 volumeTexKey = material->getTextureKey("volumeTexture");
173
174 bounds = new OctBounds();
175 bounds->octSystem = this;
176 context->bounds->addBoundsExtension(bounds);
177
178 auto device = context->device;
179 auto buffers = device->getBuffers();
180
182 desc.name = "DebugEffect";
183 desc.type = EffectDescriptionType::File;
184 switch (device->getType()) {
186 desc.vertexShader = "Engine/DebugVS.es30.glsl";
187 desc.pixelShader = "Engine/DebugPS.es30.glsl";
188 desc.flags = static_cast<EffectFlags::EEffectFlags>(desc.flags | EffectFlags::GLSL);
189 break;
190 default:
191 desc.vertexShader = "Engine/DebugVS.hlsl";
192 desc.pixelShader = "Engine/DebugPS.hlsl";
193 break;
194 }
195 effectHandle = device->getEffects()->loadEffect(desc);
196
197 VertexElement elements[] = {
198 { 0, DataFormat::X32Y32Z32_FLOAT, ElementSemantic::Position, 0, InputType::VertexData, {} }
199 };
200 auto vertexFormat = buffers->createVertexFormat(elements, 1);
201 inputLayoutHandle = buffers->loadInputLayout(&vertexFormat, 1, effectHandle);
202}
203
205{
207 auto * comp = component.resolveComponent<OctComponent>();
208 comp->system = this;
209 auto & data = getData(comp);
210 data.comp = comp;
211 for (unsigned i = 0; i < 32; i++) {
212 data.tileResponsesStash.push_back(new TileResponse());
213 }
214 data.materialInstance = context->materialInstanceManager->createMaterialInstance(material);
215 updateMaterialVariant(data);
216 return component;
217}
218
220{
221 for (auto & octComp : pool) {
222 auto & octData = getData(&octComp);
223
224 if (octData.source != octComp.source) {
225 octData.source = octComp.source;
226 updateMaterialVariant(octData);
227 octComp.forceWipe = true;
228 }
229
230 octData.currentTimestamp++;
231 bool baseBlocksModified = false;
232
233 // Sanity checks on octComp properties, force to sane ranges.
234 octComp.tileSize = std::max(8u, std::min(256u, octComp.tileSize));
235 octComp.gpuCacheSize = std::max(1u, std::min((2048 / octComp.tileSize), octComp.gpuCacheSize));
236
237 // Check if basic assumptions used by the system has changed, if so, flush everything.
238 const size_t layoutHash = hashSequence(octComp.blockExtent,
239 octComp.blockShift,
240 octComp.tileSize,
241 octComp.gpuCacheSize);
242 if (octComp.forceWipe || octComp.clearAllRegions || octData.layoutHash != layoutHash) {
243 octComp.forceWipe = false;
244 octComp.clearAllRegions = false;
245 octData.gpuCacheWipe = true;
246 octData.layoutHash = layoutHash;
247 octData.maxFrontSize = octComp.gpuCacheSize * octComp.gpuCacheSize * octComp.gpuCacheSize - 1u;
248
249 std::vector<Region> knownRegions;
250 if (!octComp.clearAllRegions) {
251 // Wipe and re-insert regions.
252 knownRegions.reserve(octData.knownRegions.size());
253 for (auto & it : octData.knownRegions) {
254 const auto * regData = it.second;
255 knownRegions.push_back(Region{ regData->regionKey, regData->min, regData->max });
256 }
257 }
258 wipe(&octData);
259 addRegions(octComp, octData, knownRegions);
260 baseBlocksModified = true;
261 }
262
263 baseBlocksModified = addRegions(octComp, octData, octComp.regionsToAdd) || baseBlocksModified;
264 baseBlocksModified = removeRegions(octComp, octData) || baseBlocksModified;
265
266 if(baseBlocksModified) {
267 buildTree(context, octComp, octData);
268 }
269
270 // Update current subset of oct-tree nodes based on viewports.
271 const auto * camComp = context->cameraSystem->getMainCamera();
272 const auto & camData = context->cameraSystem->getMainCameraData();
273 const auto * transComp = octComp.getComponent<TransformComponent>();
274 adaptiveSubset(context, octComp, octData, *transComp, *camComp, camData);
275
276 // Build list of tile requests, update timestamps
277 octComp.tileRequests.clear();
278 for (auto & item : octData.front) {
279 const auto & node = octData.nodes[item];
280 const auto tileKey = createTileKey(node.ix4, octData.alignMinToZeroShift);
281 auto staleness = octData.atlas.checkTile(tileKey, node.timestamp, octData.currentTimestamp);
282 if (staleness == 0) {
283 continue;
284 }
285 octComp.tileRequests.emplace_back(TileRequest{ tileKey, staleness });
286 }
287 std::stable_sort(octComp.tileRequests.begin(), octComp.tileRequests.end(), [](const auto &a, const auto & b) {return a.staleness > b.staleness; });
288 }
289
290}
291
292
293void Cogs::Core::Volumetric::OctSystem::buildTree(Context* /*context*/, OctComponent& /*octComp*/, OctData& octData)
294{
295 auto & base = octData.baseBlocks;
296 auto & nodes = octData.nodes;
297
298 if (base.empty()) return;
299
300 // Find min/max index.
301 glm::i16vec3 baseBlockMinIndex = base.begin()->second->ix3;
302 glm::i16vec3 baseBlockMaxIndex = base.begin()->second->ix3;
303 for (auto & it : base) {
304 baseBlockMinIndex = glm::min(baseBlockMinIndex, it.second->ix3);
305 baseBlockMaxIndex = glm::max(baseBlockMaxIndex, it.second->ix3);
306 }
307
308 // An invariant of the oct-tree buildup is that all indices are non-negative.
309
310
311
312 // Realign pyramid if we have new data on the negative sides. Try to preserve nodes by
313 // moving in steps proportional to size of top node.
314 if ((baseBlockMinIndex.x < octData.alignMinToZeroShift.x) || (baseBlockMinIndex.y < octData.alignMinToZeroShift.y) || (baseBlockMinIndex.z < octData.alignMinToZeroShift.z))
315 {
316 auto d = std::max(std::max((baseBlockMaxIndex.x - baseBlockMinIndex.x), (baseBlockMaxIndex.y - baseBlockMinIndex.y)), (baseBlockMaxIndex.z - baseBlockMinIndex.z));
317 auto l = roundUpToPowerOfTwoShift((unsigned)d);
318 auto m = -1 << l;
319 octData.alignMinToZeroShift.x = baseBlockMinIndex.x & m;
320 octData.alignMinToZeroShift.y = baseBlockMinIndex.y & m;
321 octData.alignMinToZeroShift.z = baseBlockMinIndex.z & m;
322 LOG_DEBUG(logger, "alignMinToZeroShift=[%d, %d, %d], l=%d", octData.alignMinToZeroShift.x, octData.alignMinToZeroShift.y, octData.alignMinToZeroShift.z, l);
323 }
324
325 // Build base layer
326 nodes.clear();
327 for (auto & it : octData.baseBlocks) {
328 glm::uvec4 ix4 = glm::uvec4(it.second->ix3 - octData.alignMinToZeroShift, 0);
329 nodes.emplace_back(NodeBlock{});
330 nodes.back().ix = mortonCode(static_cast<uint16_t>(ix4.x),
331 static_cast<uint16_t>(ix4.y),
332 static_cast<uint16_t>(ix4.z));
333 nodes.back().timestamp = it.second->timestamp;
334 nodes.back().ix4 = ix4;
335 nodes.back().extentMin = glm::uvec3(ix4);
336 nodes.back().extentMax = glm::uvec3(ix4) + glm::uvec3(1);
337 nodes.back().baseBlock = it.second;
338 }
339
340 // Sort by Morton code, which linearizes the 3D structure of the oct-tree.
341 std::sort(nodes.begin(), nodes.end(), [](const auto& a, const auto& b) -> bool { return a.ix < b.ix; });
342
343 // And build upper layers until we have a single apex node.
344 size_t offset = 0;
345 for (uint16_t l = 1; l < 16 && (1 < nodes.size() - offset); l++) {
346 size_t nextOffset = nodes.size();
347
348 NodeBlock * parent = nullptr;
349 for (size_t i = offset; i < nextOffset; i++) {
350 const auto child = nodes[i];
351 // Upper bits of morton code give parent index.
352 const auto parentIx = child.ix >> 3;
353
354 // Sorted by morton code, so when parent index changes, so the childs of a parent lies successively.
355 if (!parent || parent->ix != parentIx) {
356 nodes.emplace_back(NodeBlock{});
357 parent = &octData.nodes.back();
358 parent->ix = parentIx;
359 parent->timestamp = child.timestamp;
360 parent->ix4 = glm::u16vec4(child.ix4.x >> 1, child.ix4.y >> 1, child.ix4.z >> 1, l);
361 parent->extentMin = child.extentMin;
362 parent->extentMax = child.extentMax;
363 for (unsigned k = 0; k < 8; k++) parent->children[k] = ~0u;
364 }
365 // Some trickery comparing age instead of timestamps directly handle wrap-around of timestamps.
366 parent->timestamp = octData.currentTimestamp - std::min(octData.currentTimestamp - parent->timestamp,
367 octData.currentTimestamp - child.timestamp);
368 // Grow parent to include child node.
369 parent->extentMin = glm::min(parent->extentMin, child.extentMin);
370 parent->extentMax = glm::max(parent->extentMax, child.extentMax);
371 // Lower three bits of morton code give child index.
372 parent->children[child.ix & 7] = static_cast<uint32_t>(i);
373 }
374 offset = nextOffset;
375 }
376}
377
378glm::mat4 Cogs::Core::Volumetric::OctSystem::LocalFromIndexSpaceTransform(const OctComponent& octComp, const OctData& octData)
379{
380 return
381 glm::translate(glm::mat4(), octComp.blockShift) *
382 glm::scale(octComp.blockExtent) *
383 glm::translate(glm::mat4(), glm::vec3(octData.alignMinToZeroShift));
384}
385
386glm::mat4 Cogs::Core::Volumetric::OctSystem::IndexSpaceFromLocalTransform(const OctComponent& octComp, const OctData& octData)
387{
388 return
389 glm::translate(glm::mat4(), -glm::vec3(octData.alignMinToZeroShift)) *
390 glm::scale(glm::vec3(1.f / octComp.blockExtent.x,
391 1.f / octComp.blockExtent.y,
392 1.f / octComp.blockExtent.z)) *
393 glm::translate(glm::mat4(), -octComp.blockShift);
394}
395
396
397void Cogs::Core::Volumetric::OctSystem::adaptiveSubset(Context * context,
398 OctComponent& octComp, OctData& octData,
399 const TransformComponent& transComp,
400 const CameraComponent& /*camComp*/, const CameraData& camData)
401{
402 const auto & nodes = octData.nodes;
403 auto & front = octData.front;
404 auto & stack = octData.stack;
405
406 if (nodes.empty()) return;
407
408 // Fixme: use LODreference
409 // Discard only nodes that is not in any frustums, i.e. process all frustums simultaneously.
410
411
412 const auto & worldFromLocal = context->transformSystem->getLocalToWorld(&transComp);
413 const auto localFromWorld = glm::inverse(worldFromLocal);
414 const auto & ViewFromWorld = camData.viewMatrix;
415 const auto & worldFromView = camData.inverseViewMatrix;
416 const auto & ClipFromWorld = camData.viewProjection;
417 const auto LocalFromIxspc = LocalFromIndexSpaceTransform(octComp, octData);
418 const auto IxspcFromLocal = IndexSpaceFromLocalTransform(octComp, octData);
419 const auto viewFromIxspc = ViewFromWorld * worldFromLocal * LocalFromIxspc;
420 const auto ixspcFromView = IxspcFromLocal * localFromWorld * worldFromView;
421 const auto clipFromIxspc = ClipFromWorld * worldFromLocal * LocalFromIxspc;
422 const auto camOriginIxspc = glm::vec3(ixspcFromView[3]);
423
424 auto buildFront =
425 [maxFrontSize = octData.maxFrontSize,
426 &nodes,
427 &stack,
428 &viewFromIxspc,
429 &clipFromIxspc,
430 &camOriginIxspc](auto & front, const auto threshold) -> bool
431 {
432 front.clear();
433 stack.clear();
434 stack.push_back(static_cast<uint32_t>(nodes.size() - 1));
435
436 while (!stack.empty()) {
437 const auto nodeIx = stack.back();
438 stack.pop_back();
439
440 const auto & n = nodes[nodeIx];
441
442 // Check if node is inside frustum.
443 unsigned planes = 0;
444 for (unsigned i = 0; i < 8; i++) {
445 const glm::vec4 p((i & 1) == 0 ? n.extentMin.x : n.extentMax.x,
446 (i & 2) == 0 ? n.extentMin.y : n.extentMax.y,
447 (i & 4) == 0 ? n.extentMin.z : n.extentMax.z,
448 1.f);
449 auto v = viewFromIxspc * p;
450 auto c = clipFromIxspc * p;
451 planes = planes |
452 (v.z < 0.f ? 1 : 0) |
453 (c.x <= c.w ? 2 : 0) | (-c.w <= c.x ? 4 : 0) |
454 (c.y <= c.w ? 8 : 0) | (-c.w <= c.y ? 16 : 0);
455 }
456
457 if (planes == 31) {
458 // Find point inside node nearest to camera, and determine camera distance.
459 glm::vec3 nearestIxspc = glm::clamp(camOriginIxspc, glm::vec3(n.extentMin), glm::vec3(n.extentMax));
460 glm::vec4 nearestView = viewFromIxspc * glm::vec4(nearestIxspc, 1.f);
461 float camDist = -nearestView.z / nearestView.w;
462
463 if ((0 < n.ix4.w) && (camDist*threshold < (1 << n.ix4.w))) {
464 for (auto childIx : n.children) {
465 if (childIx != ~0u) stack.push_back(childIx);
466 }
467 }
468 else {
469 front.push_back(nodeIx);
470 if (maxFrontSize < front.size()) {
471 return false;
472 }
473 }
474 }
475 }
476 return true;
477 };
478
479 float tolerance = std::max(0.01f, octData.tolerance);
480 //const auto maxFrontSize = octData.maxFrontSize;
481
482 // Try building front until it is smaller than the max size, doubling the tolerance for each try.
483 bool success = false;
484 for (unsigned i = 0; i < 8 && !success; i++) {
485 success = buildFront(front, tolerance);
486 if (!success) {
487 tolerance = 2 * tolerance;
488 }
489 }
490
491 const auto minFrontSize = (size_t)std::max(1.f, 0.9f*octData.maxFrontSize);
492 for (unsigned i = 0; i < 8 && octComp.tolerance < tolerance && front.size() < minFrontSize; i++) {
493 auto t = std::max(octComp.tolerance, 0.9f*tolerance);
494 if (buildFront(octData.frontTmp, t)) {
495 tolerance = t;
496 octData.front.swap(octData.frontTmp);
497 }
498 else {
499 break;
500 }
501 }
502
503 //LOG_DEBUG(logger, "front size=%d / %d", octData.front.size(), octData.maxFrontSize);
504
505 if (octData.tolerance != tolerance) {
506 LOG_DEBUG(logger, "Current tolerance set to %f", octData.tolerance);
507 }
508 octData.tolerance = tolerance;
509
510}
ComponentType * getComponent() const
Definition: Component.h:159
virtual ComponentHandle createComponent()
Create a new component instance.
virtual void initialize(Context *context)
Initialize the system.
void update()
Updates the system state to that of the current frame.
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
class IRenderer * renderer
Renderer.
Definition: Context.h:228
std::unique_ptr< class Bounds > bounds
Bounds service instance.
Definition: Context.h:216
virtual void registerExtension(IRendererExtension *extension)=0
Register an extension with the renderer.
Defines a 4x4 transformation matrix for the entity and a global offset for root entities.
Log implementation class.
Definition: LogManager.h:139
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
@ OpenGLES30
Graphics device using the OpenGLES 3.0 API.
constexpr size_t hashSequence(const T &t, const U &u)
Hash the last two items in a sequence of objects.
Definition: HashSequence.h:8
@ VertexData
Per vertex data.
@ Position
Position semantic.
constexpr uint64_t mortonCode(uint16_t i, uint16_t j, uint16_t k)
Interleave bits of 3 values to form the 3-way Morton code.
Definition: MortonCode.h:10
uint8_t roundUpToPowerOfTwoShift(uint8_t x)
Definition: PowerOfTwo.h:170
Handle to a Component instance.
Definition: Component.h:67
Contains data describing a Camera instance and its derived data structured such as matrix data and vi...
Definition: CameraSystem.h:67
void setFloatProperty(const VariableKey key, float value)
Set the float property with the given key to value.
Material * material
Material resource this MaterialInstance is created from.
Represent the blocks at the oct-tree base level. Independent on current the particular oct-tree.
Definition: OctSystem.h:41
std::set< RegionKey > regionKeys
Regions intersecting this block.
Definition: OctSystem.h:44
glm::i16vec3 ix3
Index without block shift.
Definition: OctSystem.h:43
uint32_t timestamp
Timestamp last time a region was added to block.
Definition: OctSystem.h:42
bool clearAllRegions
If set to true, all current regions are discarded before regionsToAdd is processed.
Definition: OctComponent.h:95
std::vector< RegionKey > regionsToRemove
Set of regions to remove. Blocks with no regions are purged from the oct-tree.
Definition: OctComponent.h:103
glm::vec3 blockShift
Object space grid origin, tweak if block boundaries happen at unfortunate places.
Definition: OctComponent.h:73
bool forceWipe
Discard all processed data, but regions persist.
Definition: OctComponent.h:71
std::vector< TileRequest > tileRequests
Requests for tiles, populated by OctSystem::update, consumed by provider.
Definition: OctComponent.h:106
std::vector< Region > regionsToAdd
Regions to add this frame. It is OK to add a region multiple times, and this will invalidate regions ...
Definition: OctComponent.h:100
glm::i16vec3 alignMinToZeroShift
Shift value for baseBlock ix3 to get them non-negative.
Definition: OctSystem.h:86
std::vector< uint64_t > baseBlocks
Base blocks that intersects with this region.
Definition: OctSystem.h:66
glm::vec3 min
Object space min corner of region bounding box.
Definition: OctSystem.h:64
glm::vec3 max
Object space max corner of region bounding box.
Definition: OctSystem.h:65
void initialize(Context *context) override
Initialize the system.
Definition: OctSystem.cpp:162
ComponentModel::ComponentHandle createComponent() override
Definition: OctSystem.cpp:204
Contains an effect description used to load a single effect.
Definition: IEffects.h:55
EEffectFlags
Effect source flags.
Definition: IEffects.h:20
@ GLSL
Effect source is GLSL.
Definition: IEffects.h:26
Vertex element structure used to describe a single data element in a vertex for the input assembler.
Definition: VertexFormat.h:38