diff --git a/src/file-handler.ts b/src/file-handler.ts index 47b75e3..f617721 100644 --- a/src/file-handler.ts +++ b/src/file-handler.ts @@ -6,7 +6,7 @@ import { Events } from './events'; import { Scene } from './scene'; import { Writer, DownloadWriter, FileStreamWriter } from './serialize/writer'; import { Splat } from './splat'; -import { serializePly, serializePlyCompressed, serializeSplat, serializeViewer, ViewerExportSettings } from './splat-serialize'; +import { serializePly, serializePlyCompressed, SerializeSettings, serializeSplat, serializeViewer, ViewerExportSettings } from './splat-serialize'; import { localize } from './ui/localization'; // ts compiler and vscode find this type, but eslint does not @@ -384,7 +384,7 @@ const initFileHandler = (scene: Scene, events: Events, dropTarget: HTMLElement, const splats = getSplats(); const events = splats[0].scene.events; - const serializeSettings = { + const serializeSettings: SerializeSettings = { maxSHBands: events.invoke('view.bands') }; @@ -393,6 +393,8 @@ const initFileHandler = (scene: Scene, events: Events, dropTarget: HTMLElement, await serializePly(splats, serializeSettings, writer); break; case 'compressed-ply': + serializeSettings.minOpacity = 1 / 255; + serializeSettings.removeInvalid = true; await serializePlyCompressed(splats, serializeSettings, writer); break; case 'splat': diff --git a/src/splat-serialize.ts b/src/splat-serialize.ts index f4a60d3..3870627 100644 --- a/src/splat-serialize.ts +++ b/src/splat-serialize.ts @@ -24,6 +24,7 @@ type SerializeSettings = { maxSHBands?: number; // specifies the maximum number of bands to be exported selected?: boolean; // only export selected gaussians. used for copy/paste minOpacity?: number; // filter out gaussians with alpha less than or equal to minAlpha + removeInvalid?: boolean; // filter out gaussians with invalid data (NaN/Infinity) // the following options are used when serializing the PLY for document save // and are only supported by serializePly @@ -79,7 +80,7 @@ class GaussianFilter { test: (i: number) => boolean; constructor(serializeSettings: SerializeSettings) { - let splat = null; + let splat: Splat = null; let state: Uint8Array = null; let opacity: Float32Array = null; @@ -91,6 +92,7 @@ class GaussianFilter { const onlySelected = serializeSettings.selected ?? false; const minOpacity = serializeSettings.minOpacity ?? 0; + const removeInvalid = serializeSettings.removeInvalid ?? false; this.test = (i: number) => { // splat is deleted, always removed @@ -108,6 +110,22 @@ class GaussianFilter { return false; } + if (removeInvalid) { + const { splatData } = splat; + + // check if any property of the gaussian is NaN/Infinity + for (let j = 0; j < splatData.elements.length; ++j) { + const element = splatData.elements[j]; + for (let k = 0; k < element.properties.length; ++k) { + const prop = element.properties[k]; + const { storage } = prop; + if (storage && !Number.isFinite(storage[i])) { + return false; + } + } + } + } + return true; }; } diff --git a/src/ui/publish-settings-dialog.ts b/src/ui/publish-settings-dialog.ts index c8d7e26..e0dcd3b 100644 --- a/src/ui/publish-settings-dialog.ts +++ b/src/ui/publish-settings-dialog.ts @@ -308,7 +308,8 @@ class PublishSettingsDialog extends Container { const serializeSettings = { maxSHBands: bandsSlider.value, - minOpacity: 1 / 255 // remove completely semitransparent splats + minOpacity: 1 / 255, // remove completely semitransparent splats + removeInvalid: true // remove gaussians with any NaN data }; resolve({