mergeSplats function
SplatCloud
mergeSplats(
- List<
SplatCloud> parts, { - required Vector3 center,
- required double halfExtent,
- required int grid,
The splats of parts merged into at most grid³, one per occupied cell
of a grid³ lattice over the cube of halfExtent-size halfExtent about center.
Each merged splat is the moment match of its cell: the mean and
covariance of the cell's Gaussians weighted by opacity times footprint,
the footprint being (s₀s₁s₂)^⅔, which is what a splat covers on screen
averaged over the directions it could be seen from. Its colour is the
same weighted mean, and its opacity is the cell's summed opacity-footprint
spread over the merged splat's own footprint, capped at one — so a lone
splat merges into itself, and many small faint ones into one larger one
that is as opaque as they were together.
Implementation
SplatCloud mergeSplats(
List<SplatCloud> parts, {
required Vector3 center,
required double halfExtent,
required int grid,
}) {
final cells = Float64List(grid * grid * grid * _kCell);
final occupied = <int>[];
final covariance = Float32List(6);
final scale = halfExtent > 0 ? grid / (2 * halfExtent) : 0.0;
int cellOf(double v, double at) =>
((v - at + halfExtent) * scale).floor().clamp(0, grid - 1);
for (final part in parts) {
for (var i = 0; i < part.count; i++) {
final px = part.centers[i * 3] - center.x;
final py = part.centers[i * 3 + 1] - center.y;
final pz = part.centers[i * 3 + 2] - center.z;
final cell =
(cellOf(px, 0) * grid + cellOf(py, 0)) * grid + cellOf(pz, 0);
final at = cell * _kCell;
final footprint = math
.pow(
part.scales[i * 3] *
part.scales[i * 3 + 1] *
part.scales[i * 3 + 2],
2 / 3,
)
.toDouble();
final alpha = part.colors[i * 4 + 3];
// Never zero, so a cell of fully transparent splats still has a place
// and a shape — it merges into a transparent splat, not a division by
// nothing.
final w = math.max(alpha, 1e-6) * footprint + 1e-30;
if (cells[at] == 0) occupied.add(cell);
part.covarianceOf(i, covariance);
cells[at] += w;
cells[at + 1] += w * px;
cells[at + 2] += w * py;
cells[at + 3] += w * pz;
cells[at + 4] += w * (covariance[0] + px * px);
cells[at + 5] += w * (covariance[1] + px * py);
cells[at + 6] += w * (covariance[2] + px * pz);
cells[at + 7] += w * (covariance[3] + py * py);
cells[at + 8] += w * (covariance[4] + py * pz);
cells[at + 9] += w * (covariance[5] + pz * pz);
cells[at + 10] += w * part.colors[i * 4];
cells[at + 11] += w * part.colors[i * 4 + 1];
cells[at + 12] += w * part.colors[i * 4 + 2];
cells[at + 13] += alpha * footprint;
}
}
occupied.sort();
final n = occupied.length;
final centers = Float32List(n * 3);
final colors = Float32List(n * 4);
final scales = Float32List(n * 3);
final rotations = Float32List(n * 4);
final sigma = Float64List(9);
for (var k = 0; k < n; k++) {
final at = occupied[k] * _kCell;
final w = cells[at];
final mx = cells[at + 1] / w;
final my = cells[at + 2] / w;
final mz = cells[at + 3] / w;
// Second moment about the origin minus the mean's outer product: the
// covariance about the mean. Computed about the node's centre, not the
// world's, so the subtraction does not cancel away the precision.
final xx = cells[at + 4] / w - mx * mx;
final xy = cells[at + 5] / w - mx * my;
final xz = cells[at + 6] / w - mx * mz;
final yy = cells[at + 7] / w - my * my;
final yz = cells[at + 8] / w - my * mz;
final zz = cells[at + 9] / w - mz * mz;
sigma
..[0] = xx
..[1] = xy
..[2] = xz
..[3] = xy
..[4] = yy
..[5] = yz
..[6] = xz
..[7] = yz
..[8] = zz;
final SymmetricEigen(:values, :vectors) = symmetricEigen3(sigma);
centers[k * 3] = mx + center.x;
centers[k * 3 + 1] = my + center.y;
centers[k * 3 + 2] = mz + center.z;
var footprint = 1.0;
for (var a = 0; a < 3; a++) {
final s = math.sqrt(math.max(values[a], 1e-24));
scales[k * 3 + a] = s;
footprint *= s;
}
footprint = math.pow(footprint, 2 / 3).toDouble();
final q = Quaternion.fromRotation(
Matrix3(
vectors[0],
vectors[1],
vectors[2], //
vectors[3],
vectors[4],
vectors[5], //
vectors[6],
vectors[7],
vectors[8],
),
)..normalize();
rotations
..[k * 4] = q.x
..[k * 4 + 1] = q.y
..[k * 4 + 2] = q.z
..[k * 4 + 3] = q.w;
colors
..[k * 4] = cells[at + 10] / w
..[k * 4 + 1] = cells[at + 11] / w
..[k * 4 + 2] = cells[at + 12] / w
..[k * 4 + 3] = math.min(1.0, cells[at + 13] / footprint);
}
return SplatCloud(
centers: centers,
colors: colors,
scales: scales,
rotations: rotations,
);
}