mirror of
https://github.com/lighttransport/tinyusdz.git
synced 2026-01-18 01:11:17 +01:00
Implement high-performance spatial data structures optimized for vertex deduplication and similarity search in 3D meshes, with special optimizations for city-like point distributions. Key features: - Morton code-based sorted hash grid with O(1) average query time - Automatic multilevel grid subdivision for cells exceeding threshold (128 default) - 27-cell neighborhood search for finding similar vertices - Memory-efficient implementation: 48-58 bytes per vertex Specialized strategies for city distributions: - Adaptive octree with RANSAC-based plane detection for walls/floors - Hybrid city grid with layer separation (ground/building/aerial) - 2D grid optimization for ground plane points - Dedicated plane storage for building surfaces Performance benchmarks: - Build time: 11ms for 100K vertices, scales linearly - Query time: 5-17μs average per proximity search - Memory usage: 660MB for 14M points with 0.1m cells - Handles city distributions 2-3x more efficiently than naive approaches Test coverage includes: - Basic functionality and exact vertex matching - Large dataset handling (100K+ vertices) - Clustered data with automatic subdivision - City-like distributions with planes - Memory usage and histogram analysis 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
282 lines
11 KiB
C++
282 lines
11 KiB
C++
#include "sorted_hash_grid_extended.hh"
|
|
#include <iostream>
|
|
#include <random>
|
|
#include <chrono>
|
|
#include <vector>
|
|
#include <cmath>
|
|
#include <iomanip>
|
|
#include <fstream>
|
|
|
|
using namespace tinyusdz::spatial;
|
|
|
|
class CityPointGenerator {
|
|
private:
|
|
std::mt19937 rng_;
|
|
std::uniform_real_distribution<float> uniform_;
|
|
std::normal_distribution<float> normal_;
|
|
|
|
public:
|
|
CityPointGenerator(uint32_t seed = 42)
|
|
: rng_(seed), uniform_(0.0f, 1.0f), normal_(0.0f, 0.01f) {}
|
|
|
|
// Generate points on a rectangular plane with some noise
|
|
std::vector<std::array<float, 3>> generatePlane(
|
|
float centerX, float centerY, float centerZ,
|
|
float width, float height,
|
|
size_t pointsPerSquareMeter,
|
|
float noiseLevel = 0.001f,
|
|
bool vertical = false) {
|
|
|
|
std::vector<std::array<float, 3>> points;
|
|
|
|
float area = width * height;
|
|
size_t numPoints = static_cast<size_t>(area * pointsPerSquareMeter);
|
|
|
|
points.reserve(numPoints);
|
|
|
|
for (size_t i = 0; i < numPoints; ++i) {
|
|
float u = uniform_(rng_) - 0.5f;
|
|
float v = uniform_(rng_) - 0.5f;
|
|
|
|
float x, y, z;
|
|
|
|
if (vertical) {
|
|
// Vertical plane (building wall)
|
|
x = centerX + u * width + normal_(rng_) * noiseLevel;
|
|
y = centerY + v * height + normal_(rng_) * noiseLevel;
|
|
z = centerZ + normal_(rng_) * noiseLevel;
|
|
} else {
|
|
// Horizontal plane (ground/roof)
|
|
x = centerX + u * width + normal_(rng_) * noiseLevel;
|
|
y = centerY + normal_(rng_) * noiseLevel;
|
|
z = centerZ + v * height + normal_(rng_) * noiseLevel;
|
|
}
|
|
|
|
points.push_back({x, y, z});
|
|
}
|
|
|
|
return points;
|
|
}
|
|
|
|
// Generate a building with walls and roof
|
|
std::vector<std::array<float, 3>> generateBuilding(
|
|
float baseX, float baseZ,
|
|
float width, float depth, float height,
|
|
size_t pointDensity = 1000) {
|
|
|
|
std::vector<std::array<float, 3>> allPoints;
|
|
|
|
// Roof (horizontal plane)
|
|
auto roof = generatePlane(baseX, height, baseZ, width, depth, pointDensity * 2, 0.001f, false);
|
|
allPoints.insert(allPoints.end(), roof.begin(), roof.end());
|
|
|
|
// Four walls (vertical planes)
|
|
// Front wall
|
|
auto front = generatePlane(baseX, height/2, baseZ - depth/2, width, height, pointDensity, 0.001f, true);
|
|
allPoints.insert(allPoints.end(), front.begin(), front.end());
|
|
|
|
// Back wall
|
|
auto back = generatePlane(baseX, height/2, baseZ + depth/2, width, height, pointDensity, 0.001f, true);
|
|
allPoints.insert(allPoints.end(), back.begin(), back.end());
|
|
|
|
// Left wall
|
|
auto left = generatePlane(baseX - width/2, height/2, baseZ, depth, height, pointDensity, 0.001f, true);
|
|
allPoints.insert(allPoints.end(), left.begin(), left.end());
|
|
|
|
// Right wall
|
|
auto right = generatePlane(baseX + width/2, height/2, baseZ, depth, height, pointDensity, 0.001f, true);
|
|
allPoints.insert(allPoints.end(), right.begin(), right.end());
|
|
|
|
return allPoints;
|
|
}
|
|
|
|
// Generate a city-like distribution
|
|
std::vector<std::array<float, 3>> generateCity(
|
|
float citySize = 100.0f,
|
|
size_t numBuildings = 50,
|
|
size_t groundPointDensity = 500,
|
|
size_t buildingPointDensity = 1000) {
|
|
|
|
std::vector<std::array<float, 3>> allPoints;
|
|
|
|
// Generate ground plane with high density
|
|
std::cout << "Generating ground plane...\n";
|
|
auto ground = generatePlane(0, 0, 0, citySize, citySize, groundPointDensity, 0.01f, false);
|
|
allPoints.insert(allPoints.end(), ground.begin(), ground.end());
|
|
std::cout << " Added " << ground.size() << " ground points\n";
|
|
|
|
// Generate buildings in a grid-like pattern with some randomness
|
|
std::cout << "Generating " << numBuildings << " buildings...\n";
|
|
|
|
std::uniform_real_distribution<float> buildingPos(-citySize/2 * 0.8f, citySize/2 * 0.8f);
|
|
std::uniform_real_distribution<float> buildingWidth(2.0f, 8.0f);
|
|
std::uniform_real_distribution<float> buildingDepth(2.0f, 8.0f);
|
|
std::uniform_real_distribution<float> buildingHeight(5.0f, 30.0f);
|
|
|
|
for (size_t i = 0; i < numBuildings; ++i) {
|
|
float x = buildingPos(rng_);
|
|
float z = buildingPos(rng_);
|
|
float w = buildingWidth(rng_);
|
|
float d = buildingDepth(rng_);
|
|
float h = buildingHeight(rng_);
|
|
|
|
auto building = generateBuilding(x, z, w, d, h, buildingPointDensity);
|
|
allPoints.insert(allPoints.end(), building.begin(), building.end());
|
|
|
|
if ((i + 1) % 10 == 0) {
|
|
std::cout << " Generated " << (i + 1) << " buildings\n";
|
|
}
|
|
}
|
|
|
|
// Add some street furniture (dense clusters)
|
|
std::cout << "Adding street furniture clusters...\n";
|
|
std::normal_distribution<float> clusterDist(0.0f, 0.1f);
|
|
for (int i = 0; i < 20; ++i) {
|
|
float cx = buildingPos(rng_);
|
|
float cz = buildingPos(rng_);
|
|
|
|
// Dense cluster of points (lamp post, bench, etc.)
|
|
for (int j = 0; j < 100; ++j) {
|
|
allPoints.push_back({
|
|
cx + clusterDist(rng_),
|
|
uniform_(rng_) * 2.0f,
|
|
cz + clusterDist(rng_)
|
|
});
|
|
}
|
|
}
|
|
|
|
return allPoints;
|
|
}
|
|
};
|
|
|
|
void testCityDistribution() {
|
|
std::cout << "\n=== Testing City-like 3D Point Distribution ===\n\n";
|
|
|
|
CityPointGenerator generator(12345);
|
|
|
|
// Test different cell sizes
|
|
std::vector<float> cellSizes = {0.1f, 0.5f, 1.0f, 2.0f};
|
|
|
|
for (float cellSize : cellSizes) {
|
|
std::cout << "\n--- Testing with cell size: " << cellSize << " ---\n";
|
|
|
|
// Generate city points
|
|
auto cityPoints = generator.generateCity(100.0f, 30, 500, 800);
|
|
std::cout << "Total city points generated: " << cityPoints.size() << "\n";
|
|
|
|
// Create and populate grid
|
|
SortedHashGridExtended<float> grid(cellSize, 128, 0, 4);
|
|
|
|
std::cout << "\nAdding points to grid...\n";
|
|
auto startAdd = std::chrono::high_resolution_clock::now();
|
|
|
|
uint32_t id = 0;
|
|
for (const auto& p : cityPoints) {
|
|
grid.addVertex(p[0], p[1], p[2], id++);
|
|
}
|
|
|
|
auto endAdd = std::chrono::high_resolution_clock::now();
|
|
auto addTime = std::chrono::duration_cast<std::chrono::milliseconds>(endAdd - startAdd).count();
|
|
std::cout << "Added " << cityPoints.size() << " points in " << addTime << " ms\n";
|
|
|
|
// Build grid
|
|
std::cout << "Building spatial index...\n";
|
|
auto startBuild = std::chrono::high_resolution_clock::now();
|
|
grid.build();
|
|
auto endBuild = std::chrono::high_resolution_clock::now();
|
|
auto buildTime = std::chrono::duration_cast<std::chrono::milliseconds>(endBuild - startBuild).count();
|
|
std::cout << "Built grid in " << buildTime << " ms\n";
|
|
|
|
// Print detailed statistics
|
|
grid.printDetailedStatistics();
|
|
|
|
// Perform sample queries
|
|
std::cout << "\n--- Sample Queries ---\n";
|
|
std::vector<std::array<float, 3>> queryPoints = {
|
|
{0.0f, 0.0f, 0.0f}, // Ground center
|
|
{10.0f, 15.0f, 10.0f}, // Mid-air (building)
|
|
{-20.0f, 0.0f, -20.0f}, // Ground corner
|
|
{5.0f, 0.1f, 5.0f} // Near ground
|
|
};
|
|
|
|
for (const auto& qp : queryPoints) {
|
|
auto results = grid.findSimilarVertices(qp[0], qp[1], qp[2], cellSize * 2);
|
|
std::cout << "Query at (" << qp[0] << ", " << qp[1] << ", " << qp[2]
|
|
<< "): found " << results.size() << " neighbors\n";
|
|
}
|
|
|
|
// Performance benchmark
|
|
std::cout << "\n--- Performance Benchmark ---\n";
|
|
const int numQueries = 1000;
|
|
std::uniform_real_distribution<float> queryDist(-50.0f, 50.0f);
|
|
std::uniform_real_distribution<float> queryHeight(0.0f, 30.0f);
|
|
std::mt19937 queryRng(42);
|
|
|
|
auto startQuery = std::chrono::high_resolution_clock::now();
|
|
size_t totalResults = 0;
|
|
|
|
for (int i = 0; i < numQueries; ++i) {
|
|
float qx = queryDist(queryRng);
|
|
float qy = queryHeight(queryRng);
|
|
float qz = queryDist(queryRng);
|
|
auto results = grid.findSimilarVertices(qx, qy, qz, cellSize);
|
|
totalResults += results.size();
|
|
}
|
|
|
|
auto endQuery = std::chrono::high_resolution_clock::now();
|
|
auto queryTime = std::chrono::duration_cast<std::chrono::microseconds>(endQuery - startQuery).count();
|
|
|
|
std::cout << "Performed " << numQueries << " queries in " << queryTime << " µs\n";
|
|
std::cout << "Average query time: " << queryTime / numQueries << " µs\n";
|
|
std::cout << "Average neighbors found: " << totalResults / numQueries << "\n";
|
|
|
|
std::cout << "\n" << std::string(60, '=') << "\n";
|
|
}
|
|
}
|
|
|
|
void generateVisualizationData() {
|
|
std::cout << "\n=== Generating Visualization Data ===\n";
|
|
|
|
CityPointGenerator generator(999);
|
|
auto cityPoints = generator.generateCity(50.0f, 20, 1000, 1500);
|
|
|
|
// Save points to file for visualization
|
|
std::ofstream outFile("city_points.xyz");
|
|
if (outFile.is_open()) {
|
|
for (const auto& p : cityPoints) {
|
|
outFile << p[0] << " " << p[1] << " " << p[2] << "\n";
|
|
}
|
|
outFile.close();
|
|
std::cout << "Saved " << cityPoints.size() << " points to city_points.xyz\n";
|
|
}
|
|
|
|
// Create grid and analyze distribution
|
|
SortedHashGridExtended<float> grid(0.5f, 128, 0, 4);
|
|
for (size_t i = 0; i < cityPoints.size(); ++i) {
|
|
grid.addVertex(cityPoints[i][0], cityPoints[i][1], cityPoints[i][2], i);
|
|
}
|
|
grid.build();
|
|
|
|
// Export cell occupancy for heatmap
|
|
auto histogram = grid.getCellItemHistogram();
|
|
std::ofstream histFile("cell_histogram.csv");
|
|
if (histFile.is_open()) {
|
|
histFile << "Items,Count\n";
|
|
for (const auto& [items, count] : histogram) {
|
|
histFile << items << "," << count << "\n";
|
|
}
|
|
histFile.close();
|
|
std::cout << "Saved histogram data to cell_histogram.csv\n";
|
|
}
|
|
}
|
|
|
|
int main() {
|
|
std::cout << std::fixed << std::setprecision(2);
|
|
|
|
testCityDistribution();
|
|
generateVisualizationData();
|
|
|
|
std::cout << "\n=== All tests completed ===\n";
|
|
|
|
return 0;
|
|
} |