File size: 4,117 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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import type { GaussianAttributes } from '../ImageToGaussianModel'

export const TRIPOSPLAT_GAUSSIANS_PER_POINT = 32
export const TRIPOSPLAT_GS_FEATURE_WIDTH = 480

const XYZ_START = 0
const DC_START = 96
const SCALING_START = 192
const ROTATION_START = 288
const OPACITY_START = 416
const OFFSET_SCALE_START = 448
const OFFSET_AMPLITUDE = 0.75
const KERNEL_SIZE = 0.0009
const OPACITY_BIAS = Math.log(0.1 / 0.9)
const SCALE_BIAS = inverseSoftplus(0.004)
const OFFSET_SCALE_BIAS = inverseSoftplus(0.05)

const BASE_OFFSETS = buildBaseOffsets()

function inverseSoftplus(value: number): number {
  return Math.log(Math.expm1(value))
}

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 {
  if (value >= 0) {
    const z = Math.exp(-value)
    return 1 / (1 + z)
  }
  const z = Math.exp(value)
  return z / (1 + z)
}

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

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

/** Apply the official ElasticGaussianFixedlenDecoder representation semantics. */
export function decodeTripoSplatGaussianFeatures(
  points: Float32Array,
  features: Float32Array,
): GaussianAttributes {
  if (points.length % 3 !== 0) throw new Error('TripoSplat points must contain xyz triplets.')
  const pointCount = points.length / 3
  if (features.length !== pointCount * TRIPOSPLAT_GS_FEATURE_WIDTH) {
    throw new Error(
      `TripoSplat GS feature length ${features.length} does not match ${pointCount}x${TRIPOSPLAT_GS_FEATURE_WIDTH}.`,
    )
  }

  const count = pointCount * TRIPOSPLAT_GAUSSIANS_PER_POINT
  const positions = new Float32Array(count * 3)
  const scales = new Float32Array(count * 3)
  const rotations = new Float32Array(count * 4)
  const sh0 = new Float32Array(count * 3)
  const opacities = new Float32Array(count)

  for (let point = 0; point < pointCount; point += 1) {
    const featureBase = point * TRIPOSPLAT_GS_FEATURE_WIDTH
    for (let gaussian = 0; gaussian < TRIPOSPLAT_GAUSSIANS_PER_POINT; gaussian += 1) {
      const outputIndex = point * TRIPOSPLAT_GAUSSIANS_PER_POINT + gaussian
      const output3 = outputIndex * 3
      const output4 = outputIndex * 4
      const learnedOffsetScale = softplus(features[featureBase + OFFSET_SCALE_START + gaussian] + OFFSET_SCALE_BIAS)

      for (let axis = 0; axis < 3; axis += 1) {
        const component = gaussian * 3 + axis
        const offset =
          Math.tanh(features[featureBase + XYZ_START + component] + BASE_OFFSETS[component]) *
          OFFSET_AMPLITUDE *
          learnedOffsetScale
        positions[output3 + axis] = points[point * 3 + axis] + offset - 0.5
        sh0[output3 + axis] = features[featureBase + DC_START + component]

        const positiveScale = softplus(features[featureBase + SCALING_START + component] + SCALE_BIAS)
        scales[output3 + axis] = Math.sqrt(positiveScale * positiveScale + KERNEL_SIZE * KERNEL_SIZE)
      }

      for (let component = 0; component < 4; component += 1) {
        rotations[output4 + component] =
          features[featureBase + ROTATION_START + gaussian * 4 + component] * 0.1 + (component === 0 ? 1 : 0)
      }
      opacities[outputIndex] = sigmoid(features[featureBase + OPACITY_START + gaussian] + OPACITY_BIAS)
    }
  }

  return { positions, scales, rotations, sh0, opacities }
}