@playcanvas/splat-transform 1.4.1 → 1.5.0
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/README.md +10 -0
- package/dist/cli.mjs +103 -1
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +76 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +76 -2
- package/dist/index.mjs.map +1 -1
- package/dist/lib/data-table/filter-visibility.d.ts +17 -0
- package/dist/lib/index.d.cts +2 -1
- package/dist/lib/index.d.ts +2 -1
- package/dist/lib/process.d.ts +17 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -77,6 +77,8 @@ Actions can be repeated and applied in any order:
|
|
|
77
77
|
-S, --filter-sphere <x,y,z,radius> Remove Gaussians outside sphere (center, radius)
|
|
78
78
|
-V, --filter-value <name,cmp,value> Keep splats where <name> <cmp> <value>
|
|
79
79
|
cmp ∈ {lt,lte,gt,gte,eq,neq}
|
|
80
|
+
-F, --filter-visibility <n|n%> Keep the n most visible splats (by opacity * volume)
|
|
81
|
+
Use n% to keep a percentage of splats
|
|
80
82
|
-p, --params <key=val,...> Pass parameters to .mjs generator script
|
|
81
83
|
-l, --lod <n> Specify the level of detail of this model, n >= 0.
|
|
82
84
|
-m, --summary Print per-column statistics to stdout
|
|
@@ -170,6 +172,12 @@ splat-transform input.ply -V opacity,gt,0.5 output.ply
|
|
|
170
172
|
|
|
171
173
|
# Strip spherical harmonic bands higher than 2
|
|
172
174
|
splat-transform input.ply --filter-harmonics 2 output.ply
|
|
175
|
+
|
|
176
|
+
# Keep only the 50000 most visible splats
|
|
177
|
+
splat-transform input.ply --filter-visibility 50000 output.ply
|
|
178
|
+
|
|
179
|
+
# Keep the top 25% most visible splats
|
|
180
|
+
splat-transform input.ply -F 25% output.ply
|
|
173
181
|
```
|
|
174
182
|
|
|
175
183
|
### Advanced Usage
|
|
@@ -275,6 +283,7 @@ import {
|
|
|
275
283
|
| `processDataTable` | Apply a sequence of processing actions |
|
|
276
284
|
| `computeSummary` | Generate statistical summary of data |
|
|
277
285
|
| `sortMortonOrder` | Sort indices by Morton code for spatial locality |
|
|
286
|
+
| `sortByVisibility` | Sort indices by visibility score for filtering |
|
|
278
287
|
|
|
279
288
|
### File System Abstractions
|
|
280
289
|
|
|
@@ -351,6 +360,7 @@ type ProcessAction =
|
|
|
351
360
|
| { kind: 'filterBands'; value: 0|1|2|3 }
|
|
352
361
|
| { kind: 'filterBox'; min: Vec3; max: Vec3 }
|
|
353
362
|
| { kind: 'filterSphere'; center: Vec3; radius: number }
|
|
363
|
+
| { kind: 'filterVisibility'; count: number | null; percent: number | null }
|
|
354
364
|
| { kind: 'lod'; value: number }
|
|
355
365
|
| { kind: 'summary' }
|
|
356
366
|
| { kind: 'mortonOrder' };
|
package/dist/cli.mjs
CHANGED
|
@@ -11702,6 +11702,62 @@ const sortMortonOrder = (dataTable, indices) => {
|
|
|
11702
11702
|
generate(indices);
|
|
11703
11703
|
};
|
|
11704
11704
|
|
|
11705
|
+
/**
|
|
11706
|
+
* Sorts the provided indices by visibility score (descending order).
|
|
11707
|
+
*
|
|
11708
|
+
* Visibility is computed as: linear_opacity * volume
|
|
11709
|
+
* where:
|
|
11710
|
+
* - linear_opacity = sigmoid(opacity) = 1 / (1 + exp(-opacity))
|
|
11711
|
+
* - volume = exp(scale_0) * exp(scale_1) * exp(scale_2)
|
|
11712
|
+
*
|
|
11713
|
+
* After calling this function, indices[0] will contain the index of the most
|
|
11714
|
+
* visible splat, indices[1] the second most visible, and so on.
|
|
11715
|
+
*
|
|
11716
|
+
* @param dataTable - The DataTable containing splat data.
|
|
11717
|
+
* @param indices - Array of indices to sort in-place.
|
|
11718
|
+
*/
|
|
11719
|
+
const sortByVisibility = (dataTable, indices) => {
|
|
11720
|
+
const opacityCol = dataTable.getColumnByName('opacity');
|
|
11721
|
+
const scale0Col = dataTable.getColumnByName('scale_0');
|
|
11722
|
+
const scale1Col = dataTable.getColumnByName('scale_1');
|
|
11723
|
+
const scale2Col = dataTable.getColumnByName('scale_2');
|
|
11724
|
+
if (!opacityCol || !scale0Col || !scale1Col || !scale2Col) {
|
|
11725
|
+
logger.debug('missing required columns for visibility sorting (opacity, scale_0, scale_1, scale_2)');
|
|
11726
|
+
return;
|
|
11727
|
+
}
|
|
11728
|
+
if (indices.length === 0) {
|
|
11729
|
+
return;
|
|
11730
|
+
}
|
|
11731
|
+
const opacity = opacityCol.data;
|
|
11732
|
+
const scale0 = scale0Col.data;
|
|
11733
|
+
const scale1 = scale1Col.data;
|
|
11734
|
+
const scale2 = scale2Col.data;
|
|
11735
|
+
// Compute visibility scores for each splat
|
|
11736
|
+
const scores = new Float32Array(indices.length);
|
|
11737
|
+
for (let i = 0; i < indices.length; i++) {
|
|
11738
|
+
const ri = indices[i];
|
|
11739
|
+
// Convert logit opacity to linear using sigmoid
|
|
11740
|
+
const logitOpacity = opacity[ri];
|
|
11741
|
+
const linearOpacity = 1 / (1 + Math.exp(-logitOpacity));
|
|
11742
|
+
// Convert log scales to linear and compute volume
|
|
11743
|
+
// volume = exp(scale_0) * exp(scale_1) * exp(scale_2) = exp(scale_0 + scale_1 + scale_2)
|
|
11744
|
+
const volume = Math.exp(scale0[ri] + scale1[ri] + scale2[ri]);
|
|
11745
|
+
// Visibility score is opacity * volume
|
|
11746
|
+
scores[i] = linearOpacity * volume;
|
|
11747
|
+
}
|
|
11748
|
+
// Sort indices by score (descending - most visible first)
|
|
11749
|
+
const order = new Uint32Array(indices.length);
|
|
11750
|
+
for (let i = 0; i < order.length; i++) {
|
|
11751
|
+
order[i] = i;
|
|
11752
|
+
}
|
|
11753
|
+
order.sort((a, b) => scores[b] - scores[a]);
|
|
11754
|
+
// Apply the sorted order to indices
|
|
11755
|
+
const tmpIndices = indices.slice();
|
|
11756
|
+
for (let i = 0; i < indices.length; i++) {
|
|
11757
|
+
indices[i] = tmpIndices[order[i]];
|
|
11758
|
+
}
|
|
11759
|
+
};
|
|
11760
|
+
|
|
11705
11761
|
/**
|
|
11706
11762
|
* Abstract base class for streaming data from a source.
|
|
11707
11763
|
* Uses a pull-based model where the consumer provides the buffer.
|
|
@@ -14295,7 +14351,7 @@ class CompressedChunk {
|
|
|
14295
14351
|
}
|
|
14296
14352
|
}
|
|
14297
14353
|
|
|
14298
|
-
var version = "1.
|
|
14354
|
+
var version = "1.5.0";
|
|
14299
14355
|
|
|
14300
14356
|
const generatedByString = `Generated by splat-transform ${version}`;
|
|
14301
14357
|
const chunkProps = [
|
|
@@ -16253,6 +16309,24 @@ const processDataTable = (dataTable, processActions) => {
|
|
|
16253
16309
|
result.permuteRowsInPlace(indices);
|
|
16254
16310
|
break;
|
|
16255
16311
|
}
|
|
16312
|
+
case 'filterVisibility': {
|
|
16313
|
+
const indices = new Uint32Array(result.numRows);
|
|
16314
|
+
for (let i = 0; i < indices.length; i++) {
|
|
16315
|
+
indices[i] = i;
|
|
16316
|
+
}
|
|
16317
|
+
sortByVisibility(result, indices);
|
|
16318
|
+
// Determine how many to keep
|
|
16319
|
+
let keepCount;
|
|
16320
|
+
if (processAction.count !== null) {
|
|
16321
|
+
keepCount = Math.min(processAction.count, result.numRows);
|
|
16322
|
+
}
|
|
16323
|
+
else {
|
|
16324
|
+
keepCount = Math.round(result.numRows * (processAction.percent ?? 100) / 100);
|
|
16325
|
+
}
|
|
16326
|
+
keepCount = Math.max(0, keepCount);
|
|
16327
|
+
result = result.permuteRows(indices.subarray(0, keepCount));
|
|
16328
|
+
break;
|
|
16329
|
+
}
|
|
16256
16330
|
}
|
|
16257
16331
|
}
|
|
16258
16332
|
return result;
|
|
@@ -16530,6 +16604,7 @@ const parseArguments = async () => {
|
|
|
16530
16604
|
'filter-harmonics': { type: 'string', short: 'H', multiple: true },
|
|
16531
16605
|
'filter-box': { type: 'string', short: 'B', multiple: true },
|
|
16532
16606
|
'filter-sphere': { type: 'string', short: 'S', multiple: true },
|
|
16607
|
+
'filter-visibility': { type: 'string', short: 'F', multiple: true },
|
|
16533
16608
|
params: { type: 'string', short: 'p', multiple: true },
|
|
16534
16609
|
lod: { type: 'string', short: 'l', multiple: true },
|
|
16535
16610
|
summary: { type: 'boolean', short: 'm', multiple: true },
|
|
@@ -16731,6 +16806,31 @@ const parseArguments = async () => {
|
|
|
16731
16806
|
kind: 'mortonOrder'
|
|
16732
16807
|
});
|
|
16733
16808
|
break;
|
|
16809
|
+
case 'filter-visibility': {
|
|
16810
|
+
const value = t.value.trim();
|
|
16811
|
+
let count = null;
|
|
16812
|
+
let percent = null;
|
|
16813
|
+
if (value.endsWith('%')) {
|
|
16814
|
+
// Percentage mode
|
|
16815
|
+
percent = parseNumber(value.slice(0, -1));
|
|
16816
|
+
if (percent < 0 || percent > 100) {
|
|
16817
|
+
throw new Error(`Invalid filter-visibility percentage: ${value}. Must be between 0% and 100%.`);
|
|
16818
|
+
}
|
|
16819
|
+
}
|
|
16820
|
+
else {
|
|
16821
|
+
// Count mode
|
|
16822
|
+
count = parseInteger(value);
|
|
16823
|
+
if (count < 0) {
|
|
16824
|
+
throw new Error(`Invalid filter-visibility count: ${value}. Must be a non-negative integer.`);
|
|
16825
|
+
}
|
|
16826
|
+
}
|
|
16827
|
+
current.processActions.push({
|
|
16828
|
+
kind: 'filterVisibility',
|
|
16829
|
+
count,
|
|
16830
|
+
percent
|
|
16831
|
+
});
|
|
16832
|
+
break;
|
|
16833
|
+
}
|
|
16734
16834
|
}
|
|
16735
16835
|
}
|
|
16736
16836
|
}
|
|
@@ -16763,6 +16863,8 @@ ACTIONS (can be repeated, in any order)
|
|
|
16763
16863
|
-S, --filter-sphere <x,y,z,radius> Remove Gaussians outside sphere (center, radius)
|
|
16764
16864
|
-V, --filter-value <name,cmp,value> Keep Gaussians where <name> <cmp> <value>
|
|
16765
16865
|
cmp ∈ {lt,lte,gt,gte,eq,neq}
|
|
16866
|
+
-F, --filter-visibility <n|n%> Keep the n most visible Gaussians (by opacity * volume)
|
|
16867
|
+
Use n% to keep a percentage of Gaussians
|
|
16766
16868
|
-p, --params <key=val,...> Pass parameters to .mjs generator script
|
|
16767
16869
|
-l, --lod <n> Specify the level of detail, n >= 0
|
|
16768
16870
|
-m, --summary Print per-column statistics to stdout
|