@playcanvas/splat-transform 1.4.0 → 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 +127 -29
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +100 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +100 -30
- package/dist/index.mjs.map +1 -1
- package/dist/lib/data-table/data-table.d.ts +2 -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
|
@@ -11029,42 +11029,38 @@ class DataTable {
|
|
|
11029
11029
|
* After calling, row `i` will contain the data that was previously at row `indices[i]`.
|
|
11030
11030
|
*
|
|
11031
11031
|
* This is a memory-efficient alternative to `permuteRows` that modifies the table
|
|
11032
|
-
* in-place rather than creating a copy.
|
|
11032
|
+
* in-place rather than creating a copy. It reuses ArrayBuffers between columns to
|
|
11033
|
+
* minimize memory allocations.
|
|
11033
11034
|
*
|
|
11034
11035
|
* @param indices - Array of indices defining the permutation. Must have the same
|
|
11035
11036
|
* length as the number of rows, and must be a valid permutation
|
|
11036
11037
|
* (each index 0 to n-1 appears exactly once).
|
|
11037
11038
|
*/
|
|
11038
11039
|
permuteRowsInPlace(indices) {
|
|
11039
|
-
|
|
11040
|
-
const
|
|
11041
|
-
const
|
|
11042
|
-
|
|
11043
|
-
|
|
11044
|
-
|
|
11045
|
-
|
|
11046
|
-
// Save values at position i
|
|
11047
|
-
for (let c = 0; c < numCols; c++) {
|
|
11048
|
-
temps[c] = this.columns[c].data[i];
|
|
11040
|
+
// Cache for reusing ArrayBuffers by size
|
|
11041
|
+
const cache = new Map();
|
|
11042
|
+
const getBuffer = (size) => {
|
|
11043
|
+
const cached = cache.get(size);
|
|
11044
|
+
if (cached) {
|
|
11045
|
+
cache.delete(size);
|
|
11046
|
+
return cached;
|
|
11049
11047
|
}
|
|
11050
|
-
|
|
11051
|
-
|
|
11052
|
-
|
|
11053
|
-
|
|
11054
|
-
|
|
11055
|
-
|
|
11056
|
-
|
|
11057
|
-
|
|
11058
|
-
|
|
11059
|
-
|
|
11060
|
-
|
|
11061
|
-
|
|
11062
|
-
|
|
11063
|
-
for (let c = 0; c < numCols; c++) {
|
|
11064
|
-
this.columns[c].data[j] = this.columns[c].data[next];
|
|
11065
|
-
}
|
|
11066
|
-
j = next;
|
|
11048
|
+
return new ArrayBuffer(size);
|
|
11049
|
+
};
|
|
11050
|
+
const returnBuffer = (buffer) => {
|
|
11051
|
+
cache.set(buffer.byteLength, buffer);
|
|
11052
|
+
};
|
|
11053
|
+
const n = this.numRows;
|
|
11054
|
+
for (const column of this.columns) {
|
|
11055
|
+
const src = column.data;
|
|
11056
|
+
const constructor = src.constructor;
|
|
11057
|
+
const dst = new constructor(getBuffer(src.byteLength));
|
|
11058
|
+
// Sequential writes are cache-friendly
|
|
11059
|
+
for (let i = 0; i < n; i++) {
|
|
11060
|
+
dst[i] = src[indices[i]];
|
|
11067
11061
|
}
|
|
11062
|
+
returnBuffer(src.buffer);
|
|
11063
|
+
column.data = dst;
|
|
11068
11064
|
}
|
|
11069
11065
|
}
|
|
11070
11066
|
}
|
|
@@ -11706,6 +11702,62 @@ const sortMortonOrder = (dataTable, indices) => {
|
|
|
11706
11702
|
generate(indices);
|
|
11707
11703
|
};
|
|
11708
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
|
+
|
|
11709
11761
|
/**
|
|
11710
11762
|
* Abstract base class for streaming data from a source.
|
|
11711
11763
|
* Uses a pull-based model where the consumer provides the buffer.
|
|
@@ -14299,7 +14351,7 @@ class CompressedChunk {
|
|
|
14299
14351
|
}
|
|
14300
14352
|
}
|
|
14301
14353
|
|
|
14302
|
-
var version = "1.
|
|
14354
|
+
var version = "1.5.0";
|
|
14303
14355
|
|
|
14304
14356
|
const generatedByString = `Generated by splat-transform ${version}`;
|
|
14305
14357
|
const chunkProps = [
|
|
@@ -16257,6 +16309,24 @@ const processDataTable = (dataTable, processActions) => {
|
|
|
16257
16309
|
result.permuteRowsInPlace(indices);
|
|
16258
16310
|
break;
|
|
16259
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
|
+
}
|
|
16260
16330
|
}
|
|
16261
16331
|
}
|
|
16262
16332
|
return result;
|
|
@@ -16534,6 +16604,7 @@ const parseArguments = async () => {
|
|
|
16534
16604
|
'filter-harmonics': { type: 'string', short: 'H', multiple: true },
|
|
16535
16605
|
'filter-box': { type: 'string', short: 'B', multiple: true },
|
|
16536
16606
|
'filter-sphere': { type: 'string', short: 'S', multiple: true },
|
|
16607
|
+
'filter-visibility': { type: 'string', short: 'F', multiple: true },
|
|
16537
16608
|
params: { type: 'string', short: 'p', multiple: true },
|
|
16538
16609
|
lod: { type: 'string', short: 'l', multiple: true },
|
|
16539
16610
|
summary: { type: 'boolean', short: 'm', multiple: true },
|
|
@@ -16735,6 +16806,31 @@ const parseArguments = async () => {
|
|
|
16735
16806
|
kind: 'mortonOrder'
|
|
16736
16807
|
});
|
|
16737
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
|
+
}
|
|
16738
16834
|
}
|
|
16739
16835
|
}
|
|
16740
16836
|
}
|
|
@@ -16767,6 +16863,8 @@ ACTIONS (can be repeated, in any order)
|
|
|
16767
16863
|
-S, --filter-sphere <x,y,z,radius> Remove Gaussians outside sphere (center, radius)
|
|
16768
16864
|
-V, --filter-value <name,cmp,value> Keep Gaussians where <name> <cmp> <value>
|
|
16769
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
|
|
16770
16868
|
-p, --params <key=val,...> Pass parameters to .mjs generator script
|
|
16771
16869
|
-l, --lod <n> Specify the level of detail, n >= 0
|
|
16772
16870
|
-m, --summary Print per-column statistics to stdout
|