Spaces:
Running
Running
support splat files
Browse files- SplatTiler/SplatTiler.cpp +100 -2
- SplatTiler/SplatTiler.h +2 -2
- SplatTiler/main.cpp +10 -10
- client/src/components/MeasurePanel.jsx +2 -1
- client/src/utils/awsConfig.js +4 -5
SplatTiler/SplatTiler.cpp
CHANGED
|
@@ -48,14 +48,112 @@ struct OctreeNode {
|
|
| 48 |
bool isLeaf() const { return children.empty(); }
|
| 49 |
};
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
SplatTiler::SplatTiler() {}
|
| 52 |
SplatTiler::~SplatTiler() {}
|
| 53 |
|
| 54 |
-
bool SplatTiler::load(const std::string&
|
|
|
|
|
|
|
|
|
|
| 55 |
spz::UnpackOptions opts;
|
| 56 |
// Load as RDF (Raw) to avoid implicit conversion. We will handle orientation via the matrix.
|
| 57 |
opts.to = spz::CoordinateSystem::RDF;
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
return m_cloud.numPoints > 0;
|
| 60 |
}
|
| 61 |
|
|
|
|
| 48 |
bool isLeaf() const { return children.empty(); }
|
| 49 |
};
|
| 50 |
|
| 51 |
+
// Helper to load raw .splat files (standard 32-byte format)
|
| 52 |
+
bool loadRawSplat(const std::string& filename, spz::GaussianCloud& cloud) {
|
| 53 |
+
std::ifstream file(filename, std::ios::binary | std::ios::ate);
|
| 54 |
+
if (!file) return false;
|
| 55 |
+
|
| 56 |
+
size_t fileSize = file.tellg();
|
| 57 |
+
file.seekg(0, std::ios::beg);
|
| 58 |
+
|
| 59 |
+
if (fileSize == 0 || fileSize % 32 != 0) {
|
| 60 |
+
std::cerr << "Error: .splat file size is not a multiple of 32." << std::endl;
|
| 61 |
+
return false;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
int32_t numPoints = static_cast<int32_t>(fileSize / 32);
|
| 65 |
+
cloud.numPoints = numPoints;
|
| 66 |
+
cloud.shDegree = 0;
|
| 67 |
+
cloud.antialiased = false;
|
| 68 |
+
|
| 69 |
+
cloud.positions.resize(numPoints * 3);
|
| 70 |
+
cloud.scales.resize(numPoints * 3);
|
| 71 |
+
cloud.rotations.resize(numPoints * 4);
|
| 72 |
+
cloud.alphas.resize(numPoints);
|
| 73 |
+
cloud.colors.resize(numPoints * 3);
|
| 74 |
+
cloud.sh.clear();
|
| 75 |
+
|
| 76 |
+
struct RawSplatPoint {
|
| 77 |
+
float pos[3];
|
| 78 |
+
float scale[3];
|
| 79 |
+
uint8_t color[4];
|
| 80 |
+
uint8_t rot[4];
|
| 81 |
+
};
|
| 82 |
+
|
| 83 |
+
std::vector<RawSplatPoint> buffer(numPoints);
|
| 84 |
+
if (!file.read(reinterpret_cast<char*>(buffer.data()), fileSize)) return false;
|
| 85 |
+
|
| 86 |
+
const float SH_C0 = 0.28209479177387814f;
|
| 87 |
+
|
| 88 |
+
for (int i = 0; i < numPoints; ++i) {
|
| 89 |
+
const auto& p = buffer[i];
|
| 90 |
+
|
| 91 |
+
// Position
|
| 92 |
+
cloud.positions[i * 3 + 0] = p.pos[0];
|
| 93 |
+
cloud.positions[i * 3 + 1] = p.pos[1];
|
| 94 |
+
cloud.positions[i * 3 + 2] = p.pos[2];
|
| 95 |
+
|
| 96 |
+
// Scale (Linear -> Log)
|
| 97 |
+
cloud.scales[i * 3 + 0] = std::log(std::max(1e-6f, p.scale[0]));
|
| 98 |
+
cloud.scales[i * 3 + 1] = std::log(std::max(1e-6f, p.scale[1]));
|
| 99 |
+
cloud.scales[i * 3 + 2] = std::log(std::max(1e-6f, p.scale[2]));
|
| 100 |
+
|
| 101 |
+
// Color (Uint8 -> SH0)
|
| 102 |
+
cloud.colors[i * 3 + 0] = (p.color[0] / 255.0f - 0.5f) / SH_C0;
|
| 103 |
+
cloud.colors[i * 3 + 1] = (p.color[1] / 255.0f - 0.5f) / SH_C0;
|
| 104 |
+
cloud.colors[i * 3 + 2] = (p.color[2] / 255.0f - 0.5f) / SH_C0;
|
| 105 |
+
|
| 106 |
+
// Alpha (Uint8 -> Logit)
|
| 107 |
+
float a = p.color[3] / 255.0f;
|
| 108 |
+
if (a < 1.0f/255.0f) a = 1.0f/255.0f;
|
| 109 |
+
if (a > 254.0f/255.0f) a = 254.0f/255.0f;
|
| 110 |
+
cloud.alphas[i] = std::log(a / (1.0f - a));
|
| 111 |
+
|
| 112 |
+
// Rotation (Uint8 -> Float Quaternion)
|
| 113 |
+
float r0 = (p.rot[0] - 128.0f) / 128.0f;
|
| 114 |
+
float r1 = (p.rot[1] - 128.0f) / 128.0f;
|
| 115 |
+
float r2 = (p.rot[2] - 128.0f) / 128.0f;
|
| 116 |
+
float r3 = (p.rot[3] - 128.0f) / 128.0f;
|
| 117 |
+
|
| 118 |
+
float len2 = r0*r0 + r1*r1 + r2*r2 + r3*r3;
|
| 119 |
+
if (len2 > 0.0f) {
|
| 120 |
+
float invLen = 1.0f / std::sqrt(len2);
|
| 121 |
+
r0 *= invLen; r1 *= invLen; r2 *= invLen; r3 *= invLen;
|
| 122 |
+
} else {
|
| 123 |
+
r0 = 1.0f;
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
cloud.rotations[i * 4 + 0] = r0;
|
| 127 |
+
cloud.rotations[i * 4 + 1] = r1;
|
| 128 |
+
cloud.rotations[i * 4 + 2] = r2;
|
| 129 |
+
cloud.rotations[i * 4 + 3] = r3;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
return true;
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
SplatTiler::SplatTiler() {}
|
| 136 |
SplatTiler::~SplatTiler() {}
|
| 137 |
|
| 138 |
+
bool SplatTiler::load(const std::string& filename) {
|
| 139 |
+
std::string ext = fs::path(filename).extension().string();
|
| 140 |
+
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
|
| 141 |
+
|
| 142 |
spz::UnpackOptions opts;
|
| 143 |
// Load as RDF (Raw) to avoid implicit conversion. We will handle orientation via the matrix.
|
| 144 |
opts.to = spz::CoordinateSystem::RDF;
|
| 145 |
+
|
| 146 |
+
if (ext == ".ply") {
|
| 147 |
+
m_cloud = spz::loadSplatFromPly(filename, opts);
|
| 148 |
+
} else if (ext == ".spz") {
|
| 149 |
+
m_cloud = spz::loadSpz(filename, opts);
|
| 150 |
+
} else if (ext == ".splat") {
|
| 151 |
+
return loadRawSplat(filename, m_cloud);
|
| 152 |
+
} else {
|
| 153 |
+
std::cerr << "Error: Unsupported file extension '" << ext << "'. Supported: .ply, .spz, .splat" << std::endl;
|
| 154 |
+
return false;
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
return m_cloud.numPoints > 0;
|
| 158 |
}
|
| 159 |
|
SplatTiler/SplatTiler.h
CHANGED
|
@@ -31,8 +31,8 @@ public:
|
|
| 31 |
SplatTiler();
|
| 32 |
~SplatTiler();
|
| 33 |
|
| 34 |
-
// Load a PLY file into memory
|
| 35 |
-
bool load(const std::string&
|
| 36 |
|
| 37 |
// Run the tiling and export process
|
| 38 |
void process(const TileConfig& config);
|
|
|
|
| 31 |
SplatTiler();
|
| 32 |
~SplatTiler();
|
| 33 |
|
| 34 |
+
// Load a PLY/SPZ/SPLAT file into memory
|
| 35 |
+
bool load(const std::string& filename);
|
| 36 |
|
| 37 |
// Run the tiling and export process
|
| 38 |
void process(const TileConfig& config);
|
SplatTiler/main.cpp
CHANGED
|
@@ -6,7 +6,7 @@
|
|
| 6 |
#include "SplatTiler.h"
|
| 7 |
|
| 8 |
void printUsage(const char* progName) {
|
| 9 |
-
std::cout << "Usage: " << progName << " [options] <
|
| 10 |
std::cout << "Options:\n";
|
| 11 |
std::cout << " -o, --output <dir> Output directory (default: output_tiles)\n";
|
| 12 |
std::cout << " -m, --max-points <num> Max points per tile (default: 65536)\n";
|
|
@@ -25,7 +25,7 @@ int main(int argc, char* argv[]) {
|
|
| 25 |
}
|
| 26 |
|
| 27 |
tile3dgs::TileConfig config;
|
| 28 |
-
std::string
|
| 29 |
|
| 30 |
for (int i = 1; i < argc; ++i) {
|
| 31 |
std::string arg = argv[i];
|
|
@@ -123,25 +123,25 @@ int main(int argc, char* argv[]) {
|
|
| 123 |
printUsage(argv[0]);
|
| 124 |
return 1;
|
| 125 |
} else {
|
| 126 |
-
|
| 127 |
}
|
| 128 |
}
|
| 129 |
|
| 130 |
-
if (
|
| 131 |
-
std::cerr << "Error: No input
|
| 132 |
printUsage(argv[0]);
|
| 133 |
return 1;
|
| 134 |
}
|
| 135 |
|
| 136 |
-
if (!std::filesystem::exists(
|
| 137 |
-
std::cerr << "Error: Input file does not exist: " <<
|
| 138 |
return 1;
|
| 139 |
}
|
| 140 |
|
| 141 |
-
std::cout << "Loading " <<
|
| 142 |
tile3dgs::SplatTiler tiler;
|
| 143 |
-
if (!tiler.load(
|
| 144 |
-
std::cerr << "Failed to load
|
| 145 |
return 1;
|
| 146 |
}
|
| 147 |
|
|
|
|
| 6 |
#include "SplatTiler.h"
|
| 7 |
|
| 8 |
void printUsage(const char* progName) {
|
| 9 |
+
std::cout << "Usage: " << progName << " [options] <input_file>\n";
|
| 10 |
std::cout << "Options:\n";
|
| 11 |
std::cout << " -o, --output <dir> Output directory (default: output_tiles)\n";
|
| 12 |
std::cout << " -m, --max-points <num> Max points per tile (default: 65536)\n";
|
|
|
|
| 25 |
}
|
| 26 |
|
| 27 |
tile3dgs::TileConfig config;
|
| 28 |
+
std::string inputFile;
|
| 29 |
|
| 30 |
for (int i = 1; i < argc; ++i) {
|
| 31 |
std::string arg = argv[i];
|
|
|
|
| 123 |
printUsage(argv[0]);
|
| 124 |
return 1;
|
| 125 |
} else {
|
| 126 |
+
inputFile = arg;
|
| 127 |
}
|
| 128 |
}
|
| 129 |
|
| 130 |
+
if (inputFile.empty()) {
|
| 131 |
+
std::cerr << "Error: No input file specified.\n";
|
| 132 |
printUsage(argv[0]);
|
| 133 |
return 1;
|
| 134 |
}
|
| 135 |
|
| 136 |
+
if (!std::filesystem::exists(inputFile)) {
|
| 137 |
+
std::cerr << "Error: Input file does not exist: " << inputFile << "\n";
|
| 138 |
return 1;
|
| 139 |
}
|
| 140 |
|
| 141 |
+
std::cout << "Loading " << inputFile << "...\n";
|
| 142 |
tile3dgs::SplatTiler tiler;
|
| 143 |
+
if (!tiler.load(inputFile)) {
|
| 144 |
+
std::cerr << "Failed to load file (or file is empty).\n";
|
| 145 |
return 1;
|
| 146 |
}
|
| 147 |
|
client/src/components/MeasurePanel.jsx
CHANGED
|
@@ -22,9 +22,10 @@ import {
|
|
| 22 |
import DownloadIcon from "@mui/icons-material/Download";
|
| 23 |
import DeleteIcon from "@mui/icons-material/Delete";
|
| 24 |
import EditIcon from "@mui/icons-material/Edit";
|
|
|
|
| 25 |
|
| 26 |
const DEFAULT_CUSTOM_MODEL = {
|
| 27 |
-
bucket:
|
| 28 |
prefix: "data/3dtiles",
|
| 29 |
tileset: "custom/tileset.json",
|
| 30 |
offsetHeight: 0,
|
|
|
|
| 22 |
import DownloadIcon from "@mui/icons-material/Download";
|
| 23 |
import DeleteIcon from "@mui/icons-material/Delete";
|
| 24 |
import EditIcon from "@mui/icons-material/Edit";
|
| 25 |
+
import { getDefaultBucket } from "../utils/awsConfig";
|
| 26 |
|
| 27 |
const DEFAULT_CUSTOM_MODEL = {
|
| 28 |
+
bucket: getDefaultBucket(),
|
| 29 |
prefix: "data/3dtiles",
|
| 30 |
tileset: "custom/tileset.json",
|
| 31 |
offsetHeight: 0,
|
client/src/utils/awsConfig.js
CHANGED
|
@@ -14,10 +14,10 @@ import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
|
|
| 14 |
import { fromCognitoIdentityPool } from "@aws-sdk/credential-provider-cognito-identity";
|
| 15 |
import { CognitoIdentityClient } from "@aws-sdk/client-cognito-identity";
|
| 16 |
|
| 17 |
-
export const AWS_REGION = "us-east-2";
|
| 18 |
// Cognito pool ID is safe to embed in client code (not a secret)
|
| 19 |
-
export const COGNITO_IDENTITY_POOL_ID = "us-east-2:1387102d-5a87-4742-8a69-1baa70f37984";
|
| 20 |
-
export const DEFAULT_BUCKET = "ddy-first-bucket";
|
| 21 |
|
| 22 |
/**
|
| 23 |
* Initialize S3 client with Cognito credentials
|
|
@@ -155,6 +155,5 @@ export function getAwsRegion() {
|
|
| 155 |
* Validate AWS configuration
|
| 156 |
*/
|
| 157 |
export function validateAwsConfig() {
|
| 158 |
-
|
| 159 |
-
return true;
|
| 160 |
}
|
|
|
|
| 14 |
import { fromCognitoIdentityPool } from "@aws-sdk/credential-provider-cognito-identity";
|
| 15 |
import { CognitoIdentityClient } from "@aws-sdk/client-cognito-identity";
|
| 16 |
|
| 17 |
+
export const AWS_REGION = import.meta.env.VITE_AWS_REGION || "us-east-2";
|
| 18 |
// Cognito pool ID is safe to embed in client code (not a secret)
|
| 19 |
+
export const COGNITO_IDENTITY_POOL_ID = import.meta.env.VITE_AWS_COGNITO_IDENTITY_POOL_ID || "us-east-2:1387102d-5a87-4742-8a69-1baa70f37984";
|
| 20 |
+
export const DEFAULT_BUCKET = import.meta.env.VITE_S3_BUCKET || "ddy-first-bucket";
|
| 21 |
|
| 22 |
/**
|
| 23 |
* Initialize S3 client with Cognito credentials
|
|
|
|
| 155 |
* Validate AWS configuration
|
| 156 |
*/
|
| 157 |
export function validateAwsConfig() {
|
| 158 |
+
return !!(AWS_REGION && COGNITO_IDENTITY_POOL_ID && DEFAULT_BUCKET);
|
|
|
|
| 159 |
}
|