dylanebert HF staff commited on
Commit
ec3121c
·
1 Parent(s): cc654e9

working partial refactor

Browse files
viewer/src/lib/splat.js/cameras/Camera.ts CHANGED
@@ -1,18 +1,46 @@
1
  import { Object3D } from "../core/Object3D";
 
2
  import { Matrix4 } from "../math/Matrix4";
 
3
  import { Vector3 } from "../math/Vector3";
4
 
5
  class Camera extends Object3D {
 
 
 
 
 
 
6
  projectionMatrix: Matrix4;
7
 
8
- constructor(position: Vector3 = new Vector3(0, 0, -5)) {
 
 
 
 
 
 
 
9
  super();
10
 
11
  this.position = position;
 
 
 
 
 
12
  this.projectionMatrix = new Matrix4();
13
  }
14
 
15
- updateProjectionMatrix(width: number, height: number): void {}
 
 
 
 
 
 
 
 
16
  }
17
 
18
  export { Camera };
 
1
  import { Object3D } from "../core/Object3D";
2
+ import { Matrix3 } from "../math/Matrix3";
3
  import { Matrix4 } from "../math/Matrix4";
4
+ import { Quaternion } from "../math/Quaternion";
5
  import { Vector3 } from "../math/Vector3";
6
 
7
  class Camera extends Object3D {
8
+ fx: number;
9
+ fy: number;
10
+
11
+ near: number;
12
+ far: number;
13
+
14
  projectionMatrix: Matrix4;
15
 
16
+ constructor(
17
+ position: Vector3 = new Vector3(0, 0, -5),
18
+ rotation: Matrix3 = new Matrix3(),
19
+ fx: number = 1132,
20
+ fy: number = 1132,
21
+ near: number = 0.1,
22
+ far: number = 100
23
+ ) {
24
  super();
25
 
26
  this.position = position;
27
+ this.rotation = rotation;
28
+ this.fx = fx;
29
+ this.fy = fy;
30
+ this.near = near;
31
+ this.far = far;
32
  this.projectionMatrix = new Matrix4();
33
  }
34
 
35
+ updateProjectionMatrix(width: number, height: number): void {
36
+ // prettier-ignore
37
+ this.projectionMatrix.set(
38
+ 2 * this.fx / width, 0, 0, 0,
39
+ 0, -2 * this.fy / height, 0, 0,
40
+ 0, 0, this.far / (this.far - this.near), 1,
41
+ 0, 0, -(this.far * this.near) / (this.far - this.near), 0
42
+ );
43
+ }
44
  }
45
 
46
  export { Camera };
viewer/src/lib/splat.js/cameras/PerspectiveCamera.ts DELETED
@@ -1,30 +0,0 @@
1
- import { Camera } from "./Camera";
2
-
3
- class PerspectiveCamera extends Camera {
4
- fx: number;
5
- fy: number;
6
-
7
- near: number;
8
- far: number;
9
-
10
- constructor(fx: number = 1132, fy: number = 1132, near: number = 0.1, far: number = 100) {
11
- super();
12
-
13
- this.fx = fx;
14
- this.fy = fy;
15
- this.near = near;
16
- this.far = far;
17
- }
18
-
19
- updateProjectionMatrix(width: number, height: number): void {
20
- // prettier-ignore
21
- this.projectionMatrix.set(
22
- 2 * this.fx / width, 0, 0, 0,
23
- 0, -2 * this.fy / height, 0, 0,
24
- 0, 0, this.far / (this.far - this.near), 1,
25
- 0, 0, -this.near * this.far / (this.far - this.near), 0
26
- );
27
- }
28
- }
29
-
30
- export { PerspectiveCamera };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
viewer/src/lib/splat.js/controls/OrbitControls.ts ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Camera } from "../cameras/Camera";
2
+ import { EventDispatcher } from "../core/EventDispatcher";
3
+ import { Matrix3 } from "../math/Matrix3";
4
+ import { Quaternion } from "../math/Quaternion";
5
+ import { Vector3 } from "../math/Vector3";
6
+
7
+ class OrbitControls extends EventDispatcher {
8
+ camera: Camera;
9
+ domElement: HTMLElement;
10
+
11
+ target: Vector3 = new Vector3();
12
+ alpha: number = 0;
13
+ beta: number = 0;
14
+ radius: number = 5;
15
+
16
+ desiredTarget: Vector3;
17
+ desiredAlpha: number;
18
+ desiredBeta: number;
19
+ desiredRadius: number;
20
+
21
+ minBeta: number = (5 * Math.PI) / 180;
22
+ maxBeta: number = (85 * Math.PI) / 180;
23
+ minZoom: number = 0.1;
24
+ maxZoom: number = 30;
25
+ orbitSpeed: number = 1;
26
+ panSpeed: number = 1;
27
+ zoomSpeed: number = 1;
28
+ dampening: number = 10;
29
+
30
+ constructor(camera: Camera, domElement: HTMLElement) {
31
+ super();
32
+
33
+ this.camera = camera;
34
+ this.domElement = domElement;
35
+
36
+ this.desiredTarget = this.target.clone();
37
+ this.desiredAlpha = this.alpha;
38
+ this.desiredBeta = this.beta;
39
+ this.desiredRadius = this.radius;
40
+ }
41
+
42
+ lerp(a: number, b: number, t: number) {
43
+ return (1 - t) * a + t * b;
44
+ }
45
+
46
+ pan(dx: number, dy: number) {
47
+ const R = this.camera.rotation.buffer;
48
+ const right = new Vector3(R[0], R[3], R[6]);
49
+ const up = new Vector3(R[1], R[4], R[7]);
50
+ this.desiredTarget.add(right.multiply(dx));
51
+ this.desiredTarget.add(up.multiply(dy));
52
+ }
53
+
54
+ update(deltaTime: number) {
55
+ this.alpha = this.lerp(this.alpha, this.desiredAlpha, this.dampening * deltaTime);
56
+ this.beta = this.lerp(this.beta, this.desiredBeta, this.dampening * deltaTime);
57
+ this.radius = this.lerp(this.radius, this.desiredRadius, this.dampening * deltaTime);
58
+ this.target = this.target.lerp(this.desiredTarget, this.dampening * deltaTime);
59
+
60
+ const x = this.target.x + this.radius * Math.sin(this.alpha) * Math.cos(this.beta);
61
+ const y = this.target.y - this.radius * Math.sin(this.beta);
62
+ const z = this.target.z - this.radius * Math.cos(this.alpha) * Math.cos(this.beta);
63
+ this.camera.position.set(x, y, z);
64
+
65
+ const direction = this.target.clone().subtract(this.camera.position).normalize();
66
+ const rx = Math.asin(-direction.y);
67
+ const ry = Math.atan2(direction.x, direction.z);
68
+ this.camera.rotation = Matrix3.RotationFromEuler(new Vector3(rx, ry, 0));
69
+ }
70
+ }
71
+
72
+ export { OrbitControls };
viewer/src/lib/splat.js/core/Object3D.ts CHANGED
@@ -1,13 +1,14 @@
1
  import { Vector3 } from "../math/Vector3";
2
  import { Quaternion } from "../math/Quaternion";
 
3
 
4
  class Object3D {
5
  position: Vector3;
6
- rotation: Quaternion;
7
 
8
  constructor() {
9
  this.position = new Vector3();
10
- this.rotation = new Quaternion();
11
  }
12
  }
13
 
 
1
  import { Vector3 } from "../math/Vector3";
2
  import { Quaternion } from "../math/Quaternion";
3
+ import { Matrix3 } from "../math/Matrix3";
4
 
5
  class Object3D {
6
  position: Vector3;
7
+ rotation: Matrix3;
8
 
9
  constructor() {
10
  this.position = new Vector3();
11
+ this.rotation = new Matrix3();
12
  }
13
  }
14
 
viewer/src/lib/splat.js/core/Scene.ts CHANGED
@@ -11,10 +11,13 @@ class Scene extends Object3D {
11
  this.vertexCount = 0;
12
  }
13
 
14
- setData(data: Uint8Array, vertexCount: number): void {
15
  this.data = data;
16
- this.vertexCount = vertexCount;
17
- console.log("setData", vertexCount);
 
 
 
18
  }
19
  }
20
 
 
11
  this.vertexCount = 0;
12
  }
13
 
14
+ setData(data: Uint8Array): void {
15
  this.data = data;
16
+
17
+ const rowLength = 3 * 4 + 3 * 4 + 4 + 4;
18
+ this.vertexCount = data.length / rowLength;
19
+
20
+ console.log("setData", this.vertexCount);
21
  }
22
  }
23
 
viewer/src/lib/splat.js/index.ts CHANGED
@@ -1,12 +1,15 @@
1
  export { Camera } from "./cameras/Camera";
2
- export { PerspectiveCamera } from "./cameras/PerspectiveCamera";
3
  export { Renderer } from "./renderers/Renderer";
4
  export { Scene } from "./core/Scene";
5
-
6
  export { Loader } from "./loaders/Loader";
7
-
8
  export { WebGLRenderer } from "./renderers/WebGLRenderer";
9
-
10
  export { Quaternion } from "./math/Quaternion";
11
  export { Vector3 } from "./math/Vector3";
12
  export { Matrix4 } from "./math/Matrix4";
 
 
 
 
 
 
 
1
  export { Camera } from "./cameras/Camera";
 
2
  export { Renderer } from "./renderers/Renderer";
3
  export { Scene } from "./core/Scene";
 
4
  export { Loader } from "./loaders/Loader";
 
5
  export { WebGLRenderer } from "./renderers/WebGLRenderer";
6
+ export { OrbitControls } from "./controls/OrbitControls";
7
  export { Quaternion } from "./math/Quaternion";
8
  export { Vector3 } from "./math/Vector3";
9
  export { Matrix4 } from "./math/Matrix4";
10
+ export { Matrix3 } from "./math/Matrix3";
11
+
12
+ export { createWorker } from "./renderers/webgl/utils/worker";
13
+ export { getViewMatrix } from "./renderers/webgl/utils/transformations";
14
+ export { vertex } from "./renderers/webgl/shaders/vertex.glsl";
15
+ export { frag } from "./renderers/webgl/shaders/frag.glsl";
viewer/src/lib/splat.js/loaders/Loader.ts CHANGED
@@ -27,10 +27,7 @@ class Loader {
27
  onProgress?.(bytesRead / contentLength);
28
  }
29
 
30
- const rowLength = 3 * 4 + 3 * 4 + 4 + 4;
31
- const vertexCount = data.length / rowLength;
32
-
33
- scene.setData(data, vertexCount);
34
  }
35
  }
36
 
 
27
  onProgress?.(bytesRead / contentLength);
28
  }
29
 
30
+ scene.setData(data);
 
 
 
31
  }
32
  }
33
 
viewer/src/lib/splat.js/math/Matrix3.ts CHANGED
@@ -1,3 +1,5 @@
 
 
1
  class Matrix3 {
2
  buffer: number[];
3
 
@@ -26,6 +28,55 @@ class Matrix3 {
26
 
27
  return this;
28
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  }
30
 
31
- export { Matrix3 };
 
1
+ import type { Vector3 } from "./Vector3";
2
+
3
  class Matrix3 {
4
  buffer: number[];
5
 
 
28
 
29
  return this;
30
  }
31
+
32
+ multiply(m: Matrix3): Matrix3 {
33
+ const a = this.buffer;
34
+ const b = m.buffer;
35
+ return new Matrix3(
36
+ b[0] * a[0] + b[1] * a[3] + b[2] * a[6],
37
+ b[0] * a[1] + b[1] * a[4] + b[2] * a[7],
38
+ b[0] * a[2] + b[1] * a[5] + b[2] * a[8],
39
+ b[3] * a[0] + b[4] * a[3] + b[5] * a[6],
40
+ b[3] * a[1] + b[4] * a[4] + b[5] * a[7],
41
+ b[3] * a[2] + b[4] * a[5] + b[5] * a[8],
42
+ b[6] * a[0] + b[7] * a[3] + b[8] * a[6],
43
+ b[6] * a[1] + b[7] * a[4] + b[8] * a[7],
44
+ b[6] * a[2] + b[7] * a[5] + b[8] * a[8]
45
+ );
46
+ }
47
+
48
+ clone(): Matrix3 {
49
+ const e = this.buffer;
50
+ // prettier-ignore
51
+ return new Matrix3(
52
+ e[0], e[1], e[2],
53
+ e[3], e[4], e[5],
54
+ e[6], e[7], e[8]
55
+ );
56
+ }
57
+
58
+ static RotationFromEuler(m: Vector3): Matrix3 {
59
+ const cx = Math.cos(m.x);
60
+ const sx = Math.sin(m.x);
61
+ const cy = Math.cos(m.y);
62
+ const sy = Math.sin(m.y);
63
+ const cz = Math.cos(m.z);
64
+ const sz = Math.sin(m.z);
65
+
66
+ const rotationMatrix = [
67
+ cy * cz + sy * sx * sz,
68
+ -cy * sz + sy * sx * cz,
69
+ sy * cx,
70
+ cx * sz,
71
+ cx * cz,
72
+ -sx,
73
+ -sy * cz + cy * sx * sz,
74
+ sy * sz + cy * sx * cz,
75
+ cy * cx,
76
+ ];
77
+
78
+ return new Matrix3(...rotationMatrix);
79
+ }
80
  }
81
 
82
+ export { Matrix3 };
viewer/src/lib/splat.js/math/Matrix4.ts CHANGED
@@ -53,6 +53,17 @@ class Matrix4 {
53
  b[12] * a[3] + b[13] * a[7] + b[14] * a[11] + b[15] * a[15]
54
  );
55
  }
 
 
 
 
 
 
 
 
 
 
 
56
  }
57
 
58
  export { Matrix4 };
 
53
  b[12] * a[3] + b[13] * a[7] + b[14] * a[11] + b[15] * a[15]
54
  );
55
  }
56
+
57
+ clone(): Matrix4 {
58
+ const e = this.buffer;
59
+ // prettier-ignore
60
+ return new Matrix4(
61
+ e[0], e[1], e[2], e[3],
62
+ e[4], e[5], e[6], e[7],
63
+ e[8], e[9], e[10], e[11],
64
+ e[12], e[13], e[14], e[15]
65
+ );
66
+ }
67
  }
68
 
69
  export { Matrix4 };
viewer/src/lib/splat.js/math/Quaternion.ts CHANGED
@@ -1,3 +1,6 @@
 
 
 
1
  class Quaternion {
2
  x: number;
3
  y: number;
@@ -18,11 +21,53 @@ class Quaternion {
18
  this.w = w;
19
 
20
  return this;
21
- }
22
 
23
  flat(): number[] {
24
  return [this.x, this.y, this.z, this.w];
25
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  }
27
 
28
  export { Quaternion };
 
1
+ import { Matrix3 } from "./Matrix3";
2
+ import type { Vector3 } from "./Vector3";
3
+
4
  class Quaternion {
5
  x: number;
6
  y: number;
 
21
  this.w = w;
22
 
23
  return this;
24
+ }
25
 
26
  flat(): number[] {
27
  return [this.x, this.y, this.z, this.w];
28
  }
29
+
30
+ clone(): Quaternion {
31
+ return new Quaternion(this.x, this.y, this.z, this.w);
32
+ }
33
+
34
+ toRotationMatrix(): Matrix3 {
35
+ const xx = this.x * this.x;
36
+ const xy = this.x * this.y;
37
+ const xz = this.x * this.z;
38
+ const xw = this.x * this.w;
39
+ const yy = this.y * this.y;
40
+ const yz = this.y * this.z;
41
+ const yw = this.y * this.w;
42
+ const zz = this.z * this.z;
43
+ const zw = this.z * this.w;
44
+
45
+ const m = new Matrix3();
46
+ // prettier-ignore
47
+ m.set(
48
+ 1 - 2 * (yy + zz), 2 * (xy - zw), 2 * (xz + yw),
49
+ 2 * (xy + zw), 1 - 2 * (xx + zz), 2 * (yz - xw),
50
+ 2 * (xz - yw), 2 * (yz + xw), 1 - 2 * (xx + yy)
51
+ )
52
+
53
+ return m;
54
+ }
55
+
56
+ static FromEuler(m: Vector3) {
57
+ const cy = Math.cos(m.y / 2);
58
+ const sy = Math.sin(m.y / 2);
59
+ const cp = Math.cos(m.x / 2);
60
+ const sp = Math.sin(m.x / 2);
61
+ const cr = Math.cos(m.z / 2);
62
+ const sr = Math.sin(m.z / 2);
63
+
64
+ const w = cy * cp * cr + sy * sp * sr;
65
+ const x = cy * cp * sr - sy * sp * cr;
66
+ const y = sy * cp * sr + cy * sp * cr;
67
+ const z = sy * cp * cr - cy * sp * sr;
68
+
69
+ return new Quaternion(x, y, z, w);
70
+ }
71
  }
72
 
73
  export { Quaternion };
viewer/src/lib/splat.js/math/Vector3.ts CHANGED
@@ -17,9 +17,82 @@ class Vector3 {
17
  return this;
18
  }
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  flat(): number[] {
21
  return [this.x, this.y, this.z];
22
  }
 
 
 
 
23
  }
24
 
25
  export { Vector3 };
 
17
  return this;
18
  }
19
 
20
+ add(v: Vector3): Vector3;
21
+ add(v: number): Vector3;
22
+ add(v: Vector3 | number): Vector3 {
23
+ if (typeof v === "number") {
24
+ this.x += v;
25
+ this.y += v;
26
+ this.z += v;
27
+ } else {
28
+ this.x += v.x;
29
+ this.y += v.y;
30
+ this.z += v.z;
31
+ }
32
+
33
+ return this;
34
+ }
35
+
36
+ subtract(v: Vector3): Vector3;
37
+ subtract(v: number): Vector3;
38
+ subtract(v: Vector3 | number): Vector3 {
39
+ if (typeof v === "number") {
40
+ this.x -= v;
41
+ this.y -= v;
42
+ this.z -= v;
43
+ } else {
44
+ this.x -= v.x;
45
+ this.y -= v.y;
46
+ this.z -= v.z;
47
+ }
48
+
49
+ return this;
50
+ }
51
+
52
+ multiply(v: Vector3): Vector3;
53
+ multiply(v: number): Vector3;
54
+ multiply(v: Vector3 | number): Vector3 {
55
+ if (typeof v === "number") {
56
+ this.x *= v;
57
+ this.y *= v;
58
+ this.z *= v;
59
+ } else {
60
+ this.x *= v.x;
61
+ this.y *= v.y;
62
+ this.z *= v.z;
63
+ }
64
+
65
+ return this;
66
+ }
67
+
68
+ lerp(v: Vector3, t: number): Vector3 {
69
+ this.x += (v.x - this.x) * t;
70
+ this.y += (v.y - this.y) * t;
71
+ this.z += (v.z - this.z) * t;
72
+
73
+ return this;
74
+ }
75
+
76
+ length(): number {
77
+ return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
78
+ }
79
+
80
+ normalize(): Vector3 {
81
+ const length = this.length();
82
+ this.x /= length;
83
+ this.y /= length;
84
+ this.z /= length;
85
+
86
+ return this;
87
+ }
88
+
89
  flat(): number[] {
90
  return [this.x, this.y, this.z];
91
  }
92
+
93
+ clone(): Vector3 {
94
+ return new Vector3(this.x, this.y, this.z);
95
+ }
96
  }
97
 
98
  export { Vector3 };
viewer/src/lib/splat.js/renderers/WebGLRenderer.ts CHANGED
@@ -1,5 +1,4 @@
1
  import type { Camera } from "../cameras/Camera";
2
- import type { PerspectiveCamera } from "../cameras/PerspectiveCamera";
3
  import type { Scene } from "../core/Scene";
4
  import type { Renderer } from "./Renderer";
5
 
@@ -8,204 +7,212 @@ import { createWorker } from "./webgl/utils/worker";
8
 
9
  import { vertex } from "./webgl/shaders/vertex.glsl";
10
  import { frag } from "./webgl/shaders/frag.glsl";
11
- import type { Matrix4 } from "../math/Matrix4";
12
 
13
  export class WebGLRenderer implements Renderer {
14
  canvas: HTMLCanvasElement;
15
 
16
- render: (scene: Scene, camera: Camera) => void;
17
- dispose: () => void;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
  constructor(canvas?: HTMLCanvasElement) {
20
  this.canvas =
21
  canvas ?? (document.createElementNS("http://www.w3.org/1999/xhtml", "canvas") as HTMLCanvasElement);
 
22
 
23
- const gl = (this.canvas.getContext("webgl") ||
 
24
  this.canvas.getContext("experimental-webgl")) as WebGLRenderingContext;
 
25
 
26
- let ext: ANGLE_instanced_arrays;
27
- let worker: Worker;
28
- let vertexShader: WebGLShader;
29
- let fragmentShader: WebGLShader;
30
- let program: WebGLProgram;
31
-
32
- let u_projection: WebGLUniformLocation;
33
- let u_viewport: WebGLUniformLocation;
34
- let u_focal: WebGLUniformLocation;
35
- let u_view: WebGLUniformLocation;
36
-
37
- let vertexLocation: number;
38
- let centerLocation: number;
39
- let colorLocation: number;
40
- let covALocation: number;
41
- let covBLocation: number;
42
-
43
- let vertexBuffer: WebGLBuffer;
44
- let centerBuffer: WebGLBuffer;
45
- let colorBuffer: WebGLBuffer;
46
- let covABuffer: WebGLBuffer;
47
- let covBBuffer: WebGLBuffer;
48
-
49
- function initGLContext(width: number, height: number, scene: Scene, camera: PerspectiveCamera) {
50
- ext = gl.getExtension("ANGLE_instanced_arrays") as ANGLE_instanced_arrays;
51
-
52
- worker = new Worker(
53
- URL.createObjectURL(
54
- new Blob(["(", createWorker.toString(), ")(self)"], {
55
- type: "application/javascript",
56
- })
57
- )
58
- );
59
-
60
- // Viewport
61
- gl.viewport(0, 0, width, height);
62
- camera.updateProjectionMatrix(width, height);
63
-
64
- // Vertex shader
65
- vertexShader = gl.createShader(gl.VERTEX_SHADER) as WebGLShader;
66
- gl.shaderSource(vertexShader, vertex);
67
- gl.compileShader(vertexShader);
68
- if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
69
- console.error(gl.getShaderInfoLog(vertexShader));
70
- }
71
-
72
- // Fragment shader
73
- fragmentShader = gl.createShader(gl.FRAGMENT_SHADER) as WebGLShader;
74
- gl.shaderSource(fragmentShader, frag);
75
- gl.compileShader(fragmentShader);
76
- if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
77
- console.error(gl.getShaderInfoLog(fragmentShader));
78
- }
79
-
80
- // Program
81
- program = gl.createProgram() as WebGLProgram;
82
- gl.attachShader(program, vertexShader);
83
- gl.attachShader(program, fragmentShader);
84
- gl.linkProgram(program);
85
- gl.useProgram(program);
86
- if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
87
- console.error(gl.getProgramInfoLog(program));
88
- }
89
-
90
- // Blending
91
- gl.disable(gl.DEPTH_TEST);
92
- gl.enable(gl.BLEND);
93
- gl.blendFuncSeparate(gl.ONE_MINUS_DST_ALPHA, gl.ONE, gl.ONE_MINUS_DST_ALPHA, gl.ONE);
94
- gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
95
-
96
- // Uniforms
97
- u_projection = gl.getUniformLocation(program, "projection") as WebGLUniformLocation;
98
- gl.uniformMatrix4fv(u_projection, false, camera.projectionMatrix.buffer);
99
-
100
- u_viewport = gl.getUniformLocation(program, "viewport") as WebGLUniformLocation;
101
- gl.uniform2fv(u_viewport, new Float32Array([width, height]));
102
-
103
- u_focal = gl.getUniformLocation(program, "focal") as WebGLUniformLocation;
104
- gl.uniform2fv(u_focal, new Float32Array([camera.fx, camera.fy]));
105
-
106
- const viewMatrix = getViewMatrix(camera);
107
- u_view = gl.getUniformLocation(program, "view") as WebGLUniformLocation;
108
- gl.uniformMatrix4fv(u_view, false, viewMatrix.buffer);
109
-
110
- // Vertex buffer
111
- const triangleVertices = new Float32Array([-2, -2, 2, -2, 2, 2, -2, 2]);
112
- vertexBuffer = gl.createBuffer() as WebGLBuffer;
113
-
114
- gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
115
- gl.bufferData(gl.ARRAY_BUFFER, triangleVertices, gl.STATIC_DRAW);
116
-
117
- vertexLocation = gl.getAttribLocation(program, "position");
118
- gl.enableVertexAttribArray(vertexLocation);
119
- gl.vertexAttribPointer(vertexLocation, 2, gl.FLOAT, false, 0, 0);
120
-
121
- // Center buffer
122
- centerBuffer = gl.createBuffer() as WebGLBuffer;
123
- centerLocation = gl.getAttribLocation(program, "center");
124
-
125
- gl.enableVertexAttribArray(centerLocation);
126
- gl.bindBuffer(gl.ARRAY_BUFFER, centerBuffer);
127
- gl.vertexAttribPointer(centerLocation, 3, gl.FLOAT, false, 0, 0);
128
- ext.vertexAttribDivisorANGLE(centerLocation, 1);
129
 
130
- // Color buffer
131
- colorBuffer = gl.createBuffer() as WebGLBuffer;
132
- colorLocation = gl.getAttribLocation(program, "color");
133
 
134
- gl.enableVertexAttribArray(colorLocation);
135
- gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
136
- gl.vertexAttribPointer(colorLocation, 4, gl.FLOAT, false, 0, 0);
137
- ext.vertexAttribDivisorANGLE(colorLocation, 1);
138
 
139
- // Covariance A buffer
140
- covABuffer = gl.createBuffer() as WebGLBuffer;
141
- covALocation = gl.getAttribLocation(program, "covA");
142
 
143
- gl.enableVertexAttribArray(covALocation);
144
- gl.bindBuffer(gl.ARRAY_BUFFER, covABuffer);
145
- gl.vertexAttribPointer(covALocation, 3, gl.FLOAT, false, 0, 0);
146
- ext.vertexAttribDivisorANGLE(covALocation, 1);
147
-
148
- // Covariance B buffer
149
- covBBuffer = gl.createBuffer() as WebGLBuffer;
150
- covBLocation = gl.getAttribLocation(program, "covB");
151
-
152
- gl.enableVertexAttribArray(covBLocation);
153
- gl.bindBuffer(gl.ARRAY_BUFFER, covBBuffer);
154
- gl.vertexAttribPointer(covBLocation, 3, gl.FLOAT, false, 0, 0);
155
- ext.vertexAttribDivisorANGLE(covBLocation, 1);
156
-
157
- worker.onmessage = (e) => {
158
- if (e.data.center) {
159
- let { covA, covB, center, color } = e.data;
160
-
161
- gl.bindBuffer(gl.ARRAY_BUFFER, centerBuffer);
162
- gl.bufferData(gl.ARRAY_BUFFER, center, gl.DYNAMIC_DRAW);
163
 
164
- gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
165
- gl.bufferData(gl.ARRAY_BUFFER, color, gl.DYNAMIC_DRAW);
 
 
 
 
166
 
167
- gl.bindBuffer(gl.ARRAY_BUFFER, covABuffer);
168
- gl.bufferData(gl.ARRAY_BUFFER, covA, gl.DYNAMIC_DRAW);
 
 
 
169
 
170
- gl.bindBuffer(gl.ARRAY_BUFFER, covBBuffer);
171
- gl.bufferData(gl.ARRAY_BUFFER, covB, gl.DYNAMIC_DRAW);
172
- }
173
- };
174
  }
175
 
176
- let currentScene: Scene | null = null;
177
- let currentCamera: Camera | null = null;
178
-
179
- this.render = function (scene: Scene, camera: Camera) {
180
- if (scene !== currentScene || camera !== currentCamera) {
181
- const perspectiveCamera = camera as PerspectiveCamera;
182
- if (perspectiveCamera == null) {
183
- throw new Error("Camera is not a PerspectiveCamera");
184
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
 
186
- if (currentScene != null || currentCamera != null) {
187
- this.dispose();
188
- }
189
 
190
- currentScene = scene;
191
- currentCamera = camera;
192
 
193
- initGLContext(this.canvas.width, this.canvas.height, scene, perspectiveCamera);
 
 
 
 
194
  }
195
  };
 
196
 
197
- this.dispose = function () {
198
- gl.deleteBuffer(vertexBuffer);
199
- gl.deleteBuffer(centerBuffer);
200
- gl.deleteBuffer(colorBuffer);
201
- gl.deleteBuffer(covABuffer);
202
- gl.deleteBuffer(covBBuffer);
203
 
204
- gl.deleteShader(vertexShader);
205
- gl.deleteShader(fragmentShader);
206
- gl.deleteProgram(program);
 
 
207
 
208
- worker.terminate();
209
- };
 
 
 
 
210
  }
 
 
211
  }
 
1
  import type { Camera } from "../cameras/Camera";
 
2
  import type { Scene } from "../core/Scene";
3
  import type { Renderer } from "./Renderer";
4
 
 
7
 
8
  import { vertex } from "./webgl/shaders/vertex.glsl";
9
  import { frag } from "./webgl/shaders/frag.glsl";
 
10
 
11
  export class WebGLRenderer implements Renderer {
12
  canvas: HTMLCanvasElement;
13
 
14
+ activeCamera: Camera | null = null;
15
+ activeScene: Scene | null = null;
16
+ vertexCount: number = 0;
17
+
18
+ gl: WebGLRenderingContext | null = null;
19
+ ext: ANGLE_instanced_arrays | null = null;
20
+ worker: Worker | null = null;
21
+
22
+ projectionMatrix: number[] = [];
23
+ vertexShader: WebGLShader | null = null;
24
+ fragmentShader: WebGLShader | null = null;
25
+ program: WebGLProgram | null = null;
26
+ a_position: number = 0;
27
+ a_center: number = 0;
28
+ a_color: number = 0;
29
+ a_covA: number = 0;
30
+ a_covB: number = 0;
31
+ vertexBuffer: WebGLBuffer | null = null;
32
+ centerBuffer: WebGLBuffer | null = null;
33
+ colorBuffer: WebGLBuffer | null = null;
34
+ covABuffer: WebGLBuffer | null = null;
35
+ covBBuffer: WebGLBuffer | null = null;
36
 
37
  constructor(canvas?: HTMLCanvasElement) {
38
  this.canvas =
39
  canvas ?? (document.createElementNS("http://www.w3.org/1999/xhtml", "canvas") as HTMLCanvasElement);
40
+ }
41
 
42
+ initWebGL() {
43
+ this.gl = (this.canvas.getContext("webgl") ||
44
  this.canvas.getContext("experimental-webgl")) as WebGLRenderingContext;
45
+ this.ext = this.gl.getExtension("ANGLE_instanced_arrays") as ANGLE_instanced_arrays;
46
 
47
+ this.worker = new Worker(
48
+ URL.createObjectURL(
49
+ new Blob(["(", createWorker.toString(), ")(self)"], {
50
+ type: "application/javascript",
51
+ })
52
+ )
53
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
+ this.canvas.width = innerWidth;
56
+ this.canvas.height = innerHeight;
57
+ this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
58
 
59
+ this.activeCamera!.updateProjectionMatrix(this.canvas.width, this.canvas.height);
 
 
 
60
 
61
+ let viewMatrix = getViewMatrix(this.activeCamera!);
 
 
62
 
63
+ this.vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER) as WebGLShader;
64
+ this.gl.shaderSource(this.vertexShader, vertex);
65
+ this.gl.compileShader(this.vertexShader);
66
+ if (!this.gl.getShaderParameter(this.vertexShader, this.gl.COMPILE_STATUS)) {
67
+ console.error(this.gl.getShaderInfoLog(this.vertexShader));
68
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
+ this.fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER) as WebGLShader;
71
+ this.gl.shaderSource(this.fragmentShader, frag);
72
+ this.gl.compileShader(this.fragmentShader);
73
+ if (!this.gl.getShaderParameter(this.fragmentShader, this.gl.COMPILE_STATUS)) {
74
+ console.error(this.gl.getShaderInfoLog(this.fragmentShader));
75
+ }
76
 
77
+ this.program = this.gl.createProgram() as WebGLProgram;
78
+ this.gl.attachShader(this.program, this.vertexShader);
79
+ this.gl.attachShader(this.program, this.fragmentShader);
80
+ this.gl.linkProgram(this.program);
81
+ this.gl.useProgram(this.program);
82
 
83
+ if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) {
84
+ console.error(this.gl.getProgramInfoLog(this.program));
 
 
85
  }
86
 
87
+ this.gl.disable(this.gl.DEPTH_TEST); // Disable depth testing
88
+
89
+ // Enable blending
90
+ this.gl.enable(this.gl.BLEND);
91
+
92
+ // Set blending function
93
+ this.gl.blendFuncSeparate(this.gl.ONE_MINUS_DST_ALPHA, this.gl.ONE, this.gl.ONE_MINUS_DST_ALPHA, this.gl.ONE);
94
+
95
+ // Set blending equation
96
+ this.gl.blendEquationSeparate(this.gl.FUNC_ADD, this.gl.FUNC_ADD);
97
+
98
+ // projection
99
+ const u_projection = this.gl.getUniformLocation(this.program, "projection");
100
+ this.gl.uniformMatrix4fv(u_projection, false, this.projectionMatrix);
101
+
102
+ // viewport
103
+ const u_viewport = this.gl.getUniformLocation(this.program, "viewport");
104
+ this.gl.uniform2fv(u_viewport, new Float32Array([this.canvas.width, this.canvas.height]));
105
+
106
+ // focal
107
+ const u_focal = this.gl.getUniformLocation(this.program, "focal");
108
+ this.gl.uniform2fv(u_focal, new Float32Array([this.activeCamera!.fx, this.activeCamera!.fy]));
109
+
110
+ // view
111
+ const u_view = this.gl.getUniformLocation(this.program, "view");
112
+ this.gl.uniformMatrix4fv(u_view, false, viewMatrix.buffer);
113
+
114
+ // positions
115
+ const triangleVertices = new Float32Array([-2, -2, 2, -2, 2, 2, -2, 2]);
116
+ this.vertexBuffer = this.gl.createBuffer();
117
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);
118
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, triangleVertices, this.gl.STATIC_DRAW);
119
+
120
+ this.a_position = this.gl.getAttribLocation(this.program, "position");
121
+ this.gl.enableVertexAttribArray(this.a_position);
122
+ this.gl.vertexAttribPointer(this.a_position, 2, this.gl.FLOAT, false, 0, 0);
123
+
124
+ // center
125
+ this.centerBuffer = this.gl.createBuffer();
126
+ this.a_center = this.gl.getAttribLocation(this.program, "center");
127
+ this.gl.enableVertexAttribArray(this.a_center);
128
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.centerBuffer);
129
+ this.gl.vertexAttribPointer(this.a_center, 3, this.gl.FLOAT, false, 0, 0);
130
+ this.ext.vertexAttribDivisorANGLE(this.a_center, 1); // Use the extension here
131
+
132
+ // color
133
+ this.colorBuffer = this.gl.createBuffer();
134
+ this.a_color = this.gl.getAttribLocation(this.program, "color");
135
+ this.gl.enableVertexAttribArray(this.a_color);
136
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.colorBuffer);
137
+ this.gl.vertexAttribPointer(this.a_color, 4, this.gl.FLOAT, false, 0, 0);
138
+ this.ext.vertexAttribDivisorANGLE(this.a_color, 1); // Use the extension here
139
+
140
+ // cov
141
+ this.covABuffer = this.gl.createBuffer();
142
+ this.a_covA = this.gl.getAttribLocation(this.program, "covA");
143
+ this.gl.enableVertexAttribArray(this.a_covA);
144
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.covABuffer);
145
+ this.gl.vertexAttribPointer(this.a_covA, 3, this.gl.FLOAT, false, 0, 0);
146
+ this.ext.vertexAttribDivisorANGLE(this.a_covA, 1); // Use the extension here
147
+
148
+ this.covBBuffer = this.gl.createBuffer();
149
+ this.a_covB = this.gl.getAttribLocation(this.program, "covB");
150
+ this.gl.enableVertexAttribArray(this.a_covB);
151
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.covBBuffer);
152
+ this.gl.vertexAttribPointer(this.a_covB, 3, this.gl.FLOAT, false, 0, 0);
153
+ this.ext.vertexAttribDivisorANGLE(this.a_covB, 1); // Use the extension here
154
+
155
+ this.worker.onmessage = (e) => {
156
+ if (e.data.buffer) {
157
+ this.activeScene!.setData(new Uint8Array(e.data.buffer));
158
+ const blob = new Blob([this.activeScene!.data.buffer], {
159
+ type: "application/octet-stream",
160
+ });
161
+ const link = document.createElement("a");
162
+ link.download = "model.splat";
163
+ link.href = URL.createObjectURL(blob);
164
+ document.body.appendChild(link);
165
+ link.click();
166
+ } else {
167
+ let { covA, covB, center, color } = e.data;
168
+ this.vertexCount = center.length / 3;
169
+
170
+ const gl = this.gl!;
171
+
172
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.centerBuffer);
173
+ gl.bufferData(gl.ARRAY_BUFFER, center, gl.DYNAMIC_DRAW);
174
+
175
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
176
+ gl.bufferData(gl.ARRAY_BUFFER, color, gl.DYNAMIC_DRAW);
177
+
178
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.covABuffer);
179
+ gl.bufferData(gl.ARRAY_BUFFER, covA, gl.DYNAMIC_DRAW);
180
+
181
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.covBBuffer);
182
+ gl.bufferData(gl.ARRAY_BUFFER, covB, gl.DYNAMIC_DRAW);
183
+ }
184
+ };
185
 
186
+ this.frame = () => {
187
+ this.activeCamera!.updateProjectionMatrix(this.canvas.width, this.canvas.height);
 
188
 
189
+ const viewProj = this.activeCamera!.projectionMatrix.multiply(viewMatrix);
190
+ this.worker!.postMessage({ view: viewProj.buffer });
191
 
192
+ if (this.vertexCount > 0) {
193
+ this.gl!.uniformMatrix4fv(u_view, false, viewMatrix.buffer);
194
+ this.ext!.drawArraysInstancedANGLE(this.gl!.TRIANGLE_FAN, 0, 4, this.vertexCount);
195
+ } else {
196
+ this.gl!.clear(this.gl!.COLOR_BUFFER_BIT);
197
  }
198
  };
199
+ }
200
 
201
+ frame: () => void = () => {};
 
 
 
 
 
202
 
203
+ render(scene: Scene, camera: Camera) {
204
+ if (this.activeScene !== scene || this.activeCamera !== camera) {
205
+ if (this.activeScene !== null && this.activeCamera !== null) {
206
+ this.dispose();
207
+ }
208
 
209
+ this.activeScene = scene;
210
+ this.activeCamera = camera;
211
+ this.initWebGL();
212
+ }
213
+
214
+ this.frame();
215
  }
216
+
217
+ dispose() {}
218
  }
viewer/src/lib/splat.js/renderers/webgl/utils/transformations.ts CHANGED
@@ -1,77 +1,8 @@
1
  import type { Camera } from "$lib/splat.js/cameras/Camera";
2
- import { Vector3 } from "$lib/splat.js/math/Vector3";
3
- import { Matrix3 } from "$lib/splat.js/math/Matrix3";
4
  import { Matrix4 } from "$lib/splat.js/math/Matrix4";
5
- import type { Quaternion } from "$lib/splat.js/math/Quaternion";
6
-
7
- export function quaternionToEuler(q: Quaternion) {
8
- return new Vector3(
9
- Math.atan2(2 * (q.w * q.x + q.y * q.z), 1 - 2 * (q.x * q.x + q.y * q.y)),
10
- Math.asin(2 * (q.w * q.y - q.z * q.x)),
11
- Math.atan2(2 * (q.w * q.z + q.x * q.y), 1 - 2 * (q.y * q.y + q.z * q.z))
12
- );
13
- }
14
-
15
- export function eulerToMatrix(v: Vector3) {
16
- const x = v.x;
17
- const y = v.y;
18
- const z = v.z;
19
-
20
- const cx = Math.cos(x);
21
- const sx = Math.sin(x);
22
- const cy = Math.cos(y);
23
- const sy = Math.sin(y);
24
- const cz = Math.cos(z);
25
- const sz = Math.sin(z);
26
-
27
- const rotationMatrix = [
28
- cy * cz + sy * sx * sz,
29
- -cy * sz + sy * sx * cz,
30
- sy * cx,
31
- cx * sz,
32
- cx * cz,
33
- -sx,
34
- -sy * cz + cy * sx * sz,
35
- sy * sz + cy * sx * cz,
36
- cy * cx,
37
- ];
38
-
39
- return new Matrix4(...rotationMatrix);
40
- }
41
-
42
- export function quatToMatrix(q: Quaternion) {
43
- const x = q.x;
44
- const y = q.y;
45
- const z = q.z;
46
- const w = q.w;
47
-
48
- const xx = x * x;
49
- const xy = x * y;
50
- const xz = x * z;
51
- const xw = x * w;
52
- const yy = y * y;
53
- const yz = y * z;
54
- const yw = y * w;
55
- const zz = z * z;
56
- const zw = z * w;
57
-
58
- const rotationMatrix = [
59
- 1 - 2 * (yy + zz),
60
- 2 * (xy - zw),
61
- 2 * (xz + yw),
62
- 2 * (xy + zw),
63
- 1 - 2 * (xx + zz),
64
- 2 * (yz - xw),
65
- 2 * (xz - yw),
66
- 2 * (yz + xw),
67
- 1 - 2 * (xx + yy),
68
- ];
69
-
70
- return new Matrix3(...rotationMatrix);
71
- }
72
 
73
  export function getViewMatrix(camera: Camera) {
74
- const R = quatToMatrix(camera.rotation).buffer;
75
  const t = camera.position.flat();
76
  const camToWorld = [
77
  [R[0], R[1], R[2], 0],
 
1
  import type { Camera } from "$lib/splat.js/cameras/Camera";
 
 
2
  import { Matrix4 } from "$lib/splat.js/math/Matrix4";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
  export function getViewMatrix(camera: Camera) {
5
+ const R = camera.rotation.buffer;
6
  const t = camera.position.flat();
7
  const camToWorld = [
8
  [R[0], R[1], R[2], 0],
viewer/src/routes/viewer/[slug]/+page.svelte CHANGED
@@ -3,7 +3,6 @@
3
  import type { IViewer } from "./IViewer";
4
  import { BabylonViewer } from "./BabylonViewer";
5
  import { SplatViewer } from "./SplatViewer";
6
- // import { SplatViewer } from "./SplatViewer.Legacy";
7
 
8
  export let data: {
9
  scene: {
 
3
  import type { IViewer } from "./IViewer";
4
  import { BabylonViewer } from "./BabylonViewer";
5
  import { SplatViewer } from "./SplatViewer";
 
6
 
7
  export let data: {
8
  scene: {
viewer/src/routes/viewer/[slug]/SplatViewer.Legacy.ts DELETED
@@ -1,1021 +0,0 @@
1
- import type { IViewer } from "./IViewer";
2
-
3
- class OrbitCamera {
4
- position: number[];
5
- rotation: number[];
6
- fy: number;
7
- fx: number;
8
- alpha: number;
9
- beta: number;
10
- radius: number;
11
- target: number[];
12
- desiredAlpha: number;
13
- desiredBeta: number;
14
- desiredRadius: number;
15
- desiredTarget: number[];
16
- damping: number;
17
-
18
- minBeta = (5 * Math.PI) / 180;
19
- maxBeta = (85 * Math.PI) / 180;
20
- minZoom = 0.5;
21
- maxZoom = 30;
22
- orbitSpeed = 0.005;
23
- panSpeed = 0.01;
24
- zoomSpeed = 0.05;
25
-
26
- constructor(alpha = 0, beta = 0, radius = 5, target = [0, 0, 0]) {
27
- this.position = [0, 0, -radius];
28
- this.rotation = [
29
- [1, 0, 0],
30
- [0, 1, 0],
31
- [0, 0, 1],
32
- ].flat();
33
- this.fx = 1132;
34
- this.fy = 1132;
35
- this.alpha = alpha;
36
- this.beta = beta;
37
- this.radius = radius;
38
- this.target = target;
39
- this.desiredAlpha = alpha;
40
- this.desiredBeta = beta;
41
- this.desiredRadius = radius;
42
- this.desiredTarget = target;
43
- this.damping = 0.4;
44
- this.updateCameraPositionAndRotation();
45
- }
46
-
47
- lerp(start: number, end: number, factor: number) {
48
- return (1 - factor) * start + factor * end;
49
- }
50
-
51
- pan(dx: number, dy: number) {
52
- const right = [this.rotation[0], this.rotation[3], this.rotation[6]];
53
- const up = [this.rotation[1], this.rotation[4], this.rotation[7]];
54
- for (let i = 0; i < 3; i++) {
55
- this.desiredTarget[i] += right[i] * dx + up[i] * dy;
56
- }
57
- }
58
-
59
- getZoomNorm(): number {
60
- return 0.1 + (0.9 * (this.desiredRadius - this.minZoom)) / (this.maxZoom - this.minZoom);
61
- }
62
-
63
- update() {
64
- this.alpha = this.lerp(this.alpha, this.desiredAlpha, this.damping);
65
- this.beta = this.lerp(this.beta, this.desiredBeta, this.damping);
66
- this.radius = this.lerp(this.radius, this.desiredRadius, this.damping);
67
- for (let i = 0; i < 3; i++) {
68
- this.target[i] = this.lerp(this.target[i], this.desiredTarget[i], this.damping);
69
- }
70
- this.updateCameraPositionAndRotation();
71
- }
72
-
73
- updateCameraPositionAndRotation() {
74
- const x = this.target[0] + this.radius * Math.sin(this.alpha) * Math.cos(this.beta);
75
- const y = this.target[1] - this.radius * Math.sin(this.beta);
76
- const z = this.target[2] - this.radius * Math.cos(this.alpha) * Math.cos(this.beta);
77
- this.position = [x, y, z];
78
-
79
- const direction = normalize(subtract(this.target, this.position));
80
- const eulerAngles = directionToEuler(direction);
81
- this.rotation = eulerToRotationMatrix(eulerAngles[0], eulerAngles[1], eulerAngles[2]);
82
- }
83
- }
84
-
85
- function directionToEuler(direction: number[]) {
86
- const x = Math.asin(-direction[1]);
87
- const y = Math.atan2(direction[0], direction[2]);
88
- return [x, y, 0];
89
- }
90
-
91
- function eulerToRotationMatrix(x: number, y: number, z: number) {
92
- const cx = Math.cos(x);
93
- const sx = Math.sin(x);
94
- const cy = Math.cos(y);
95
- const sy = Math.sin(y);
96
- const cz = Math.cos(z);
97
- const sz = Math.sin(z);
98
-
99
- const rotationMatrix = [
100
- cy * cz + sy * sx * sz,
101
- -cy * sz + sy * sx * cz,
102
- sy * cx,
103
- cx * sz,
104
- cx * cz,
105
- -sx,
106
- -sy * cz + cy * sx * sz,
107
- sy * sz + cy * sx * cz,
108
- cy * cx,
109
- ];
110
-
111
- return rotationMatrix;
112
- }
113
-
114
- function normalize(a: number[]) {
115
- const len = Math.hypot(...a);
116
- return a.map((v) => v / len);
117
- }
118
-
119
- function subtract(a: number[], b: number[]) {
120
- return a.map((v, i) => v - b[i]);
121
- }
122
-
123
- function getProjectionMatrix(fx: number, fy: number, width: number, height: number) {
124
- const znear = 0.2;
125
- const zfar = 200;
126
- return [
127
- [(2 * fx) / width, 0, 0, 0],
128
- [0, -(2 * fy) / height, 0, 0],
129
- [0, 0, zfar / (zfar - znear), 1],
130
- [0, 0, -(zfar * znear) / (zfar - znear), 0],
131
- ].flat();
132
- }
133
-
134
- function getViewMatrix(camera: any) {
135
- const R = camera.rotation.flat();
136
- const t = camera.position;
137
- const camToWorld = [
138
- [R[0], R[1], R[2], 0],
139
- [R[3], R[4], R[5], 0],
140
- [R[6], R[7], R[8], 0],
141
- [
142
- -t[0] * R[0] - t[1] * R[3] - t[2] * R[6],
143
- -t[0] * R[1] - t[1] * R[4] - t[2] * R[7],
144
- -t[0] * R[2] - t[1] * R[5] - t[2] * R[8],
145
- 1,
146
- ],
147
- ].flat();
148
- return camToWorld;
149
- }
150
-
151
- function multiply4(a: number[], b: number[]) {
152
- return [
153
- b[0] * a[0] + b[1] * a[4] + b[2] * a[8] + b[3] * a[12],
154
- b[0] * a[1] + b[1] * a[5] + b[2] * a[9] + b[3] * a[13],
155
- b[0] * a[2] + b[1] * a[6] + b[2] * a[10] + b[3] * a[14],
156
- b[0] * a[3] + b[1] * a[7] + b[2] * a[11] + b[3] * a[15],
157
- b[4] * a[0] + b[5] * a[4] + b[6] * a[8] + b[7] * a[12],
158
- b[4] * a[1] + b[5] * a[5] + b[6] * a[9] + b[7] * a[13],
159
- b[4] * a[2] + b[5] * a[6] + b[6] * a[10] + b[7] * a[14],
160
- b[4] * a[3] + b[5] * a[7] + b[6] * a[11] + b[7] * a[15],
161
- b[8] * a[0] + b[9] * a[4] + b[10] * a[8] + b[11] * a[12],
162
- b[8] * a[1] + b[9] * a[5] + b[10] * a[9] + b[11] * a[13],
163
- b[8] * a[2] + b[9] * a[6] + b[10] * a[10] + b[11] * a[14],
164
- b[8] * a[3] + b[9] * a[7] + b[10] * a[11] + b[11] * a[15],
165
- b[12] * a[0] + b[13] * a[4] + b[14] * a[8] + b[15] * a[12],
166
- b[12] * a[1] + b[13] * a[5] + b[14] * a[9] + b[15] * a[13],
167
- b[12] * a[2] + b[13] * a[6] + b[14] * a[10] + b[15] * a[14],
168
- b[12] * a[3] + b[13] * a[7] + b[14] * a[11] + b[15] * a[15],
169
- ];
170
- }
171
-
172
- function createWorker(self: Worker) {
173
- let buffer: ArrayBuffer;
174
- let vertexCount = 0;
175
- let viewProj: Float32Array;
176
- // 6*4 + 4 + 4 = 8*4
177
- // XYZ - Position (Float32)
178
- // XYZ - Scale (Float32)
179
- // RGBA - colors (uint8)
180
- // IJKL - quaternion/rot (uint8)
181
- const rowLength = 3 * 4 + 3 * 4 + 4 + 4;
182
- let depthIndex = new Uint32Array();
183
-
184
- function processPlyBuffer(inputBuffer: ArrayBuffer) {
185
- const ubuf = new Uint8Array(inputBuffer);
186
- // 10KB ought to be enough for a header...
187
- const header = new TextDecoder().decode(ubuf.slice(0, 1024 * 10));
188
- const header_end = "end_header\n";
189
- const header_end_index = header.indexOf(header_end);
190
- if (header_end_index < 0) throw new Error("Unable to read .ply file header");
191
- const vertexCount = parseInt(/element vertex (\d+)\n/.exec(header)![1]);
192
- console.log("Vertex Count", vertexCount);
193
- let row_offset: number = 0;
194
- let offsets: { [key: string]: number } = {};
195
- let types: { [key: string]: string } = {};
196
- const TYPE_MAP: { [key: string]: string } = {
197
- double: "getFloat64",
198
- int: "getInt32",
199
- uint: "getUint32",
200
- float: "getFloat32",
201
- short: "getInt16",
202
- ushort: "getUint16",
203
- uchar: "getUint8",
204
- };
205
- for (let prop of header
206
- .slice(0, header_end_index)
207
- .split("\n")
208
- .filter((k) => k.startsWith("property "))) {
209
- const [_p, type, name] = prop.split(" ");
210
- const arrayType = TYPE_MAP[type] || "getInt8";
211
- types[name] = arrayType;
212
- offsets[name] = row_offset;
213
- row_offset += parseInt(arrayType.replace(/[^\d]/g, "")) / 8;
214
- }
215
-
216
- let dataView = new DataView(inputBuffer, header_end_index + header_end.length);
217
- let row: number = 0;
218
- const attrs: any = new Proxy(
219
- {},
220
- {
221
- get(_target, prop: string) {
222
- if (!types[prop]) throw new Error(prop + " not found");
223
- const type = types[prop] as keyof DataView;
224
- const dataViewMethod = dataView[type] as any;
225
- return dataViewMethod(row * row_offset + offsets[prop], true);
226
- },
227
- }
228
- );
229
-
230
- // 6*4 + 4 + 4 = 8*4
231
- // XYZ - Position (Float32)
232
- // XYZ - Scale (Float32)
233
- // RGBA - colors (uint8)
234
- // IJKL - quaternion/rot (uint8)
235
- const rowLength = 3 * 4 + 3 * 4 + 4 + 4;
236
- const buffer = new ArrayBuffer(rowLength * vertexCount);
237
-
238
- for (let j = 0; j < vertexCount; j++) {
239
- row = j;
240
-
241
- const position = new Float32Array(buffer, j * rowLength, 3);
242
- const scales = new Float32Array(buffer, j * rowLength + 4 * 3, 3);
243
- const rgba = new Uint8ClampedArray(buffer, j * rowLength + 4 * 3 + 4 * 3, 4);
244
- const rot = new Uint8ClampedArray(buffer, j * rowLength + 4 * 3 + 4 * 3 + 4, 4);
245
-
246
- if (types["scale_0"]) {
247
- const qlen = Math.sqrt(attrs.rot_0 ** 2 + attrs.rot_1 ** 2 + attrs.rot_2 ** 2 + attrs.rot_3 ** 2);
248
-
249
- rot[0] = (attrs.rot_0 / qlen) * 128 + 128;
250
- rot[1] = (attrs.rot_1 / qlen) * 128 + 128;
251
- rot[2] = (attrs.rot_2 / qlen) * 128 + 128;
252
- rot[3] = (attrs.rot_3 / qlen) * 128 + 128;
253
-
254
- scales[0] = Math.exp(attrs.scale_0);
255
- scales[1] = Math.exp(attrs.scale_1);
256
- scales[2] = Math.exp(attrs.scale_2);
257
- } else {
258
- scales[0] = 0.01;
259
- scales[1] = 0.01;
260
- scales[2] = 0.01;
261
-
262
- rot[0] = 255;
263
- rot[1] = 0;
264
- rot[2] = 0;
265
- rot[3] = 0;
266
- }
267
-
268
- position[0] = attrs.x;
269
- position[1] = attrs.y;
270
- position[2] = attrs.z;
271
-
272
- if (types["f_dc_0"]) {
273
- const SH_C0 = 0.28209479177387814;
274
- rgba[0] = (0.5 + SH_C0 * attrs.f_dc_0) * 255;
275
- rgba[1] = (0.5 + SH_C0 * attrs.f_dc_1) * 255;
276
- rgba[2] = (0.5 + SH_C0 * attrs.f_dc_2) * 255;
277
- } else {
278
- rgba[0] = attrs.red;
279
- rgba[1] = attrs.green;
280
- rgba[2] = attrs.blue;
281
- }
282
- if (types["opacity"]) {
283
- rgba[3] = (1 / (1 + Math.exp(-attrs.opacity))) * 255;
284
- } else {
285
- rgba[3] = 255;
286
- }
287
- }
288
- return buffer;
289
- }
290
-
291
- const runSort = (viewProj: Float32Array) => {
292
- if (!buffer) return;
293
-
294
- const f_buffer = new Float32Array(buffer);
295
- const u_buffer = new Uint8Array(buffer);
296
-
297
- const covA = new Float32Array(3 * vertexCount);
298
- const covB = new Float32Array(3 * vertexCount);
299
-
300
- const center = new Float32Array(3 * vertexCount);
301
- const color = new Float32Array(4 * vertexCount);
302
-
303
- let maxDepth = -Infinity;
304
- let minDepth = Infinity;
305
- let sizeList = new Int32Array(vertexCount);
306
- for (let i = 0; i < vertexCount; i++) {
307
- let depth =
308
- ((viewProj[2] * f_buffer[8 * i + 0] +
309
- viewProj[6] * f_buffer[8 * i + 1] +
310
- viewProj[10] * f_buffer[8 * i + 2]) *
311
- 4096) |
312
- 0;
313
- sizeList[i] = depth;
314
- if (depth > maxDepth) maxDepth = depth;
315
- if (depth < minDepth) minDepth = depth;
316
- }
317
-
318
- // This is a 16 bit single-pass counting sort
319
- let depthInv = (256 * 256) / (maxDepth - minDepth);
320
- let counts0 = new Uint32Array(256 * 256);
321
- for (let i = 0; i < vertexCount; i++) {
322
- sizeList[i] = ((sizeList[i] - minDepth) * depthInv) | 0;
323
- counts0[sizeList[i]]++;
324
- }
325
- let starts0 = new Uint32Array(256 * 256);
326
- for (let i = 1; i < 256 * 256; i++) starts0[i] = starts0[i - 1] + counts0[i - 1];
327
- depthIndex = new Uint32Array(vertexCount);
328
- for (let i = 0; i < vertexCount; i++) depthIndex[starts0[sizeList[i]]++] = i;
329
-
330
- for (let j = 0; j < vertexCount; j++) {
331
- const i = depthIndex[j];
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
- let scale = [f_buffer[8 * i + 3 + 0], f_buffer[8 * i + 3 + 1], f_buffer[8 * i + 3 + 2]];
343
- let rot = [
344
- (u_buffer[32 * i + 28 + 0] - 128) / 128,
345
- (u_buffer[32 * i + 28 + 1] - 128) / 128,
346
- (u_buffer[32 * i + 28 + 2] - 128) / 128,
347
- (u_buffer[32 * i + 28 + 3] - 128) / 128,
348
- ];
349
-
350
- const R = [
351
- 1.0 - 2.0 * (rot[2] * rot[2] + rot[3] * rot[3]),
352
- 2.0 * (rot[1] * rot[2] + rot[0] * rot[3]),
353
- 2.0 * (rot[1] * rot[3] - rot[0] * rot[2]),
354
-
355
- 2.0 * (rot[1] * rot[2] - rot[0] * rot[3]),
356
- 1.0 - 2.0 * (rot[1] * rot[1] + rot[3] * rot[3]),
357
- 2.0 * (rot[2] * rot[3] + rot[0] * rot[1]),
358
-
359
- 2.0 * (rot[1] * rot[3] + rot[0] * rot[2]),
360
- 2.0 * (rot[2] * rot[3] - rot[0] * rot[1]),
361
- 1.0 - 2.0 * (rot[1] * rot[1] + rot[2] * rot[2]),
362
- ];
363
-
364
- // Compute the matrix product of S and R (M = S * R)
365
- const M = [
366
- scale[0] * R[0],
367
- scale[0] * R[1],
368
- scale[0] * R[2],
369
- scale[1] * R[3],
370
- scale[1] * R[4],
371
- scale[1] * R[5],
372
- scale[2] * R[6],
373
- scale[2] * R[7],
374
- scale[2] * R[8],
375
- ];
376
-
377
- covA[3 * j + 0] = M[0] * M[0] + M[3] * M[3] + M[6] * M[6];
378
- covA[3 * j + 1] = M[0] * M[1] + M[3] * M[4] + M[6] * M[7];
379
- covA[3 * j + 2] = M[0] * M[2] + M[3] * M[5] + M[6] * M[8];
380
- covB[3 * j + 0] = M[1] * M[1] + M[4] * M[4] + M[7] * M[7];
381
- covB[3 * j + 1] = M[1] * M[2] + M[4] * M[5] + M[7] * M[8];
382
- covB[3 * j + 2] = M[2] * M[2] + M[5] * M[5] + M[8] * M[8];
383
- }
384
-
385
- self.postMessage({ covA, center, color, covB, viewProj }, [
386
- covA.buffer,
387
- center.buffer,
388
- color.buffer,
389
- covB.buffer,
390
- ]);
391
- };
392
-
393
- const throttledSort = () => {
394
- if (!sortRunning) {
395
- sortRunning = true;
396
- let lastView = viewProj;
397
- runSort(lastView);
398
- setTimeout(() => {
399
- sortRunning = false;
400
- if (lastView !== viewProj) {
401
- throttledSort();
402
- }
403
- }, 0);
404
- }
405
- };
406
-
407
- let sortRunning: boolean = false;
408
- self.onmessage = (e) => {
409
- if (e.data.ply) {
410
- vertexCount = 0;
411
- runSort(viewProj);
412
- buffer = processPlyBuffer(e.data.ply);
413
- vertexCount = Math.floor(buffer.byteLength / rowLength);
414
- postMessage({ buffer: buffer });
415
- } else if (e.data.buffer) {
416
- buffer = e.data.buffer;
417
- vertexCount = e.data.vertexCount;
418
- } else if (e.data.vertexCount) {
419
- vertexCount = e.data.vertexCount;
420
- } else if (e.data.view) {
421
- viewProj = e.data.view;
422
- throttledSort();
423
- }
424
- };
425
- }
426
-
427
- function preventDefault(e: Event) {
428
- e.preventDefault();
429
- e.stopPropagation();
430
- }
431
-
432
- const vertexShaderSource = `
433
- precision mediump float;
434
- attribute vec2 position;
435
-
436
- attribute vec4 color;
437
- attribute vec3 center;
438
- attribute vec3 covA;
439
- attribute vec3 covB;
440
-
441
- uniform mat4 projection, view;
442
- uniform vec2 focal;
443
- uniform vec2 viewport;
444
-
445
- varying vec4 vColor;
446
- varying vec2 vPosition;
447
-
448
- mat3 transpose(mat3 m) {
449
- return mat3(
450
- m[0][0], m[1][0], m[2][0],
451
- m[0][1], m[1][1], m[2][1],
452
- m[0][2], m[1][2], m[2][2]
453
- );
454
- }
455
-
456
- void main () {
457
- vec4 camspace = view * vec4(center, 1);
458
- vec4 pos2d = projection * camspace;
459
-
460
- float bounds = 1.2 * pos2d.w;
461
- if (pos2d.z < -pos2d.w || pos2d.x < -bounds || pos2d.x > bounds
462
- || pos2d.y < -bounds || pos2d.y > bounds) {
463
- gl_Position = vec4(0.0, 0.0, 2.0, 1.0);
464
- return;
465
- }
466
-
467
- mat3 Vrk = mat3(
468
- covA.x, covA.y, covA.z,
469
- covA.y, covB.x, covB.y,
470
- covA.z, covB.y, covB.z
471
- );
472
-
473
- mat3 J = mat3(
474
- focal.x / camspace.z, 0., -(focal.x * camspace.x) / (camspace.z * camspace.z),
475
- 0., -focal.y / camspace.z, (focal.y * camspace.y) / (camspace.z * camspace.z),
476
- 0., 0., 0.
477
- );
478
-
479
- mat3 W = transpose(mat3(view));
480
- mat3 T = W * J;
481
- mat3 cov = transpose(T) * Vrk * T;
482
-
483
- vec2 vCenter = vec2(pos2d) / pos2d.w;
484
-
485
- float diagonal1 = cov[0][0] + 0.3;
486
- float offDiagonal = cov[0][1];
487
- float diagonal2 = cov[1][1] + 0.3;
488
-
489
- float mid = 0.5 * (diagonal1 + diagonal2);
490
- float radius = length(vec2((diagonal1 - diagonal2) / 2.0, offDiagonal));
491
- float lambda1 = mid + radius;
492
- float lambda2 = max(mid - radius, 0.1);
493
- vec2 diagonalVector = normalize(vec2(offDiagonal, lambda1 - diagonal1));
494
- vec2 v1 = min(sqrt(2.0 * lambda1), 1024.0) * diagonalVector;
495
- vec2 v2 = min(sqrt(2.0 * lambda2), 1024.0) * vec2(diagonalVector.y, -diagonalVector.x);
496
-
497
- vColor = color;
498
- vPosition = position;
499
-
500
- gl_Position = vec4(
501
- vCenter
502
- + position.x * v1 / viewport * 2.0
503
- + position.y * v2 / viewport * 2.0, 0.0, 1.0
504
- );
505
- }
506
- `;
507
-
508
- const fragmentShaderSource = `
509
- precision mediump float;
510
-
511
- varying vec4 vColor;
512
- varying vec2 vPosition;
513
-
514
- void main () {
515
- float A = -dot(vPosition, vPosition);
516
- if (A < -4.0) discard;
517
- float B = exp(A) * vColor.a;
518
- gl_FragColor = vec4(B * vColor.rgb, B);
519
- }
520
- `;
521
-
522
- export class SplatViewer implements IViewer {
523
- canvas: HTMLCanvasElement;
524
- gl: WebGLRenderingContext;
525
- ext: ANGLE_instanced_arrays;
526
- camera: OrbitCamera;
527
-
528
- splatData: Uint8Array;
529
- vertexCount: number;
530
- worker: Worker;
531
-
532
- projectionMatrix: number[];
533
- vertexShader: WebGLShader;
534
- fragmentShader: WebGLShader;
535
- program: WebGLProgram;
536
- a_position: number;
537
- a_center: number;
538
- a_color: number;
539
- a_covA: number;
540
- a_covB: number;
541
- vertexBuffer: WebGLBuffer | null;
542
- centerBuffer: WebGLBuffer | null;
543
- colorBuffer: WebGLBuffer | null;
544
- covABuffer: WebGLBuffer | null;
545
- covBBuffer: WebGLBuffer | null;
546
-
547
- dragging = false;
548
- panning = false;
549
- lastX: number = 0;
550
- lastY: number = 0;
551
-
552
- animationFrameId: number | null = null;
553
- disposed: boolean = false;
554
-
555
- constructor(canvas: HTMLCanvasElement) {
556
- this.disposed = false;
557
- this.canvas = canvas;
558
- this.gl = this.initWebGL();
559
- this.ext = this.gl.getExtension("ANGLE_instanced_arrays") as ANGLE_instanced_arrays;
560
- this.camera = new OrbitCamera();
561
-
562
- this.splatData = new Uint8Array();
563
- this.vertexCount = 0;
564
-
565
- this.worker = new Worker(
566
- URL.createObjectURL(
567
- new Blob(["(", createWorker.toString(), ")(self)"], {
568
- type: "application/javascript",
569
- })
570
- )
571
- );
572
-
573
- this.canvas.width = innerWidth;
574
- this.canvas.height = innerHeight;
575
- this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
576
-
577
- this.projectionMatrix = getProjectionMatrix(
578
- this.camera.fx,
579
- this.camera.fy,
580
- this.canvas.width,
581
- this.canvas.height
582
- );
583
-
584
- let viewMatrix = getViewMatrix(this.camera);
585
-
586
- this.vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER) as WebGLShader;
587
- this.gl.shaderSource(this.vertexShader, vertexShaderSource);
588
- this.gl.compileShader(this.vertexShader);
589
- if (!this.gl.getShaderParameter(this.vertexShader, this.gl.COMPILE_STATUS)) {
590
- console.error(this.gl.getShaderInfoLog(this.vertexShader));
591
- }
592
-
593
- this.fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER) as WebGLShader;
594
- this.gl.shaderSource(this.fragmentShader, fragmentShaderSource);
595
- this.gl.compileShader(this.fragmentShader);
596
- if (!this.gl.getShaderParameter(this.fragmentShader, this.gl.COMPILE_STATUS)) {
597
- console.error(this.gl.getShaderInfoLog(this.fragmentShader));
598
- }
599
-
600
- this.program = this.gl.createProgram() as WebGLProgram;
601
- this.gl.attachShader(this.program, this.vertexShader);
602
- this.gl.attachShader(this.program, this.fragmentShader);
603
- this.gl.linkProgram(this.program);
604
- this.gl.useProgram(this.program);
605
-
606
- if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) {
607
- console.error(this.gl.getProgramInfoLog(this.program));
608
- }
609
-
610
- this.gl.disable(this.gl.DEPTH_TEST); // Disable depth testing
611
-
612
- // Enable blending
613
- this.gl.enable(this.gl.BLEND);
614
-
615
- // Set blending function
616
- this.gl.blendFuncSeparate(this.gl.ONE_MINUS_DST_ALPHA, this.gl.ONE, this.gl.ONE_MINUS_DST_ALPHA, this.gl.ONE);
617
-
618
- // Set blending equation
619
- this.gl.blendEquationSeparate(this.gl.FUNC_ADD, this.gl.FUNC_ADD);
620
-
621
- // projection
622
- const u_projection = this.gl.getUniformLocation(this.program, "projection");
623
- this.gl.uniformMatrix4fv(u_projection, false, this.projectionMatrix);
624
-
625
- // viewport
626
- const u_viewport = this.gl.getUniformLocation(this.program, "viewport");
627
- this.gl.uniform2fv(u_viewport, new Float32Array([this.canvas.width, this.canvas.height]));
628
-
629
- // focal
630
- const u_focal = this.gl.getUniformLocation(this.program, "focal");
631
- this.gl.uniform2fv(u_focal, new Float32Array([this.camera.fx, this.camera.fy]));
632
-
633
- // view
634
- const u_view = this.gl.getUniformLocation(this.program, "view");
635
- this.gl.uniformMatrix4fv(u_view, false, viewMatrix);
636
-
637
- // positions
638
- const triangleVertices = new Float32Array([-2, -2, 2, -2, 2, 2, -2, 2]);
639
- this.vertexBuffer = this.gl.createBuffer();
640
- this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);
641
- this.gl.bufferData(this.gl.ARRAY_BUFFER, triangleVertices, this.gl.STATIC_DRAW);
642
-
643
- this.a_position = this.gl.getAttribLocation(this.program, "position");
644
- this.gl.enableVertexAttribArray(this.a_position);
645
- this.gl.vertexAttribPointer(this.a_position, 2, this.gl.FLOAT, false, 0, 0);
646
-
647
- // center
648
- this.centerBuffer = this.gl.createBuffer();
649
- this.a_center = this.gl.getAttribLocation(this.program, "center");
650
- this.gl.enableVertexAttribArray(this.a_center);
651
- this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.centerBuffer);
652
- this.gl.vertexAttribPointer(this.a_center, 3, this.gl.FLOAT, false, 0, 0);
653
- this.ext.vertexAttribDivisorANGLE(this.a_center, 1); // Use the extension here
654
-
655
- // color
656
- this.colorBuffer = this.gl.createBuffer();
657
- this.a_color = this.gl.getAttribLocation(this.program, "color");
658
- this.gl.enableVertexAttribArray(this.a_color);
659
- this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.colorBuffer);
660
- this.gl.vertexAttribPointer(this.a_color, 4, this.gl.FLOAT, false, 0, 0);
661
- this.ext.vertexAttribDivisorANGLE(this.a_color, 1); // Use the extension here
662
-
663
- // cov
664
- this.covABuffer = this.gl.createBuffer();
665
- this.a_covA = this.gl.getAttribLocation(this.program, "covA");
666
- this.gl.enableVertexAttribArray(this.a_covA);
667
- this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.covABuffer);
668
- this.gl.vertexAttribPointer(this.a_covA, 3, this.gl.FLOAT, false, 0, 0);
669
- this.ext.vertexAttribDivisorANGLE(this.a_covA, 1); // Use the extension here
670
-
671
- this.covBBuffer = this.gl.createBuffer();
672
- this.a_covB = this.gl.getAttribLocation(this.program, "covB");
673
- this.gl.enableVertexAttribArray(this.a_covB);
674
- this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.covBBuffer);
675
- this.gl.vertexAttribPointer(this.a_covB, 3, this.gl.FLOAT, false, 0, 0);
676
- this.ext.vertexAttribDivisorANGLE(this.a_covB, 1); // Use the extension here
677
-
678
- console.log(this.vertexBuffer, this.centerBuffer, this.colorBuffer, this.covABuffer, this.covBBuffer);
679
-
680
- this.worker.onmessage = (e) => {
681
- if (e.data.buffer) {
682
- this.splatData = new Uint8Array(e.data.buffer);
683
- const blob = new Blob([this.splatData.buffer], {
684
- type: "application/octet-stream",
685
- });
686
- const link = document.createElement("a");
687
- link.download = "model.splat";
688
- link.href = URL.createObjectURL(blob);
689
- document.body.appendChild(link);
690
- link.click();
691
- } else {
692
- let { covA, covB, center, color } = e.data;
693
- vertexCount = center.length / 3;
694
-
695
- this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.centerBuffer);
696
- this.gl.bufferData(this.gl.ARRAY_BUFFER, center, this.gl.DYNAMIC_DRAW);
697
-
698
- this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.colorBuffer);
699
- this.gl.bufferData(this.gl.ARRAY_BUFFER, color, this.gl.DYNAMIC_DRAW);
700
-
701
- this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.covABuffer);
702
- this.gl.bufferData(this.gl.ARRAY_BUFFER, covA, this.gl.DYNAMIC_DRAW);
703
-
704
- this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.covBBuffer);
705
- this.gl.bufferData(this.gl.ARRAY_BUFFER, covB, this.gl.DYNAMIC_DRAW);
706
- }
707
- };
708
-
709
- let vertexCount = 0;
710
-
711
- const frame = () => {
712
- this.camera.update();
713
- viewMatrix = getViewMatrix(this.camera);
714
-
715
- const viewProj = multiply4(this.projectionMatrix, viewMatrix);
716
- this.worker.postMessage({ view: viewProj });
717
-
718
- if (vertexCount > 0) {
719
- this.gl.uniformMatrix4fv(u_view, false, viewMatrix);
720
- this.ext.drawArraysInstancedANGLE(this.gl.TRIANGLE_FAN, 0, 4, vertexCount);
721
- } else {
722
- this.gl.clear(this.gl.COLOR_BUFFER_BIT);
723
- }
724
-
725
- if (!this.disposed) {
726
- this.animationFrameId = requestAnimationFrame(frame);
727
- }
728
- };
729
-
730
- this.animationFrameId = requestAnimationFrame(frame);
731
-
732
- document.addEventListener("dragenter", preventDefault);
733
- document.addEventListener("dragover", preventDefault);
734
- document.addEventListener("dragleave", preventDefault);
735
- document.addEventListener("contextmenu", preventDefault);
736
-
737
- this.handleTouchStart = this.handleTouchStart.bind(this);
738
- this.handleTouchEnd = this.handleTouchEnd.bind(this);
739
- this.handleTouchMove = this.handleTouchMove.bind(this);
740
- this.handleMouseDown = this.handleMouseDown.bind(this);
741
- this.handleMouseUp = this.handleMouseUp.bind(this);
742
- this.handleMouseMove = this.handleMouseMove.bind(this);
743
- this.handleMouseWheel = this.handleMouseWheel.bind(this);
744
- this.handleDrop = this.handleDrop.bind(this);
745
- this.handleResize = this.handleResize.bind(this);
746
-
747
- this.canvas.addEventListener("touchstart", this.handleTouchStart);
748
- this.canvas.addEventListener("touchend", this.handleTouchEnd);
749
- this.canvas.addEventListener("touchmove", this.handleTouchMove);
750
- this.canvas.addEventListener("mousedown", this.handleMouseDown);
751
- this.canvas.addEventListener("mouseup", this.handleMouseUp);
752
- this.canvas.addEventListener("mousemove", this.handleMouseMove);
753
- this.canvas.addEventListener("wheel", this.handleMouseWheel);
754
- this.canvas.addEventListener("drop", this.handleDrop);
755
-
756
- window.addEventListener("resize", this.handleResize);
757
- }
758
-
759
- initWebGL(): WebGLRenderingContext {
760
- const gl = this.canvas.getContext("webgl") || this.canvas.getContext("experimental-webgl");
761
- if (!gl) {
762
- alert("WebGL is not supported on your browser!");
763
- }
764
- return gl as WebGLRenderingContext;
765
- }
766
-
767
- async loadScene(url: string, onProgress?: (progress: number) => void) {
768
- const req = await fetch(url, {
769
- mode: "cors",
770
- credentials: "omit",
771
- });
772
- console.log(req);
773
- if (req.status != 200) {
774
- throw new Error(req.status + " Unable to load " + req.url);
775
- }
776
-
777
- const rowLength = 3 * 4 + 3 * 4 + 4 + 4;
778
- const reader = req.body!.getReader();
779
- this.splatData = new Uint8Array(req.headers.get("content-length") as any);
780
-
781
- console.log("Vertex Count", this.splatData.length / rowLength);
782
-
783
- let bytesRead = 0;
784
- let lastVertexCount = -1;
785
- let stopLoading = false;
786
-
787
- while (true) {
788
- const { done, value } = await reader.read();
789
- if (done || stopLoading) break;
790
-
791
- this.splatData.set(value, bytesRead);
792
- bytesRead += value.length;
793
-
794
- if (onProgress) {
795
- onProgress(bytesRead / this.splatData.length);
796
- }
797
-
798
- if (this.vertexCount > lastVertexCount) {
799
- this.worker.postMessage({
800
- buffer: this.splatData.buffer,
801
- vertexCount: Math.floor(bytesRead / rowLength),
802
- });
803
- lastVertexCount = this.vertexCount;
804
- }
805
- }
806
- if (!stopLoading) {
807
- this.worker.postMessage({
808
- buffer: this.splatData.buffer,
809
- vertexCount: Math.floor(bytesRead / rowLength),
810
- });
811
- }
812
- }
813
-
814
- handleTouchStart(e: TouchEvent) {
815
- e.preventDefault();
816
- this.dragging = true;
817
- this.panning = e.touches.length === 2;
818
- this.lastX = e.touches[0].clientX;
819
- this.lastY = e.touches[0].clientY;
820
- }
821
-
822
- handleTouchEnd(e: TouchEvent) {
823
- e.preventDefault();
824
- this.dragging = false;
825
- this.panning = false;
826
- }
827
-
828
- handleTouchMove(e: TouchEvent) {
829
- e.preventDefault();
830
- if (!this.dragging) return;
831
- const dx = e.touches[0].clientX - this.lastX;
832
- const dy = e.touches[0].clientY - this.lastY;
833
- const zoomNorm = this.camera.getZoomNorm();
834
-
835
- if (this.panning) {
836
- this.camera.pan(-dx * this.camera.panSpeed * zoomNorm, -dy * this.camera.panSpeed * zoomNorm);
837
- } else {
838
- this.camera.desiredAlpha -= dx * this.camera.orbitSpeed;
839
- this.camera.desiredBeta += dy * this.camera.orbitSpeed;
840
- this.camera.desiredBeta = Math.min(
841
- Math.max(this.camera.desiredBeta, this.camera.minBeta),
842
- this.camera.maxBeta
843
- );
844
- }
845
-
846
- this.lastX = e.touches[0].clientX;
847
- this.lastY = e.touches[0].clientY;
848
- }
849
-
850
- handleMouseDown(e: MouseEvent) {
851
- this.dragging = true;
852
- this.panning = e.button === 2;
853
- this.lastX = e.clientX;
854
- this.lastY = e.clientY;
855
- }
856
-
857
- handleMouseUp(e: MouseEvent) {
858
- this.dragging = false;
859
- this.panning = false;
860
- }
861
-
862
- handleMouseMove(e: MouseEvent) {
863
- if (!this.dragging) return;
864
- const dx = e.clientX - this.lastX;
865
- const dy = e.clientY - this.lastY;
866
- const zoomNorm = this.camera.getZoomNorm();
867
-
868
- if (this.panning) {
869
- this.camera.pan(-dx * this.camera.panSpeed * zoomNorm, -dy * this.camera.panSpeed * zoomNorm);
870
- } else {
871
- this.camera.desiredAlpha -= dx * this.camera.orbitSpeed;
872
- this.camera.desiredBeta += dy * this.camera.orbitSpeed;
873
- this.camera.desiredBeta = Math.min(
874
- Math.max(this.camera.desiredBeta, this.camera.minBeta),
875
- this.camera.maxBeta
876
- );
877
- }
878
-
879
- this.lastX = e.clientX;
880
- this.lastY = e.clientY;
881
- }
882
-
883
- handleMouseWheel(e: WheelEvent) {
884
- const zoomNorm = this.camera.getZoomNorm();
885
- this.camera.desiredRadius += e.deltaY * this.camera.zoomSpeed * zoomNorm;
886
- this.camera.desiredRadius = Math.min(
887
- Math.max(this.camera.desiredRadius, this.camera.minZoom),
888
- this.camera.maxZoom
889
- );
890
- }
891
-
892
- handleDrop(e: DragEvent) {
893
- e.preventDefault();
894
- e.stopPropagation();
895
- this.selectFile(e.dataTransfer!.files[0]);
896
- }
897
-
898
- selectFile(file: File) {
899
- const reader = new FileReader();
900
- reader.onload = () => {
901
- this.splatData = new Uint8Array(reader.result as ArrayBuffer);
902
- if (
903
- this.splatData[0] !== 112 ||
904
- this.splatData[1] !== 108 ||
905
- this.splatData[2] !== 121 ||
906
- this.splatData[3] !== 10
907
- ) {
908
- alert("Invalid file format");
909
- return;
910
- }
911
- this.worker.postMessage({ ply: this.splatData.buffer });
912
- };
913
- reader.readAsArrayBuffer(file);
914
- }
915
-
916
- handleResize() {
917
- this.canvas.width = innerWidth;
918
- this.canvas.height = innerHeight;
919
- this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
920
-
921
- this.projectionMatrix = getProjectionMatrix(
922
- this.camera.fx,
923
- this.camera.fy,
924
- this.canvas.width,
925
- this.canvas.height
926
- );
927
-
928
- const u_projection = this.gl.getUniformLocation(this.program, "projection");
929
- this.gl.uniformMatrix4fv(u_projection, false, this.projectionMatrix);
930
-
931
- const u_viewport = this.gl.getUniformLocation(this.program, "viewport");
932
- this.gl.uniform2fv(u_viewport, new Float32Array([this.canvas.width, this.canvas.height]));
933
- }
934
-
935
- dispose() {
936
- this.worker.terminate();
937
-
938
- this.gl.disableVertexAttribArray(this.a_position);
939
- this.gl.disableVertexAttribArray(this.a_center);
940
- this.gl.disableVertexAttribArray(this.a_color);
941
- this.gl.disableVertexAttribArray(this.a_covA);
942
- this.gl.disableVertexAttribArray(this.a_covB);
943
-
944
- this.gl.deleteBuffer(this.vertexBuffer);
945
- this.gl.deleteBuffer(this.centerBuffer);
946
- this.gl.deleteBuffer(this.colorBuffer);
947
- this.gl.deleteBuffer(this.covABuffer);
948
- this.gl.deleteBuffer(this.covBBuffer);
949
-
950
- this.gl.detachShader(this.program, this.vertexShader);
951
- this.gl.detachShader(this.program, this.fragmentShader);
952
- this.gl.deleteShader(this.vertexShader);
953
- this.gl.deleteShader(this.fragmentShader);
954
-
955
- this.gl.deleteProgram(this.program);
956
-
957
- this.vertexBuffer = null;
958
- this.centerBuffer = null;
959
- this.colorBuffer = null;
960
- this.covABuffer = null;
961
- this.covBBuffer = null;
962
-
963
- this.splatData = new Uint8Array();
964
-
965
- if (this.animationFrameId) {
966
- cancelAnimationFrame(this.animationFrameId);
967
- }
968
-
969
- this.disposed = true;
970
-
971
- document.removeEventListener("dragenter", preventDefault);
972
- document.removeEventListener("dragover", preventDefault);
973
- document.removeEventListener("dragleave", preventDefault);
974
- document.removeEventListener("contextmenu", preventDefault);
975
-
976
- this.canvas.removeEventListener("touchstart", this.handleTouchStart);
977
- this.canvas.removeEventListener("touchend", this.handleTouchEnd);
978
- this.canvas.removeEventListener("touchmove", this.handleTouchMove);
979
- this.canvas.removeEventListener("mousedown", this.handleMouseDown);
980
- this.canvas.removeEventListener("mouseup", this.handleMouseUp);
981
- this.canvas.removeEventListener("mousemove", this.handleMouseMove);
982
- this.canvas.removeEventListener("wheel", this.handleMouseWheel);
983
- this.canvas.removeEventListener("drop", this.handleDrop);
984
-
985
- window.removeEventListener("resize", this.handleResize);
986
- }
987
-
988
- async capture(): Promise<string | null> {
989
- return new Promise((resolve) => {
990
- requestAnimationFrame(() => {
991
- const offscreenCanvas = document.createElement("canvas");
992
- offscreenCanvas.width = 512;
993
- offscreenCanvas.height = 512;
994
- const offscreenContext = offscreenCanvas.getContext("2d")!;
995
-
996
- const x = (this.canvas.width - offscreenCanvas.width) / 2;
997
- const y = (this.canvas.height - offscreenCanvas.height) / 2;
998
-
999
- offscreenContext.drawImage(
1000
- this.canvas,
1001
- x,
1002
- y,
1003
- offscreenCanvas.width,
1004
- offscreenCanvas.height,
1005
- 0,
1006
- 0,
1007
- offscreenCanvas.width,
1008
- offscreenCanvas.height
1009
- );
1010
- const dataURL = offscreenCanvas.toDataURL("image/png");
1011
- offscreenCanvas.remove();
1012
-
1013
- resolve(dataURL);
1014
- });
1015
- });
1016
- }
1017
-
1018
- getStats(): { name: string; value: any }[] {
1019
- return [];
1020
- }
1021
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
viewer/src/routes/viewer/[slug]/SplatViewer.ts CHANGED
@@ -2,38 +2,515 @@ import type { IViewer } from "./IViewer";
2
  import * as SPLAT from "$lib/splat.js";
3
 
4
  export class SplatViewer implements IViewer {
5
- scene: SPLAT.Scene;
 
 
6
  camera: SPLAT.Camera;
7
- renderer: SPLAT.Renderer;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  disposed: boolean = false;
9
 
10
  constructor(canvas: HTMLCanvasElement) {
11
- this.scene = new SPLAT.Scene();
12
- this.camera = new SPLAT.PerspectiveCamera();
13
- this.renderer = new SPLAT.WebGLRenderer(canvas);
14
- }
 
 
15
 
16
- async loadScene(url: string, onProgress?: (progress: number) => void) {
17
- await SPLAT.Loader.LoadAsync(url, this.scene, onProgress);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
  const frame = () => {
20
- this.renderer.render(this.scene, this.camera);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
  if (!this.disposed) {
23
- requestAnimationFrame(frame);
24
  }
25
  };
26
 
27
- this.disposed = false;
28
- requestAnimationFrame(frame);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  }
30
 
31
  dispose() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  this.disposed = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  }
34
 
35
  async capture(): Promise<string | null> {
36
- return null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  }
38
 
39
  getStats(): { name: string; value: any }[] {
 
2
  import * as SPLAT from "$lib/splat.js";
3
 
4
  export class SplatViewer implements IViewer {
5
+ canvas: HTMLCanvasElement;
6
+ gl: WebGLRenderingContext;
7
+ ext: ANGLE_instanced_arrays;
8
  camera: SPLAT.Camera;
9
+ orbitControls: SPLAT.OrbitControls;
10
+
11
+ splatData: Uint8Array;
12
+ vertexCount: number;
13
+ worker: Worker;
14
+
15
+ vertexShader: WebGLShader;
16
+ fragmentShader: WebGLShader;
17
+ program: WebGLProgram;
18
+ a_position: number;
19
+ a_center: number;
20
+ a_color: number;
21
+ a_covA: number;
22
+ a_covB: number;
23
+ vertexBuffer: WebGLBuffer | null;
24
+ centerBuffer: WebGLBuffer | null;
25
+ colorBuffer: WebGLBuffer | null;
26
+ covABuffer: WebGLBuffer | null;
27
+ covBBuffer: WebGLBuffer | null;
28
+
29
+ dragging = false;
30
+ panning = false;
31
+ lastX: number = 0;
32
+ lastY: number = 0;
33
+
34
+ animationFrameId: number | null = null;
35
  disposed: boolean = false;
36
 
37
  constructor(canvas: HTMLCanvasElement) {
38
+ this.disposed = false;
39
+ this.canvas = canvas;
40
+ this.gl = this.initWebGL();
41
+ this.ext = this.gl.getExtension("ANGLE_instanced_arrays") as ANGLE_instanced_arrays;
42
+ this.camera = new SPLAT.Camera();
43
+ this.orbitControls = new SPLAT.OrbitControls(this.camera, this.canvas);
44
 
45
+ this.splatData = new Uint8Array();
46
+ this.vertexCount = 0;
47
+
48
+ this.worker = new Worker(
49
+ URL.createObjectURL(
50
+ new Blob(["(", SPLAT.createWorker.toString(), ")(self)"], {
51
+ type: "application/javascript",
52
+ })
53
+ )
54
+ );
55
+
56
+ const gl = this.gl;
57
+
58
+ this.canvas.width = innerWidth;
59
+ this.canvas.height = innerHeight;
60
+ gl.viewport(0, 0, this.canvas.width, this.canvas.height);
61
+ this.camera.updateProjectionMatrix(this.canvas.width, this.canvas.height);
62
+
63
+ let viewMatrix = SPLAT.getViewMatrix(this.camera);
64
+
65
+ this.vertexShader = gl.createShader(gl.VERTEX_SHADER) as WebGLShader;
66
+ gl.shaderSource(this.vertexShader, SPLAT.vertex);
67
+ gl.compileShader(this.vertexShader);
68
+ if (!gl.getShaderParameter(this.vertexShader, gl.COMPILE_STATUS)) {
69
+ console.error(gl.getShaderInfoLog(this.vertexShader));
70
+ }
71
+
72
+ this.fragmentShader = gl.createShader(gl.FRAGMENT_SHADER) as WebGLShader;
73
+ gl.shaderSource(this.fragmentShader, SPLAT.frag);
74
+ gl.compileShader(this.fragmentShader);
75
+ if (!gl.getShaderParameter(this.fragmentShader, gl.COMPILE_STATUS)) {
76
+ console.error(gl.getShaderInfoLog(this.fragmentShader));
77
+ }
78
+
79
+ this.program = gl.createProgram() as WebGLProgram;
80
+ gl.attachShader(this.program, this.vertexShader);
81
+ gl.attachShader(this.program, this.fragmentShader);
82
+ gl.linkProgram(this.program);
83
+ gl.useProgram(this.program);
84
+
85
+ if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
86
+ console.error(gl.getProgramInfoLog(this.program));
87
+ }
88
+
89
+ gl.disable(gl.DEPTH_TEST); // Disable depth testing
90
+
91
+ // Enable blending
92
+ gl.enable(gl.BLEND);
93
+
94
+ // Set blending function
95
+ gl.blendFuncSeparate(gl.ONE_MINUS_DST_ALPHA, gl.ONE, gl.ONE_MINUS_DST_ALPHA, gl.ONE);
96
+
97
+ // Set blending equation
98
+ gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
99
+
100
+ // projection
101
+ const u_projection = gl.getUniformLocation(this.program, "projection");
102
+ gl.uniformMatrix4fv(u_projection, false, this.camera.projectionMatrix.buffer);
103
+
104
+ // viewport
105
+ const u_viewport = gl.getUniformLocation(this.program, "viewport");
106
+ gl.uniform2fv(u_viewport, new Float32Array([this.canvas.width, this.canvas.height]));
107
+
108
+ // focal
109
+ const u_focal = gl.getUniformLocation(this.program, "focal");
110
+ gl.uniform2fv(u_focal, new Float32Array([this.camera.fx, this.camera.fy]));
111
+
112
+ // view
113
+ const u_view = gl.getUniformLocation(this.program, "view");
114
+ gl.uniformMatrix4fv(u_view, false, viewMatrix.buffer);
115
+
116
+ // positions
117
+ const triangleVertices = new Float32Array([-2, -2, 2, -2, 2, 2, -2, 2]);
118
+ this.vertexBuffer = gl.createBuffer();
119
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
120
+ gl.bufferData(gl.ARRAY_BUFFER, triangleVertices, gl.STATIC_DRAW);
121
+
122
+ this.a_position = gl.getAttribLocation(this.program, "position");
123
+ gl.enableVertexAttribArray(this.a_position);
124
+ gl.vertexAttribPointer(this.a_position, 2, gl.FLOAT, false, 0, 0);
125
+
126
+ // center
127
+ this.centerBuffer = gl.createBuffer();
128
+ this.a_center = gl.getAttribLocation(this.program, "center");
129
+ gl.enableVertexAttribArray(this.a_center);
130
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.centerBuffer);
131
+ gl.vertexAttribPointer(this.a_center, 3, gl.FLOAT, false, 0, 0);
132
+ this.ext.vertexAttribDivisorANGLE(this.a_center, 1); // Use the extension here
133
+
134
+ // color
135
+ this.colorBuffer = gl.createBuffer();
136
+ this.a_color = gl.getAttribLocation(this.program, "color");
137
+ gl.enableVertexAttribArray(this.a_color);
138
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
139
+ gl.vertexAttribPointer(this.a_color, 4, gl.FLOAT, false, 0, 0);
140
+ this.ext.vertexAttribDivisorANGLE(this.a_color, 1); // Use the extension here
141
+
142
+ // cov
143
+ this.covABuffer = gl.createBuffer();
144
+ this.a_covA = gl.getAttribLocation(this.program, "covA");
145
+ gl.enableVertexAttribArray(this.a_covA);
146
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.covABuffer);
147
+ gl.vertexAttribPointer(this.a_covA, 3, gl.FLOAT, false, 0, 0);
148
+ this.ext.vertexAttribDivisorANGLE(this.a_covA, 1); // Use the extension here
149
+
150
+ this.covBBuffer = gl.createBuffer();
151
+ this.a_covB = gl.getAttribLocation(this.program, "covB");
152
+ gl.enableVertexAttribArray(this.a_covB);
153
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.covBBuffer);
154
+ gl.vertexAttribPointer(this.a_covB, 3, gl.FLOAT, false, 0, 0);
155
+ this.ext.vertexAttribDivisorANGLE(this.a_covB, 1); // Use the extension here
156
+
157
+ this.worker.onmessage = (e) => {
158
+ if (e.data.buffer) {
159
+ this.splatData = new Uint8Array(e.data.buffer);
160
+ const blob = new Blob([this.splatData.buffer], {
161
+ type: "application/octet-stream",
162
+ });
163
+ const link = document.createElement("a");
164
+ link.download = "model.splat";
165
+ link.href = URL.createObjectURL(blob);
166
+ document.body.appendChild(link);
167
+ link.click();
168
+ } else {
169
+ let { covA, covB, center, color } = e.data;
170
+ vertexCount = center.length / 3;
171
+
172
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.centerBuffer);
173
+ gl.bufferData(gl.ARRAY_BUFFER, center, gl.DYNAMIC_DRAW);
174
+
175
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
176
+ gl.bufferData(gl.ARRAY_BUFFER, color, gl.DYNAMIC_DRAW);
177
+
178
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.covABuffer);
179
+ gl.bufferData(gl.ARRAY_BUFFER, covA, gl.DYNAMIC_DRAW);
180
+
181
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.covBBuffer);
182
+ gl.bufferData(gl.ARRAY_BUFFER, covB, gl.DYNAMIC_DRAW);
183
+ }
184
+ };
185
+
186
+ let vertexCount = 0;
187
+ let lastTime = performance.now();
188
 
189
  const frame = () => {
190
+ const currentTime = performance.now();
191
+ const deltaTime = (currentTime - lastTime) / 1000;
192
+ lastTime = currentTime;
193
+ console.log(deltaTime);
194
+
195
+ this.orbitControls.update(deltaTime);
196
+
197
+ this.camera.updateProjectionMatrix(this.canvas.width, this.canvas.height);
198
+ viewMatrix = SPLAT.getViewMatrix(this.camera);
199
+
200
+ const viewProj = this.camera.projectionMatrix.multiply(viewMatrix);
201
+ this.worker.postMessage({ view: viewProj.buffer });
202
+
203
+ if (vertexCount > 0) {
204
+ gl.uniformMatrix4fv(u_view, false, viewMatrix.buffer);
205
+ this.ext.drawArraysInstancedANGLE(gl.TRIANGLE_FAN, 0, 4, vertexCount);
206
+ } else {
207
+ gl.clear(gl.COLOR_BUFFER_BIT);
208
+ }
209
 
210
  if (!this.disposed) {
211
+ this.animationFrameId = requestAnimationFrame(frame);
212
  }
213
  };
214
 
215
+ this.animationFrameId = requestAnimationFrame(frame);
216
+
217
+ document.addEventListener("dragenter", this.preventDefault);
218
+ document.addEventListener("dragover", this.preventDefault);
219
+ document.addEventListener("dragleave", this.preventDefault);
220
+ document.addEventListener("contextmenu", this.preventDefault);
221
+
222
+ this.handleTouchStart = this.handleTouchStart.bind(this);
223
+ this.handleTouchEnd = this.handleTouchEnd.bind(this);
224
+ this.handleTouchMove = this.handleTouchMove.bind(this);
225
+ this.handleMouseDown = this.handleMouseDown.bind(this);
226
+ this.handleMouseUp = this.handleMouseUp.bind(this);
227
+ this.handleMouseMove = this.handleMouseMove.bind(this);
228
+ this.handleMouseWheel = this.handleMouseWheel.bind(this);
229
+ this.handleDrop = this.handleDrop.bind(this);
230
+ this.handleResize = this.handleResize.bind(this);
231
+
232
+ this.canvas.addEventListener("touchstart", this.handleTouchStart);
233
+ this.canvas.addEventListener("touchend", this.handleTouchEnd);
234
+ this.canvas.addEventListener("touchmove", this.handleTouchMove);
235
+ this.canvas.addEventListener("mousedown", this.handleMouseDown);
236
+ this.canvas.addEventListener("mouseup", this.handleMouseUp);
237
+ this.canvas.addEventListener("mousemove", this.handleMouseMove);
238
+ this.canvas.addEventListener("wheel", this.handleMouseWheel);
239
+ this.canvas.addEventListener("drop", this.handleDrop);
240
+
241
+ window.addEventListener("resize", this.handleResize);
242
+ }
243
+
244
+ initWebGL(): WebGLRenderingContext {
245
+ const gl = this.canvas.getContext("webgl") || this.canvas.getContext("experimental-webgl");
246
+ if (!gl) {
247
+ alert("WebGL is not supported on your browser!");
248
+ }
249
+ return gl as WebGLRenderingContext;
250
+ }
251
+
252
+ preventDefault(e: Event) {
253
+ e.preventDefault();
254
+ e.stopPropagation();
255
+ }
256
+
257
+ async loadScene(url: string, onProgress?: (progress: number) => void) {
258
+ const req = await fetch(url, {
259
+ mode: "cors",
260
+ credentials: "omit",
261
+ });
262
+ console.log(req);
263
+ if (req.status != 200) {
264
+ throw new Error(req.status + " Unable to load " + req.url);
265
+ }
266
+
267
+ const rowLength = 3 * 4 + 3 * 4 + 4 + 4;
268
+ const reader = req.body!.getReader();
269
+ this.splatData = new Uint8Array(req.headers.get("content-length") as any);
270
+
271
+ console.log("Vertex Count", this.splatData.length / rowLength);
272
+
273
+ let bytesRead = 0;
274
+ let lastVertexCount = -1;
275
+ let stopLoading = false;
276
+
277
+ while (true) {
278
+ const { done, value } = await reader.read();
279
+ if (done || stopLoading) break;
280
+
281
+ this.splatData.set(value, bytesRead);
282
+ bytesRead += value.length;
283
+
284
+ if (onProgress) {
285
+ onProgress(bytesRead / this.splatData.length);
286
+ }
287
+
288
+ if (this.vertexCount > lastVertexCount) {
289
+ this.worker.postMessage({
290
+ buffer: this.splatData.buffer,
291
+ vertexCount: Math.floor(bytesRead / rowLength),
292
+ });
293
+ lastVertexCount = this.vertexCount;
294
+ }
295
+ }
296
+ if (!stopLoading) {
297
+ this.worker.postMessage({
298
+ buffer: this.splatData.buffer,
299
+ vertexCount: Math.floor(bytesRead / rowLength),
300
+ });
301
+ }
302
+ }
303
+
304
+ handleTouchStart(e: TouchEvent) {
305
+ e.preventDefault();
306
+ this.dragging = true;
307
+ this.panning = e.touches.length === 2;
308
+ this.lastX = e.touches[0].clientX;
309
+ this.lastY = e.touches[0].clientY;
310
+ }
311
+
312
+ handleTouchEnd(e: TouchEvent) {
313
+ e.preventDefault();
314
+ this.dragging = false;
315
+ this.panning = false;
316
+ }
317
+
318
+ computeZoomNorm() {
319
+ return (
320
+ 0.1 +
321
+ (0.9 * (this.orbitControls.desiredRadius - this.orbitControls.minZoom)) /
322
+ (this.orbitControls.maxZoom - this.orbitControls.minZoom)
323
+ );
324
+ }
325
+
326
+ handleTouchMove(e: TouchEvent) {
327
+ e.preventDefault();
328
+ if (!this.dragging) return;
329
+ const dx = e.touches[0].clientX - this.lastX;
330
+ const dy = e.touches[0].clientY - this.lastY;
331
+ const zoomNorm = this.computeZoomNorm();
332
+
333
+ if (this.panning) {
334
+ this.orbitControls.pan(
335
+ -dx * this.orbitControls.panSpeed * 0.01 * zoomNorm,
336
+ -dy * this.orbitControls.panSpeed * 0.01 * zoomNorm
337
+ );
338
+ } else {
339
+ this.orbitControls.desiredAlpha -= dx * this.orbitControls.orbitSpeed * 0.005;
340
+ this.orbitControls.desiredBeta += dy * this.orbitControls.orbitSpeed * 0.005;
341
+ this.orbitControls.desiredBeta = Math.min(
342
+ Math.max(this.orbitControls.desiredBeta, this.orbitControls.minBeta),
343
+ this.orbitControls.maxBeta
344
+ );
345
+ }
346
+
347
+ this.lastX = e.touches[0].clientX;
348
+ this.lastY = e.touches[0].clientY;
349
+ }
350
+
351
+ handleMouseDown(e: MouseEvent) {
352
+ this.dragging = true;
353
+ this.panning = e.button === 2;
354
+ this.lastX = e.clientX;
355
+ this.lastY = e.clientY;
356
+ }
357
+
358
+ handleMouseUp(e: MouseEvent) {
359
+ this.dragging = false;
360
+ this.panning = false;
361
+ }
362
+
363
+ handleMouseMove(e: MouseEvent) {
364
+ if (!this.dragging) return;
365
+ const dx = e.clientX - this.lastX;
366
+ const dy = e.clientY - this.lastY;
367
+ const zoomNorm = this.computeZoomNorm();
368
+
369
+ if (this.panning) {
370
+ this.orbitControls.pan(
371
+ -dx * this.orbitControls.panSpeed * 0.01 * zoomNorm,
372
+ -dy * this.orbitControls.panSpeed * 0.01 * zoomNorm
373
+ );
374
+ } else {
375
+ this.orbitControls.desiredAlpha -= dx * this.orbitControls.orbitSpeed * 0.005;
376
+ this.orbitControls.desiredBeta += dy * this.orbitControls.orbitSpeed * 0.005;
377
+ this.orbitControls.desiredBeta = Math.min(
378
+ Math.max(this.orbitControls.desiredBeta, this.orbitControls.minBeta),
379
+ this.orbitControls.maxBeta
380
+ );
381
+ }
382
+
383
+ this.lastX = e.clientX;
384
+ this.lastY = e.clientY;
385
+ }
386
+
387
+ handleMouseWheel(e: WheelEvent) {
388
+ const zoomNorm = this.computeZoomNorm();
389
+ this.orbitControls.desiredRadius += e.deltaY * this.orbitControls.zoomSpeed * 0.02 * zoomNorm;
390
+ this.orbitControls.desiredRadius = Math.min(
391
+ Math.max(this.orbitControls.desiredRadius, this.orbitControls.minZoom),
392
+ this.orbitControls.maxZoom
393
+ );
394
+ }
395
+
396
+ handleDrop(e: DragEvent) {
397
+ e.preventDefault();
398
+ e.stopPropagation();
399
+ this.selectFile(e.dataTransfer!.files[0]);
400
+ }
401
+
402
+ selectFile(file: File) {
403
+ const reader = new FileReader();
404
+ reader.onload = () => {
405
+ this.splatData = new Uint8Array(reader.result as ArrayBuffer);
406
+ if (
407
+ this.splatData[0] !== 112 ||
408
+ this.splatData[1] !== 108 ||
409
+ this.splatData[2] !== 121 ||
410
+ this.splatData[3] !== 10
411
+ ) {
412
+ alert("Invalid file format");
413
+ return;
414
+ }
415
+ this.worker.postMessage({ ply: this.splatData.buffer });
416
+ };
417
+ reader.readAsArrayBuffer(file);
418
+ }
419
+
420
+ handleResize() {
421
+ this.canvas.width = innerWidth;
422
+ this.canvas.height = innerHeight;
423
+ this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
424
+ this.camera.updateProjectionMatrix(this.canvas.width, this.canvas.height);
425
+
426
+ const u_projection = this.gl.getUniformLocation(this.program, "projection");
427
+ this.gl.uniformMatrix4fv(u_projection, false, this.camera.projectionMatrix.buffer);
428
+
429
+ const u_viewport = this.gl.getUniformLocation(this.program, "viewport");
430
+ this.gl.uniform2fv(u_viewport, new Float32Array([this.canvas.width, this.canvas.height]));
431
  }
432
 
433
  dispose() {
434
+ this.worker.terminate();
435
+
436
+ this.gl.disableVertexAttribArray(this.a_position);
437
+ this.gl.disableVertexAttribArray(this.a_center);
438
+ this.gl.disableVertexAttribArray(this.a_color);
439
+ this.gl.disableVertexAttribArray(this.a_covA);
440
+ this.gl.disableVertexAttribArray(this.a_covB);
441
+
442
+ this.gl.deleteBuffer(this.vertexBuffer);
443
+ this.gl.deleteBuffer(this.centerBuffer);
444
+ this.gl.deleteBuffer(this.colorBuffer);
445
+ this.gl.deleteBuffer(this.covABuffer);
446
+ this.gl.deleteBuffer(this.covBBuffer);
447
+
448
+ this.gl.detachShader(this.program, this.vertexShader);
449
+ this.gl.detachShader(this.program, this.fragmentShader);
450
+ this.gl.deleteShader(this.vertexShader);
451
+ this.gl.deleteShader(this.fragmentShader);
452
+
453
+ this.gl.deleteProgram(this.program);
454
+
455
+ this.vertexBuffer = null;
456
+ this.centerBuffer = null;
457
+ this.colorBuffer = null;
458
+ this.covABuffer = null;
459
+ this.covBBuffer = null;
460
+
461
+ this.splatData = new Uint8Array();
462
+
463
+ if (this.animationFrameId) {
464
+ cancelAnimationFrame(this.animationFrameId);
465
+ }
466
+
467
  this.disposed = true;
468
+
469
+ document.removeEventListener("dragenter", this.preventDefault);
470
+ document.removeEventListener("dragover", this.preventDefault);
471
+ document.removeEventListener("dragleave", this.preventDefault);
472
+ document.removeEventListener("contextmenu", this.preventDefault);
473
+
474
+ this.canvas.removeEventListener("touchstart", this.handleTouchStart);
475
+ this.canvas.removeEventListener("touchend", this.handleTouchEnd);
476
+ this.canvas.removeEventListener("touchmove", this.handleTouchMove);
477
+ this.canvas.removeEventListener("mousedown", this.handleMouseDown);
478
+ this.canvas.removeEventListener("mouseup", this.handleMouseUp);
479
+ this.canvas.removeEventListener("mousemove", this.handleMouseMove);
480
+ this.canvas.removeEventListener("wheel", this.handleMouseWheel);
481
+ this.canvas.removeEventListener("drop", this.handleDrop);
482
+
483
+ window.removeEventListener("resize", this.handleResize);
484
  }
485
 
486
  async capture(): Promise<string | null> {
487
+ return new Promise((resolve) => {
488
+ requestAnimationFrame(() => {
489
+ const offscreenCanvas = document.createElement("canvas");
490
+ offscreenCanvas.width = 512;
491
+ offscreenCanvas.height = 512;
492
+ const offscreenContext = offscreenCanvas.getContext("2d")!;
493
+
494
+ const x = (this.canvas.width - offscreenCanvas.width) / 2;
495
+ const y = (this.canvas.height - offscreenCanvas.height) / 2;
496
+
497
+ offscreenContext.drawImage(
498
+ this.canvas,
499
+ x,
500
+ y,
501
+ offscreenCanvas.width,
502
+ offscreenCanvas.height,
503
+ 0,
504
+ 0,
505
+ offscreenCanvas.width,
506
+ offscreenCanvas.height
507
+ );
508
+ const dataURL = offscreenCanvas.toDataURL("image/png");
509
+ offscreenCanvas.remove();
510
+
511
+ resolve(dataURL);
512
+ });
513
+ });
514
  }
515
 
516
  getStats(): { name: string; value: any }[] {