History

Completed worklist job #19486

Job #19486 Don't render the duplicate triangles between adjacent voxels. Added checkable item to "developer" menu to activate/deactivate cull shared faces. Cull all shared voxel faces regardless of distribution in the octree. Includes leaf to leaf, leaf to non-leaf, and non-leaf to non-leaf comparison of contiguous faces. Added new GL primitive renderer to manage the transfer of cube faces from the voxel system to GL. Added some useful classes for queue and lock management. Modified statistics collection to report number of completely culled nodes. Extra debugging info also includes the interior occlusions bit mask of leaf voxels.

matsukaze committed 12 years ago

Commit bbf4f881 parent d19cb6d16 files · +2346 −94Browse files

Showing the changes to interface/src/VoxelSystem.cpp (1 of 16 files). Show all

Unified · Split

interface/src/VoxelSystem.cpp+518−88
@@ -60,7 +60,9 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels) · 59 unmodified lines
60 : NodeData(),60 : NodeData(),
61 _treeScale(treeScale),61 _treeScale(treeScale),
62 _maxVoxels(maxVoxels),62 _maxVoxels(maxVoxels),
63 _initialized(false) {63 _initialized(false),
64 _usePrimitiveRenderer(false),
65 _renderer(0) {
64 66
65 _voxelsInReadArrays = _voxelsInWriteArrays = _voxelsUpdated = 0;67 _voxelsInReadArrays = _voxelsInWriteArrays = _voxelsUpdated = 0;
66 _writeRenderFullVBO = true;68 _writeRenderFullVBO = true;
@@ -102,12 +104,13 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels) · 35 unmodified lines
102 _inhideOutOfView = false;104 _inhideOutOfView = false;
103 _treeIsBusy = false;105 _treeIsBusy = false;
104 106
107
105}108}
106 109
107void VoxelSystem::elementDeleted(OctreeElement* element) {110void VoxelSystem::elementDeleted(OctreeElement* element) {
108 VoxelTreeElement* voxel = (VoxelTreeElement*)element;111 VoxelTreeElement* voxel = (VoxelTreeElement*)element;
109 if (voxel->getVoxelSystem() == this) {112 if (voxel->getVoxelSystem() == this) {
110 if (_voxelsInWriteArrays != 0) {113 if ((_voxelsInWriteArrays != 0) || _usePrimitiveRenderer) {
111 forceRemoveNodeFromArrays(voxel);114 forceRemoveNodeFromArrays(voxel);
112 } else {115 } else {
113 if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) {116 if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) {
@@ -212,16 +215,16 @@ void VoxelSystem::freeBufferIndex(glBufferIndex index) { · 98 unmodified lines
212 }215 }
213 }216 }
214 if (!inList) {217 if (!inList) {
215 // make the index available for next node that needs to be drawn218 // make the index available for next node that needs to be drawn
216 _freeIndexLock.lock();219 _freeIndexLock.lock();
217 _freeIndexes.push_back(index);220 _freeIndexes.push_back(index);
218 _freeIndexLock.unlock();221 _freeIndexLock.unlock();
219 222
220 // make the VBO slot "invisible" in case this slot is not used223 // make the VBO slot "invisible" in case this slot is not used
221 const glm::vec3 startVertex(FLT_MAX, FLT_MAX, FLT_MAX);224 const glm::vec3 startVertex(FLT_MAX, FLT_MAX, FLT_MAX);
222 const float voxelScale = 0;225 const float voxelScale = 0;
223 const nodeColor BLACK = {0, 0, 0, 0};226 const nodeColor BLACK = {0, 0, 0, 0};
224 updateArraysDetails(index, startVertex, voxelScale, BLACK);227 updateArraysDetails(index, startVertex, voxelScale, BLACK);
225 }228 }
226}229}
227 230
@@ -250,6 +253,8 @@ VoxelSystem::~VoxelSystem() { · 22 unmodified lines
250 253
251 cleanupVoxelMemory();254 cleanupVoxelMemory();
252 delete _tree;255 delete _tree;
256
257 _renderer = 0;
253}258}
254 259
255void VoxelSystem::setMaxVoxels(int maxVoxels) {260void VoxelSystem::setMaxVoxels(int maxVoxels) {
@@ -258,12 +263,12 @@ void VoxelSystem::setMaxVoxels(int maxVoxels) { · 2 unmodified lines
258 }263 }
259 bool wasInitialized = _initialized;264 bool wasInitialized = _initialized;
260 if (wasInitialized) {265 if (wasInitialized) {
261 clearAllNodesBufferIndex();266 clearAllNodesBufferIndex();
262 cleanupVoxelMemory();267 cleanupVoxelMemory();
263 }268 }
264 _maxVoxels = maxVoxels;269 _maxVoxels = maxVoxels;
265 if (wasInitialized) {270 if (wasInitialized) {
266 initVoxelMemory();271 initVoxelMemory();
267 }272 }
268 if (wasInitialized) {273 if (wasInitialized) {
269 forceRedrawEntireTree();274 forceRedrawEntireTree();
@@ -281,6 +286,7 @@ void VoxelSystem::setUseVoxelShader(bool useVoxelShader) { · 11 unmodified lines
281 cleanupVoxelMemory();286 cleanupVoxelMemory();
282 }287 }
283 _useVoxelShader = useVoxelShader;288 _useVoxelShader = useVoxelShader;
289 _usePrimitiveRenderer = false;
284 if (wasInitialized) {290 if (wasInitialized) {
285 initVoxelMemory();291 initVoxelMemory();
286 }292 }
@@ -345,7 +351,8 @@ void VoxelSystem::cleanupVoxelMemory() { · 58 unmodified lines
345 351
346 _writeVoxelShaderData = _readVoxelShaderData = NULL;352 _writeVoxelShaderData = _readVoxelShaderData = NULL;
347 353
348 } else {354 } else
355 if (! _usePrimitiveRenderer) {
349 // Destroy glBuffers356 // Destroy glBuffers
350 glDeleteBuffers(1, &_vboVerticesID);357 glDeleteBuffers(1, &_vboVerticesID);
351 glDeleteBuffers(1, &_vboColorsID);358 glDeleteBuffers(1, &_vboColorsID);
@@ -366,8 +373,11 @@ void VoxelSystem::cleanupVoxelMemory() { · 14 unmodified lines
366 _writeVerticesArray = NULL;373 _writeVerticesArray = NULL;
367 _readColorsArray = NULL;374 _readColorsArray = NULL;
368 _writeColorsArray = NULL;375 _writeColorsArray = NULL;
369
370 }376 }
377 else {
378 delete _renderer;
379 _renderer = 0;
380 }
371 delete[] _writeVoxelDirtyArray;381 delete[] _writeVoxelDirtyArray;
372 delete[] _readVoxelDirtyArray;382 delete[] _readVoxelDirtyArray;
373 _writeVoxelDirtyArray = _readVoxelDirtyArray = NULL;383 _writeVoxelDirtyArray = _readVoxelDirtyArray = NULL;
@@ -452,7 +462,8 @@ void VoxelSystem::initVoxelMemory() { · 78 unmodified lines
452 462
453 _readVoxelShaderData = new VoxelShaderVBOData[_maxVoxels];463 _readVoxelShaderData = new VoxelShaderVBOData[_maxVoxels];
454 _memoryUsageRAM += (sizeof(VoxelShaderVBOData) * _maxVoxels);464 _memoryUsageRAM += (sizeof(VoxelShaderVBOData) * _maxVoxels);
455 } else {465 } else
466 if (! _usePrimitiveRenderer) {
456 467
457 // Global Normals mode uses a technique of not including normals on any voxel vertices, and instead468 // Global Normals mode uses a technique of not including normals on any voxel vertices, and instead
458 // rendering the voxel faces in 6 passes that use a global call to glNormal3f()469 // rendering the voxel faces in 6 passes that use a global call to glNormal3f()
@@ -496,7 +507,6 @@ void VoxelSystem::initVoxelMemory() { · 37 unmodified lines
496 _readColorsArray = new GLubyte[vertexPointsPerVoxel * _maxVoxels];507 _readColorsArray = new GLubyte[vertexPointsPerVoxel * _maxVoxels];
497 _memoryUsageRAM += (sizeof(GLubyte) * vertexPointsPerVoxel * _maxVoxels);508 _memoryUsageRAM += (sizeof(GLubyte) * vertexPointsPerVoxel * _maxVoxels);
498 509
499
500 // create our simple fragment shader if we're the first system to init510 // create our simple fragment shader if we're the first system to init
501 if (!_perlinModulateProgram.isLinked()) {511 if (!_perlinModulateProgram.isLinked()) {
502 switchToResourcesParentIfRequired();512 switchToResourcesParentIfRequired();
@@ -516,6 +526,27 @@ void VoxelSystem::initVoxelMemory() { · 13 unmodified lines
516 _shadowMapProgram.release();526 _shadowMapProgram.release();
517 }527 }
518 }528 }
529 else {
530 _renderer = new PrimitiveRenderer(_maxVoxels);
531 // create our simple fragment shader if we're the first system to init
532 if (!_perlinModulateProgram.isLinked()) {
533 switchToResourcesParentIfRequired();
534 _perlinModulateProgram.addShaderFromSourceFile(QGLShader::Vertex, "resources/shaders/perlin_modulate.vert");
535 _perlinModulateProgram.addShaderFromSourceFile(QGLShader::Fragment, "resources/shaders/perlin_modulate.frag");
536 _perlinModulateProgram.link();
537
538 _perlinModulateProgram.bind();
539 _perlinModulateProgram.setUniformValue("permutationNormalTexture", 0);
540 _perlinModulateProgram.release();
541
542 _shadowMapProgram.addShaderFromSourceFile(QGLShader::Fragment, "resources/shaders/shadow_map.frag");
543 _shadowMapProgram.link();
544
545 _shadowMapProgram.bind();
546 _shadowMapProgram.setUniformValue("shadowMap", 0);
547 _shadowMapProgram.release();
548 }
549 }
519 550
520 _initialized = true;551 _initialized = true;
521 552
@@ -655,8 +686,15 @@ void VoxelSystem::setupNewVoxelsForDrawing() { · 133 unmodified lines
655 };686 };
656 PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), buffer);687 PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), buffer);
657 _callsToTreesToArrays++;688 _callsToTreesToArrays++;
689
658 if (_writeRenderFullVBO) {690 if (_writeRenderFullVBO) {
659 clearFreeBufferIndexes();691 if (_usePrimitiveRenderer) {
692 delete _renderer;
693 _renderer = new PrimitiveRenderer(_maxVoxels);
694 }
695 else {
696 clearFreeBufferIndexes();
697 }
660 }698 }
661 _voxelsUpdated = newTreeToArrays(_tree->getRoot());699 _voxelsUpdated = newTreeToArrays(_tree->getRoot());
662 _tree->clearDirtyBit(); // after we pull the trees into the array, we can consider the tree clean700 _tree->clearDirtyBit(); // after we pull the trees into the array, we can consider the tree clean
@@ -672,17 +710,24 @@ void VoxelSystem::setupNewVoxelsForDrawing() { · 9 unmodified lines
672 _voxelsUpdated = 0;710 _voxelsUpdated = 0;
673 }711 }
674 712
675 // lock on the buffer write lock so we can't modify the data when the GPU is reading it713 if (_usePrimitiveRenderer) {
676 _bufferWriteLock.lock();714 if (_voxelsUpdated) {
715 _voxelsDirty=true;
716 }
717 }
718 else {
719 // lock on the buffer write lock so we can't modify the data when the GPU is reading it
720 _bufferWriteLock.lock();
677 721
678 if (_voxelsUpdated) {722 if (_voxelsUpdated) {
679 _voxelsDirty=true;723 _voxelsDirty=true;
680 }724 }
681 725
682 // copy the newly written data to the arrays designated for reading, only does something if _voxelsDirty && _voxelsUpdated726 // copy the newly written data to the arrays designated for reading, only does something if _voxelsDirty && _voxelsUpdated
683 copyWrittenDataToReadArrays(didWriteFullVBO);727 copyWrittenDataToReadArrays(didWriteFullVBO);
684 728
685 _bufferWriteLock.unlock();729 _bufferWriteLock.unlock();
730 }
686 731
687 uint64_t end = usecTimestampNow();732 uint64_t end = usecTimestampNow();
688 int elapsedmsec = (end - start) / 1000;733 int elapsedmsec = (end - start) / 1000;
@@ -710,23 +755,28 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) { · 21 unmodified lines
710 return; // bail early, it hasn't been long enough since the last time we ran755 return; // bail early, it hasn't been long enough since the last time we ran
711 }756 }
712 757
713 // lock on the buffer write lock so we can't modify the data when the GPU is reading it758 if (_usePrimitiveRenderer) {
714 {759 _voxelsDirty = true; // if we got this far, then we can assume some voxels are dirty
715 PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),760 _voxelsUpdated = 0;
716 "setupNewVoxelsForDrawingSingleNode()... _bufferWriteLock.lock();" );761 }
717 _bufferWriteLock.lock();762 else {
718 }763 // lock on the buffer write lock so we can't modify the data when the GPU is reading it
719 764 {
720 _voxelsDirty = true; // if we got this far, then we can assume some voxels are dirty765 PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
766 "setupNewVoxelsForDrawingSingleNode()... _bufferWriteLock.lock();" );
767 _bufferWriteLock.lock();
768 }
721 769
722 // copy the newly written data to the arrays designated for reading, only does something if _voxelsDirty && _voxelsUpdated770 _voxelsDirty = true; // if we got this far, then we can assume some voxels are dirty
723 copyWrittenDataToReadArrays(_writeRenderFullVBO);
724 771
725 // after...772 // copy the newly written data to the arrays designated for reading, only does something if _voxelsDirty && _voxelsUpdated
726 _voxelsUpdated = 0;773 copyWrittenDataToReadArrays(_writeRenderFullVBO);
727 774
728 _bufferWriteLock.unlock();775 // after...
776 _voxelsUpdated = 0;
729 777
778 _bufferWriteLock.unlock();
779 }
730 uint64_t end = usecTimestampNow();780 uint64_t end = usecTimestampNow();
731 int elapsedmsec = (end - start) / 1000;781 int elapsedmsec = (end - start) / 1000;
732 _setupNewVoxelsForDrawingLastFinished = end;782 _setupNewVoxelsForDrawingLastFinished = end;
@@ -946,14 +996,24 @@ int VoxelSystem::forceRemoveNodeFromArrays(VoxelTreeElement* node) { · 213 unmodified lines
946 return 0;996 return 0;
947 }997 }
948 998
949 // if the node is not in the VBOs then we have nothing to do!999 if (_usePrimitiveRenderer) {
950 if (node->isKnownBufferIndex()) {1000 int primitiveIndex = node->getPrimitiveIndex();
951 // If this node has not yet been written to the array, then add it to the end of the array.1001 if (primitiveIndex) {
952 glBufferIndex nodeIndex = node->getBufferIndex();1002 _renderer->remove(primitiveIndex);
953 node->setBufferIndex(GLBUFFER_INDEX_UNKNOWN);1003 node->setPrimitiveIndex(0);
954 freeBufferIndex(nodeIndex); // NOTE: This will make the node invisible!1004 return 1;
955 return 1; // updated!1005 }
956 }1006 }
1007 else {
1008 // if the node is not in the VBOs then we have nothing to do!
1009 if (node->isKnownBufferIndex()) {
1010 // If this node has not yet been written to the array, then add it to the end of the array.
1011 glBufferIndex nodeIndex = node->getBufferIndex();
1012 node->setBufferIndex(GLBUFFER_INDEX_UNKNOWN);
1013 freeBufferIndex(nodeIndex); // NOTE: This will make the node invisible!
1014 return 1; // updated!
1015 }
1016 }
957 return 0; // not-updated1017 return 0; // not-updated
958}1018}
959 1019
@@ -980,20 +1040,46 @@ int VoxelSystem::updateNodeInArrays(VoxelTreeElement* node, bool reuseIndex, boo · 20 unmodified lines
980 if (forceDraw || node->isDirty()) {1040 if (forceDraw || node->isDirty()) {
981 // If we're should render, use our legit location and scale,1041 // If we're should render, use our legit location and scale,
982 if (node->getShouldRender()) {1042 if (node->getShouldRender()) {
983 glm::vec3 startVertex = node->getCorner();1043 glm::vec3 startVertex = node->getCorner();
984 float voxelScale = node->getScale();1044 float voxelScale = node->getScale();
985 1045 nodeColor const & color = node->getColor();
986 glBufferIndex nodeIndex = GLBUFFER_INDEX_UNKNOWN;1046
987 if (reuseIndex && node->isKnownBufferIndex()) {1047 if (_usePrimitiveRenderer) {
988 nodeIndex = node->getBufferIndex();1048 int primitiveIndex = node->getPrimitiveIndex();
989 } else {1049 if (primitiveIndex) {
990 nodeIndex = getNextBufferIndex();1050 _renderer->remove(primitiveIndex);
991 node->setBufferIndex(nodeIndex);1051 node->setPrimitiveIndex(0);
992 node->setVoxelSystem(this);1052 }
993 }1053 else {
994 // populate the array with points for the 8 vertices and RGB color for each added vertex1054 node->setVoxelSystem(this);
995 updateArraysDetails(nodeIndex, startVertex, voxelScale, node->getColor());1055 }
996 return 1; // updated!1056 inspectForInteriorOcclusionsOperation(node, 0);
1057
1058 if (node->getInteriorOcclusions() != OctreeElement::HalfSpace::All)
1059 {
1060 Cube *cube = new Cube(
1061 startVertex.x, startVertex.y, startVertex.z, voxelScale,
1062 color[RED_INDEX], color[GREEN_INDEX], color[BLUE_INDEX],
1063 node->getInteriorOcclusions());
1064 if (cube) {
1065 primitiveIndex = _renderer->add(cube);
1066 node->setPrimitiveIndex(primitiveIndex);
1067 }
1068 }
1069 }
1070 else {
1071 glBufferIndex nodeIndex = GLBUFFER_INDEX_UNKNOWN;
1072 if (reuseIndex && node->isKnownBufferIndex()) {
1073 nodeIndex = node->getBufferIndex();
1074 } else {
1075 nodeIndex = getNextBufferIndex();
1076 node->setBufferIndex(nodeIndex);
1077 node->setVoxelSystem(this);
1078 }
1079 // populate the array with points for the 8 vertices and RGB color for each added vertex
1080 updateArraysDetails(nodeIndex, startVertex, voxelScale, node->getColor());
1081 }
1082 return 1; // updated!
997 } else {1083 } else {
998 // If we shouldn't render, and we're in reuseIndex mode, then free our index, this only operates1084 // If we shouldn't render, and we're in reuseIndex mode, then free our index, this only operates
999 // on nodes with known index values, so it's safe to call for any node.1085 // on nodes with known index values, so it's safe to call for any node.
@@ -1140,15 +1226,17 @@ void VoxelSystem::updateVBOs() { · 140 unmodified lines
1140 };1226 };
1141 // would like to include _callsToTreesToArrays1227 // would like to include _callsToTreesToArrays
1142 PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), buffer);1228 PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), buffer);
1143 if (_voxelsDirty) {1229 if (! _usePrimitiveRenderer) {
1144 if (_readRenderFullVBO) {1230 if (_voxelsDirty) {
1145 updateFullVBOs();1231 if (_readRenderFullVBO) {
1146 } else {1232 updateFullVBOs();
1147 updatePartialVBOs();1233 } else {
1148 }1234 updatePartialVBOs();
1149 _voxelsDirty = false;1235 }
1150 _readRenderFullVBO = false;1236 _voxelsDirty = false;
1151 }1237 _readRenderFullVBO = false;
1238 }
1239 }
1152 _callsToTreesToArrays = 0; // clear it1240 _callsToTreesToArrays = 0; // clear it
1153}1241}
1154 1242
@@ -1274,7 +1362,8 @@ void VoxelSystem::render(bool texture) { · 119 unmodified lines
1274 glDisableVertexAttribArray(attributeLocation);1362 glDisableVertexAttribArray(attributeLocation);
1275 glDisable(GL_VERTEX_PROGRAM_POINT_SIZE);1363 glDisable(GL_VERTEX_PROGRAM_POINT_SIZE);
1276 }1364 }
1277 } else {1365 } else
1366 if (!_usePrimitiveRenderer) {
1278 PerformanceWarning warn(showWarnings, "render().. TRIANGLES...");1367 PerformanceWarning warn(showWarnings, "render().. TRIANGLES...");
1279 1368
1280 {1369 {
@@ -1348,6 +1437,12 @@ void VoxelSystem::render(bool texture) { · 67 unmodified lines
1348 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);1437 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
1349 }1438 }
1350 }1439 }
1440 else {
1441 applyScaleAndBindProgram(texture);
1442 _renderer->render();
1443 removeScaleAndReleaseProgram(texture);
1444
1445 }
1351}1446}
1352 1447
1353void VoxelSystem::applyScaleAndBindProgram(bool texture) {1448void VoxelSystem::applyScaleAndBindProgram(bool texture) {
@@ -1411,6 +1506,7 @@ bool VoxelSystem::clearAllNodesBufferIndexOperation(OctreeElement* element, void · 57 unmodified lines
1411 _nodeCount++;1506 _nodeCount++;
1412 VoxelTreeElement* voxel = (VoxelTreeElement*)element;1507 VoxelTreeElement* voxel = (VoxelTreeElement*)element;
1413 voxel->setBufferIndex(GLBUFFER_INDEX_UNKNOWN);1508 voxel->setBufferIndex(GLBUFFER_INDEX_UNKNOWN);
1509 voxel->setPrimitiveIndex(0);
1414 return true;1510 return true;
1415}1511}
1416 1512
@@ -1424,6 +1520,215 @@ void VoxelSystem::clearAllNodesBufferIndex() { · 7 unmodified lines
1424 }1520 }
1425}1521}
1426 1522
1523bool VoxelSystem::inspectForInteriorOcclusionsOperation(OctreeElement* element, void* extraData) {
1524 _nodeCount++;
1525 VoxelTreeElement* voxel = (VoxelTreeElement*)element;
1526
1527 // Nothing to do at the leaf level
1528 if (voxel->isLeaf()) {
1529 return false;
1530 }
1531
1532 // Bit mask of occluded shared faces indexed by child
1533 unsigned char occludedSharedFace[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
1534
1535 // Traverse all pair combinations of children
1536 for (int i = NUMBER_OF_CHILDREN; --i >= 0; ) {
1537
1538 VoxelTreeElement *childA = voxel->getChildAtIndex(i);
1539 if (childA) {
1540
1541 // Get the child A's occluding faces, for a leaf that will be
1542 // all six voxel faces, and for a non leaf, that will be
1543 // all faces which are completely covered by four child octants.
1544 unsigned char exteriorOcclusionsA = childA->getExteriorOcclusions();
1545
1546 if (exteriorOcclusionsA == 0) {
1547 // There is nothing to be done with this child, next...
1548 continue;
1549 }
1550
1551 for (int j = i; --j >= 0; ) {
1552
1553 VoxelTreeElement *childB = voxel->getChildAtIndex(j);
1554 if (childB) {
1555
1556 // Get child B's occluding faces
1557 unsigned char exteriorOcclusionsB = childB->getExteriorOcclusions();
1558
1559 if (exteriorOcclusionsB == 0) {
1560 // There is nothing to be done with this child, next...
1561 continue;
1562 }
1563
1564 // Determine the shared halfspace partition between siblings A and B,
1565 // i.e., near/far, left/right, or top/bottom
1566 unsigned char partition = octantIndexToSharedBitMask[i][j] &
1567 exteriorOcclusionsA & exteriorOcclusionsB;
1568
1569 // Determine which face of each sibling is occluded.
1570 // Note the intentionally crossed indicies. It is necessary because
1571 // the octantIndexToBitMask is a partition occupancy mask. For
1572 // example, if the near-left-top (NLT) and near-left-bottom (NLB) child voxels
1573 // exist, the shared partition is top-bottom (TB), and thus the occluded
1574 // shared face of the NLT voxel is its bottom face.
1575 occludedSharedFace[i] |= (partition & octantIndexToBitMask[j]);
1576 occludedSharedFace[j] |= (partition & octantIndexToBitMask[i]);
1577 }
1578 }
1579 // Combine this voxel's interior excluded shared face only to those children which are coincident
1580 // with the excluded face.
1581 occludedSharedFace[i] |= (voxel->getInteriorOcclusions() & octantIndexToBitMask[i]);
1582
1583 // Inform the child
1584 childA->setInteriorOcclusions(occludedSharedFace[i]);
1585 if (occludedSharedFace[i]) {
1586 //const glm::vec3& v = voxel->getCorner();
1587 //float s = voxel->getScale();
1588
1589 //qDebug("Child %d of voxel at %f %f %f size: %f has %02x occlusions", i, v.x, v.y, v.z, s, occludedSharedFace[i]);
1590 }
1591 }
1592 }
1593 return true;
1594}
1595
1596bool VoxelSystem::inspectForExteriorOcclusionsOperation(OctreeElement* element, void* extraData) {
1597 _nodeCount++;
1598 VoxelTreeElement* voxel = (VoxelTreeElement*)element;
1599
1600 // Nothing to do at the leaf level
1601 if (voxel->isLeaf()) {
1602 return false;
1603 }
1604
1605 // Count of exterior occluding faces of this voxel element indexed
1606 // by half space partition
1607 unsigned int exteriorOcclusionsCt[6] = { 0, 0, 0, 0, 0, 0 };
1608
1609 // Traverse all children
1610 for (int i = NUMBER_OF_CHILDREN; --i >= 0; ) {
1611
1612 VoxelTreeElement *child = voxel->getChildAtIndex(i);
1613 if (child) {
1614 // Get the child's occluding faces, for a leaf, that will be
1615 // all six voxel faces, and for a non leaf, that will be
1616 // all faces which are completely covered by four child octants.
1617 unsigned char exteriorOcclusionsOfChild = child->getExteriorOcclusions();
1618 exteriorOcclusionsOfChild &= octantIndexToBitMask[i];
1619
1620 for (int j = 6; --j >= 0; ) {
1621
1622 // Determine if the halfspace partition indexed by 1 << j is
1623 // present in the exterior occlusions of the child.
1624 unsigned char partition = exteriorOcclusionsOfChild & (1 << j);
1625
1626 if (partition) {
1627 exteriorOcclusionsCt[j]++;
1628 }
1629 }
1630 }
1631 }
1632 {
1633 // Derive the exterior occlusions of the voxel elements from the exclusions
1634 // of its children
1635 unsigned char exteriorOcclusions = 0;
1636 for (int i = 6; --i >= 0; ) {
1637 if (exteriorOcclusionsCt[i] == 4) {
1638
1639 // Exactly four octants qualify for full exterior occlusion
1640 exteriorOcclusions |= (1 << i);
1641 }
1642 }
1643
1644 // Inform the voxel element
1645 voxel->setExteriorOcclusions(exteriorOcclusions);
1646
1647 if (exteriorOcclusions == OctreeElement::HalfSpace::All) {
1648 //const glm::vec3& v = voxel->getCorner();
1649 //float s = voxel->getScale();
1650
1651 //qDebug("Completely occupied voxel at %f %f %f size: %f", v.x, v.y, v.z, s);
1652
1653 // TODO: All of the exterior faces of this voxel element are
1654 // occluders, which means that this element is completely
1655 // occupied. Hence, the subtree from this node could be
1656 // pruned and replaced by a leaf voxel, if the visible
1657 // properties of the children are the same
1658 }
1659 else
1660 if (exteriorOcclusions) {
1661 //const glm::vec3& v = voxel->getCorner();
1662 //float s = voxel->getScale();
1663
1664 //qDebug("Partially occupied voxel at %f %f %f size: %f with %02x", v.x, v.y, v.z, s, exteriorOcclusions);
1665 }
1666 }
1667 return true;
1668}
1669
1670bool VoxelSystem::clearOcclusionsOperation(OctreeElement* element, void* extraData) {
1671 _nodeCount++;
1672 VoxelTreeElement* voxel = (VoxelTreeElement*)element;
1673
1674 bool rc;
1675 if (voxel->isLeaf()) {
1676
1677 // By definition the the exterior faces of a leaf voxel are
1678 // always occluders.
1679 voxel->setExteriorOcclusions(OctreeElement::HalfSpace::All);
1680
1681 // And the sibling occluders
1682 voxel->setInteriorOcclusions(0);
1683 rc = false;
1684 }
1685 else {
1686 voxel->setExteriorOcclusions(0);
1687 voxel->setInteriorOcclusions(0);
1688 rc = true;
1689 }
1690
1691 return rc;
1692}
1693
1694void VoxelSystem::cullSharedFaces() {
1695 _nodeCount = 0;
1696
1697 if (Menu::getInstance()->isOptionChecked(MenuOption::CullSharedFaces)) {
1698 cleanupVoxelMemory();
1699 _useVoxelShader = false;
1700 _usePrimitiveRenderer = true;
1701 initVoxelMemory();
1702 clearAllNodesBufferIndex();
1703 lockTree();
1704 _tree->recurseTreeWithPostOperation(inspectForExteriorOcclusionsOperation);
1705 _tree->recurseTreeWithPostOperation(inspectForExteriorOcclusionsOperation);
1706 unlockTree();
1707
1708 _nodeCount = 0;
1709 lockTree();
1710 _tree->recurseTreeWithOperation(inspectForInteriorOcclusionsOperation);
1711 unlockTree();
1712 qDebug("culling shared faces in %d nodes", _nodeCount);
1713 }
1714 else {
1715 cleanupVoxelMemory();
1716 _usePrimitiveRenderer = false;
1717 initVoxelMemory();
1718 clearAllNodesBufferIndex();
1719 lockTree();
1720 _tree->recurseTreeWithOperation(clearOcclusionsOperation);
1721 unlockTree();
1722
1723
1724 qDebug("unculling shared faces in %d nodes", _nodeCount);
1725 }
1726 _writeRenderFullVBO = true;
1727 _tree->setDirtyBit();
1728 setupNewVoxelsForDrawing();
1729
1730}
1731
1427bool VoxelSystem::forceRedrawEntireTreeOperation(OctreeElement* element, void* extraData) {1732bool VoxelSystem::forceRedrawEntireTreeOperation(OctreeElement* element, void* extraData) {
1428 _nodeCount++;1733 _nodeCount++;
1429 element->setDirtyBit();1734 element->setDirtyBit();
@@ -1997,7 +2302,7 @@ bool VoxelSystem::hideAllSubTreeOperation(OctreeElement* element, void* extraDat · 567 unmodified lines
1997 }2302 }
1998 2303
1999 args->nodesOutside++;2304 args->nodesOutside++;
2000 if (voxel->isKnownBufferIndex()) {2305 if (voxel->isKnownBufferIndex() || voxel->getPrimitiveIndex()) {
2001 args->nodesRemoved++;2306 args->nodesRemoved++;
2002 bool falseColorize = false;2307 bool falseColorize = false;
2003 if (falseColorize) {2308 if (falseColorize) {
@@ -2039,7 +2344,7 @@ bool VoxelSystem::showAllSubTreeOperation(OctreeElement* element, void* extraDat · 35 unmodified lines
2039 bool shouldRender = voxel->calculateShouldRender(&args->thisViewFrustum, voxelSizeScale, boundaryLevelAdjust);2344 bool shouldRender = voxel->calculateShouldRender(&args->thisViewFrustum, voxelSizeScale, boundaryLevelAdjust);
2040 voxel->setShouldRender(shouldRender);2345 voxel->setShouldRender(shouldRender);
2041 2346
2042 if (shouldRender && !voxel->isKnownBufferIndex()) {2347 if (shouldRender && !(voxel->isKnownBufferIndex() || voxel->getPrimitiveIndex())) {
2043 bool falseColorize = false;2348 bool falseColorize = false;
2044 if (falseColorize) {2349 if (falseColorize) {
2045 voxel->setFalseColor(0,0,255); // false colorize2350 voxel->setFalseColor(0,0,255); // false colorize
@@ -2047,6 +2352,12 @@ bool VoxelSystem::showAllSubTreeOperation(OctreeElement* element, void* extraDat · 1 unmodified line
2047 // These are both needed to force redraw...2352 // These are both needed to force redraw...
2048 voxel->setDirtyBit();2353 voxel->setDirtyBit();
2049 voxel->markWithChangedTime();2354 voxel->markWithChangedTime();
2355 // and this?
2356 {
2357 VoxelSystem* thisVoxelSystem = args->thisVoxelSystem;
2358 thisVoxelSystem->_voxelsUpdated += thisVoxelSystem->updateNodeInArrays(voxel, true, true);
2359 thisVoxelSystem->setupNewVoxelsForDrawingSingleNode();
2360 }
2050 args->nodesShown++;2361 args->nodesShown++;
2051 }2362 }
2052 2363
@@ -2128,7 +2439,7 @@ bool VoxelSystem::hideOutOfViewOperation(OctreeElement* element, void* extraData · 75 unmodified lines
2128 // if the child node INTERSECTs the view, then we want to check to see if it thinks it should render2439 // if the child node INTERSECTs the view, then we want to check to see if it thinks it should render
2129 // if it should render but is missing it's VBO index, then we want to flip it on, and we can stop recursing from2440 // if it should render but is missing it's VBO index, then we want to flip it on, and we can stop recursing from
2130 // here because we know will block any children anyway2441 // here because we know will block any children anyway
2131 if (voxel->getShouldRender() && !voxel->isKnownBufferIndex()) {2442 if (voxel->getShouldRender() && !(voxel->isKnownBufferIndex() || voxel->getPrimitiveIndex())) {
2132 voxel->setDirtyBit(); // will this make it draw?2443 voxel->setDirtyBit(); // will this make it draw?
2133 args->nodesShown++;2444 args->nodesShown++;
2134 return false;2445 return false;
@@ -2225,7 +2536,9 @@ public: · 90 unmodified lines
2225 nodesInVBONotShouldRender(0),2536 nodesInVBONotShouldRender(0),
2226 nodesInVBOOverExpectedMax(0),2537 nodesInVBOOverExpectedMax(0),
2227 duplicateVBOIndex(0),2538 duplicateVBOIndex(0),
2228 leafNodes(0)2539 leafNodes(0),
2540 culledLeafNodes(0),
2541 nodesInPrimitiveRenderer(0)
2229 {2542 {
2230 hasIndexFound = new bool[maxVoxels];2543 hasIndexFound = new bool[maxVoxels];
2231 memset(hasIndexFound, false, maxVoxels * sizeof(bool));2544 memset(hasIndexFound, false, maxVoxels * sizeof(bool));
@@ -2244,6 +2557,8 @@ public: · 12 unmodified lines
2244 unsigned long nodesInVBOOverExpectedMax;2557 unsigned long nodesInVBOOverExpectedMax;
2245 unsigned long duplicateVBOIndex;2558 unsigned long duplicateVBOIndex;
2246 unsigned long leafNodes;2559 unsigned long leafNodes;
2560 unsigned long culledLeafNodes; ///< Number of completely culled nodes because of face sharing
2561 unsigned long nodesInPrimitiveRenderer;
2247 2562
2248 unsigned long expectedMax;2563 unsigned long expectedMax;
2249 2564
@@ -2258,6 +2573,9 @@ bool VoxelSystem::collectStatsForTreesAndVBOsOperation(OctreeElement* element, v · 8 unmodified lines
2258 2573
2259 if (voxel->isLeaf()) {2574 if (voxel->isLeaf()) {
2260 args->leafNodes++;2575 args->leafNodes++;
2576 if (voxel->getInteriorOcclusions() == OctreeElement::HalfSpace::All) {
2577 args->culledLeafNodes++;
2578 }
2261 }2579 }
2262 2580
2263 if (voxel->isColored()) {2581 if (voxel->isColored()) {
@@ -2272,15 +2590,25 @@ bool VoxelSystem::collectStatsForTreesAndVBOsOperation(OctreeElement* element, v · 8 unmodified lines
2272 args->dirtyNodes++;2590 args->dirtyNodes++;
2273 }2591 }
2274 2592
2275 if (voxel->isKnownBufferIndex()) {2593 unsigned long nodeIndex = 0;
2276 args->nodesInVBO++;2594 if (voxel->isKnownBufferIndex()) {
2277 unsigned long nodeIndex = voxel->getBufferIndex();2595 args->nodesInVBO++;
2596 nodeIndex = voxel->getBufferIndex();
2597 }
2598
2599 if (voxel->getPrimitiveIndex()) {
2600 args->nodesInPrimitiveRenderer++;
2601 nodeIndex = voxel->getPrimitiveIndex();
2602 }
2603
2604 if (voxel->isKnownBufferIndex() || (voxel->getPrimitiveIndex() != 0)) {
2278 2605
2279 const bool extraDebugging = false; // enable for extra debugging2606 const bool extraDebugging = false; // enable for extra debugging
2280 if (extraDebugging) {2607 if (extraDebugging) {
2281 qDebug("node In VBO... [%f,%f,%f] %f ... index=%ld, isDirty=%s, shouldRender=%s",2608 qDebug("node In VBO... [%f,%f,%f] %f ... index=%ld, isDirty=%s, shouldRender=%s, culledFaces=0x%02x \n",
2282 voxel->getCorner().x, voxel->getCorner().y, voxel->getCorner().z, voxel->getScale(),2609 voxel->getCorner().x, voxel->getCorner().y, voxel->getCorner().z, voxel->getScale(),
2283 nodeIndex, debug::valueOf(voxel->isDirty()), debug::valueOf(voxel->getShouldRender()));2610 nodeIndex, debug::valueOf(voxel->isDirty()), debug::valueOf(voxel->getShouldRender()),
2611 voxel->getInteriorOcclusions());
2284 }2612 }
2285 2613
2286 if (args->hasIndexFound[nodeIndex]) {2614 if (args->hasIndexFound[nodeIndex]) {
@@ -2309,12 +2637,14 @@ void VoxelSystem::collectStatsForTreesAndVBOs() { · 22 unmodified lines
2309 glBufferIndex minDirty = GLBUFFER_INDEX_UNKNOWN;2637 glBufferIndex minDirty = GLBUFFER_INDEX_UNKNOWN;
2310 glBufferIndex maxDirty = 0;2638 glBufferIndex maxDirty = 0;
2311 2639
2312 for (glBufferIndex i = 0; i < _voxelsInWriteArrays; i++) {2640 if (!_usePrimitiveRenderer) {
2313 if (_writeVoxelDirtyArray[i]) {2641 for (glBufferIndex i = 0; i < _voxelsInWriteArrays; i++) {
2314 minDirty = std::min(minDirty,i);2642 if (_writeVoxelDirtyArray[i]) {
2315 maxDirty = std::max(maxDirty,i);2643 minDirty = std::min(minDirty,i);
2316 }2644 maxDirty = std::max(maxDirty,i);
2317 }2645 }
2646 }
2647 }
2318 2648
2319 collectStatsForTreesAndVBOsArgs args(_maxVoxels);2649 collectStatsForTreesAndVBOsArgs args(_maxVoxels);
2320 args.expectedMax = _voxelsInWriteArrays;2650 args.expectedMax = _voxelsInWriteArrays;
@@ -2323,7 +2653,7 @@ void VoxelSystem::collectStatsForTreesAndVBOs() { · 2 unmodified lines
2323 2653
2324 _tree->recurseTreeWithOperation(collectStatsForTreesAndVBOsOperation,&args);2654 _tree->recurseTreeWithOperation(collectStatsForTreesAndVBOsOperation,&args);
2325 2655
2326 qDebug("Local Voxel Tree Statistics:\n total nodes %ld \n leaves %ld \n dirty %ld \n colored %ld \n shouldRender %ld",2656 qDebug("Local Voxel Tree Statistics:\n total nodes %ld \n leaves %ld \n dirty %ld \n colored %ld \n shouldRender %ld \n",
2327 args.totalNodes, args.leafNodes, args.dirtyNodes, args.coloredNodes, args.shouldRenderNodes);2657 args.totalNodes, args.leafNodes, args.dirtyNodes, args.coloredNodes, args.shouldRenderNodes);
2328 2658
2329 qDebug(" _voxelsDirty=%s \n _voxelsInWriteArrays=%ld \n minDirty=%ld \n maxDirty=%ld", debug::valueOf(_voxelsDirty),2659 qDebug(" _voxelsDirty=%s \n _voxelsInWriteArrays=%ld \n minDirty=%ld \n maxDirty=%ld", debug::valueOf(_voxelsDirty),
@@ -2332,6 +2662,9 @@ void VoxelSystem::collectStatsForTreesAndVBOs() { · 2 unmodified lines
2332 qDebug(" inVBO %ld \n nodesInVBOOverExpectedMax %ld \n duplicateVBOIndex %ld \n nodesInVBONotShouldRender %ld",2662 qDebug(" inVBO %ld \n nodesInVBOOverExpectedMax %ld \n duplicateVBOIndex %ld \n nodesInVBONotShouldRender %ld",
2333 args.nodesInVBO, args.nodesInVBOOverExpectedMax, args.duplicateVBOIndex, args.nodesInVBONotShouldRender);2663 args.nodesInVBO, args.nodesInVBOOverExpectedMax, args.duplicateVBOIndex, args.nodesInVBONotShouldRender);
2334 2664
2665 qDebug(" inPrimitiveRenderer %ld \n completely culled %ld \n",
2666 args.nodesInPrimitiveRenderer, args.culledLeafNodes);
2667
2335 glBufferIndex minInVBO = GLBUFFER_INDEX_UNKNOWN;2668 glBufferIndex minInVBO = GLBUFFER_INDEX_UNKNOWN;
2336 glBufferIndex maxInVBO = 0;2669 glBufferIndex maxInVBO = 0;
2337 2670
@@ -2775,5 +3108,102 @@ void VoxelSystem::beginLoadingLocalVoxelCache() { · 437 unmodified lines
2775 qDebug() << "DONE beginLoadingLocalVoxelCache()";3108 qDebug() << "DONE beginLoadingLocalVoxelCache()";
2776}3109}
2777 3110
3111// Octant bitmask array indexed by octant. The mask value indicates the octant's halfspace partitioning. The index
3112// value corresponds to the voxel's octal code derived in "pointToVoxel" in SharedUtil.cpp, which, BTW, does *not*
3113// correspond to the "ChildIndex" enum value in OctreeElement.h
3114unsigned char VoxelSystem::octantIndexToBitMask[8] = {
3115 OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Left | OctreeElement::HalfSpace::Near,
3116 OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Left | OctreeElement::HalfSpace::Far,
3117 OctreeElement::HalfSpace::Top | OctreeElement::HalfSpace::Left | OctreeElement::HalfSpace::Near,
3118 OctreeElement::HalfSpace::Top | OctreeElement::HalfSpace::Left | OctreeElement::HalfSpace::Far,
3119 OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Near,
3120 OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Far,
3121 OctreeElement::HalfSpace::Top | OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Near,
3122 OctreeElement::HalfSpace::Top | OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Far,
3123};
2778 3124
3125// Two dimensional array map indexed by octant row and column. The mask value
3126// indicates the two faces shared by the octants
3127unsigned char VoxelSystem::octantIndexToSharedBitMask[8][8] = {
3128 { // Index 0: Bottom-Left-Near
3129 0, // Bottom-Left-Near
3130 OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Bottom-Left-Far
3131 OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Top-Left-Near
3132 0, // Top-Left-Far
3133 OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Bottom-Right-Near
3134 0, // Bottom-Right-Far
3135 0, // Top-Right-Near
3136 0, // Top-Right-Far
3137 },
3138 { // Index 1: Bottom-Left-Far
3139 OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Bottom-Left-Near
3140 0, // Bottom-Left-Far
3141 0, // Top-Left-Near
3142 OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Top-Left-Far
3143 0, // Bottom-Right-Near
3144 OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Bottom-Right-Far
3145 0, // Top-Right-Near
3146 0, // Top-Right-Far
3147 },
3148 { // Index 2: Top-Left-Near
3149 OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Bottom-Left-Near
3150 0, // Bottom-Left-Far
3151 0, // Top-Left-Near
3152 OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Top-Left-Far
3153 0, // Bottom-Right-Near
3154 0, // Bottom-Right-Far
3155 OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Top-Right-Near
3156 0, // Top-Right-Far
3157 },
3158 { // Index 3: Top-Left-Far
3159 0, // Bottom-Left-Near
3160 OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Bottom-Left-Far
3161 OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Top-Left-Near
3162 0, // Top-Left-Far
3163 0, // Bottom-Right-Near
3164 0, // Bottom-Right-Far
3165 0, // Top-Right-Near
3166 OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Top-Right-Far
3167 },
3168 { // Index 4: Bottom-Right-Near
3169 OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Bottom-Left-Near
3170 0, // Bottom-Left-Far
3171 0, // Top-Left-Near
3172 0, // Top-Left-Far
3173 0, // Bottom-Right-Near
3174 OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Bottom-Right-Far
3175 OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Top-Right-Near
3176 0, // Top-Right-Far
3177 },
3178 { // Index 5: Bottom-Right-Far
3179 0, // Bottom-Left-Near
3180 OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Bottom-Left-Far
3181 0, // Top-Left-Near
3182 0, // Top-Left-Far
3183 OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Bottom-Right-Near
3184 0, // Bottom-Right-Far
3185 0, // Top-Right-Near
3186 OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Top-Right-Far
3187 },
3188 { // Index 6: Top-Right-Near
3189 0, // Bottom-Left-Near
3190 0, // Bottom-Left-Far
3191 OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Top-Left-Near
3192 0, // Top-Left-Far
3193 OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Bottom-Right-Near
3194 0, // Bottom-Right-Far
3195 0, // Top-Right-Near
3196 OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Top-Right-Far
3197 },
3198 { // Index 7: Top-Right-Far
3199 0, // Bottom-Left-Near
3200 0, // Bottom-Left-Far
3201 0, // Top-Left-Near
3202 OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Top-Left-Far
3203 0, // Bottom-Right-Near
3204 OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Bottom-Right-Far
3205 OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Top-Right-Near
3206 0, // Top-Right-Far
3207 },
3208};
2779 3209