Skip to content

--filter-sphere parses array index as parseNumber min-bound, breaks any coordinate < array index #236

Description

@rickycambrian

Repro (v2.0.6, both CLI and any node-version)

node node_modules/@playcanvas/splat-transform/bin/cli.mjs \
  input.ply \
  --filter-sphere 0,1.6,0,15 \
  output.ply
✗ Error: Value must be >= 2, got 0
    at parseNumber (.../dist/cli.mjs:29410:19)
    at Array.map (<anonymous>)
    at parseArguments (.../dist/cli.mjs:29635:42)

Any --filter-sphere x,y,z,r invocation where z is less than 2 (or y < 1, or r < 3) throws.

Cause

src/cli/index.ts line 477:

case 'filter-sphere': {
    const parts = t.value.split(',').map((p: string) => p.trim());
    if (parts.length !== 4) {
        throw new Error(`Invalid filter-sphere value: ${t.value}`);
    }
    const values = parts.map(parseNumber);   // ← BUG: passes (element, index) to parseNumber
    ...
}

Array.prototype.map calls the callback as (element, index, array). parseNumber is defined at line 236 as:

const parseNumber = (value: string, min?: number): number => {
    ...
    if (min !== undefined && result < min) {
        throw new Error(`Value must be >= ${min}, got ${value}`);
    }
    ...
};

So parts.map(parseNumber) evaluates:

  • parseNumber(parts[0], 0) → x must be ≥ 0
  • parseNumber(parts[1], 1) → y must be ≥ 1
  • parseNumber(parts[2], 2) → z must be ≥ 2
  • parseNumber(parts[3], 3) → radius must be ≥ 3

Which is obviously wrong — sphere centers can legitimately have negative coordinates.

Comparison: filter-box parses correctly

The neighbouring filter-box case (lines 449-463) uses an explicit for-loop with single-arg calls and is unaffected:

for (let i = 0; i < 6; ++i) {
    if (parts[i] === '' || parts[i] === '-') {
        values[i] = defaults[i];
    } else {
        values[i] = parseNumber(parts[i]);   // ← single-arg, correct
    }
}

Suggested fix (one line)

Replace line 477:

const values = parts.map(parseNumber);

with:

const values = parts.map((p: string) => parseNumber(p));

(Or apply the same defaults-aware for-loop pattern used by filter-box for consistency.)

Happy to send a PR if useful.

Environment

  • @playcanvas/splat-transform 2.0.6 (commit aa3dbae on main)
  • Node v22 / macOS 25.4.0 / darwin-arm64
  • Real-world scene: filtering a 528K-gaussian splat by sphere around (0, 1.6, 0) to remove far-out floaters that explode the AABB; --filter-box is a working workaround but spheres are a more natural shape for many interiors.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions