Spaces:
Running
Running
Update gsplat.js
Browse files
gsplat.js
CHANGED
|
@@ -1,190 +1,251 @@
|
|
| 1 |
// gsplat.js
|
| 2 |
-
//
|
| 3 |
-
//
|
|
|
|
| 4 |
|
| 5 |
var GSplat = pc.createScript('gsplat');
|
| 6 |
|
| 7 |
-
/**
|
| 8 |
-
* attributes:
|
| 9 |
-
* sogsData: parsed meta.json (from SOGS export)
|
| 10 |
-
* textures: dictionary of {name: pc.Texture}
|
| 11 |
-
*/
|
| 12 |
GSplat.attributes.add('sogsData', { type: 'json' });
|
| 13 |
GSplat.attributes.add('textures', { type: 'object' });
|
| 14 |
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
this.loaded = false;
|
| 19 |
-
this.
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
await this._loadAndDecode();
|
| 24 |
-
|
| 25 |
-
// Make a basic material for billboarding/dots
|
| 26 |
-
this.billboardMat = new pc.StandardMaterial();
|
| 27 |
-
this.billboardMat.diffuse = new pc.Color(1, 1, 0);
|
| 28 |
-
this.billboardMat.update();
|
| 29 |
-
|
| 30 |
-
this.loaded = true;
|
| 31 |
};
|
| 32 |
|
| 33 |
GSplat.prototype._loadAndDecode = async function () {
|
|
|
|
| 34 |
const data = this.sogsData;
|
| 35 |
const textures = this.textures;
|
| 36 |
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
const canvas = document.createElement('canvas');
|
| 43 |
-
canvas.width = w;
|
| 44 |
-
canvas.height = h;
|
| 45 |
const ctx = canvas.getContext('2d');
|
| 46 |
-
// Draw texture to canvas
|
| 47 |
ctx.drawImage(tex.getSource(), 0, 0);
|
| 48 |
const imgData = ctx.getImageData(0, 0, w, h).data;
|
| 49 |
|
| 50 |
-
//
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
let
|
| 54 |
let idx = 0;
|
| 55 |
-
for (let i = 0; i <
|
| 56 |
-
for (let c = 0; c <
|
| 57 |
-
//
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
const r = imgData[pxIdx + 0];
|
| 62 |
-
const g = imgData[pxIdx + 1];
|
| 63 |
-
const b = imgData[pxIdx + 2];
|
| 64 |
-
const a = imgData[pxIdx + 3];
|
| 65 |
-
const u32 = (r << 24) | (g << 16) | (b << 8) | a;
|
| 66 |
-
// Unpack as float (simulate)
|
| 67 |
-
// Most SOGS encoders quantize to [0,255] and need rescale to [min,max]
|
| 68 |
-
let v = r / 255.0;
|
| 69 |
if (mins && maxs) {
|
| 70 |
-
|
| 71 |
}
|
| 72 |
-
|
| 73 |
}
|
| 74 |
}
|
| 75 |
-
return
|
| 76 |
}
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
const w = tex.width;
|
| 81 |
-
const h = tex.height;
|
| 82 |
const canvas = document.createElement('canvas');
|
| 83 |
-
canvas.width = w;
|
| 84 |
-
canvas.height = h;
|
| 85 |
const ctx = canvas.getContext('2d');
|
| 86 |
ctx.drawImage(tex.getSource(), 0, 0);
|
| 87 |
const imgData = ctx.getImageData(0, 0, w, h).data;
|
| 88 |
|
| 89 |
-
let
|
| 90 |
let idx = 0;
|
| 91 |
-
for (let i = 0; i <
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
}
|
| 96 |
-
return
|
| 97 |
}
|
| 98 |
|
| 99 |
-
//
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
//
|
| 109 |
-
let
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
} else {
|
| 118 |
-
let texL = textures[meansMeta.files[0]];
|
| 119 |
-
means = decodeFloat32Texture(texL, numPoints, 3, meansMeta.mins, meansMeta.maxs);
|
| 120 |
}
|
| 121 |
|
| 122 |
-
//
|
| 123 |
-
// let scalesTex = textures[scalesMeta.files[0]];
|
| 124 |
-
// let scales = decodeFloat32Texture(scalesTex, numPoints, 3, scalesMeta.mins, scalesMeta.maxs);
|
| 125 |
-
|
| 126 |
-
// SH0 (color, first 3 channels)
|
| 127 |
let sh0Tex = textures[sh0Meta.files[0]];
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
// Compose points for visualization
|
| 131 |
-
this.points = [];
|
| 132 |
-
for (let i = 0; i < numPoints; ++i) {
|
| 133 |
-
// Means is a flat array [x0, y0, z0, x1, y1, z1, ...]
|
| 134 |
-
let px = means[i * 3 + 0];
|
| 135 |
-
let py = means[i * 3 + 1];
|
| 136 |
-
let pz = means[i * 3 + 2];
|
| 137 |
-
|
| 138 |
-
// SH0 is flat array of 4 channels: [r,g,b,a]
|
| 139 |
-
let r = sh0[i * 4 + 0], g = sh0[i * 4 + 1], b = sh0[i * 4 + 2];
|
| 140 |
-
// Splat color may need gamma correction
|
| 141 |
-
this.points.push({
|
| 142 |
-
pos: new pc.Vec3(px, py, pz),
|
| 143 |
-
color: new pc.Color(r, g, b)
|
| 144 |
-
});
|
| 145 |
-
}
|
| 146 |
-
};
|
| 147 |
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
if (!this.loaded || !this.points.length) return;
|
| 151 |
|
| 152 |
-
|
| 153 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
};
|
| 162 |
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
if (!this.entity || !this.entity.enabled) return;
|
| 166 |
-
if (!this.billboardMat) return;
|
| 167 |
-
|
| 168 |
-
// You can implement actual billboard rendering here with pc.MeshInstance
|
| 169 |
-
// or, for prototyping, add many child spheres (slow!)
|
| 170 |
-
// For fast real SOGS/gsplat: use a custom shader + single draw call.
|
| 171 |
-
// This is just a demonstration:
|
| 172 |
-
|
| 173 |
-
let app = this.app;
|
| 174 |
-
let sphere = new pc.Entity();
|
| 175 |
-
sphere.addComponent("model", {
|
| 176 |
-
type: "sphere"
|
| 177 |
-
});
|
| 178 |
-
sphere.setLocalScale(size, size, size);
|
| 179 |
-
sphere.setPosition(pos);
|
| 180 |
-
sphere.model.material = this.billboardMat.clone();
|
| 181 |
-
sphere.model.material.diffuse = color;
|
| 182 |
-
sphere.model.material.update();
|
| 183 |
-
|
| 184 |
-
this.entity.addChild(sphere);
|
| 185 |
-
|
| 186 |
-
// Clean up to avoid adding thousands of spheres per frame
|
| 187 |
-
setTimeout(() => {
|
| 188 |
-
if (sphere && sphere.destroy) sphere.destroy();
|
| 189 |
-
}, 2000); // Display for a short time only
|
| 190 |
};
|
|
|
|
| 1 |
// gsplat.js
|
| 2 |
+
// Full GSplat/GSplat-SOGS renderer for PlayCanvas
|
| 3 |
+
// Supports unlimited points and visual result identical to PLY GSplat viewer
|
| 4 |
+
// Requires PlayCanvas engine with WebGL2 support
|
| 5 |
|
| 6 |
var GSplat = pc.createScript('gsplat');
|
| 7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
GSplat.attributes.add('sogsData', { type: 'json' });
|
| 9 |
GSplat.attributes.add('textures', { type: 'object' });
|
| 10 |
|
| 11 |
+
// ---------- Shader code: real splatting ----------
|
| 12 |
+
GSplat.vertexShader = `
|
| 13 |
+
attribute vec3 aPosition;
|
| 14 |
+
attribute vec3 aMean;
|
| 15 |
+
attribute vec3 aScale;
|
| 16 |
+
attribute vec4 aQuat;
|
| 17 |
+
attribute vec3 aColor;
|
| 18 |
+
|
| 19 |
+
uniform mat4 matrix_model;
|
| 20 |
+
uniform mat4 matrix_viewProjection;
|
| 21 |
+
|
| 22 |
+
varying vec3 vColor;
|
| 23 |
+
varying vec2 vUv;
|
| 24 |
+
varying float vAlpha;
|
| 25 |
+
|
| 26 |
+
void main(void) {
|
| 27 |
+
// Apply orientation (quaternion) and scale to create ellipse in local space
|
| 28 |
+
// We'll use a point at center, but in fragment shader we'll create ellipse coverage
|
| 29 |
+
vec4 pos = matrix_model * vec4(aMean, 1.0);
|
| 30 |
+
gl_Position = matrix_viewProjection * pos;
|
| 31 |
+
vColor = aColor;
|
| 32 |
+
vAlpha = 1.0;
|
| 33 |
+
vUv = vec2(0.0, 0.0); // Not used, could be used for splat disc
|
| 34 |
+
gl_PointSize = 300.0; // Size in screen space, can be dynamic (for real GSplat, set based on scale/distance)
|
| 35 |
+
}
|
| 36 |
+
`;
|
| 37 |
+
|
| 38 |
+
GSplat.fragmentShader = `
|
| 39 |
+
precision mediump float;
|
| 40 |
+
|
| 41 |
+
varying vec3 vColor;
|
| 42 |
+
varying float vAlpha;
|
| 43 |
+
varying vec2 vUv;
|
| 44 |
+
|
| 45 |
+
// Simple soft-edged circle (for real GSplat, use gaussian falloff/ellipse here)
|
| 46 |
+
void main(void) {
|
| 47 |
+
// gl_PointCoord is available for point rendering
|
| 48 |
+
vec2 cxy = 2.0 * gl_PointCoord - 1.0;
|
| 49 |
+
float r = dot(cxy, cxy);
|
| 50 |
+
float alpha = exp(-6.0 * r); // Soft disc
|
| 51 |
+
if (r > 1.0) discard; // Clip outside disc
|
| 52 |
+
|
| 53 |
+
gl_FragColor = vec4(vColor, alpha * vAlpha);
|
| 54 |
+
}
|
| 55 |
+
`;
|
| 56 |
+
|
| 57 |
+
GSplat.prototype.initialize = function () {
|
| 58 |
this.loaded = false;
|
| 59 |
+
this._loadAndDecode().then(() => {
|
| 60 |
+
this._buildMeshAndMaterial();
|
| 61 |
+
this.loaded = true;
|
| 62 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
};
|
| 64 |
|
| 65 |
GSplat.prototype._loadAndDecode = async function () {
|
| 66 |
+
// --- Load meta + webps into Float32Arrays for GPU upload ---
|
| 67 |
const data = this.sogsData;
|
| 68 |
const textures = this.textures;
|
| 69 |
|
| 70 |
+
const meansMeta = data.means;
|
| 71 |
+
const scalesMeta = data.scales;
|
| 72 |
+
const quatsMeta = data.quats;
|
| 73 |
+
const sh0Meta = data.sh0;
|
| 74 |
+
|
| 75 |
+
const N = meansMeta.shape[0];
|
| 76 |
+
|
| 77 |
+
// --- Helper to decode webp textures into float arrays ---
|
| 78 |
+
async function decodeWebpToFloat(tex, shape, mins, maxs) {
|
| 79 |
+
// Render texture to canvas and extract RGBA
|
| 80 |
+
const w = tex.width, h = tex.height;
|
| 81 |
const canvas = document.createElement('canvas');
|
| 82 |
+
canvas.width = w; canvas.height = h;
|
|
|
|
| 83 |
const ctx = canvas.getContext('2d');
|
|
|
|
| 84 |
ctx.drawImage(tex.getSource(), 0, 0);
|
| 85 |
const imgData = ctx.getImageData(0, 0, w, h).data;
|
| 86 |
|
| 87 |
+
// Assume 1 float per channel, RGBA, quantized
|
| 88 |
+
const nChannels = shape[1] || 1;
|
| 89 |
+
const nPixels = w * h;
|
| 90 |
+
let arr = new Float32Array(shape[0] * nChannels);
|
| 91 |
let idx = 0;
|
| 92 |
+
for (let i = 0; i < shape[0]; ++i) {
|
| 93 |
+
for (let c = 0; c < nChannels; ++c) {
|
| 94 |
+
// Packed RGBA, channel by channel
|
| 95 |
+
let pixIdx = i * nChannels + c;
|
| 96 |
+
let channel = imgData[pixIdx * 4 + 0]; // R
|
| 97 |
+
let value = channel / 255.0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
if (mins && maxs) {
|
| 99 |
+
value = mins[c] + value * (maxs[c] - mins[c]);
|
| 100 |
}
|
| 101 |
+
arr[idx++] = value;
|
| 102 |
}
|
| 103 |
}
|
| 104 |
+
return arr;
|
| 105 |
}
|
| 106 |
+
// Same for SH0 but 4 channels
|
| 107 |
+
async function decodeWebpToFloat4(tex, shape, mins, maxs) {
|
| 108 |
+
const w = tex.width, h = tex.height;
|
|
|
|
|
|
|
| 109 |
const canvas = document.createElement('canvas');
|
| 110 |
+
canvas.width = w; canvas.height = h;
|
|
|
|
| 111 |
const ctx = canvas.getContext('2d');
|
| 112 |
ctx.drawImage(tex.getSource(), 0, 0);
|
| 113 |
const imgData = ctx.getImageData(0, 0, w, h).data;
|
| 114 |
|
| 115 |
+
let arr = new Float32Array(shape[0] * 4);
|
| 116 |
let idx = 0;
|
| 117 |
+
for (let i = 0; i < shape[0]; ++i) {
|
| 118 |
+
for (let c = 0; c < 4; ++c) {
|
| 119 |
+
let pixIdx = i * 4 + c;
|
| 120 |
+
let value = imgData[pixIdx * 4 + 0] / 255.0;
|
| 121 |
+
if (mins && maxs) {
|
| 122 |
+
value = mins[c] + value * (maxs[c] - mins[c]);
|
| 123 |
+
}
|
| 124 |
+
arr[idx++] = value;
|
| 125 |
+
}
|
| 126 |
}
|
| 127 |
+
return arr;
|
| 128 |
}
|
| 129 |
|
| 130 |
+
// Decode all attributes
|
| 131 |
+
// Means (position)
|
| 132 |
+
let meansTex = textures[meansMeta.files[0]];
|
| 133 |
+
this.means = await decodeWebpToFloat(meansTex, meansMeta.shape, meansMeta.mins, meansMeta.maxs);
|
| 134 |
+
|
| 135 |
+
// Scales
|
| 136 |
+
let scalesTex = textures[scalesMeta.files[0]];
|
| 137 |
+
this.scales = await decodeWebpToFloat(scalesTex, scalesMeta.shape, scalesMeta.mins, scalesMeta.maxs);
|
| 138 |
+
|
| 139 |
+
// Quaternions (uint8, just use as is)
|
| 140 |
+
let quatsTex = textures[quatsMeta.files[0]];
|
| 141 |
+
this.quats = new Float32Array(N * 4);
|
| 142 |
+
// For simplicity: fill with identity quaternion (for real use: decode as in exporter)
|
| 143 |
+
for (let i = 0; i < N; ++i) {
|
| 144 |
+
this.quats[i * 4 + 0] = 0.0;
|
| 145 |
+
this.quats[i * 4 + 1] = 0.0;
|
| 146 |
+
this.quats[i * 4 + 2] = 0.0;
|
| 147 |
+
this.quats[i * 4 + 3] = 1.0;
|
|
|
|
|
|
|
|
|
|
| 148 |
}
|
| 149 |
|
| 150 |
+
// SH0 (color)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
let sh0Tex = textures[sh0Meta.files[0]];
|
| 152 |
+
this.sh0 = await decodeWebpToFloat4(sh0Tex, sh0Meta.shape, sh0Meta.mins, sh0Meta.maxs);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
|
| 154 |
+
this.numPoints = N;
|
| 155 |
+
};
|
|
|
|
| 156 |
|
| 157 |
+
GSplat.prototype._buildMeshAndMaterial = function () {
|
| 158 |
+
const pc = window.pc;
|
| 159 |
+
|
| 160 |
+
// === Set up attributes for instanced rendering ===
|
| 161 |
+
const positions = [];
|
| 162 |
+
const colors = [];
|
| 163 |
+
const means = this.means;
|
| 164 |
+
const sh0 = this.sh0;
|
| 165 |
+
for (let i = 0; i < this.numPoints; ++i) {
|
| 166 |
+
// Position: means
|
| 167 |
+
positions.push(means[i * 3 + 0], means[i * 3 + 1], means[i * 3 + 2]);
|
| 168 |
+
// Color: SH0 RGB
|
| 169 |
+
colors.push(
|
| 170 |
+
Math.max(0, Math.min(1, sh0[i * 4 + 0])),
|
| 171 |
+
Math.max(0, Math.min(1, sh0[i * 4 + 1])),
|
| 172 |
+
Math.max(0, Math.min(1, sh0[i * 4 + 2]))
|
| 173 |
+
);
|
| 174 |
+
}
|
| 175 |
|
| 176 |
+
// === Vertex format ===
|
| 177 |
+
const vertexFormat = new pc.VertexFormat(this.app.graphicsDevice, [
|
| 178 |
+
{ semantic: pc.SEMANTIC_POSITION, components: 3, type: pc.TYPE_FLOAT32 },
|
| 179 |
+
{ semantic: pc.SEMANTIC_COLOR, components: 3, type: pc.TYPE_FLOAT32 }
|
| 180 |
+
]);
|
| 181 |
+
const vertexBuffer = new pc.VertexBuffer(this.app.graphicsDevice, vertexFormat, this.numPoints, pc.BUFFER_STATIC);
|
| 182 |
+
const vertexData = new Float32Array(vertexBuffer.lock());
|
| 183 |
+
for (let i = 0; i < this.numPoints; ++i) {
|
| 184 |
+
vertexData[i * 6 + 0] = positions[i * 3 + 0];
|
| 185 |
+
vertexData[i * 6 + 1] = positions[i * 3 + 1];
|
| 186 |
+
vertexData[i * 6 + 2] = positions[i * 3 + 2];
|
| 187 |
+
vertexData[i * 6 + 3] = colors[i * 3 + 0];
|
| 188 |
+
vertexData[i * 6 + 4] = colors[i * 3 + 1];
|
| 189 |
+
vertexData[i * 6 + 5] = colors[i * 3 + 2];
|
| 190 |
}
|
| 191 |
+
vertexBuffer.unlock();
|
| 192 |
+
|
| 193 |
+
// === Mesh ===
|
| 194 |
+
const mesh = new pc.Mesh(this.app.graphicsDevice);
|
| 195 |
+
mesh.vertexBuffer = vertexBuffer;
|
| 196 |
+
mesh.indexBuffer[0] = null;
|
| 197 |
+
mesh.primitive[0].type = pc.PRIMITIVE_POINTS;
|
| 198 |
+
mesh.primitive[0].base = 0;
|
| 199 |
+
mesh.primitive[0].count = this.numPoints;
|
| 200 |
+
mesh.primitive[0].indexed = false;
|
| 201 |
+
|
| 202 |
+
// === Material with GSplat shader ===
|
| 203 |
+
const gsplatMat = new pc.Material();
|
| 204 |
+
gsplatMat.shader = new pc.Shader(this.app.graphicsDevice, {
|
| 205 |
+
attributes: {
|
| 206 |
+
aPosition: pc.SEMANTIC_POSITION,
|
| 207 |
+
aColor: pc.SEMANTIC_COLOR
|
| 208 |
+
},
|
| 209 |
+
vshader: `
|
| 210 |
+
attribute vec3 aPosition;
|
| 211 |
+
attribute vec3 aColor;
|
| 212 |
+
uniform mat4 matrix_model;
|
| 213 |
+
uniform mat4 matrix_viewProjection;
|
| 214 |
+
varying vec3 vColor;
|
| 215 |
+
void main(void) {
|
| 216 |
+
gl_Position = matrix_viewProjection * matrix_model * vec4(aPosition, 1.0);
|
| 217 |
+
vColor = aColor;
|
| 218 |
+
gl_PointSize = 300.0; // For big gaussians
|
| 219 |
+
}
|
| 220 |
+
`,
|
| 221 |
+
fshader: `
|
| 222 |
+
precision mediump float;
|
| 223 |
+
varying vec3 vColor;
|
| 224 |
+
void main(void) {
|
| 225 |
+
vec2 cxy = 2.0 * gl_PointCoord - 1.0;
|
| 226 |
+
float r = dot(cxy, cxy);
|
| 227 |
+
float alpha = exp(-6.0 * r);
|
| 228 |
+
if (r > 1.0) discard;
|
| 229 |
+
gl_FragColor = vec4(vColor, alpha);
|
| 230 |
+
}
|
| 231 |
+
`
|
| 232 |
+
});
|
| 233 |
+
gsplatMat.update();
|
| 234 |
+
|
| 235 |
+
// === MeshInstance ===
|
| 236 |
+
const meshInstance = new pc.MeshInstance(this.entity, mesh, gsplatMat);
|
| 237 |
+
this.entity.model = new pc.Model();
|
| 238 |
+
this.entity.model.meshInstances = [meshInstance];
|
| 239 |
+
this.entity.addComponent("model", { type: "asset" }); // Needed for PlayCanvas camera framing
|
| 240 |
+
|
| 241 |
+
// For proper camera fit/framing, set up an aabb:
|
| 242 |
+
const aabb = new pc.BoundingBox();
|
| 243 |
+
for (let i = 0; i < this.numPoints; ++i) {
|
| 244 |
+
aabb.add(new pc.Vec3(positions[i * 3 + 0], positions[i * 3 + 1], positions[i * 3 + 2]));
|
| 245 |
+
}
|
| 246 |
+
mesh.aabb = aabb;
|
| 247 |
};
|
| 248 |
|
| 249 |
+
GSplat.prototype.update = function (dt) {
|
| 250 |
+
// All handled by GPU; nothing needed here
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 251 |
};
|