File size: 3,648 Bytes
31c7d49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import { createGaussianScene, type GaussianScene, type GaussianSceneMetadata } from '@ai3d/gaussian-scene'

export const GAUSSIANS_PER_POINT = 32
export const GAUSSIAN_FEATURE_WIDTH = 480

const OPACITY_BIAS = Math.log(0.1 / 0.9)
const SCALE_BIAS = Math.log(Math.expm1(0.004))
const OFFSET_SCALE_BIAS = Math.log(Math.expm1(0.05))
const BASE_OFFSETS = buildBaseOffsets()

function softplus(value: number): number {
  if (value > 20) return value
  if (value < -20) return Math.exp(value)
  return Math.log1p(Math.exp(value))
}

function sigmoid(value: number): number {
  return value >= 0 ? 1 / (1 + Math.exp(-value)) : Math.exp(value) / (1 + Math.exp(value))
}

function radicalInverse(base: number, value: number): number {
  let output = 0
  let inversePower = 1 / base
  while (value > 0) {
    output += (value % base) * inversePower
    value = Math.floor(value / base)
    inversePower /= base
  }
  return output
}

function buildBaseOffsets(): Float32Array {
  const offsets = new Float32Array(GAUSSIANS_PER_POINT * 3)
  for (let index = 0; index < GAUSSIANS_PER_POINT; index += 1) {
    const values = [index / GAUSSIANS_PER_POINT, radicalInverse(2, index), radicalInverse(3, index)]
    for (let axis = 0; axis < 3; axis += 1) {
      offsets[index * 3 + axis] = Math.atanh((values[axis] * 2 - 1) / 1.5)
    }
  }
  return offsets
}

export interface DecodeGaussiansOptions {
  metadata: GaussianSceneMetadata
}

/** Decode the official fixed-length ElasticGaussian 480-feature representation. */
export function decodeGaussians(
  points: Float32Array,
  features: Float32Array,
  options: DecodeGaussiansOptions,
): GaussianScene {
  if (points.length % 3 !== 0) throw new RangeError('points must contain xyz triplets.')
  const pointCount = points.length / 3
  if (features.length !== pointCount * GAUSSIAN_FEATURE_WIDTH) {
    throw new RangeError(`features must contain ${pointCount * GAUSSIAN_FEATURE_WIDTH} values.`)
  }
  const count = pointCount * GAUSSIANS_PER_POINT
  const positions = new Float32Array(count * 3)
  const scales = new Float32Array(count * 3)
  const rotations = new Float32Array(count * 4)
  const sphericalHarmonics = new Float32Array(count * 3)
  const opacities = new Float32Array(count)
  for (let point = 0; point < pointCount; point += 1) {
    const featureBase = point * GAUSSIAN_FEATURE_WIDTH
    for (let gaussian = 0; gaussian < GAUSSIANS_PER_POINT; gaussian += 1) {
      const output = point * GAUSSIANS_PER_POINT + gaussian
      const output3 = output * 3
      const output4 = output * 4
      const learnedOffsetScale = softplus(features[featureBase + 448 + gaussian] + OFFSET_SCALE_BIAS)
      for (let axis = 0; axis < 3; axis += 1) {
        const component = gaussian * 3 + axis
        positions[output3 + axis] = points[point * 3 + axis]
          + Math.tanh(features[featureBase + component] + BASE_OFFSETS[component]) * 0.75 * learnedOffsetScale
          - 0.5
        sphericalHarmonics[output3 + axis] = features[featureBase + 96 + component]
        const scale = softplus(features[featureBase + 192 + component] + SCALE_BIAS)
        scales[output3 + axis] = Math.sqrt(scale * scale + 0.0009 ** 2)
      }
      for (let component = 0; component < 4; component += 1) {
        rotations[output4 + component] = features[featureBase + 288 + gaussian * 4 + component] * 0.1
          + (component === 0 ? 1 : 0)
      }
      opacities[output] = sigmoid(features[featureBase + 416 + gaussian] + OPACITY_BIAS)
    }
  }
  return createGaussianScene({
    count,
    positions,
    scales,
    rotations,
    opacities,
    sphericalHarmonics,
    metadata: options.metadata,
  })
}