/// Create an octree over the given set of points with position P /// /// The points for consideration in the current node are the set /// P[inds[beginIndex..endIndex]]; the tree building process sorts the inds /// array in place so that points for the output leaf nodes are held in /// the range P[inds[node.beginIndex, node.endIndex)]]. center is the central /// split point for splitting children of the current node; radius is the /// current node radius measured along one of the axes. static OctreeNode* makeTree(int depth, size_t* inds, size_t beginIndex, size_t endIndex, const V3f* P, const V3f& center, float halfWidth, ProgressFunc& progressFunc) { OctreeNode* node = new OctreeNode(center, halfWidth); const size_t pointsPerNode = 100000; // Limit max depth of tree to prevent infinite recursion when // greater than pointsPerNode points lie at the same position in // space. floats effectively have 24 bit of precision in the // mantissa, so there's never any point splitting more than 24 times. const int maxDepth = 24; size_t* beginPtr = inds + beginIndex; size_t* endPtr = inds + endIndex; if (endIndex - beginIndex <= pointsPerNode || depth >= maxDepth) { // Leaf node: set up indices into point list std::random_shuffle(beginPtr, endPtr); for (size_t i = beginIndex; i < endIndex; ++i) node->bbox.extendBy(P[inds[i]]); node->beginIndex = beginIndex; node->endIndex = endIndex; progressFunc(endIndex - beginIndex); return node; } // Partition points into the 8 child nodes size_t* childRanges[9] = {0}; multi_partition(beginPtr, endPtr, OctreeChildIdx(P, center), &childRanges[1], 8); childRanges[0] = beginPtr; // Recursively generate child nodes float h = halfWidth/2; for (int i = 0; i < 8; ++i) { size_t childBeginIndex = childRanges[i] - inds; size_t childEndIndex = childRanges[i+1] - inds; if (childEndIndex == childBeginIndex) continue; V3f c = center + V3f((i % 2 == 0) ? -h : h, ((i/2) % 2 == 0) ? -h : h, ((i/4) % 2 == 0) ? -h : h); node->children[i] = makeTree(depth+1, inds, childBeginIndex, childEndIndex, P, c, h, progressFunc); node->bbox.extendBy(node->children[i]->bbox); } return node; }
bool PointArray::loadFile(QString fileName, size_t maxPointCount) { QTime loadTimer; loadTimer.start(); setFileName(fileName); // Read file into point data fields. Use very basic file type detection // based on extension. uint64_t totPoints = 0; Imath::Box3d bbox; V3d offset(0); V3d centroid(0); emit loadStepStarted("Reading file"); if (fileName.endsWith(".las") || fileName.endsWith(".laz")) { if (!loadLas(fileName, maxPointCount, m_fields, offset, m_npoints, totPoints, bbox, centroid)) { return false; } } else if (fileName.endsWith(".ply")) { if (!loadPly(fileName, maxPointCount, m_fields, offset, m_npoints, totPoints, bbox, centroid)) { return false; } } #if 0 else if (fileName.endsWith(".dat")) { // Load crappy db format for debugging std::ifstream file(fileName.toUtf8(), std::ios::binary); file.seekg(0, std::ios::end); totPoints = file.tellg()/(4*sizeof(float)); file.seekg(0); m_fields.push_back(GeomField(TypeSpec::vec3float32(), "position", totPoints)); m_fields.push_back(GeomField(TypeSpec::float32(), "intensity", totPoints)); float* position = m_fields[0].as<float>(); float* intensity = m_fields[1].as<float>(); for (size_t i = 0; i < totPoints; ++i) { file.read((char*)position, 3*sizeof(float)); file.read((char*)intensity, 1*sizeof(float)); bbox.extendBy(V3d(position[0], position[1], position[2])); position += 3; intensity += 1; } m_npoints = totPoints; } #endif else { // Last resort: try loading as text if (!loadText(fileName, maxPointCount, m_fields, offset, m_npoints, totPoints, bbox, centroid)) { return false; } } // Search for position field m_positionFieldIdx = -1; for (size_t i = 0; i < m_fields.size(); ++i) { if (m_fields[i].name == "position" && m_fields[i].spec.count == 3) { m_positionFieldIdx = (int)i; break; } } if (m_positionFieldIdx == -1) { g_logger.error("No position field found in file %s", fileName); return false; } m_P = (V3f*)m_fields[m_positionFieldIdx].as<float>(); setBoundingBox(bbox); setOffset(offset); setCentroid(centroid); emit loadProgress(100); g_logger.info("Loaded %d of %d points from file %s in %.2f seconds", m_npoints, totPoints, fileName, loadTimer.elapsed()/1000.0); if (totPoints == 0) { m_rootNode.reset(new OctreeNode(V3f(0), 1)); return true; } // Sort points into octree order emit loadStepStarted("Sorting points"); std::unique_ptr<size_t[]> inds(new size_t[m_npoints]); for (size_t i = 0; i < m_npoints; ++i) inds[i] = i; // Expand the bound so that it's cubic. Not exactly sure it's required // here, but cubic nodes sometimes work better the points are better // distributed for LoD, splitting is unbiased, etc. Imath::Box3f rootBound(bbox.min - offset, bbox.max - offset); V3f diag = rootBound.size(); float rootRadius = std::max(std::max(diag.x, diag.y), diag.z) / 2; ProgressFunc progressFunc(*this); m_rootNode.reset(makeTree(0, &inds[0], 0, m_npoints, &m_P[0], rootBound.center(), rootRadius, progressFunc)); // Reorder point fields into octree order emit loadStepStarted("Reordering fields"); for (size_t i = 0; i < m_fields.size(); ++i) { g_logger.debug("Reordering field %d: %s", i, m_fields[i]); reorder(m_fields[i], inds.get(), m_npoints); emit loadProgress(int(100*(i+1)/m_fields.size())); } m_P = (V3f*)m_fields[m_positionFieldIdx].as<float>(); return true; }