Skip to content

Commit

Permalink
Add splat export (#19)
Browse files Browse the repository at this point in the history
* add splat export

* v0.9.5
  • Loading branch information
slimbuck authored Nov 7, 2023
1 parent b362cc9 commit 075f14e
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 12 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "super-splat",
"version": "0.9.4",
"version": "0.9.5",
"author": "PlayCanvas<support@playcanvas.com>",
"homepage": "https://playcanvas.com",
"description": "All the splat things",
Expand Down
93 changes: 88 additions & 5 deletions src/editor-ops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,79 @@ const convertPly = (splatData: SplatData, modelMat: Mat4) => {
return result;
};

const convertSplat = (splatData: SplatData, modelMat: Mat4) => {
// count the number of non-deleted splats
const x = splatData.getProp('x');
const y = splatData.getProp('y');
const z = splatData.getProp('z');
const opacity = splatData.getProp('opacity');
const rot_0 = splatData.getProp('rot_0');
const rot_1 = splatData.getProp('rot_1');
const rot_2 = splatData.getProp('rot_2');
const rot_3 = splatData.getProp('rot_3');
const f_dc_0 = splatData.getProp('f_dc_0');
const f_dc_1 = splatData.getProp('f_dc_1');
const f_dc_2 = splatData.getProp('f_dc_2');
const scale_0 = splatData.getProp('scale_0');
const scale_1 = splatData.getProp('scale_1');
const scale_2 = splatData.getProp('scale_2');

// count number of non-deleted splats
let numSplats = 0;
for (let i = 0; i < splatData.numSplats; ++i) {
numSplats += opacity[i] !== deletedOpacity ? 1 : 0;
}

// position.xyz: float32, scale.xyz: float32, color.rgba: uint8, quaternion.ijkl: uint8
const result = new Uint8Array(numSplats * 32);
const dataView = new DataView(result.buffer);

// we must undo the transform we apply at load time to output data
const mat = new Mat4();
mat.setScale(-1, -1, 1);
mat.invert();
mat.mul2(mat, modelMat);

const quat = new Quat();
quat.setFromMat4(mat);

const v = new Vec3();
const q = new Quat();

const clamp = (x: number) => Math.max(0, Math.min(255, x));
let idx = 0;

for (let i = 0; i < splatData.numSplats; ++i) {
if (opacity[i] === deletedOpacity) continue;

const off = idx++ * 32;

v.set(x[i], y[i], z[i]);
mat.transformPoint(v, v);
dataView.setFloat32(off + 0, v.x, true);
dataView.setFloat32(off + 4, v.y, true);
dataView.setFloat32(off + 8, v.z, true);

dataView.setFloat32(off + 12, Math.exp(scale_0[i]), true);
dataView.setFloat32(off + 16, Math.exp(scale_1[i]), true);
dataView.setFloat32(off + 20, Math.exp(scale_2[i]), true);

const SH_C0 = 0.28209479177387814;
dataView.setUint8(off + 24, clamp((0.5 + SH_C0 * f_dc_0[i]) * 255));
dataView.setUint8(off + 25, clamp((0.5 + SH_C0 * f_dc_1[i]) * 255));
dataView.setUint8(off + 26, clamp((0.5 + SH_C0 * f_dc_2[i]) * 255));
dataView.setUint8(off + 27, clamp((1 / (1 + Math.exp(-opacity[i]))) * 255));

q.set(rot_1[i], rot_2[i], rot_3[i], rot_0[i]).mul2(quat, q).normalize();
dataView.setUint8(off + 28, clamp(q.w * 128 + 128));
dataView.setUint8(off + 29, clamp(q.x * 128 + 128));
dataView.setUint8(off + 30, clamp(q.y * 128 + 128));
dataView.setUint8(off + 31, clamp(q.z * 128 + 128));
}

return result;
};

// register for editor and scene events
const registerEvents = (scene: Scene, editorUI: EditorUI) => {
const debugs = new Map<SplatData, SplatDebug>();
Expand Down Expand Up @@ -601,11 +674,21 @@ const registerEvents = (scene: Scene, editorUI: EditorUI) => {
});
});

events.on('export', () => {
forEachSplat((splatData: SplatData, resource: any) => {
const data = convertPly(splatData, resource.instances[0].entity.getWorldTransform());
download(resource.name + '.ply', data);
});
events.on('export', (format: string) => {
switch (format) {
case 'ply':
forEachSplat((splatData: SplatData, resource: any) => {
const data = convertPly(splatData, resource.instances[0].entity.getWorldTransform());
download(resource.name + '.ply', data);
});
break;
case 'splat':
forEachSplat((splatData: SplatData, resource: any) => {
const data = convertSplat(splatData, resource.instances[0].entity.getWorldTransform());
download(resource.name + '.splat', data);
});
break;
}
});

events.on('undo', () => {
Expand Down
18 changes: 14 additions & 4 deletions src/ui/control-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -581,12 +581,18 @@ class ControlPanel extends Container {
headerText: 'Export to'
});

const exportButton = new Button({
const exportPlyButton = new Button({
class: 'control-element',
text: 'Ply file'
});

exportPanel.append(exportButton);
const exportSplatButton = new Button({
class: 'control-element',
text: 'Splat file'
});

exportPanel.append(exportPlyButton);
exportPanel.append(exportSplatButton);

// keyboard
const keyboardPanel = new Panel({
Expand Down Expand Up @@ -767,8 +773,12 @@ class ControlPanel extends Container {
this.events.fire('reset');
});

exportButton.on('click', () => {
this.events.fire('export');
exportPlyButton.on('click', () => {
this.events.fire('export', 'ply');
});

exportSplatButton.on('click', () => {
this.events.fire('export', 'splat');
});

this.events.on('splat:count', (count: number) => {
Expand Down

0 comments on commit 075f14e

Please sign in to comment.