dengdeyan commited on
Commit
aa00da8
·
1 Parent(s): be9292f

support splat files

Browse files
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& plyFilename) {
 
 
 
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
- m_cloud = spz::loadSplatFromPly(plyFilename, opts);
 
 
 
 
 
 
 
 
 
 
 
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& plyFilename);
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] <input_ply>\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,7 +25,7 @@ int main(int argc, char* argv[]) {
25
  }
26
 
27
  tile3dgs::TileConfig config;
28
- std::string inputPly;
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
- inputPly = arg;
127
  }
128
  }
129
 
130
- if (inputPly.empty()) {
131
- std::cerr << "Error: No input PLY file specified.\n";
132
  printUsage(argv[0]);
133
  return 1;
134
  }
135
 
136
- if (!std::filesystem::exists(inputPly)) {
137
- std::cerr << "Error: Input file does not exist: " << inputPly << "\n";
138
  return 1;
139
  }
140
 
141
- std::cout << "Loading " << inputPly << "...\n";
142
  tile3dgs::SplatTiler tiler;
143
- if (!tiler.load(inputPly)) {
144
- std::cerr << "Failed to load PLY file (or file is empty).\n";
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: import.meta.env.VITE_S3_BUCKET || "ddy-first-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
- // Configuration is hardcoded, always valid
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
  }