// // Render2.cpp // // Copyright (c) 2014 HighFidelity, Inc. All rights reserved. // #include "svoviewer.h" ///////////////////////////////////////////////////////////////////////////// // Helper functions // Precision dependent hack. After debugging - change this to a magnitude function. // simple version for clarity/debugging. int SvoViewer::ptCompFunc(const void * a, const void * b) { if ((*(glm::vec3*)a).x < (*(glm::vec3*)b).x) return -1; else if ((*(glm::vec3*)a).x > (*(glm::vec3*)b).x) return 1; if ((*(glm::vec3*)a).y < (*(glm::vec3*)b).y) return -1; else if ((*(glm::vec3*)a).y > (*(glm::vec3*)b).y) return 1; if ((*(glm::vec3*)a).z < (*(glm::vec3*)b).z) return -1; else if ((*(glm::vec3*)a).z > (*(glm::vec3*)b).z) return 1; return 0; } //#define PRECISION_ERR .00000001 #define PRECISION_ERR .00001 // aggressive mode //(0.00097656250 /2) // Space of smallest voxel should define our error bounds here. Test this if time allows. int SvoViewer::ptCloseEnough(const void * a, const void * b) { glm::vec3 diffVec = (*(glm::vec3*)a) - (*(glm::vec3*)b); if (fabs(diffVec.x) < PRECISION_ERR && fabs(diffVec.y) < PRECISION_ERR && fabs(diffVec.z) < PRECISION_ERR) return 0; //float len = diffVec.length(); //if (len < PRECISION_ERR) return 0; if ((*(glm::vec3*)a).x < (*(glm::vec3*)b).x) return -1; else if ((*(glm::vec3*)a).x > (*(glm::vec3*)b).x) return 1; if ((*(glm::vec3*)a).y < (*(glm::vec3*)b).y) return -1; else if ((*(glm::vec3*)a).y > (*(glm::vec3*)b).y) return 1; if ((*(glm::vec3*)a).z < (*(glm::vec3*)b).z) return -1; else if ((*(glm::vec3*)a).z > (*(glm::vec3*)b).z) return 1; return 0; } // return parameterized intersection in t. bool SvoViewer::parameterizedRayPlaneIntersection(const glm::vec3 origin, const glm::vec3 direction, const glm::vec3 planePt, const glm::vec3 planeNormal, float *t) { float denom = glm::dot(direction, planeNormal); if (denom < PRECISION_ERR) return false; glm::vec3 p010 = planePt - origin; *t = glm::dot(p010, planeNormal) / denom; return true; } ///////////////////////////////////////////////////////////////////////////// // 2nd stab at optimizing this. Cull back faces more aggressively. struct VoxelOpt2RenderAssembleData { Vertex* vtxBuffer; VoxelDimIdxSet* idxSet; int vtxCount; int faceCenterCount; glm::vec3 * faceCenterList; int discardedCount; }; bool SvoViewer::VoxelOpt2RenderAssemblePerVoxel(OctreeElement* node, void* extraData) { VoxelTreeElement* voxel = (VoxelTreeElement*)node; VoxelOpt2RenderAssembleData* args = (VoxelOpt2RenderAssembleData*)extraData; if (!node->isLeaf()) return true; AABox box = voxel->getAABox(); glm::vec3 p0, p1, p2, p3, hackCenterVal; glm::vec3 cubeVerts[GLOBAL_NORMALS_VERTICES_PER_VOXEL]; for (int i = 0; i < GLOBAL_NORMALS_VERTICES_PER_VOXEL; i++) // Cache, as aabox reconstructs with every call. cubeVerts[i] = box.getVertex((BoxVertex)i); bool doAddFace[NUM_CUBE_FACES] = {true, false, true, true, true, true}; // Cull bottom faces by default. // Accumulate all the faces that need to be added. for (int i = 0; i < NUM_CUBE_FACES; i++) { p0 = cubeVerts[SvoViewerNames::cubeFaceVtxs[i][0]]; p1 = cubeVerts[SvoViewerNames::cubeFaceVtxs[i][1]]; p2 = cubeVerts[SvoViewerNames::cubeFaceVtxs[i][2]]; p3 = cubeVerts[SvoViewerNames::cubeFaceVtxs[i][3]]; hackCenterVal = computeQuickAndDirtyQuadCenter(p0, p1, p2, p3); // Search for this in the face center list //glm::vec3 * foundVal = (glm::vec3*)bsearch(&hackCenterVal, args->faceCenterList, args->faceCenterCount, sizeof(glm::vec3), ptCompFunc); // BSEARCH FAILING! What the? wrote my own index approximate version. int foundBVS = 0; int idxIntoList = binVecSearch(hackCenterVal, args->faceCenterList, args->faceCenterCount, &foundBVS); if (foundBVS == 0) { assert(0); continue; } // What the? assert(idxIntoList <= args->faceCenterCount-1); // Now check face center list values that are immmediately adjacent to this value. If they're equal, don't add this face as // another leaf voxel must contain this triangle too. bool foundMatch = false; if (idxIntoList != 0) { if (ptCloseEnough(&hackCenterVal, &args->faceCenterList[idxIntoList-1]) == 0) foundMatch = true; } if (idxIntoList != args->faceCenterCount-1 && foundMatch == false) { if (ptCloseEnough(&hackCenterVal, &args->faceCenterList[idxIntoList+1]) == 0) foundMatch = true; } if (foundMatch) { doAddFace[i] = false; // Remove. args->discardedCount++; } } #define VTX_NOT_USED 255 unsigned char vtxToAddMap[GLOBAL_NORMALS_VERTICES_PER_VOXEL]; // Map from vertex to actual position in the new vtx list. memset(vtxToAddMap, VTX_NOT_USED, sizeof(vtxToAddMap)); // Figure out what vertices to add. NOTE - QUICK and dirty. easy opt - precalulate bit pattern for every face and just & it. bool useVtx[GLOBAL_NORMALS_VERTICES_PER_VOXEL]; memset(useVtx, 0, sizeof(useVtx)); for ( int face = 0; face < NUM_CUBE_FACES; face++) // Vertex add order. { if (doAddFace[face]) { for (int vOrder = 0; vOrder < 4; vOrder++) { useVtx[SvoViewerNames::cubeFaceVtxs[face][vOrder]] = true; } } } unsigned char vtxAddedCount = 0; int baseVtxCount = args->vtxCount; for (int i = 0; i < GLOBAL_NORMALS_VERTICES_PER_VOXEL; i++) { if (useVtx[i]) { vtxToAddMap[i] = vtxAddedCount; vtxAddedCount++; args->vtxBuffer[args->vtxCount].position = cubeVerts[i]; args->vtxBuffer[args->vtxCount].position *= 100; args->vtxBuffer[args->vtxCount].position.x -= 25; args->vtxBuffer[args->vtxCount].position.y -= 4; args->vtxBuffer[args->vtxCount].color[0] = voxel->getColor()[0]; args->vtxBuffer[args->vtxCount].color[1] = voxel->getColor()[1]; args->vtxBuffer[args->vtxCount].color[2] = voxel->getColor()[2]; args->vtxBuffer[args->vtxCount].color[3] = 1; cubeVerts[i] = args->vtxBuffer[args->vtxCount].position; args->vtxCount++; } } // Assemble the index lists. for ( int face = 0; face < NUM_CUBE_FACES; face++) { if (doAddFace[face]) { for (int i = 0; i < 6; i++) // 2 * 3 triangles. { args->idxSet->idxBuff[face][args->idxSet->idxCount[face]] = baseVtxCount + vtxToAddMap[ SvoViewerNames::cubeFaceIndices[face][i] ]; args->idxSet->idxCount[face]++; } for (int i = 0; i < 4; i++) { args->idxSet->bounds[face].AddToSet( cubeVerts[SvoViewerNames::cubeFaceVtxs[face][i]] ); } args->idxSet->elemCount[face] += 2; } } return true; } void SvoViewer::InitializeVoxelOpt2RenderSystem() { quint64 startInit = usecTimestampNow(); if (_voxelOptRenderInitialized) return; _numSegments = 0; _totalPossibleElems = 0; memset(_numChildNodeLeaves, 0, sizeof(_numChildNodeLeaves)); memset(_segmentNodeReferences, 0, sizeof(_segmentNodeReferences)); // Set up the segments. Find the number of leaves at each subtree. OctreeElement * rootNode = _systemTree.getRoot(); OctreeElement* node0fromRoot = rootNode->getChildAtIndex(0); // ALL the interesting data for our test SVO is in this node! HACK!! //int rootNumChildren = rootNode->getChildCount(); for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { OctreeElement* childNode1stOrder = node0fromRoot->getChildAtIndex(i); if (childNode1stOrder == NULL) continue; // Grab 2nd order nodes for better separation. At some point, this would need to be done intelligently. for (int j = 0; j < NUMBER_OF_CHILDREN; j++) { OctreeElement* childNode2ndOrder = childNode1stOrder->getChildAtIndex(j); if (childNode2ndOrder == NULL) continue; //int num2ndOrderChildren = childNode2ndOrder->getChildCount(); // Figure out how populated this child is. FindNumLeavesData data; data.numLeaves = 0; _systemTree.recurseNodeWithOperation(childNode2ndOrder, &FindNumLeaves, &data, 0); // Some of these nodes have a single leaf. Ignore for the moment. We really only want the big segments in this test. Add this back in at some point. if (data.numLeaves > 1) { _numChildNodeLeaves[_numSegments] = data.numLeaves; _segmentNodeReferences[_numSegments] = childNode2ndOrder; _totalPossibleElems += data.numLeaves * NUM_CUBE_FACES * 2; _numSegments++; qDebug("child node %d %d has %d leaves and %d children itself\n", i, j, data.numLeaves, childNode2ndOrder->getChildCount()); if (_numSegments >= MAX_NUM_OCTREE_PARTITIONS ) { qDebug("Out of segment space??? What the?\n"); break; } } } if (_numSegments >= MAX_NUM_OCTREE_PARTITIONS ) { qDebug("Out of segment space??? What the?\n"); break; } } // Set up the VBO's. Once for every partition we stored. for (int i = 0; i < _numSegments; i++) { // compute the visible set of this segment first. glm::vec3* faceCenters = new glm::vec3[NUM_CUBE_FACES *_numChildNodeLeaves[i]]; VisibleFacesData visFaceData; visFaceData.ptList = faceCenters; visFaceData.count = 0; _systemTree.recurseNodeWithOperation(_segmentNodeReferences[i], &TrackVisibleFaces, &visFaceData, 0); // Now there's a list of all the face centers. Sort it. qsort(faceCenters, visFaceData.count, sizeof(glm::vec3), ptCompFunc); qDebug("Creating VBO's. Sorted neighbor list %d\n", i); _readVertexStructs = new Vertex[GLOBAL_NORMALS_VERTICES_PER_VOXEL * _numChildNodeLeaves[i]]; memset(&_segmentIdxBuffers[i], 0, sizeof(VoxelDimIdxSet)); // Don't do it this way if we ever use a vtable for AABoundingVolumes! for (int k = 0; k < NUM_CUBE_FACES; k++) { _segmentIdxBuffers[i].idxBuff[k] = new GLuint[2 * 3 * _numChildNodeLeaves[i]]; assert(_segmentIdxBuffers[i].idxBuff[k] != NULL); } VoxelOpt2RenderAssembleData args; args.vtxBuffer = _readVertexStructs; args.vtxCount = 0; args.faceCenterCount = visFaceData.count; args.faceCenterList = visFaceData.ptList; args.discardedCount = 0; args.idxSet = &_segmentIdxBuffers[i]; _systemTree.recurseNodeWithOperation(_segmentNodeReferences[i], &VoxelOpt2RenderAssemblePerVoxel, &args, 0); SetupGlVBO(&_vboOVerticesIds[i], args.vtxCount * sizeof(Vertex), GL_ARRAY_BUFFER, GL_STATIC_DRAW, _readVertexStructs); unsigned int idxCount = 0; for (int k = 0; k < NUM_CUBE_FACES; k++) { SetupGlVBO(&_segmentIdxBuffers[i].idxIds[k], _segmentIdxBuffers[i].idxCount[k] * sizeof(GLuint), GL_ARRAY_BUFFER, GL_STATIC_DRAW, _segmentIdxBuffers[i].idxBuff[k]); idxCount += _segmentIdxBuffers[i].idxCount[k]; _segmentIdxBuffers[i].bounds[k].setIsSingleDirection(true, SvoViewerNames::faceNormals[k]); } qDebug("Partition %d, vertices %d, indices %d, discarded %d\n", i, args.vtxCount, idxCount, args.discardedCount); delete [] _readVertexStructs; //delete [] _readIndicesArray; delete [] faceCenters; for (int k = 0; k < NUM_CUBE_FACES; k++) if (_segmentIdxBuffers[i].idxBuff[k] != NULL) delete [] _segmentIdxBuffers[i].idxBuff[k]; } _voxelOptRenderInitialized = true; UpdateOpt2BVFaceVisibility(); quint64 endInit = usecTimestampNow(); quint64 elapsed = endInit - startInit; qDebug() << "init elapsed:" << ((float)elapsed / (float)1000000.0f) << "seconds"; } void SvoViewer::RenderTreeSystemAsOpt2Voxels() { glEnable(GL_LIGHTING); glEnable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); // disable specular lighting for ground and voxels glMaterialfv(GL_FRONT, GL_SPECULAR, NO_SPECULAR_COLOR); setupWorldLight(); _numElemsDrawn = 0; for (int i = 0; i < _numSegments; i++) { if (_displayOnlyPartition == i || _displayOnlyPartition == NO_PARTITION ) { glBindBuffer(GL_ARRAY_BUFFER, _vboOVerticesIds[i]); // NOTE: mac compiler doesn't support offsetof() for non-POD types, which apparently glm::vec3 is //glVertexAttribPointer(ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex,position)); glVertexAttribPointer(ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0); glEnableVertexAttribArray(ATTRIB_POSITION); // NOTE: mac compiler doesn't support offsetof() for non-POD types, which apparently glm::vec3 is //glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex,color)); glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof(Vertex), (void*)sizeof(glm::vec3)); glEnableVertexAttribArray(ATTRIB_COLOR); //glVertexPointer(3, GL_FLOAT, sizeof(Vertex), (void*)offsetof(Vertex,position)); glEnableClientState(GL_COLOR_ARRAY); // NOTE: mac compiler doesn't support offsetof() for non-POD types, which apparently glm::vec3 is //glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Vertex), (void*)offsetof(Vertex,color)); glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Vertex), (void*)sizeof(glm::vec3)); for (int j = 0; j < NUM_CUBE_FACES; j++) { // Add aggressive LOD check here. if (_segmentIdxBuffers[i].visibleFace[j] || _useBoundingVolumes != true) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _segmentIdxBuffers[i].idxIds[j]);//_vboOIndicesIds[i]); glDrawElements(GL_TRIANGLES, _segmentIdxBuffers[i].elemCount[j]*3, GL_UNSIGNED_INT, NULL); _numElemsDrawn += _segmentIdxBuffers[i].elemCount[j]; } } } } glDisableVertexAttribArray(ATTRIB_POSITION); glDisableVertexAttribArray(ATTRIB_COLOR); } // special rules for single direction bv sets. Basically, we intersect a lookat ray from the camera with two opposite faces and discard // the entire set of the face that is further away as it must be back facing. void SvoViewer::UpdateOpt2BVFaceVisibility() { if (_currentShaderModel != RENDER_OPT_CULLED_POLYS || _voxelOptRenderInitialized != true ) return; //float faceParamVals[NUM_CUBE_FACES]; glm::vec3 pos = _myCamera.getPosition(); for (int i = 0; i < _numSegments; i++) { VoxelDimIdxSet* setPtr = &_segmentIdxBuffers[i]; // Fast cull check. setPtr->visibleFace[0] = (_segmentIdxBuffers[i].bounds[0].within(pos.y, 1) >= 0) ? true : false; setPtr->visibleFace[1] = (_segmentIdxBuffers[i].bounds[1].within(pos.y, 1) <= 0) ? true : false; setPtr->visibleFace[2] = (_segmentIdxBuffers[i].bounds[2].within(pos.x, 0) >= 0) ? true : false; setPtr->visibleFace[3] = (_segmentIdxBuffers[i].bounds[3].within(pos.x, 0) <= 0) ? true : false; setPtr->visibleFace[4] = (_segmentIdxBuffers[i].bounds[4].within(pos.z, 2) <= 0) ? true : false; setPtr->visibleFace[5] = (_segmentIdxBuffers[i].bounds[5].within(pos.z, 2) >= 0) ? true : false; // Make sure its actually on the screen. /*for (int j = 0; j < NUM_CUBE_FACES; j++) { if (setPtr->visibleFace[j]) { if (visibleAngleSubtended(&_segmentIdxBuffers[i].bounds[j], &_myCamera, &_viewFrustum) <= 0) setPtr->visibleFace[j] = false; } }*/ } /* for (int j = 0; j < NUM_CUBE_FACES; j++) { setPtr->visibleFace[i] = true; AABoundingVolume* volume = &_segmentIdxBuffers[i].bounds[j]; glm::vec3 randomPlaneVtx = volume->getCorner((BoxVertex)SvoViewerNames::cubeFaceIndices[j][0]); glm::vec3 raydir = randomPlaneVtx - pos; rayder /= glm::length(raydir); if (glm::dot(target, raydir) < 1) raydir *= -1; if (!parameterizedRayPlaneIntersection(pos, raydir, randomPlaneVtx, SvoViewerNames::faceNormals[j], &faceParamVals[j])) faceParamVals[j] = -1; } */ } void SvoViewer::StopUsingVoxelOpt2RenderSystem() { glDisableVertexAttribArray(ATTRIB_POSITION); glDisableVertexAttribArray(ATTRIB_COLOR); glDisable(GL_LIGHTING); }