SplatTools.Camera (SplatTools v0.1.0)

Copy Markdown View Source

Derives a camera that frames a splat.

Every viewer and thumbnail renderer needs a starting viewpoint, and no converter supplies one — splat-transform's default camera is a fixed 2,1,-2 looking at the origin regardless of what the scene actually contains, which points at nothing for most real captures.

Percentiles, not extremes

Bounds come from percentiles rather than min/max. A real capture has stray splats — reconstruction noise flung far from the scene — and a single one is enough to inflate the bounding box by an order of magnitude and push the camera so far back the room becomes a dot. Trimming the outer 1% at each end costs nothing and is the difference between a usable framing and a wasted render.

Note that the trim is a whole number of splats: at the 1% default it removes nothing at all until there are at least 100 of them. That is the right behaviour — on tiny inputs every point is signal — but it does mean small fixtures exercise the min/max path, not the percentile one.

Rows, not columns

Non-finite values are filtered by row: if any of a splat's x, y or z is NaN, the whole splat is dropped. Filtering each axis independently would disagree with --filter-nan, which drops whole splats — so a row of {NaN, 1.0e9, 0.0} would contribute its enormous y to the bounds while being absent from the converted file, and the camera would frame geometry that is not there.

Summary

Types

Axis-aligned bounds, as {min, max} per axis.

t()

A camera pose, in the scene's own coordinate space.

Functions

Computes percentile bounds from packed little-endian float32 columns.

Derives a camera that frames the given bounds.

Derives a camera directly from packed columns.

Formats a camera as splat-transform's x,y,z argument form.

Types

bounds()

@type bounds() :: %{
  x: {float(), float()},
  y: {float(), float()},
  z: {float(), float()}
}

Axis-aligned bounds, as {min, max} per axis.

t()

@type t() :: %{
  position: {float(), float(), float()},
  target: {float(), float(), float()},
  fov: float()
}

A camera pose, in the scene's own coordinate space.

Functions

bounds(columns, opts \\ [])

@spec bounds(
  %{required(String.t()) => binary()},
  keyword()
) :: {:ok, bounds()} | {:error, SplatTools.Error.t()}

Computes percentile bounds from packed little-endian float32 columns.

Takes the output shape of Ply.columns!/3 — one binary per property — so no intermediate list of boxed floats is built for the whole file.

Options

  • :percentile — fraction trimmed from each end, default 0.01. 0.0 gives true min/max. Must be in [0.0, 0.5): at 0.5 every value is trimmed, and above it the trim crosses over and returns a low above the high.

from_bounds(bounds, opts \\ [])

@spec from_bounds(
  bounds(),
  keyword()
) :: {:ok, t()} | {:error, SplatTools.Error.t()}

Derives a camera that frames the given bounds.

Places the camera on a diagonal from the scene centre, far enough back that the whole bounding sphere fits the vertical field of view, with a margin.

Options

  • :fov — vertical field of view in degrees, default 50.0. Must be in (0, 180).
  • :margin — extra distance as a fraction, default 0.25
  • :elevation — how far above centre to sit, as a fraction of scene height, default 0.35

from_columns(columns, opts \\ [])

@spec from_columns(
  %{required(String.t()) => binary()},
  keyword()
) :: {:ok, t()} | {:error, SplatTools.Error.t()}

Derives a camera directly from packed columns.

The common path: Ply.columns!/3 then this.

to_arg(arg)

@spec to_arg({float(), float(), float()}) :: String.t()

Formats a camera as splat-transform's x,y,z argument form.

iex> SplatTools.Camera.to_arg({1.0, 2.0, 3.5})
"1.0,2.0,3.5"