@playcanvas/splat-transform 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +19 -19
- package/README.md +127 -127
- package/bin/cli.mjs +5 -5
- package/dist/index.mjs +157 -142
- package/dist/index.mjs.map +1 -1
- package/package.json +57 -59
package/LICENSE
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
Copyright (c) 2011-
|
|
2
|
-
|
|
3
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
-
in the Software without restriction, including without limitation the rights
|
|
6
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
-
furnished to do so, subject to the following conditions:
|
|
9
|
-
|
|
10
|
-
The above copyright notice and this permission notice shall be included in
|
|
11
|
-
all copies or substantial portions of the Software.
|
|
12
|
-
|
|
13
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
-
THE SOFTWARE.
|
|
1
|
+
Copyright (c) 2011-2025 PlayCanvas Ltd.
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,127 +1,127 @@
|
|
|
1
|
-
# SplatTransform - 3D Gaussian Splat Converter
|
|
2
|
-
|
|
3
|
-
SplatTransform is an open source CLI tool for reading gaussian splat PLY files and writing them to PLY, Compressed PLY, CSV, and SOGS format.
|
|
4
|
-
|
|
5
|
-
Multiple files may be combined and transformed before being written to the output.
|
|
6
|
-
|
|
7
|
-
## Installation
|
|
8
|
-
|
|
9
|
-
First install the package globally:
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
npm install -g @playcanvas/splat-transform
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Usage
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
splat-transform [GLOBAL] <input.{ply|splat|ksplat}> [ACTIONS] ... <output.{ply|compressed.ply|meta.json|csv}> [ACTIONS]
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
**Key points:**
|
|
22
|
-
- Every time an `*.ply*` appears, it becomes the current working set; the following ACTIONS are applied in the order listed
|
|
23
|
-
- The last file on the command line is treated as the output; anything after it is interpreted as actions that modify the final result
|
|
24
|
-
|
|
25
|
-
## Supported Formats
|
|
26
|
-
|
|
27
|
-
**Input:**
|
|
28
|
-
- `.ply` - Standard PLY format
|
|
29
|
-
- `.splat` - Binary splat format (antimatter15 format)
|
|
30
|
-
- `.ksplat` - Compressed binary splat format (mkkellogg format)
|
|
31
|
-
|
|
32
|
-
**Output:**
|
|
33
|
-
- `.ply` - Standard PLY format
|
|
34
|
-
- `.compressed.ply` - Compressed PLY format
|
|
35
|
-
- `meta.json` - SOGS format (JSON + WebP images)
|
|
36
|
-
- `.csv` - Comma-separated values
|
|
37
|
-
|
|
38
|
-
## Actions
|
|
39
|
-
|
|
40
|
-
Actions can be repeated and applied in any order:
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
-t, --translate x,y,z Translate splats by (x, y, z)
|
|
44
|
-
-r, --rotate x,y,z Rotate splats by Euler angles (deg)
|
|
45
|
-
-s, --scale x Uniformly scale splats by factor x
|
|
46
|
-
-n, --filterNaN Remove any Gaussian containing NaN/Inf
|
|
47
|
-
-c, --filterByValue name,cmp,value Keep splats where <name> <cmp> <value>
|
|
48
|
-
cmp ∈ {lt,lte,gt,gte,eq,neq}
|
|
49
|
-
-b, --filterBands {0|1|2|3} Strip spherical-harmonic bands > N
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## Global Options
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
-w, --overwrite Overwrite output file if it already exists
|
|
56
|
-
-h, --help Show help and exit
|
|
57
|
-
-v, --version Show version and exit
|
|
58
|
-
-g, --no-gpu Disable gpu when compressing spherical harmonics.
|
|
59
|
-
-i, --iterations <number> Specify the number of iterations when compressing spherical harmonics. More iterations generally lead to better results. Default is 10.
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
## Examples
|
|
63
|
-
|
|
64
|
-
### Basic Operations
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
# Simple format conversion
|
|
68
|
-
splat-transform input.ply output.csv
|
|
69
|
-
|
|
70
|
-
# Convert from .splat format
|
|
71
|
-
splat-transform input.splat output.ply
|
|
72
|
-
|
|
73
|
-
# Convert from .ksplat format
|
|
74
|
-
splat-transform input.ksplat output.ply
|
|
75
|
-
|
|
76
|
-
# Convert to compressed PLY
|
|
77
|
-
splat-transform input.ply output.compressed.ply
|
|
78
|
-
|
|
79
|
-
# Convert to SOGS format
|
|
80
|
-
splat-transform input.ply output/meta.json
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### Transformations
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
# Scale and translate
|
|
87
|
-
splat-transform bunny.ply -s 0.5 -t 0,0,10 bunny_scaled.ply
|
|
88
|
-
|
|
89
|
-
# Rotate by 90 degrees around Y axis
|
|
90
|
-
splat-transform input.ply -r 0,90,0 output.ply
|
|
91
|
-
|
|
92
|
-
# Chain multiple transformations
|
|
93
|
-
splat-transform input.ply -s 2 -t 1,0,0 -r 0,0,45 output.ply
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
### Filtering
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
# Remove entries containing NaN and Inf
|
|
100
|
-
splat-transform input.ply --filterNaN output.ply
|
|
101
|
-
|
|
102
|
-
# Filter by opacity values (keep only splats with opacity > 0.5)
|
|
103
|
-
splat-transform input.ply -c opacity,gt,0.5 output.ply
|
|
104
|
-
|
|
105
|
-
# Strip spherical harmonic bands higher than 2
|
|
106
|
-
splat-transform input.ply --filterBands 2 output.ply
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### Advanced Usage
|
|
110
|
-
|
|
111
|
-
```bash
|
|
112
|
-
# Combine multiple files with different transforms
|
|
113
|
-
splat-transform -w cloudA.ply -r 0,90,0 cloudB.ply -s 2 merged.compressed.ply
|
|
114
|
-
|
|
115
|
-
# Apply final transformations to combined result
|
|
116
|
-
splat-transform input1.ply input2.ply output.ply -t 0,0,10 -s 0.5
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
## Getting Help
|
|
120
|
-
|
|
121
|
-
```bash
|
|
122
|
-
# Show version
|
|
123
|
-
splat-transform --version
|
|
124
|
-
|
|
125
|
-
# Show help
|
|
126
|
-
splat-transform --help
|
|
127
|
-
```
|
|
1
|
+
# SplatTransform - 3D Gaussian Splat Converter
|
|
2
|
+
|
|
3
|
+
SplatTransform is an open source CLI tool for reading gaussian splat PLY files and writing them to PLY, Compressed PLY, CSV, and SOGS format.
|
|
4
|
+
|
|
5
|
+
Multiple files may be combined and transformed before being written to the output.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
First install the package globally:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g @playcanvas/splat-transform
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
splat-transform [GLOBAL] <input.{ply|splat|ksplat}> [ACTIONS] ... <output.{ply|compressed.ply|meta.json|csv}> [ACTIONS]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Key points:**
|
|
22
|
+
- Every time an `*.ply*` appears, it becomes the current working set; the following ACTIONS are applied in the order listed
|
|
23
|
+
- The last file on the command line is treated as the output; anything after it is interpreted as actions that modify the final result
|
|
24
|
+
|
|
25
|
+
## Supported Formats
|
|
26
|
+
|
|
27
|
+
**Input:**
|
|
28
|
+
- `.ply` - Standard PLY format
|
|
29
|
+
- `.splat` - Binary splat format (antimatter15 format)
|
|
30
|
+
- `.ksplat` - Compressed binary splat format (mkkellogg format)
|
|
31
|
+
|
|
32
|
+
**Output:**
|
|
33
|
+
- `.ply` - Standard PLY format
|
|
34
|
+
- `.compressed.ply` - Compressed PLY format
|
|
35
|
+
- `meta.json` - SOGS format (JSON + WebP images)
|
|
36
|
+
- `.csv` - Comma-separated values
|
|
37
|
+
|
|
38
|
+
## Actions
|
|
39
|
+
|
|
40
|
+
Actions can be repeated and applied in any order:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
-t, --translate x,y,z Translate splats by (x, y, z)
|
|
44
|
+
-r, --rotate x,y,z Rotate splats by Euler angles (deg)
|
|
45
|
+
-s, --scale x Uniformly scale splats by factor x
|
|
46
|
+
-n, --filterNaN Remove any Gaussian containing NaN/Inf
|
|
47
|
+
-c, --filterByValue name,cmp,value Keep splats where <name> <cmp> <value>
|
|
48
|
+
cmp ∈ {lt,lte,gt,gte,eq,neq}
|
|
49
|
+
-b, --filterBands {0|1|2|3} Strip spherical-harmonic bands > N
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Global Options
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
-w, --overwrite Overwrite output file if it already exists
|
|
56
|
+
-h, --help Show help and exit
|
|
57
|
+
-v, --version Show version and exit
|
|
58
|
+
-g, --no-gpu Disable gpu when compressing spherical harmonics.
|
|
59
|
+
-i, --iterations <number> Specify the number of iterations when compressing spherical harmonics. More iterations generally lead to better results. Default is 10.
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Examples
|
|
63
|
+
|
|
64
|
+
### Basic Operations
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Simple format conversion
|
|
68
|
+
splat-transform input.ply output.csv
|
|
69
|
+
|
|
70
|
+
# Convert from .splat format
|
|
71
|
+
splat-transform input.splat output.ply
|
|
72
|
+
|
|
73
|
+
# Convert from .ksplat format
|
|
74
|
+
splat-transform input.ksplat output.ply
|
|
75
|
+
|
|
76
|
+
# Convert to compressed PLY
|
|
77
|
+
splat-transform input.ply output.compressed.ply
|
|
78
|
+
|
|
79
|
+
# Convert to SOGS format
|
|
80
|
+
splat-transform input.ply output/meta.json
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Transformations
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Scale and translate
|
|
87
|
+
splat-transform bunny.ply -s 0.5 -t 0,0,10 bunny_scaled.ply
|
|
88
|
+
|
|
89
|
+
# Rotate by 90 degrees around Y axis
|
|
90
|
+
splat-transform input.ply -r 0,90,0 output.ply
|
|
91
|
+
|
|
92
|
+
# Chain multiple transformations
|
|
93
|
+
splat-transform input.ply -s 2 -t 1,0,0 -r 0,0,45 output.ply
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Filtering
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Remove entries containing NaN and Inf
|
|
100
|
+
splat-transform input.ply --filterNaN output.ply
|
|
101
|
+
|
|
102
|
+
# Filter by opacity values (keep only splats with opacity > 0.5)
|
|
103
|
+
splat-transform input.ply -c opacity,gt,0.5 output.ply
|
|
104
|
+
|
|
105
|
+
# Strip spherical harmonic bands higher than 2
|
|
106
|
+
splat-transform input.ply --filterBands 2 output.ply
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Advanced Usage
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# Combine multiple files with different transforms
|
|
113
|
+
splat-transform -w cloudA.ply -r 0,90,0 cloudB.ply -s 2 merged.compressed.ply
|
|
114
|
+
|
|
115
|
+
# Apply final transformations to combined result
|
|
116
|
+
splat-transform input1.ply input2.ply output.ply -t 0,0,10 -s 0.5
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Getting Help
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# Show version
|
|
123
|
+
splat-transform --version
|
|
124
|
+
|
|
125
|
+
# Show help
|
|
126
|
+
splat-transform --help
|
|
127
|
+
```
|
package/bin/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { main } from '../dist/index.mjs';
|
|
4
|
-
|
|
5
|
-
await main();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { main } from '../dist/index.mjs';
|
|
4
|
+
|
|
5
|
+
await main();
|
package/dist/index.mjs
CHANGED
|
@@ -1884,7 +1884,7 @@ let Quat$1 = class Quat {
|
|
|
1884
1884
|
}
|
|
1885
1885
|
};
|
|
1886
1886
|
|
|
1887
|
-
var version$1 = "0.5.
|
|
1887
|
+
var version$1 = "0.5.2";
|
|
1888
1888
|
|
|
1889
1889
|
class Column {
|
|
1890
1890
|
name;
|
|
@@ -89014,116 +89014,131 @@ class KdTree {
|
|
|
89014
89014
|
}
|
|
89015
89015
|
}
|
|
89016
89016
|
|
|
89017
|
-
const clusterWgsl = (numColumns, numPoints, numCentroids) => {
|
|
89018
|
-
|
|
89019
|
-
|
|
89020
|
-
enable f16;
|
|
89021
|
-
|
|
89022
|
-
@group(0) @binding(0) var<storage, read> points: array
|
|
89023
|
-
@group(0) @binding(1) var<storage, read> centroids: array
|
|
89024
|
-
@group(0) @binding(2) var<storage, read_write> results: array<u32>;
|
|
89025
|
-
|
|
89026
|
-
const numColumns = ${numColumns};
|
|
89027
|
-
const numPoints = ${numPoints};
|
|
89028
|
-
const numCentroids = ${numCentroids};
|
|
89029
|
-
|
|
89030
|
-
const chunkSize = 128u; // must be a multiple of 64
|
|
89031
|
-
var<workgroup> sharedChunk: array
|
|
89032
|
-
|
|
89033
|
-
// calculate the squared distance between the point and centroid
|
|
89034
|
-
fn calcDistanceSqr(point: array
|
|
89035
|
-
var result = 0.0;
|
|
89036
|
-
|
|
89037
|
-
var ci = centroid * numColumns;
|
|
89038
|
-
|
|
89039
|
-
for (var i = 0u; i < numColumns; i++) {
|
|
89040
|
-
let v = f32(point[i] - sharedChunk[ci+i]);
|
|
89041
|
-
result += v * v;
|
|
89042
|
-
}
|
|
89043
|
-
|
|
89044
|
-
return result;
|
|
89045
|
-
}
|
|
89046
|
-
|
|
89047
|
-
@compute @workgroup_size(64)
|
|
89048
|
-
fn main(
|
|
89049
|
-
@builtin(local_invocation_index) local_id : u32,
|
|
89050
|
-
@builtin(global_invocation_id) global_id: vec3u,
|
|
89051
|
-
@builtin(num_workgroups) num_workgroups: vec3u
|
|
89052
|
-
) {
|
|
89053
|
-
// calculate row index for this thread point
|
|
89054
|
-
let pointIndex = global_id.x + global_id.y * num_workgroups.x * 64;
|
|
89055
|
-
|
|
89056
|
-
// copy the point data from global memory
|
|
89057
|
-
var point: array
|
|
89058
|
-
if (pointIndex < numPoints) {
|
|
89059
|
-
for (var i = 0u; i < numColumns; i++) {
|
|
89060
|
-
point[i] = points[pointIndex * numColumns + i];
|
|
89061
|
-
}
|
|
89062
|
-
}
|
|
89063
|
-
|
|
89064
|
-
var mind = 1000000.0;
|
|
89065
|
-
var mini = 0u;
|
|
89066
|
-
|
|
89067
|
-
// work through the list of centroids in shared memory chunks
|
|
89068
|
-
let numChunks = u32(ceil(f32(numCentroids) / f32(chunkSize)));
|
|
89069
|
-
for (var i = 0u; i < numChunks; i++) {
|
|
89070
|
-
|
|
89071
|
-
// copy this thread's slice of the centroid shared chunk data
|
|
89072
|
-
let dstRow = local_id * (chunkSize / 64u);
|
|
89073
|
-
let srcRow = min(numCentroids, i * chunkSize + local_id * chunkSize / 64u);
|
|
89074
|
-
let numRows = min(numCentroids, srcRow + chunkSize / 64u) - srcRow;
|
|
89075
|
-
|
|
89076
|
-
var dst = dstRow * numColumns;
|
|
89077
|
-
var src = srcRow * numColumns;
|
|
89078
|
-
|
|
89079
|
-
for (var c = 0u; c < numRows * numColumns; c++) {
|
|
89080
|
-
sharedChunk[dst + c] = centroids[src + c];
|
|
89081
|
-
}
|
|
89082
|
-
|
|
89083
|
-
// wait for all threads to finish writing their part of centroids shared memory buffer
|
|
89084
|
-
workgroupBarrier();
|
|
89085
|
-
|
|
89086
|
-
// loop over the next chunk of centroids finding the closest
|
|
89087
|
-
if (pointIndex < numPoints) {
|
|
89088
|
-
let thisChunkSize = min(chunkSize, numCentroids - i * chunkSize);
|
|
89089
|
-
for (var c = 0u; c < thisChunkSize; c++) {
|
|
89090
|
-
let d = calcDistanceSqr(point, c);
|
|
89091
|
-
if (d < mind) {
|
|
89092
|
-
mind = d;
|
|
89093
|
-
mini = i * chunkSize + c;
|
|
89094
|
-
}
|
|
89095
|
-
}
|
|
89096
|
-
}
|
|
89097
|
-
|
|
89098
|
-
// next loop will overwrite the shared memory, so wait
|
|
89099
|
-
workgroupBarrier();
|
|
89100
|
-
}
|
|
89101
|
-
|
|
89102
|
-
if (pointIndex < numPoints) {
|
|
89103
|
-
results[pointIndex] = mini;
|
|
89104
|
-
}
|
|
89105
|
-
}
|
|
89017
|
+
const clusterWgsl = (numColumns, numPoints, numCentroids, useF16) => {
|
|
89018
|
+
const floatType = useF16 ? 'f16' : 'f32';
|
|
89019
|
+
return `
|
|
89020
|
+
${useF16 ? 'enable f16;' : ''}
|
|
89021
|
+
|
|
89022
|
+
@group(0) @binding(0) var<storage, read> points: array<${floatType}>;
|
|
89023
|
+
@group(0) @binding(1) var<storage, read> centroids: array<${floatType}>;
|
|
89024
|
+
@group(0) @binding(2) var<storage, read_write> results: array<u32>;
|
|
89025
|
+
|
|
89026
|
+
const numColumns = ${numColumns};
|
|
89027
|
+
const numPoints = ${numPoints};
|
|
89028
|
+
const numCentroids = ${numCentroids};
|
|
89029
|
+
|
|
89030
|
+
const chunkSize = 128u; // must be a multiple of 64
|
|
89031
|
+
var<workgroup> sharedChunk: array<${floatType}, numColumns * chunkSize>;
|
|
89032
|
+
|
|
89033
|
+
// calculate the squared distance between the point and centroid
|
|
89034
|
+
fn calcDistanceSqr(point: array<${floatType}, numColumns>, centroid: u32) -> f32 {
|
|
89035
|
+
var result = 0.0;
|
|
89036
|
+
|
|
89037
|
+
var ci = centroid * numColumns;
|
|
89038
|
+
|
|
89039
|
+
for (var i = 0u; i < numColumns; i++) {
|
|
89040
|
+
let v = f32(point[i] - sharedChunk[ci+i]);
|
|
89041
|
+
result += v * v;
|
|
89042
|
+
}
|
|
89043
|
+
|
|
89044
|
+
return result;
|
|
89045
|
+
}
|
|
89046
|
+
|
|
89047
|
+
@compute @workgroup_size(64)
|
|
89048
|
+
fn main(
|
|
89049
|
+
@builtin(local_invocation_index) local_id : u32,
|
|
89050
|
+
@builtin(global_invocation_id) global_id: vec3u,
|
|
89051
|
+
@builtin(num_workgroups) num_workgroups: vec3u
|
|
89052
|
+
) {
|
|
89053
|
+
// calculate row index for this thread point
|
|
89054
|
+
let pointIndex = global_id.x + global_id.y * num_workgroups.x * 64;
|
|
89055
|
+
|
|
89056
|
+
// copy the point data from global memory
|
|
89057
|
+
var point: array<${floatType}, numColumns>;
|
|
89058
|
+
if (pointIndex < numPoints) {
|
|
89059
|
+
for (var i = 0u; i < numColumns; i++) {
|
|
89060
|
+
point[i] = points[pointIndex * numColumns + i];
|
|
89061
|
+
}
|
|
89062
|
+
}
|
|
89063
|
+
|
|
89064
|
+
var mind = 1000000.0;
|
|
89065
|
+
var mini = 0u;
|
|
89066
|
+
|
|
89067
|
+
// work through the list of centroids in shared memory chunks
|
|
89068
|
+
let numChunks = u32(ceil(f32(numCentroids) / f32(chunkSize)));
|
|
89069
|
+
for (var i = 0u; i < numChunks; i++) {
|
|
89070
|
+
|
|
89071
|
+
// copy this thread's slice of the centroid shared chunk data
|
|
89072
|
+
let dstRow = local_id * (chunkSize / 64u);
|
|
89073
|
+
let srcRow = min(numCentroids, i * chunkSize + local_id * chunkSize / 64u);
|
|
89074
|
+
let numRows = min(numCentroids, srcRow + chunkSize / 64u) - srcRow;
|
|
89075
|
+
|
|
89076
|
+
var dst = dstRow * numColumns;
|
|
89077
|
+
var src = srcRow * numColumns;
|
|
89078
|
+
|
|
89079
|
+
for (var c = 0u; c < numRows * numColumns; c++) {
|
|
89080
|
+
sharedChunk[dst + c] = centroids[src + c];
|
|
89081
|
+
}
|
|
89082
|
+
|
|
89083
|
+
// wait for all threads to finish writing their part of centroids shared memory buffer
|
|
89084
|
+
workgroupBarrier();
|
|
89085
|
+
|
|
89086
|
+
// loop over the next chunk of centroids finding the closest
|
|
89087
|
+
if (pointIndex < numPoints) {
|
|
89088
|
+
let thisChunkSize = min(chunkSize, numCentroids - i * chunkSize);
|
|
89089
|
+
for (var c = 0u; c < thisChunkSize; c++) {
|
|
89090
|
+
let d = calcDistanceSqr(point, c);
|
|
89091
|
+
if (d < mind) {
|
|
89092
|
+
mind = d;
|
|
89093
|
+
mini = i * chunkSize + c;
|
|
89094
|
+
}
|
|
89095
|
+
}
|
|
89096
|
+
}
|
|
89097
|
+
|
|
89098
|
+
// next loop will overwrite the shared memory, so wait
|
|
89099
|
+
workgroupBarrier();
|
|
89100
|
+
}
|
|
89101
|
+
|
|
89102
|
+
if (pointIndex < numPoints) {
|
|
89103
|
+
results[pointIndex] = mini;
|
|
89104
|
+
}
|
|
89105
|
+
}
|
|
89106
89106
|
`;
|
|
89107
89107
|
};
|
|
89108
89108
|
const roundUp = (value, multiple) => {
|
|
89109
89109
|
return Math.ceil(value / multiple) * multiple;
|
|
89110
89110
|
};
|
|
89111
|
-
const interleaveData = (dataTable) => {
|
|
89111
|
+
const interleaveData = (dataTable, useF16) => {
|
|
89112
89112
|
const { numRows, numColumns } = dataTable;
|
|
89113
|
-
|
|
89114
|
-
|
|
89115
|
-
|
|
89116
|
-
|
|
89117
|
-
|
|
89113
|
+
if (useF16) {
|
|
89114
|
+
const result = new Uint16Array(roundUp(numColumns * numRows, 2));
|
|
89115
|
+
for (let c = 0; c < numColumns; ++c) {
|
|
89116
|
+
const column = dataTable.columns[c];
|
|
89117
|
+
for (let r = 0; r < numRows; ++r) {
|
|
89118
|
+
result[r * numColumns + c] = FloatPacking.float2Half(column.data[r]);
|
|
89119
|
+
}
|
|
89118
89120
|
}
|
|
89121
|
+
return result;
|
|
89122
|
+
}
|
|
89123
|
+
else {
|
|
89124
|
+
const result = new Float32Array(numColumns * numRows);
|
|
89125
|
+
for (let c = 0; c < numColumns; ++c) {
|
|
89126
|
+
const column = dataTable.columns[c];
|
|
89127
|
+
for (let r = 0; r < numRows; ++r) {
|
|
89128
|
+
result[r * numColumns + c] = column.data[r];
|
|
89129
|
+
}
|
|
89130
|
+
}
|
|
89131
|
+
return result;
|
|
89119
89132
|
}
|
|
89120
|
-
return result;
|
|
89121
89133
|
};
|
|
89122
89134
|
class GpuCluster {
|
|
89123
89135
|
execute;
|
|
89124
89136
|
destroy;
|
|
89125
89137
|
constructor(gpuDevice, points, numCentroids) {
|
|
89126
89138
|
const device = gpuDevice.app.graphicsDevice;
|
|
89139
|
+
// Check if device supports f16
|
|
89140
|
+
const useF16 = !!('supportsShaderF16' in device && device.supportsShaderF16);
|
|
89141
|
+
const bytesPerFloat = useF16 ? 2 : 4;
|
|
89127
89142
|
const bindGroupFormat = new BindGroupFormat(device, [
|
|
89128
89143
|
new BindStorageBufferFormat('pointsBuffer', SHADERSTAGE_COMPUTE, true),
|
|
89129
89144
|
new BindStorageBufferFormat('centroidsBuffer', SHADERSTAGE_COMPUTE, true),
|
|
@@ -89134,16 +89149,16 @@ class GpuCluster {
|
|
|
89134
89149
|
const shader = new Shader(device, {
|
|
89135
89150
|
name: 'compute-cluster',
|
|
89136
89151
|
shaderLanguage: SHADERLANGUAGE_WGSL,
|
|
89137
|
-
cshader: clusterWgsl(numColumns, numPoints, numCentroids),
|
|
89152
|
+
cshader: clusterWgsl(numColumns, numPoints, numCentroids, useF16),
|
|
89138
89153
|
// @ts-ignore
|
|
89139
89154
|
computeBindGroupFormat: bindGroupFormat
|
|
89140
89155
|
});
|
|
89141
89156
|
const compute = new Compute(device, shader, 'compute-cluster');
|
|
89142
|
-
const pointsBuffer = new StorageBuffer(device, roundUp(numColumns * numPoints, 2) * 2, BUFFERUSAGE_COPY_DST);
|
|
89143
|
-
const centroidsBuffer = new StorageBuffer(device, numColumns * numCentroids *
|
|
89157
|
+
const pointsBuffer = new StorageBuffer(device, useF16 ? roundUp(numColumns * numPoints, 2) * 2 : numColumns * numPoints * 4, BUFFERUSAGE_COPY_DST);
|
|
89158
|
+
const centroidsBuffer = new StorageBuffer(device, numColumns * numCentroids * bytesPerFloat, BUFFERUSAGE_COPY_DST);
|
|
89144
89159
|
const resultsBuffer = new StorageBuffer(device, numPoints * 4, BUFFERUSAGE_COPY_SRC | BUFFERUSAGE_COPY_DST);
|
|
89145
89160
|
// interleave the points table data and write to gpu
|
|
89146
|
-
const interleavedPoints = interleaveData(points);
|
|
89161
|
+
const interleavedPoints = interleaveData(points, useF16);
|
|
89147
89162
|
pointsBuffer.write(0, interleavedPoints, 0, interleavedPoints.length);
|
|
89148
89163
|
compute.setParameter('columns', numColumns);
|
|
89149
89164
|
compute.setParameter('points', numPoints);
|
|
@@ -89153,7 +89168,7 @@ class GpuCluster {
|
|
|
89153
89168
|
compute.setParameter('resultsBuffer', resultsBuffer);
|
|
89154
89169
|
this.execute = async (centroids, labels) => {
|
|
89155
89170
|
// interleave centroids and write to gpu
|
|
89156
|
-
const interleavedCentroids = interleaveData(centroids);
|
|
89171
|
+
const interleavedCentroids = interleaveData(centroids, useF16);
|
|
89157
89172
|
centroidsBuffer.write(0, interleavedCentroids, 0, interleavedCentroids.length);
|
|
89158
89173
|
// calculate the workgroup layout to minimize the number of empty workgroups
|
|
89159
89174
|
const groups = Math.ceil(points.numRows / 64);
|
|
@@ -89758,46 +89773,46 @@ const parseArguments = () => {
|
|
|
89758
89773
|
}
|
|
89759
89774
|
return { files, options };
|
|
89760
89775
|
};
|
|
89761
|
-
const usage = `
|
|
89762
|
-
Apply geometric transforms & filters to Gaussian-splat point clouds
|
|
89763
|
-
===================================================================
|
|
89764
|
-
|
|
89765
|
-
USAGE
|
|
89766
|
-
splat-transform [GLOBAL] <input.{ply|splat|ksplat}> [ACTIONS] ... <output.{ply|compressed.ply|meta.json|csv}> [ACTIONS]
|
|
89767
|
-
|
|
89768
|
-
• Every time an input file appears, it becomes the current working set; the following
|
|
89769
|
-
ACTIONS are applied in the order listed.
|
|
89770
|
-
• The last file on the command line is treated as the output; anything after it is
|
|
89771
|
-
interpreted as actions that modify the final result.
|
|
89772
|
-
|
|
89773
|
-
SUPPORTED INPUTS
|
|
89774
|
-
.ply .splat .ksplat
|
|
89775
|
-
|
|
89776
|
-
SUPPORTED OUTPUTS
|
|
89777
|
-
.ply .compressed.ply meta.json (SOGS) .csv
|
|
89778
|
-
|
|
89779
|
-
ACTIONS (can be repeated, in any order)
|
|
89780
|
-
-t, --translate x,y,z Translate splats by (x, y, z)
|
|
89781
|
-
-r, --rotate x,y,z Rotate splats by Euler angles (deg)
|
|
89782
|
-
-s, --scale x Uniformly scale splats by factor x
|
|
89783
|
-
-n, --filterNaN Remove any Gaussian containing NaN/Inf
|
|
89784
|
-
-c, --filterByValue name,cmp,value Keep splats where <name> <cmp> <value>
|
|
89785
|
-
cmp ∈ {lt,lte,gt,gte,eq,neq}
|
|
89786
|
-
-b, --filterBands {0|1|2|3} Strip spherical-harmonic bands > N
|
|
89787
|
-
|
|
89788
|
-
GLOBAL OPTIONS
|
|
89789
|
-
-w, --overwrite Overwrite output file if it already exists. Default is false.
|
|
89790
|
-
-h, --help Show this help and exit.
|
|
89791
|
-
-v, --version Show version and exit.
|
|
89792
|
-
-g, --no-gpu Disable gpu when compressing spherical harmonics.
|
|
89793
|
-
-i, --iterations <number> Specify the number of iterations when compressing spherical harmonics. More iterations generally lead to better results. Default is 10.
|
|
89794
|
-
|
|
89795
|
-
EXAMPLES
|
|
89796
|
-
# Simple scale-then-translate
|
|
89797
|
-
splat-transform bunny.ply -s 0.5 -t 0,0,10 bunny_scaled.ply
|
|
89798
|
-
|
|
89799
|
-
# Chain two inputs and write compressed output, overwriting if necessary
|
|
89800
|
-
splat-transform -w cloudA.ply -r 0,90,0 cloudB.ply -s 2 merged.compressed.ply
|
|
89776
|
+
const usage = `
|
|
89777
|
+
Apply geometric transforms & filters to Gaussian-splat point clouds
|
|
89778
|
+
===================================================================
|
|
89779
|
+
|
|
89780
|
+
USAGE
|
|
89781
|
+
splat-transform [GLOBAL] <input.{ply|splat|ksplat}> [ACTIONS] ... <output.{ply|compressed.ply|meta.json|csv}> [ACTIONS]
|
|
89782
|
+
|
|
89783
|
+
• Every time an input file appears, it becomes the current working set; the following
|
|
89784
|
+
ACTIONS are applied in the order listed.
|
|
89785
|
+
• The last file on the command line is treated as the output; anything after it is
|
|
89786
|
+
interpreted as actions that modify the final result.
|
|
89787
|
+
|
|
89788
|
+
SUPPORTED INPUTS
|
|
89789
|
+
.ply .splat .ksplat
|
|
89790
|
+
|
|
89791
|
+
SUPPORTED OUTPUTS
|
|
89792
|
+
.ply .compressed.ply meta.json (SOGS) .csv
|
|
89793
|
+
|
|
89794
|
+
ACTIONS (can be repeated, in any order)
|
|
89795
|
+
-t, --translate x,y,z Translate splats by (x, y, z)
|
|
89796
|
+
-r, --rotate x,y,z Rotate splats by Euler angles (deg)
|
|
89797
|
+
-s, --scale x Uniformly scale splats by factor x
|
|
89798
|
+
-n, --filterNaN Remove any Gaussian containing NaN/Inf
|
|
89799
|
+
-c, --filterByValue name,cmp,value Keep splats where <name> <cmp> <value>
|
|
89800
|
+
cmp ∈ {lt,lte,gt,gte,eq,neq}
|
|
89801
|
+
-b, --filterBands {0|1|2|3} Strip spherical-harmonic bands > N
|
|
89802
|
+
|
|
89803
|
+
GLOBAL OPTIONS
|
|
89804
|
+
-w, --overwrite Overwrite output file if it already exists. Default is false.
|
|
89805
|
+
-h, --help Show this help and exit.
|
|
89806
|
+
-v, --version Show version and exit.
|
|
89807
|
+
-g, --no-gpu Disable gpu when compressing spherical harmonics.
|
|
89808
|
+
-i, --iterations <number> Specify the number of iterations when compressing spherical harmonics. More iterations generally lead to better results. Default is 10.
|
|
89809
|
+
|
|
89810
|
+
EXAMPLES
|
|
89811
|
+
# Simple scale-then-translate
|
|
89812
|
+
splat-transform bunny.ply -s 0.5 -t 0,0,10 bunny_scaled.ply
|
|
89813
|
+
|
|
89814
|
+
# Chain two inputs and write compressed output, overwriting if necessary
|
|
89815
|
+
splat-transform -w cloudA.ply -r 0,90,0 cloudB.ply -s 2 merged.compressed.ply
|
|
89801
89816
|
`;
|
|
89802
89817
|
const main = async () => {
|
|
89803
89818
|
console.log(`splat-transform v${version$1}`);
|