antimatter15 commited on
Commit
930af7c
1 Parent(s): 20ce3c7

initial commit

Browse files
Files changed (4) hide show
  1. .gitattributes +1 -0
  2. .gitignore +2 -0
  3. index.html +180 -0
  4. main.js +1176 -0
.gitattributes ADDED
@@ -0,0 +1 @@
 
 
1
+ *.{ply,splat} filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ .DS_Store
2
+
index.html ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" dir="ltr">
3
+ <head>
4
+ <title>WebGL Gaussian Splat Viewer</title>
5
+ <meta charset="utf-8" />
6
+ <meta
7
+ name="viewport"
8
+ content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no"
9
+ />
10
+ <meta name="apple-mobile-web-app-capable" content="yes" />
11
+ <meta
12
+ name="apple-mobile-web-app-status-bar-style"
13
+ content="black-translucent"
14
+ />
15
+ <style>
16
+ body {
17
+ overflow: hidden;
18
+ margin: 0;
19
+ height: 100vh;
20
+ width: 100vw;
21
+ touch-action: none;
22
+ font-family: sans-serif;
23
+ background: black;
24
+ text-shadow: 0 0 3px black;
25
+ }
26
+ a, body {
27
+ color: white;
28
+ }
29
+ #info {
30
+ z-index: 100;
31
+ position: absolute;
32
+ top: 10px;
33
+ left: 10px;
34
+ }
35
+ h3 {
36
+ margin: 5px 0;
37
+ }
38
+ p {
39
+ margin: 5px 0;
40
+ font-size: small;
41
+ }
42
+
43
+ .cube-wrapper {
44
+ transform-style: preserve-3d;
45
+ }
46
+
47
+ .cube {
48
+ transform-style: preserve-3d;
49
+ transform: rotateX(45deg) rotateZ(45deg);
50
+ animation: rotation 2s infinite;
51
+ }
52
+
53
+ .cube-faces {
54
+ transform-style: preserve-3d;
55
+ height: 80px;
56
+ width: 80px;
57
+ position: relative;
58
+ transform-origin: 0 0;
59
+ transform: translateX(0) translateY(0) translateZ(-40px);
60
+ }
61
+
62
+ .cube-face {
63
+ position: absolute;
64
+ inset: 0;
65
+ background: #0017ff;
66
+ border: solid 1px #ffffff;
67
+ }
68
+ .cube-face.top {
69
+ transform: translateZ(80px);
70
+ }
71
+ .cube-face.front {
72
+ transform-origin: 0 50%;
73
+ transform: rotateY(-90deg);
74
+ }
75
+ .cube-face.back {
76
+ transform-origin: 0 50%;
77
+ transform: rotateY(-90deg) translateZ(-80px);
78
+ }
79
+ .cube-face.right {
80
+ transform-origin: 50% 0;
81
+ transform: rotateX(-90deg) translateY(-80px);
82
+ }
83
+ .cube-face.left {
84
+ transform-origin: 50% 0;
85
+ transform: rotateX(-90deg) translateY(-80px) translateZ(80px);
86
+ }
87
+
88
+ @keyframes rotation {
89
+ 0% {
90
+ transform: rotateX(45deg) rotateY(0) rotateZ(45deg);
91
+ animation-timing-function: cubic-bezier(
92
+ 0.17,
93
+ 0.84,
94
+ 0.44,
95
+ 1
96
+ );
97
+ }
98
+ 50% {
99
+ transform: rotateX(45deg) rotateY(0) rotateZ(225deg);
100
+ animation-timing-function: cubic-bezier(
101
+ 0.76,
102
+ 0.05,
103
+ 0.86,
104
+ 0.06
105
+ );
106
+ }
107
+ 100% {
108
+ transform: rotateX(45deg) rotateY(0) rotateZ(405deg);
109
+ animation-timing-function: cubic-bezier(
110
+ 0.17,
111
+ 0.84,
112
+ 0.44,
113
+ 1
114
+ );
115
+ }
116
+ }
117
+
118
+ .scene,
119
+ #message {
120
+ position: absolute;
121
+ display: flex;
122
+ top: 0;
123
+ right: 0;
124
+ left: 0;
125
+ bottom: 0;
126
+ z-index: 2;
127
+ height: 100%;
128
+ width: 100%;
129
+ align-items: center;
130
+ justify-content: center;
131
+ }
132
+ #message {
133
+ font-weight: bold;
134
+ font-size: large;
135
+ color: red;
136
+ }
137
+
138
+ #progress {
139
+ position: absolute;
140
+ top: 0;
141
+ height: 5px;
142
+ background: blue;
143
+ z-index: 99;
144
+ transition: width 0.1s ease-in-out;
145
+ }
146
+ </style>
147
+ </head>
148
+ <body>
149
+ <div id="info">
150
+ <h3>WebGL 3D Gaussian Splat Viewer</h3>
151
+ <p>Use mouse or arrow keys to navigate.</p>
152
+ <small>
153
+ By <a href="https://twitter.com/antimatter15">Kevin Kwok</a>.
154
+ Code on
155
+ <a href="https://github.com/antimatter15/splat">Github</a
156
+ >.
157
+ </small>
158
+ </div>
159
+
160
+ <div id="progress"></div>
161
+
162
+ <div id="message"></div>
163
+ <div class="scene" id="spinner">
164
+ <div class="cube-wrapper">
165
+ <div class="cube">
166
+ <div class="cube-faces">
167
+ <div class="cube-face bottom"></div>
168
+ <div class="cube-face top"></div>
169
+ <div class="cube-face left"></div>
170
+ <div class="cube-face right"></div>
171
+ <div class="cube-face back"></div>
172
+ <div class="cube-face front"></div>
173
+ </div>
174
+ </div>
175
+ </div>
176
+ </div>
177
+
178
+ <script src="main.js"></script>
179
+ </body>
180
+ </html>
main.js ADDED
@@ -0,0 +1,1176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ let cameras = [
2
+ {
3
+ id: 0,
4
+ img_name: "00001",
5
+ width: 1959,
6
+ height: 1090,
7
+ position: [
8
+ -3.0089893469241797, -0.11086489695181866, -3.7527640949141428,
9
+ ],
10
+ rotation: [
11
+ [0.876134201218856, 0.06925962026449776, 0.47706599800804744],
12
+ [-0.04747421839895102, 0.9972110940209488, -0.057586739349882114],
13
+ [-0.4797239414934443, 0.027805376500959853, 0.8769787916452908],
14
+ ],
15
+ fy: 1164.6601287484507,
16
+ fx: 1159.5880733038064,
17
+ },
18
+ {
19
+ id: 1,
20
+ img_name: "00009",
21
+ width: 1959,
22
+ height: 1090,
23
+ position: [
24
+ -2.5199776022057296, -0.09704735754873686, -3.6247725540304545,
25
+ ],
26
+ rotation: [
27
+ [0.9982731285632193, -0.011928707708098955, -0.05751927260507243],
28
+ [0.0065061360949636325, 0.9955928229282383, -0.09355533724430458],
29
+ [0.058381769258182864, 0.09301955098900708, 0.9939511719154457],
30
+ ],
31
+ fy: 1164.6601287484507,
32
+ fx: 1159.5880733038064,
33
+ },
34
+ {
35
+ id: 2,
36
+ img_name: "00017",
37
+ width: 1959,
38
+ height: 1090,
39
+ position: [
40
+ -0.7737533667465242, -0.3364271945329695, -2.9358969417573753,
41
+ ],
42
+ rotation: [
43
+ [0.9998813418672372, 0.013742375651625236, -0.0069605529394208224],
44
+ [-0.014268370388586709, 0.996512943252834, -0.08220929105659476],
45
+ [0.00580653013657589, 0.08229885200307129, 0.9965907801935302],
46
+ ],
47
+ fy: 1164.6601287484507,
48
+ fx: 1159.5880733038064,
49
+ },
50
+ {
51
+ id: 3,
52
+ img_name: "00025",
53
+ width: 1959,
54
+ height: 1090,
55
+ position: [
56
+ 1.2198221749590001, -0.2196687861401182, -2.3183162007028453,
57
+ ],
58
+ rotation: [
59
+ [0.9208648867765482, 0.0012010625395201253, 0.389880004297208],
60
+ [-0.06298204172269357, 0.987319521752825, 0.14571693239364383],
61
+ [-0.3847611242348369, -0.1587410451475895, 0.9092635249821667],
62
+ ],
63
+ fy: 1164.6601287484507,
64
+ fx: 1159.5880733038064,
65
+ },
66
+ {
67
+ id: 4,
68
+ img_name: "00033",
69
+ width: 1959,
70
+ height: 1090,
71
+ position: [
72
+ 1.742387858893817, -0.13848225198886954, -2.0566370113193146,
73
+ ],
74
+ rotation: [
75
+ [0.24669889292141334, -0.08370189346592856, -0.9654706879349405],
76
+ [0.11343747891376445, 0.9919082664242816, -0.05700815184573074],
77
+ [0.9624300466054861, -0.09545671285663988, 0.2541976029815521],
78
+ ],
79
+ fy: 1164.6601287484507,
80
+ fx: 1159.5880733038064,
81
+ },
82
+ {
83
+ id: 5,
84
+ img_name: "00041",
85
+ width: 1959,
86
+ height: 1090,
87
+ position: [
88
+ 3.6567309419223935, -0.16470990600750707, -1.3458085590422042,
89
+ ],
90
+ rotation: [
91
+ [0.2341293058324528, -0.02968330457755884, -0.9717522161434825],
92
+ [0.10270823606832301, 0.99469554638321, -0.005638106875665722],
93
+ [0.9667649592295676, -0.09848690996657204, 0.2359360976431732],
94
+ ],
95
+ fy: 1164.6601287484507,
96
+ fx: 1159.5880733038064,
97
+ },
98
+ {
99
+ id: 6,
100
+ img_name: "00049",
101
+ width: 1959,
102
+ height: 1090,
103
+ position: [
104
+ 3.9013554243203497, -0.2597500978038105, -0.8106154188297828,
105
+ ],
106
+ rotation: [
107
+ [0.6717235545638952, -0.015718162115524837, -0.7406351366386528],
108
+ [0.055627354673906296, 0.9980224478387622, 0.029270992841185218],
109
+ [0.7387104058127439, -0.060861588786650656, 0.6712695459756353],
110
+ ],
111
+ fy: 1164.6601287484507,
112
+ fx: 1159.5880733038064,
113
+ },
114
+ {
115
+ id: 7,
116
+ img_name: "00057",
117
+ width: 1959,
118
+ height: 1090,
119
+ position: [4.742994605467533, -0.05591660945412069, 0.9500365976084458],
120
+ rotation: [
121
+ [-0.17042655709210375, 0.01207080756938, -0.9852964448542146],
122
+ [0.1165090336695526, 0.9931575292530063, -0.00798543433078162],
123
+ [0.9784581921120181, -0.1161568667478904, -0.1706667764862097],
124
+ ],
125
+ fy: 1164.6601287484507,
126
+ fx: 1159.5880733038064,
127
+ },
128
+ {
129
+ id: 8,
130
+ img_name: "00065",
131
+ width: 1959,
132
+ height: 1090,
133
+ position: [4.34676307626522, 0.08168160516967145, 1.0876221470355405],
134
+ rotation: [
135
+ [-0.003575447631888379, -0.044792503246552894, -0.9989899137764799],
136
+ [0.10770152645126597, 0.9931680875192705, -0.04491693593046672],
137
+ [0.9941768441149182, -0.10775333677534978, 0.0012732004866391048],
138
+ ],
139
+ fy: 1164.6601287484507,
140
+ fx: 1159.5880733038064,
141
+ },
142
+ {
143
+ id: 9,
144
+ img_name: "00073",
145
+ width: 1959,
146
+ height: 1090,
147
+ position: [3.264984351114202, 0.078974937336732, 1.0117200284114904],
148
+ rotation: [
149
+ [-0.026919994628162257, -0.1565891128261527, -0.9872968974090509],
150
+ [0.08444552208239385, 0.983768234577625, -0.1583319754069128],
151
+ [0.9960643893290491, -0.0876350978794554, -0.013259786205163005],
152
+ ],
153
+ fy: 1164.6601287484507,
154
+ fx: 1159.5880733038064,
155
+ },
156
+ ];
157
+
158
+ const camera = cameras[0];
159
+
160
+ function getProjectionMatrix(fx, fy, width, height) {
161
+ const znear = 0.2;
162
+ const zfar = 200;
163
+ return [
164
+ [(2 * fx) / width, 0, 0, 0],
165
+ [0, (2 * fy) / height, 0, 0],
166
+ [0, 0, zfar / (zfar - znear), 1],
167
+ [0, 0, -(zfar * znear) / (zfar - znear), 0],
168
+ ].flat();
169
+ }
170
+
171
+ function getViewMatrix(camera) {
172
+ const R = camera.rotation.flat();
173
+ const t = camera.position;
174
+ const camToWorld = [
175
+ [R[0], R[1], R[2], 0],
176
+ [R[3], R[4], R[5], 0],
177
+ [R[6], R[7], R[8], 0],
178
+ [
179
+ -t[0] * R[0] - t[1] * R[3] - t[2] * R[6],
180
+ -t[0] * R[1] - t[1] * R[4] - t[2] * R[7],
181
+ -t[0] * R[2] - t[1] * R[5] - t[2] * R[8],
182
+ 1,
183
+ ],
184
+ ].flat();
185
+ return camToWorld;
186
+ }
187
+
188
+ function multiply4(a, b) {
189
+ return [
190
+ b[0] * a[0] + b[1] * a[4] + b[2] * a[8] + b[3] * a[12],
191
+ b[0] * a[1] + b[1] * a[5] + b[2] * a[9] + b[3] * a[13],
192
+ b[0] * a[2] + b[1] * a[6] + b[2] * a[10] + b[3] * a[14],
193
+ b[0] * a[3] + b[1] * a[7] + b[2] * a[11] + b[3] * a[15],
194
+ b[4] * a[0] + b[5] * a[4] + b[6] * a[8] + b[7] * a[12],
195
+ b[4] * a[1] + b[5] * a[5] + b[6] * a[9] + b[7] * a[13],
196
+ b[4] * a[2] + b[5] * a[6] + b[6] * a[10] + b[7] * a[14],
197
+ b[4] * a[3] + b[5] * a[7] + b[6] * a[11] + b[7] * a[15],
198
+ b[8] * a[0] + b[9] * a[4] + b[10] * a[8] + b[11] * a[12],
199
+ b[8] * a[1] + b[9] * a[5] + b[10] * a[9] + b[11] * a[13],
200
+ b[8] * a[2] + b[9] * a[6] + b[10] * a[10] + b[11] * a[14],
201
+ b[8] * a[3] + b[9] * a[7] + b[10] * a[11] + b[11] * a[15],
202
+ b[12] * a[0] + b[13] * a[4] + b[14] * a[8] + b[15] * a[12],
203
+ b[12] * a[1] + b[13] * a[5] + b[14] * a[9] + b[15] * a[13],
204
+ b[12] * a[2] + b[13] * a[6] + b[14] * a[10] + b[15] * a[14],
205
+ b[12] * a[3] + b[13] * a[7] + b[14] * a[11] + b[15] * a[15],
206
+ ];
207
+ }
208
+
209
+ function invert4(a) {
210
+ let b00 = a[0] * a[5] - a[1] * a[4];
211
+ let b01 = a[0] * a[6] - a[2] * a[4];
212
+ let b02 = a[0] * a[7] - a[3] * a[4];
213
+ let b03 = a[1] * a[6] - a[2] * a[5];
214
+ let b04 = a[1] * a[7] - a[3] * a[5];
215
+ let b05 = a[2] * a[7] - a[3] * a[6];
216
+ let b06 = a[8] * a[13] - a[9] * a[12];
217
+ let b07 = a[8] * a[14] - a[10] * a[12];
218
+ let b08 = a[8] * a[15] - a[11] * a[12];
219
+ let b09 = a[9] * a[14] - a[10] * a[13];
220
+ let b10 = a[9] * a[15] - a[11] * a[13];
221
+ let b11 = a[10] * a[15] - a[11] * a[14];
222
+ let det =
223
+ b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
224
+ if (!det) return null;
225
+ return [
226
+ (a[5] * b11 - a[6] * b10 + a[7] * b09) / det,
227
+ (a[2] * b10 - a[1] * b11 - a[3] * b09) / det,
228
+ (a[13] * b05 - a[14] * b04 + a[15] * b03) / det,
229
+ (a[10] * b04 - a[9] * b05 - a[11] * b03) / det,
230
+ (a[6] * b08 - a[4] * b11 - a[7] * b07) / det,
231
+ (a[0] * b11 - a[2] * b08 + a[3] * b07) / det,
232
+ (a[14] * b02 - a[12] * b05 - a[15] * b01) / det,
233
+ (a[8] * b05 - a[10] * b02 + a[11] * b01) / det,
234
+ (a[4] * b10 - a[5] * b08 + a[7] * b06) / det,
235
+ (a[1] * b08 - a[0] * b10 - a[3] * b06) / det,
236
+ (a[12] * b04 - a[13] * b02 + a[15] * b00) / det,
237
+ (a[9] * b02 - a[8] * b04 - a[11] * b00) / det,
238
+ (a[5] * b07 - a[4] * b09 - a[6] * b06) / det,
239
+ (a[0] * b09 - a[1] * b07 + a[2] * b06) / det,
240
+ (a[13] * b01 - a[12] * b03 - a[14] * b00) / det,
241
+ (a[8] * b03 - a[9] * b01 + a[10] * b00) / det,
242
+ ];
243
+ }
244
+
245
+ function rotate4(a, rad, x, y, z) {
246
+ let len = Math.hypot(x, y, z);
247
+ x /= len;
248
+ y /= len;
249
+ z /= len;
250
+ let s = Math.sin(rad);
251
+ let c = Math.cos(rad);
252
+ let t = 1 - c;
253
+ let b00 = x * x * t + c;
254
+ let b01 = y * x * t + z * s;
255
+ let b02 = z * x * t - y * s;
256
+ let b10 = x * y * t - z * s;
257
+ let b11 = y * y * t + c;
258
+ let b12 = z * y * t + x * s;
259
+ let b20 = x * z * t + y * s;
260
+ let b21 = y * z * t - x * s;
261
+ let b22 = z * z * t + c;
262
+ return [
263
+ a[0] * b00 + a[4] * b01 + a[8] * b02,
264
+ a[1] * b00 + a[5] * b01 + a[9] * b02,
265
+ a[2] * b00 + a[6] * b01 + a[10] * b02,
266
+ a[3] * b00 + a[7] * b01 + a[11] * b02,
267
+ a[0] * b10 + a[4] * b11 + a[8] * b12,
268
+ a[1] * b10 + a[5] * b11 + a[9] * b12,
269
+ a[2] * b10 + a[6] * b11 + a[10] * b12,
270
+ a[3] * b10 + a[7] * b11 + a[11] * b12,
271
+ a[0] * b20 + a[4] * b21 + a[8] * b22,
272
+ a[1] * b20 + a[5] * b21 + a[9] * b22,
273
+ a[2] * b20 + a[6] * b21 + a[10] * b22,
274
+ a[3] * b20 + a[7] * b21 + a[11] * b22,
275
+ ...a.slice(12, 16),
276
+ ];
277
+ }
278
+
279
+ function translate4(a, x, y, z) {
280
+ return [
281
+ ...a.slice(0, 12),
282
+ a[0] * x + a[4] * y + a[8] * z + a[12],
283
+ a[1] * x + a[5] * y + a[9] * z + a[13],
284
+ a[2] * x + a[6] * y + a[10] * z + a[14],
285
+ a[3] * x + a[7] * y + a[11] * z + a[15],
286
+ ];
287
+ }
288
+
289
+ function createWorker(self) {
290
+ let buffer;
291
+ let vertexCount = 0;
292
+ let viewProj;
293
+ // 6*4 + 4 + 4 = 8*4
294
+ // XYZ - Position (Float32)
295
+ // XYZ - Scale (Float32)
296
+ // RGBA - colors (uint8)
297
+ // IJKL - quaternion/rot (uint8)
298
+ const rowLength = 3 * 4 + 3 * 4 + 4 + 4;
299
+
300
+ const runSort = (viewProj) => {
301
+ if (!buffer) return;
302
+
303
+ // console.time("sort");
304
+ const f_buffer = new Float32Array(buffer);
305
+ const u_buffer = new Uint8Array(buffer);
306
+
307
+ const depthList = new Float32Array(vertexCount);
308
+ const depthIndex = new Uint32Array(vertexCount);
309
+ for (let j = 0; j < vertexCount; j++) {
310
+ // For some reason dividing by wumbo actually causes
311
+ // problems, so this is the unnormalized camera space homo
312
+ depthList[j] =
313
+ viewProj[2] * f_buffer[8 * j + 0] +
314
+ viewProj[6] * f_buffer[8 * j + 1] +
315
+ viewProj[10] * f_buffer[8 * j + 2];
316
+ depthIndex[j] = j;
317
+ }
318
+ depthIndex.sort((a, b) => depthList[a] - depthList[b]);
319
+
320
+ const quat = new Float32Array(4 * vertexCount);
321
+ const scale = new Float32Array(3 * vertexCount);
322
+ const center = new Float32Array(3 * vertexCount);
323
+ const color = new Float32Array(4 * vertexCount);
324
+
325
+ for (let j = 0; j < vertexCount; j++) {
326
+ const i = depthIndex[j];
327
+
328
+ quat[4 * j + 0] = (u_buffer[32 * i + 28 + 0] - 128) / 128;
329
+ quat[4 * j + 1] = (u_buffer[32 * i + 28 + 1] - 128) / 128;
330
+ quat[4 * j + 2] = (u_buffer[32 * i + 28 + 2] - 128) / 128;
331
+ quat[4 * j + 3] = (u_buffer[32 * i + 28 + 3] - 128) / 128;
332
+
333
+ center[3 * j + 0] = f_buffer[8 * i + 0];
334
+ center[3 * j + 1] = f_buffer[8 * i + 1];
335
+ center[3 * j + 2] = f_buffer[8 * i + 2];
336
+
337
+ color[4 * j + 0] = u_buffer[32 * i + 24 + 0] / 255;
338
+ color[4 * j + 1] = u_buffer[32 * i + 24 + 1] / 255;
339
+ color[4 * j + 2] = u_buffer[32 * i + 24 + 2] / 255;
340
+ color[4 * j + 3] = u_buffer[32 * i + 24 + 3] / 255;
341
+
342
+ scale[3 * j + 0] = f_buffer[8 * i + 3 + 0];
343
+ scale[3 * j + 1] = f_buffer[8 * i + 3 + 1];
344
+ scale[3 * j + 2] = f_buffer[8 * i + 3 + 2];
345
+ }
346
+
347
+ self.postMessage({ quat, center, color, scale }, [
348
+ quat.buffer,
349
+ center.buffer,
350
+ color.buffer,
351
+ scale.buffer,
352
+ ]);
353
+
354
+ // console.timeEnd("sort");
355
+ };
356
+
357
+ function processPlyBuffer(inputBuffer) {
358
+ const ubuf = new Uint8Array(inputBuffer);
359
+ // 10KB ought to be enough for a header...
360
+ const header = new TextDecoder().decode(ubuf.slice(0, 1024 * 10));
361
+ const header_end = "end_header\n";
362
+ const header_end_index = header.indexOf(header_end);
363
+ if (header_end_index < 0)
364
+ throw new Error("Unable to read .ply file header");
365
+ const vertexCount = parseInt(/element vertex (\d+)\n/.exec(header)[1]);
366
+ console.log("Vertex Count", vertexCount);
367
+ let row_offset = 0,
368
+ offsets = {},
369
+ types = {};
370
+ const TYPE_MAP = {
371
+ double: "getFloat64",
372
+ int: "getInt32",
373
+ uint: "getUint32",
374
+ float: "getFloat32",
375
+ short: "getInt16",
376
+ ushort: "getUint16",
377
+ uchar: "getUint8",
378
+ };
379
+ for (let prop of header
380
+ .slice(0, header_end_index)
381
+ .split("\n")
382
+ .filter((k) => k.startsWith("property "))) {
383
+ const [p, type, name] = prop.split(" ");
384
+ const arrayType = TYPE_MAP[type] || "getInt8";
385
+ types[name] = arrayType;
386
+ offsets[name] = row_offset;
387
+ row_offset += parseInt(arrayType.replace(/[^\d]/g, "")) / 8;
388
+ }
389
+ console.log("Bytes per row", row_offset, types, offsets);
390
+
391
+ let dataView = new DataView(
392
+ inputBuffer,
393
+ header_end_index + header_end.length,
394
+ );
395
+ let row = 0;
396
+ const attrs = new Proxy(
397
+ {},
398
+ {
399
+ get(target, prop) {
400
+ if (!types[prop]) throw new Error(prop + " not found");
401
+ return dataView[types[prop]](
402
+ row * row_offset + offsets[prop],
403
+ true,
404
+ );
405
+ },
406
+ },
407
+ );
408
+
409
+ console.time("calculate importance");
410
+ let sizeList = new Float32Array(vertexCount);
411
+ let sizeIndex = new Uint32Array(vertexCount);
412
+ for (row = 0; row < vertexCount; row++) {
413
+ sizeIndex[row] = row;
414
+ if (!types["scale_0"]) continue;
415
+ const size =
416
+ Math.exp(attrs.scale_0) *
417
+ Math.exp(attrs.scale_1) *
418
+ Math.exp(attrs.scale_2);
419
+ const opacity = 1 / (1 + Math.exp(-attrs.opacity));
420
+ sizeList[row] = size * opacity;
421
+ }
422
+ console.timeEnd("calculate importance");
423
+
424
+ console.time("sort");
425
+ sizeIndex.sort((b, a) => sizeList[a] - sizeList[b]);
426
+ console.timeEnd("sort");
427
+
428
+ // 6*4 + 4 + 4 = 8*4
429
+ // XYZ - Position (Float32)
430
+ // XYZ - Scale (Float32)
431
+ // RGBA - colors (uint8)
432
+ // IJKL - quaternion/rot (uint8)
433
+ const rowLength = 3 * 4 + 3 * 4 + 4 + 4;
434
+ const buffer = new ArrayBuffer(rowLength * vertexCount);
435
+
436
+ console.time("build buffer");
437
+ for (let j = 0; j < vertexCount; j++) {
438
+ row = sizeIndex[j];
439
+
440
+ const position = new Float32Array(buffer, j * rowLength, 3);
441
+ const scales = new Float32Array(buffer, j * rowLength + 4 * 3, 3);
442
+ const rgba = new Uint8ClampedArray(
443
+ buffer,
444
+ j * rowLength + 4 * 3 + 4 * 3,
445
+ 4,
446
+ );
447
+ const rot = new Uint8ClampedArray(
448
+ buffer,
449
+ j * rowLength + 4 * 3 + 4 * 3 + 4,
450
+ 4,
451
+ );
452
+
453
+ if (types["scale_0"]) {
454
+ const qlen = Math.sqrt(
455
+ attrs.rot_0 ** 2 +
456
+ attrs.rot_1 ** 2 +
457
+ attrs.rot_2 ** 2 +
458
+ attrs.rot_3 ** 2,
459
+ );
460
+
461
+ rot[0] = (attrs.rot_0 / qlen) * 128 + 128;
462
+ rot[1] = (attrs.rot_1 / qlen) * 128 + 128;
463
+ rot[2] = (attrs.rot_2 / qlen) * 128 + 128;
464
+ rot[3] = (attrs.rot_3 / qlen) * 128 + 128;
465
+
466
+ scales[0] = Math.exp(attrs.scale_0);
467
+ scales[1] = Math.exp(attrs.scale_1);
468
+ scales[2] = Math.exp(attrs.scale_2);
469
+ } else {
470
+ scales[0] = 0.01;
471
+ scales[1] = 0.01;
472
+ scales[2] = 0.01;
473
+
474
+ rot[0] = 255;
475
+ rot[1] = 0;
476
+ rot[2] = 0;
477
+ rot[3] = 0;
478
+ }
479
+
480
+ position[0] = attrs.x;
481
+ position[1] = attrs.y;
482
+ position[2] = attrs.z;
483
+
484
+ if (types["f_dc_0"]) {
485
+ // rgba[0] = (1 / (1 + Math.exp(-attrs.f_dc_0))) * 255;
486
+ // rgba[1] = (1 / (1 + Math.exp(-attrs.f_dc_1))) * 255;
487
+ // rgba[2] = (1 / (1 + Math.exp(-attrs.f_dc_2))) * 255;
488
+ const SH_C0 = 0.28209479177387814;
489
+ rgba[0] = (0.5 + SH_C0 * attrs.f_dc_0) * 255;
490
+ rgba[1] = (0.5 + SH_C0 * attrs.f_dc_1) * 255;
491
+ rgba[2] = (0.5 + SH_C0 * attrs.f_dc_2) * 255;
492
+ } else {
493
+ rgba[0] = attrs.red;
494
+ rgba[1] = attrs.green;
495
+ rgba[2] = attrs.blue;
496
+ }
497
+ if (types["opacity"]) {
498
+ rgba[3] = (1 / (1 + Math.exp(-attrs.opacity))) * 255;
499
+ } else {
500
+ rgba[3] = 255;
501
+ }
502
+ }
503
+ console.timeEnd("build buffer");
504
+ return buffer;
505
+ }
506
+
507
+ const throttledSort = () => {
508
+ if (!sortRunning) {
509
+ sortRunning = true;
510
+ let lastView = viewProj;
511
+ runSort(lastView);
512
+ setTimeout(() => {
513
+ sortRunning = false;
514
+ if (lastView !== viewProj) {
515
+ throttledSort();
516
+ }
517
+ }, 0);
518
+ }
519
+ };
520
+
521
+ let sortRunning;
522
+ self.onmessage = (e) => {
523
+ if (e.data.ply) {
524
+ vertexCount = 0;
525
+ runSort(viewProj);
526
+ buffer = processPlyBuffer(e.data.ply);
527
+ vertexCount = Math.floor(buffer.byteLength / rowLength);
528
+ postMessage({ buffer: buffer });
529
+ } else if (e.data.buffer) {
530
+ buffer = e.data.buffer;
531
+ vertexCount = e.data.vertexCount;
532
+ } else if (e.data.vertexCount) {
533
+ vertexCount = e.data.vertexCount;
534
+ } else if (e.data.view) {
535
+ viewProj = e.data.view;
536
+ throttledSort();
537
+ }
538
+ };
539
+ }
540
+
541
+ const vertexShaderSource = `
542
+ precision mediump float;
543
+ attribute vec2 position;
544
+
545
+ attribute vec4 color;
546
+ attribute vec4 quat;
547
+ attribute vec3 scale;
548
+ attribute vec3 center;
549
+
550
+ uniform mat4 projection, view;
551
+ uniform vec2 focal;
552
+
553
+ varying vec4 vColor;
554
+ varying vec3 vConic;
555
+ varying vec2 vCenter;
556
+ varying vec2 vPosition;
557
+ uniform vec2 viewport;
558
+
559
+ mat3 transpose(mat3 m) {
560
+ return mat3(
561
+ m[0][0], m[1][0], m[2][0],
562
+ m[0][1], m[1][1], m[2][1],
563
+ m[0][2], m[1][2], m[2][2]
564
+ );
565
+ }
566
+
567
+
568
+ mat3 compute_cov3d(vec3 scale, vec4 rot) {
569
+ mat3 S = mat3(
570
+ scale.x, 0.0, 0.0,
571
+ 0.0, scale.y, 0.0,
572
+ 0.0, 0.0, scale.z
573
+ );
574
+ mat3 R = mat3(
575
+ 1.0 - 2.0 * (rot.z * rot.z + rot.w * rot.w), 2.0 * (rot.y * rot.z - rot.x * rot.w), 2.0 * (rot.y * rot.w + rot.x * rot.z),
576
+ 2.0 * (rot.y * rot.z + rot.x * rot.w), 1.0 - 2.0 * (rot.y * rot.y + rot.w * rot.w), 2.0 * (rot.z * rot.w - rot.x * rot.y),
577
+ 2.0 * (rot.y * rot.w - rot.x * rot.z), 2.0 * (rot.z * rot.w + rot.x * rot.y), 1.0 - 2.0 * (rot.y * rot.y + rot.z * rot.z)
578
+ );
579
+ mat3 M = S * R;
580
+ return transpose(M) * M;
581
+ }
582
+
583
+ vec3 compute_cov2d(vec3 center, vec3 scale, vec4 rot){
584
+ mat3 Vrk = compute_cov3d(scale, rot);
585
+ vec4 t = view * vec4(center, 1.0);
586
+ vec2 lims = 1.3 * 0.5 * viewport / focal;
587
+ t.xy = min(lims, max(-lims, t.xy / t.z)) * t.z;
588
+ mat3 J = mat3(
589
+ focal.x / t.z, 0., -(focal.x * t.x) / (t.z * t.z),
590
+ 0., focal.y / t.z, -(focal.y * t.y) / (t.z * t.z),
591
+ 0., 0., 0.
592
+ );
593
+ mat3 W = transpose(mat3(view));
594
+ mat3 T = W * J;
595
+ mat3 cov = transpose(T) * transpose(Vrk) * T;
596
+ return vec3(cov[0][0] + 0.3, cov[0][1], cov[1][1] + 0.3);
597
+ }
598
+
599
+ void main () {
600
+ vec4 camspace = view * vec4(center, 1);
601
+ vec4 pos2d = projection * mat4(1,0,0,0,0,-1,0,0,0,0,1,0,0,0,0,1) * camspace;
602
+
603
+ vec3 cov2d = compute_cov2d(center, scale, quat);
604
+ float det = cov2d.x * cov2d.z - cov2d.y * cov2d.y;
605
+ vec3 conic = vec3(cov2d.z, cov2d.y, cov2d.x) / det;
606
+ float mid = 0.5 * (cov2d.x + cov2d.z);
607
+ float lambda1 = mid + sqrt(max(0.1, mid * mid - det));
608
+ float lambda2 = mid - sqrt(max(0.1, mid * mid - det));
609
+ vec2 v1 = 7.0 * sqrt(lambda1) * normalize(vec2(cov2d.y, lambda1 - cov2d.x));
610
+ vec2 v2 = 7.0 * sqrt(lambda2) * normalize(vec2(-(lambda1 - cov2d.x),cov2d.y));
611
+
612
+ vColor = color;
613
+ vConic = conic;
614
+ vCenter = vec2(pos2d) / pos2d.w;
615
+
616
+ vPosition = vec2(vCenter + position.x * (position.y < 0.0 ? v1 : v2) / viewport);
617
+ gl_Position = vec4(vPosition, pos2d.z / pos2d.w, 1);
618
+
619
+ }
620
+ `;
621
+
622
+ const fragmentShaderSource = `
623
+ precision mediump float;
624
+
625
+ varying vec4 vColor;
626
+ varying vec3 vConic;
627
+ varying vec2 vCenter;
628
+ uniform vec2 viewport;
629
+ uniform vec2 focal;
630
+
631
+ void main () {
632
+ vec2 d = (vCenter - 2.0 * (gl_FragCoord.xy/viewport - vec2(0.5, 0.5))) * viewport * 0.5;
633
+ float power = -0.5 * (vConic.x * d.x * d.x + vConic.z * d.y * d.y) - vConic.y * d.x * d.y;
634
+ if (power > 0.0) discard;
635
+ float alpha = min(0.99, vColor.a * exp(power));
636
+ if(alpha < 0.02) discard;
637
+
638
+ gl_FragColor = vec4(alpha * vColor.rgb, alpha);
639
+ }
640
+ `;
641
+
642
+ // let viewMatrix = getViewMatrix(camera);
643
+ let defaultViewMatrix = [
644
+ 0.47, 0.04, 0.88, 0, -0.11, 0.99, 0.02, 0, -0.88, -0.11, 0.47, 0, 0.07,
645
+ 0.03, 6.55, 1,
646
+ ];
647
+ let viewMatrix = defaultViewMatrix;
648
+
649
+ async function main() {
650
+ let carousel = true;
651
+ const params = new URLSearchParams(location.search);
652
+ try {
653
+ viewMatrix = JSON.parse(location.hash.slice(1));
654
+ carousel = false;
655
+ } catch (err) {}
656
+ const url = new URL(
657
+ params.get("url") || "train.splat",
658
+ "https://antimatter15.com/splat-data/",
659
+ );
660
+ const req = await fetch(url, {
661
+ mode: "cors", // no-cors, *cors, same-origin
662
+ credentials: "omit", // include, *same-origin, omit
663
+ });
664
+ console.log(req);
665
+ if (req.status != 200)
666
+ throw new Error("Unable to load " + req.url + ": " + req.statusText);
667
+
668
+ const rowLength = 3 * 4 + 3 * 4 + 4 + 4;
669
+ const reader = req.body.getReader();
670
+ let splatData = new Uint8Array(req.headers.get("content-length"));
671
+
672
+ const worker = new Worker(
673
+ URL.createObjectURL(
674
+ new Blob(["(", createWorker.toString(), ")(self)"], {
675
+ type: "application/javascript",
676
+ }),
677
+ ),
678
+ );
679
+
680
+ const canvas = document.createElement("canvas");
681
+ canvas.width = innerWidth / 2;
682
+ canvas.height = innerHeight / 2;
683
+ canvas.style.position = "absolute";
684
+ canvas.style.top = 0;
685
+ canvas.style.left = 0;
686
+ canvas.style.width = "100%";
687
+ canvas.style.height = "100%";
688
+ document.body.appendChild(canvas);
689
+
690
+ const fps = document.createElement("div");
691
+ fps.style.position = "absolute";
692
+ fps.style.bottom = "10px";
693
+ fps.style.right = "10px";
694
+ document.body.appendChild(fps);
695
+
696
+ let projectionMatrix = getProjectionMatrix(
697
+ camera.fx / 2,
698
+ camera.fy / 2,
699
+ canvas.width,
700
+ canvas.height,
701
+ );
702
+
703
+ const gl = canvas.getContext("webgl");
704
+ const ext = gl.getExtension("ANGLE_instanced_arrays");
705
+
706
+ const vertexShader = gl.createShader(gl.VERTEX_SHADER);
707
+ gl.shaderSource(vertexShader, vertexShaderSource);
708
+ gl.compileShader(vertexShader);
709
+ if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS))
710
+ console.error(gl.getShaderInfoLog(vertexShader));
711
+
712
+ const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
713
+ gl.shaderSource(fragmentShader, fragmentShaderSource);
714
+ gl.compileShader(fragmentShader);
715
+ if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS))
716
+ console.error(gl.getShaderInfoLog(fragmentShader));
717
+
718
+ const program = gl.createProgram();
719
+ gl.attachShader(program, vertexShader);
720
+ gl.attachShader(program, fragmentShader);
721
+ gl.linkProgram(program);
722
+ gl.useProgram(program);
723
+
724
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS))
725
+ console.error(gl.getProgramInfoLog(program));
726
+
727
+ gl.disable(gl.DEPTH_TEST); // Disable depth testing
728
+
729
+ // Enable blending
730
+ gl.enable(gl.BLEND);
731
+
732
+ // Set blending function
733
+ gl.blendFuncSeparate(
734
+ gl.ONE_MINUS_DST_ALPHA,
735
+ gl.ONE,
736
+ gl.ONE_MINUS_DST_ALPHA,
737
+ gl.ONE,
738
+ );
739
+
740
+ // Set blending equation
741
+ gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
742
+
743
+ // projection
744
+ const u_projection = gl.getUniformLocation(program, "projection");
745
+ gl.uniformMatrix4fv(u_projection, false, projectionMatrix);
746
+
747
+ // viewport
748
+ const u_viewport = gl.getUniformLocation(program, "viewport");
749
+ gl.uniform2fv(u_viewport, new Float32Array([canvas.width, canvas.height]));
750
+
751
+ // focal
752
+ const u_focal = gl.getUniformLocation(program, "focal");
753
+ gl.uniform2fv(u_focal, new Float32Array([camera.fx / 2, camera.fy / 2]));
754
+
755
+ // view
756
+ const u_view = gl.getUniformLocation(program, "view");
757
+ gl.uniformMatrix4fv(u_view, false, viewMatrix);
758
+
759
+ // positions
760
+ const triangleVertices = new Float32Array([1, -1, 1, 1, -1, 1, -1, -1]);
761
+ const vertexBuffer = gl.createBuffer();
762
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
763
+ gl.bufferData(gl.ARRAY_BUFFER, triangleVertices, gl.STATIC_DRAW);
764
+ const a_position = gl.getAttribLocation(program, "position");
765
+ gl.enableVertexAttribArray(a_position);
766
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
767
+ gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0);
768
+
769
+ // center
770
+ const centerBuffer = gl.createBuffer();
771
+ // gl.bindBuffer(gl.ARRAY_BUFFER, centerBuffer);
772
+ // gl.bufferData(gl.ARRAY_BUFFER, center, gl.STATIC_DRAW);
773
+ const a_center = gl.getAttribLocation(program, "center");
774
+ gl.enableVertexAttribArray(a_center);
775
+ gl.bindBuffer(gl.ARRAY_BUFFER, centerBuffer);
776
+ gl.vertexAttribPointer(a_center, 3, gl.FLOAT, false, 0, 0);
777
+ ext.vertexAttribDivisorANGLE(a_center, 1); // Use the extension here
778
+
779
+ // color
780
+ const colorBuffer = gl.createBuffer();
781
+ // gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
782
+ // gl.bufferData(gl.ARRAY_BUFFER, color, gl.STATIC_DRAW);
783
+ const a_color = gl.getAttribLocation(program, "color");
784
+ gl.enableVertexAttribArray(a_color);
785
+ gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
786
+ gl.vertexAttribPointer(a_color, 4, gl.FLOAT, false, 0, 0);
787
+ ext.vertexAttribDivisorANGLE(a_color, 1); // Use the extension here
788
+
789
+ // quat
790
+ const quatBuffer = gl.createBuffer();
791
+ // gl.bindBuffer(gl.ARRAY_BUFFER, quatBuffer);
792
+ // gl.bufferData(gl.ARRAY_BUFFER, quat, gl.STATIC_DRAW);
793
+ const a_quat = gl.getAttribLocation(program, "quat");
794
+ gl.enableVertexAttribArray(a_quat);
795
+ gl.bindBuffer(gl.ARRAY_BUFFER, quatBuffer);
796
+ gl.vertexAttribPointer(a_quat, 4, gl.FLOAT, false, 0, 0);
797
+ ext.vertexAttribDivisorANGLE(a_quat, 1); // Use the extension here
798
+
799
+ // scale
800
+ const scaleBuffer = gl.createBuffer();
801
+ // gl.bindBuffer(gl.ARRAY_BUFFER, scaleBuffer);
802
+ // gl.bufferData(gl.ARRAY_BUFFER, scale, gl.STATIC_DRAW);
803
+ const a_scale = gl.getAttribLocation(program, "scale");
804
+ gl.enableVertexAttribArray(a_scale);
805
+ gl.bindBuffer(gl.ARRAY_BUFFER, scaleBuffer);
806
+ gl.vertexAttribPointer(a_scale, 3, gl.FLOAT, false, 0, 0);
807
+ ext.vertexAttribDivisorANGLE(a_scale, 1); // Use the extension here
808
+
809
+ worker.onmessage = (e) => {
810
+ if (e.data.buffer) {
811
+ splatData = new Uint8Array(e.data.buffer);
812
+ const blob = new Blob([splatData.buffer], {
813
+ type: "application/octet-stream",
814
+ });
815
+ const link = document.createElement("a");
816
+ link.download = "model.splat";
817
+ link.href = URL.createObjectURL(blob);
818
+ document.body.appendChild(link);
819
+ link.click();
820
+ } else {
821
+ let { quat, scale, center, color } = e.data;
822
+ vertexCount = quat.length / 4;
823
+
824
+ gl.bindBuffer(gl.ARRAY_BUFFER, centerBuffer);
825
+ gl.bufferData(gl.ARRAY_BUFFER, center, gl.STATIC_DRAW);
826
+
827
+ gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
828
+ gl.bufferData(gl.ARRAY_BUFFER, color, gl.STATIC_DRAW);
829
+
830
+ gl.bindBuffer(gl.ARRAY_BUFFER, quatBuffer);
831
+ gl.bufferData(gl.ARRAY_BUFFER, quat, gl.STATIC_DRAW);
832
+
833
+ gl.bindBuffer(gl.ARRAY_BUFFER, scaleBuffer);
834
+ gl.bufferData(gl.ARRAY_BUFFER, scale, gl.STATIC_DRAW);
835
+ }
836
+ };
837
+
838
+ let activeKeys = [];
839
+
840
+ window.addEventListener("keydown", (e) => {
841
+ carousel = false;
842
+ if (!activeKeys.includes(e.key)) activeKeys.push(e.key);
843
+ if (/\d/.test(e.key)) {
844
+ viewMatrix = getViewMatrix(cameras[parseInt(e.key)]);
845
+ }
846
+ if (e.key == "v") {
847
+ location.hash =
848
+ "#" +
849
+ JSON.stringify(
850
+ viewMatrix.map((k) => Math.round(k * 100) / 100),
851
+ );
852
+ } else if (e.key === "p") {
853
+ carousel = true;
854
+ }
855
+ });
856
+ window.addEventListener("keyup", (e) => {
857
+ activeKeys = activeKeys.filter((k) => k !== e.key);
858
+ });
859
+
860
+ window.addEventListener(
861
+ "wheel",
862
+ (e) => {
863
+ carousel = false;
864
+ e.preventDefault();
865
+ const lineHeight = 10;
866
+ const scale =
867
+ e.deltaMode == 1
868
+ ? lineHeight
869
+ : e.deltaMode == 2
870
+ ? innerHeight
871
+ : 1;
872
+ const dy = e.deltaY * scale;
873
+ let inv = invert4(viewMatrix);
874
+ inv = translate4(inv, 0, 0, (-5 * dy) / innerHeight);
875
+ viewMatrix = invert4(inv);
876
+ },
877
+ { passive: false },
878
+ );
879
+
880
+ let startX, startY, down;
881
+ document.addEventListener("mousedown", (e) => {
882
+ carousel = false;
883
+ e.preventDefault();
884
+ startX = e.clientX;
885
+ startY = e.clientY;
886
+ down = 1;
887
+ });
888
+ document.addEventListener("contextmenu", (e) => {
889
+ carousel = false;
890
+ e.preventDefault();
891
+ startX = e.clientX;
892
+ startY = e.clientY;
893
+ down = 2;
894
+ });
895
+ document.addEventListener("mousemove", (e) => {
896
+ e.preventDefault();
897
+ if (down == 1) {
898
+ let inv = invert4(viewMatrix);
899
+ inv = rotate4(
900
+ inv,
901
+ (-2 * (e.clientX - startX)) / innerWidth,
902
+ 0,
903
+ 1,
904
+ 0,
905
+ );
906
+ inv = translate4(
907
+ inv,
908
+ (5 * (e.clientX - startX)) / innerWidth,
909
+ 0,
910
+ 0,
911
+ );
912
+ inv = translate4(
913
+ inv,
914
+ 0,
915
+ 0,
916
+ (10 * (e.clientY - startY)) / innerHeight,
917
+ );
918
+ viewMatrix = invert4(inv);
919
+
920
+ startX = e.clientX;
921
+ startY = e.clientY;
922
+ } else if (down == 2) {
923
+ let inv = invert4(viewMatrix);
924
+ // inv = rotateY(inv, );
925
+ inv = translate4(
926
+ inv,
927
+ (-10 * (e.clientX - startX)) / innerWidth,
928
+ (-10 * (e.clientY - startY)) / innerHeight,
929
+ 0,
930
+ );
931
+ viewMatrix = invert4(inv);
932
+
933
+ startX = e.clientX;
934
+ startY = e.clientY;
935
+ }
936
+ });
937
+ document.addEventListener("mouseup", (e) => {
938
+ e.preventDefault();
939
+ down = false;
940
+ startX = 0;
941
+ startY = 0;
942
+ });
943
+
944
+ document.addEventListener(
945
+ "touchstart",
946
+ (e) => {
947
+ e.preventDefault();
948
+ if (e.touches.length === 1) {
949
+ carousel = false;
950
+ startX = e.touches[0].clientX;
951
+ startY = e.touches[0].clientY;
952
+ down = 1;
953
+ }
954
+ },
955
+ { passive: false },
956
+ );
957
+ document.addEventListener(
958
+ "touchmove",
959
+ (e) => {
960
+ e.preventDefault();
961
+ if (e.touches.length === 1 && down) {
962
+ let inv = invert4(viewMatrix);
963
+ inv = rotate4(
964
+ inv,
965
+ (-0.6 * (e.touches[0].clientX - startX)) / innerWidth,
966
+ 0,
967
+ 1,
968
+ 0,
969
+ );
970
+ inv = translate4(
971
+ inv,
972
+ 0,
973
+ 0,
974
+ (25 * (e.touches[0].clientY - startY)) / innerHeight,
975
+ );
976
+ viewMatrix = invert4(inv);
977
+
978
+ startX = e.touches[0].clientX;
979
+ startY = e.touches[0].clientY;
980
+ }
981
+ },
982
+ { passive: false },
983
+ );
984
+ document.addEventListener(
985
+ "touchend",
986
+ (e) => {
987
+ e.preventDefault();
988
+ down = false;
989
+ startX = 0;
990
+ startY = 0;
991
+ },
992
+ { passive: false },
993
+ );
994
+
995
+ let jumpDelta = 0;
996
+ let vertexCount = 0;
997
+
998
+ let lastFrame = 0;
999
+ let avgFps = 0;
1000
+ let start = Date.now() + 2000;
1001
+ const frame = (now) => {
1002
+ let inv = invert4(viewMatrix);
1003
+ // let preY = inv[13];
1004
+ if (activeKeys.includes("ArrowUp")) inv = translate4(inv, 0, 0, 0.1);
1005
+ if (activeKeys.includes("ArrowDown")) inv = translate4(inv, 0, 0, -0.1);
1006
+ if (activeKeys.includes("ArrowLeft"))
1007
+ inv = rotate4(inv, -0.01, 0, 1, 0);
1008
+ if (activeKeys.includes("ArrowRight"))
1009
+ inv = rotate4(inv, 0.01, 0, 1, 0);
1010
+ if (activeKeys.includes("a")) inv = translate4(inv, -0.02, 0, 0);
1011
+ if (activeKeys.includes("d")) inv = translate4(inv, 0.02, 0, 0);
1012
+ if (activeKeys.includes("q")) inv = rotate4(inv, 0.01, 0, 0, 1);
1013
+ if (activeKeys.includes("e")) inv = rotate4(inv, -0.01, 0, 0, 1);
1014
+ if (activeKeys.includes("w")) inv = rotate4(inv, 0.005, 1, 0, 0);
1015
+ if (activeKeys.includes("s")) inv = rotate4(inv, -0.005, 1, 0, 0);
1016
+ // inv[13] = preY;
1017
+ viewMatrix = invert4(inv);
1018
+
1019
+ if (carousel) {
1020
+ let inv = invert4(defaultViewMatrix);
1021
+
1022
+ const t = Math.sin((Date.now() - start) / 5000);
1023
+ inv = translate4(inv, 2.5 * t, 0, 10 * (1 - Math.cos(t)));
1024
+ inv = rotate4(inv, -0.4 * t, 0, 1, 0);
1025
+
1026
+ viewMatrix = invert4(inv);
1027
+ }
1028
+
1029
+ if (activeKeys.includes(" ")) {
1030
+ jumpDelta = Math.min(1, jumpDelta + 0.05);
1031
+ } else {
1032
+ jumpDelta = Math.max(0, jumpDelta - 0.05);
1033
+ }
1034
+
1035
+ let inv2 = invert4(viewMatrix);
1036
+ inv2[13] -= jumpDelta;
1037
+ inv2 = rotate4(inv2, -0.2 * jumpDelta, 1, 0, 0);
1038
+ let actualViewMatrix = invert4(inv2);
1039
+
1040
+ const viewProj = multiply4(projectionMatrix, actualViewMatrix);
1041
+ worker.postMessage({ view: viewProj });
1042
+
1043
+ const currentFps = 1000 / (now - lastFrame) || 0;
1044
+ avgFps = avgFps * 0.9 + currentFps * 0.1;
1045
+
1046
+ if (vertexCount > 0) {
1047
+ document.getElementById("spinner").style.display = "none";
1048
+
1049
+ gl.uniformMatrix4fv(u_view, false, actualViewMatrix);
1050
+
1051
+ // gl.clearColor(0.0, 0.0, 0.0, 1.0); // Set the clear color to black with full opacity
1052
+ // gl.clear(gl.COLOR_BUFFER_BIT);
1053
+
1054
+ // gl.clearDepth(100000.0);
1055
+ // gl.clear(gl.DEPTH_BUFFER_BIT);
1056
+
1057
+ ext.drawArraysInstancedANGLE(gl.TRIANGLE_STRIP, 0, 4, vertexCount);
1058
+ } else {
1059
+ gl.clear(gl.COLOR_BUFFER_BIT);
1060
+ document.getElementById("spinner").style.display = "";
1061
+ }
1062
+ const progress = (100 * vertexCount) / (splatData.length / rowLength);
1063
+ if (progress < 100) {
1064
+ document.getElementById("progress").style.width = progress + "%";
1065
+ } else {
1066
+ document.getElementById("progress").style.display = "none";
1067
+ }
1068
+ fps.innerText =
1069
+ Math.round(avgFps) +
1070
+ " fps";
1071
+ lastFrame = now;
1072
+ requestAnimationFrame(frame);
1073
+ };
1074
+
1075
+ frame();
1076
+
1077
+ const selectFile = (file) => {
1078
+ const fr = new FileReader();
1079
+ if (/\.json$/i.test(file.name)) {
1080
+ fr.onload = () => {
1081
+ cameras = JSON.parse(fr.result);
1082
+ viewMatrix = getViewMatrix(cameras[0]);
1083
+ projectionMatrix = getProjectionMatrix(
1084
+ camera.fx / 2,
1085
+ camera.fy / 2,
1086
+ canvas.width,
1087
+ canvas.height,
1088
+ );
1089
+ gl.uniformMatrix4fv(u_projection, false, projectionMatrix);
1090
+
1091
+ console.log("Loaded Cameras");
1092
+ };
1093
+ fr.readAsText(file);
1094
+ } else {
1095
+ stopLoading = true;
1096
+ fr.onload = () => {
1097
+ splatData = new Uint8Array(fr.result);
1098
+ console.log("Loaded", Math.floor(splatData.length / rowLength));
1099
+
1100
+ if (
1101
+ splatData[0] == 112 &&
1102
+ splatData[1] == 108 &&
1103
+ splatData[2] == 121 &&
1104
+ splatData[3] == 10
1105
+ ) {
1106
+ // ply file magic header means it should be handled differently
1107
+ worker.postMessage({ ply: splatData.buffer });
1108
+ } else {
1109
+ worker.postMessage({
1110
+ buffer: splatData.buffer,
1111
+ vertexCount: Math.floor(splatData.length / rowLength),
1112
+ });
1113
+ }
1114
+ };
1115
+ fr.readAsArrayBuffer(file);
1116
+ }
1117
+ };
1118
+
1119
+ window.addEventListener("hashchange", (e) => {
1120
+ try {
1121
+ viewMatrix = JSON.parse(location.hash.slice(1));
1122
+ carousel = false;
1123
+ } catch (err) {}
1124
+ });
1125
+
1126
+ document.addEventListener("dragenter", (e) => {
1127
+ e.preventDefault();
1128
+ e.stopPropagation();
1129
+ });
1130
+
1131
+ document.addEventListener("dragover", (e) => {
1132
+ e.preventDefault();
1133
+ e.stopPropagation();
1134
+ });
1135
+
1136
+ document.addEventListener("dragleave", (e) => {
1137
+ e.preventDefault();
1138
+ e.stopPropagation();
1139
+ });
1140
+
1141
+ document.addEventListener("drop", (e) => {
1142
+ e.preventDefault();
1143
+ e.stopPropagation();
1144
+ selectFile(e.dataTransfer.files[0]);
1145
+ });
1146
+
1147
+ let bytesRead = 0;
1148
+ let lastVertexCount = -1;
1149
+ let stopLoading = false;
1150
+
1151
+ while (true) {
1152
+ const { done, value } = await reader.read();
1153
+ if (done || stopLoading) break;
1154
+
1155
+ splatData.set(value, bytesRead);
1156
+ bytesRead += value.length;
1157
+
1158
+ if (vertexCount > lastVertexCount) {
1159
+ worker.postMessage({
1160
+ buffer: splatData.buffer,
1161
+ vertexCount: Math.floor(bytesRead / rowLength),
1162
+ });
1163
+ lastVertexCount = vertexCount;
1164
+ }
1165
+ }
1166
+ if (!stopLoading)
1167
+ worker.postMessage({
1168
+ buffer: splatData.buffer,
1169
+ vertexCount: Math.floor(bytesRead / rowLength),
1170
+ });
1171
+ }
1172
+
1173
+ main().catch((err) => {
1174
+ document.getElementById("spinner").style.display = "none";
1175
+ document.getElementById("message").innerText = err.toString();
1176
+ });