/// A mesh must have an array of positions and connectivity. It may also have per-vertex normals /// If per-vertex normals are omitted, then per-face normals are used instead. /// /// The mesh does NOT delete the input arrays /// /// \param rPositions /// Array of vertex positions /// \param pFaceNormals /// Array of face normals. Must be same length as nTriangleCount /// \param nTriangleCount /// Number of triangles /// \param pIndices /// An array of 3*nTriangleCount vertex indices JRTMesh* JRTMesh::CreateMesh(const Vec3f* pPositions, const Vec3f* pNormals, UINT nVertices, UINT nTriangleCount, const UINT* pIndices) { JRTMesh* pMesh = new JRTMesh(); pMesh->m_nVertexCount = nVertices; pMesh->m_nTriangleCount = nTriangleCount; // copy vertex positions pMesh->m_Positions.assign (pPositions, pPositions + nVertices); // copy normals pMesh->m_FaceNormals.assign (pNormals, pNormals + nTriangleCount); // create triangles pMesh->m_Triangles.resize (nTriangleCount); for (UINT i = 0; i < nTriangleCount; i++) { pMesh->m_Triangles[i].m_pMesh = pMesh; JRT_ASSERT(pIndices[0] < nVertices); JRT_ASSERT(pIndices[1] < nVertices); JRT_ASSERT(pIndices[2] < nVertices); pMesh->m_Triangles[i].m_pV1 = reinterpret_cast<const float*> (&pMesh->m_Positions[ pIndices[0] ]); pMesh->m_Triangles[i].m_pV2 = reinterpret_cast<const float*> (&pMesh->m_Positions[ pIndices[1] ]); pMesh->m_Triangles[i].m_pV3 = reinterpret_cast<const float*> (&pMesh->m_Positions[ pIndices[2] ]); pIndices += 3; } return pMesh; }
JRTKDTree* JRTKDTreeBuilder::BuildTree(const std::vector<JRTMesh*>& rMeshes) { JRTKDTree* pTree = NULL; std::vector<JRTKDNode> nodes; std::vector<UINT> indices; std::vector<const JRTTriangle*> triArray; JRTBoundingBox scene_bounds(Vec3f(FLT_MAX), Vec3f(-FLT_MAX)); // build an array over all of the triangles for (UINT i = 0; i < rMeshes.size(); i++) { const JRTTriangle* pTri = rMeshes[i]->GetTriangles(); for (UINT j = 0; j < rMeshes[i]->GetTriangleCount(); j++) { triArray.push_back(pTri); pTri++; } } JRT_ASSERT(triArray.size() > 0); // compute scene bounding box for (UINT i = 0; i < triArray.size(); i++) { // this will cause the fourth component of each point // passed to Expand() to be junk. This is ok scene_bounds.Expand(triArray[i]->GetV1()); scene_bounds.Expand(triArray[i]->GetV2()); scene_bounds.Expand(triArray[i]->GetV3()); } // slight hack: Expand bounding box a little bit. This fixes some precision issues with flat polygons on the edge of the model scene_bounds.GetMin() += Vec3f(-0.001f, -0.001f, -0.001f); scene_bounds.GetMax() += Vec3f(0.001f, 0.001f, 0.001f); // construct the tree BuildTreeImpl(scene_bounds, triArray, nodes, indices); pTree = new JRTKDTree; pTree->m_pIndexArray = new UINT[ indices.size() ]; pTree->m_pMailboxes = new UINT[ triArray.size() ]; pTree->m_bBackFacing = new bool [ triArray.size() ]; // initialize the tree structure pTree->m_pNodeArray = (JRTKDNode*)_aligned_malloc(sizeof(JRTKDNode) * nodes.size(), 16); pTree->m_pTriArray = (JRTCoreTriangle*)_aligned_malloc(sizeof(JRTCoreTriangle) * triArray.size(), 16); if (!pTree->m_pNodeArray || !pTree->m_pTriArray) { JRT_SAFE_DELETE(pTree); return NULL; } pTree->m_nNodeCount = (UINT)nodes.size(); pTree->m_nTriangleCount = (UINT)triArray.size(); pTree->m_nIndexCount = (UINT) indices.size(); pTree->m_treeBounds = scene_bounds; // copy node array for (UINT i = 0; i < nodes.size(); i++) { pTree->m_pNodeArray[i] = nodes[i]; } // create index array for (UINT i = 0; i < indices.size(); i++) { pTree->m_pIndexArray[i] = indices[i]; } // create mailbox array memset(pTree->m_pMailboxes, 0, sizeof(UINT)*triArray.size()); pTree->m_nNextRayID = 1; // preprocess triangles and copy to array for (UINT i = 0; i < triArray.size(); i++) { PreprocessTri(triArray[i]->GetV1(), triArray[i]->GetV2(), triArray[i]->GetV3(), &pTree->m_pTriArray[i]); pTree->m_pTriArray[i].pMesh = triArray[i]->GetMesh(); pTree->m_pTriArray[i].nTriIndex = triArray[i]->GetIndexInMesh(); } // initialize array of flags telling whether or not a triangle is backfacing (TOOTLE SPECIFIC) for (UINT i = 0; i < triArray.size(); i++) { pTree->m_bBackFacing[i] = false; } return pTree; }
void JRTHeuristicKDTreeBuilder::BuildTreeRecursive(UINT nDepthLimit, const JRTBoundingBox& rNodeBounds, SplitVec splits[3], std::vector<TriangleBB*>& rBBsThisNode, JRTKDNode* pNode, std::vector<JRTKDNode>& rNodesOut, std::vector<UINT>& rTrisOut) { UINT nTriCount = (UINT)rBBsThisNode.size(); JRT_ASSERT(splits[0].size() == splits[1].size() && splits[1].size() == splits[2].size()); // initialize best cost to cost of not splitting float fBestCost = INTERSECT_COST * nTriCount; // find the optimal split bool bSplit = false; float fSplitValue; UINT eSplitAxis = X_AXIS; float fNodeBBInvArea = 1.0f / Max(0.0000001f, rNodeBounds.GetSurfaceArea()); float fNodeVolume = rNodeBounds.GetVolume(); for (UINT axis = X_AXIS; axis <= Z_AXIS; axis++) { LocateBestSplit(fNodeBBInvArea, rNodeBounds, splits, axis, nTriCount, fBestCost, bSplit, eSplitAxis, fSplitValue); } if (!bSplit || nDepthLimit == 0) { // we either don't want to split, or can't split, so make a leaf pNode->leaf.is_leaf = true; pNode->leaf.triangle_count = 0; pNode->leaf.triangle_start = (UINT)rTrisOut.size(); for (UINT i = 0; i < rBBsThisNode.size(); i++) { // do robust tri-box clipping at the leaves const Vec3f* pV1 = &rBBsThisNode[i]->pTri->GetV1(); const Vec3f* pV2 = &rBBsThisNode[i]->pTri->GetV2(); const Vec3f* pV3 = &rBBsThisNode[i]->pTri->GetV3(); if (rNodeBounds.TriangleIntersect(pV1, pV2, pV3)) { rTrisOut.push_back(rBBsThisNode[i]->nIndex); pNode->leaf.triangle_count++; } } } else { // make a non-leaf pNode->inner.axis = eSplitAxis; pNode->inner.is_leaf = false; pNode->inner.position = fSplitValue; JRT_ASSERT(fSplitValue > rNodeBounds.GetMin()[eSplitAxis] && fSplitValue < rNodeBounds.GetMax()[eSplitAxis]); ClassifyBBs(splits[0], m_bbs, eSplitAxis, fSplitValue); // partition the splits SplitVec frontSplits[3]; SplitVec backSplits[3]; for (int j = X_AXIS; j <= Z_AXIS; j++) { PartitionSplits(fSplitValue, eSplitAxis, splits[j], m_bbs, backSplits[j], frontSplits[j]); // clear old split vec to save memory splits[j].clear(); } // partition BBs std::vector<TriangleBB*> backBBs; std::vector<TriangleBB*> frontBBs; PartitionBBs(eSplitAxis, fSplitValue, rBBsThisNode, backBBs, frontBBs); rBBsThisNode.clear(); // save memory // create new nodes // by convention, always create front child right before back child pNode->inner.front_offset = (UINT)rNodesOut.size(); UINT nFront = (UINT)rNodesOut.size(); UINT nBack = nFront + 1; rNodesOut.push_back(JRTKDNode()); rNodesOut.push_back(JRTKDNode()); // split the node bounding box JRTBoundingBox front_bounds, back_bounds; rNodeBounds.Split(eSplitAxis, fSplitValue, front_bounds, back_bounds); // recursively build the subtrees BuildTreeRecursive(nDepthLimit - 1, front_bounds, frontSplits, frontBBs, &rNodesOut[ nFront ], rNodesOut, rTrisOut); BuildTreeRecursive(nDepthLimit - 1, back_bounds, backSplits, backBBs, &rNodesOut[ nBack ], rNodesOut, rTrisOut); } }
void JRTHeuristicKDTreeBuilder::LocateBestSplit(float fNodeBBInvArea, const JRTBoundingBox& rNodeBounds, const SplitVec splits[3], UINT axis, UINT nTriCount, float& fBestCost, bool& bSplit, UINT& eSplitAxis, float& fSplitValue) { JRTBoundingBox frontBounds = rNodeBounds, backBounds = rNodeBounds; // std::ofstream foo("foo.csv"); const SplitVec& rSplitVec = splits[axis]; float fNodeMin = rNodeBounds.GetMin()[axis]; float fNodeMax = rNodeBounds.GetMax()[axis]; UINT nSplits = (UINT)rSplitVec.size(); if (nSplits == 0) { bSplit = false; eSplitAxis = X_AXIS; return; } // find the best split along the current axis // to do this, we sweep the bounding box planes in left-to-right order // - for each 'opening' plane, we compute the cost, and then increment the # of triangles // on the 'behind' side of the plane (since the triangle will be behind all subsequent planes) // For each 'closing' plane, we decrement the number of tris in front before computing the cost UINT nTrisBehind = 0; UINT nTrisInFront = nTriCount; UINT i = 0; Split* split = rSplitVec[i]; // advance forwards past any splits which are before the bounding box start i = 0; while (i < nSplits && rSplitVec[i]->value <= fNodeMin) { if (rSplitVec[i]->bMaxSplit) { nTrisInFront--; } else { nTrisBehind++; } i++; } // figure out which split to stop at, start at the end and go backwards while (nSplits > 0 && rSplitVec[nSplits - 1]->value >= fNodeMax) { nSplits--; } // precompute some stuff that we'll use to compute surface areas Vec3f bbSize = rNodeBounds.GetMax() - rNodeBounds.GetMin(); float bbSizeU = UCOMP(bbSize, axis); float bbSizeV = VCOMP(bbSize, axis); float sa_const = bbSizeU * bbSizeV; // surface area of root bounding box. we omit the multiplies by two since they cancel out float farea = bbSizeU * bbSizeV + bbSize[axis] * bbSizeU + bbSize[axis] * bbSizeV; // pre-compute the divide if (farea == 0.0f) { farea = 0.00000001f; } farea = 1.0f / farea; bool bHaveSplit = false; // iterate over all of the splits that lie inside the node bounding box for (i = i; i < nSplits; i++) { Split* split = rSplitVec[i]; nTrisInFront -= (split->bMaxSplit) ? 1 : 0; nTrisBehind += (split->bMaxSplit) ? 0 : 1; // evaluate the cost of this split using the surface area heuristic //backBounds.GetMax()[axis] = split->value; //frontBounds.GetMin()[axis] = split->value; //float back_area = backBounds.GetSurfaceArea(); //float front_area = frontBounds.GetSurfaceArea(); // compute surface area for each side of the box. Note that we deliberately leave // off the multiply by two since it cancels out float back_area = sa_const + (bbSizeU + bbSizeV) * (split->value - fNodeMin); float front_area = sa_const + (bbSizeU + bbSizeV) * (fNodeMax - split->value); float cost = 1.0f + INTERSECT_COST * farea * ((back_area * nTrisBehind) + (front_area * nTrisInFront)); //foo << cost <<","<<split->value << "," << nTrisInFront<< "," << nTrisBehind << std::endl; if (cost < fBestCost) { fBestCost = cost; fSplitValue = split->value; bHaveSplit = true; JRT_ASSERT(fSplitValue >= rNodeBounds.GetMin()[axis] && fSplitValue <= rNodeBounds.GetMax()[axis]); } } if (bHaveSplit) { bSplit = true; eSplitAxis = (Axis)axis; } //foo.close(); }