Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ Actions can be repeated and applied in any order:
```none
-h, --help Show this help and exit
-v, --version Show version and exit
-q, --quiet Suppress non-error output
-w, --overwrite Overwrite output file if it exists
-c, --cpu Use CPU for SOG spherical harmonic compression
-i, --iterations <n> Iterations for SOG SH compression (more=better). Default: 10
Expand Down
2 changes: 2 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export default [
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'jsdoc/require-param-type': 'off',
'jsdoc/require-returns-type': 'off',
'lines-between-class-members': 'off',
'no-await-in-loop': 'off',
'require-atomic-updates': 'off'
Expand Down
4 changes: 3 additions & 1 deletion src/gpu/gpu-device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import {
} from 'playcanvas';
import { create, globals } from 'webgpu';

import { logger } from '../logger';

const initializeGlobals = () => {
Object.assign(globalThis, globals);

Expand Down Expand Up @@ -132,7 +134,7 @@ const createDevice = async () => {

// print gpu info
const info = (graphicsDevice as any).gpuAdapter.info;
console.log(`Created gpu device="${info.device || '-'}" arch="${info.architecture || '-'}" descr="${info.description || '-'}"`);
logger.debug(`Created gpu device="${info.device || '-'}" arch="${info.architecture || '-'}" descr="${info.description || '-'}"`);

// create the application
const app = new Application(canvas, { graphicsDevice });
Expand Down
25 changes: 16 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Vec3 } from 'playcanvas';

import { version } from '../package.json';
import { Column, DataTable, TypedArray } from './data-table';
import { logger } from './logger';
import { ProcessAction, processDataTable } from './process';
import { isCompressedPly, decompressPly } from './readers/decompress-ply';
import { readKsplat } from './readers/read-ksplat';
Expand Down Expand Up @@ -87,7 +88,7 @@ const readFile = async (filename: string, options: Options, params: Param[]): Pr
const inputFormat = getInputFormat(filename);
let result: DataTable[];

console.log(`reading '${filename}'...`);
logger.info(`reading '${filename}'...`);

if (inputFormat === 'mjs') {
result = [await readMjs(filename, params)];
Expand Down Expand Up @@ -126,7 +127,7 @@ const writeFile = async (filename: string, dataTable: DataTable, options: Option
// get the output format, throws on failure
const outputFormat = getOutputFormat(filename);

console.log(`writing '${filename}'...`);
logger.info(`writing '${filename}'...`);

// write to a temporary file and rename on success
const tmpFilename = `.${basename(filename)}.${process.pid}.${Date.now()}.${randomBytes(6).toString('hex')}.tmp`;
Expand Down Expand Up @@ -259,6 +260,7 @@ const parseArguments = () => {
overwrite: { type: 'boolean', short: 'w', default: false },
help: { type: 'boolean', short: 'h', default: false },
version: { type: 'boolean', short: 'v', default: false },
quiet: { type: 'boolean', short: 'q', default: false },
cpu: { type: 'boolean', short: 'c', default: false },
iterations: { type: 'string', short: 'i', default: '10' },
'lod-select': { type: 'string', short: 'O', default: '' },
Expand Down Expand Up @@ -323,6 +325,7 @@ const parseArguments = () => {
overwrite: v.overwrite,
help: v.help,
version: v.version,
quiet: v.quiet,
cpu: v.cpu,
iterations: parseInteger(v.iterations),
lodSelect: v['lod-select'].split(',').filter(v => !!v).map(parseInteger),
Expand Down Expand Up @@ -486,6 +489,7 @@ ACTIONS (can be repeated, in any order)
GLOBAL OPTIONS
-h, --help Show this help and exit
-v, --version Show version and exit
-q, --quiet Suppress non-error output
-w, --overwrite Overwrite output file if it exists
-c, --cpu Use CPU for SOG spherical harmonic compression
-i, --iterations <n> Iterations for SOG SH compression (more=better). Default: 10
Expand All @@ -512,21 +516,24 @@ EXAMPLES
`;

const main = async () => {
console.log(`splat-transform v${version}`);

const startTime = hrtime();

// read args
const { files, options } = parseArguments();

// configure logger
logger.setQuiet(options.quiet);

logger.info(`splat-transform v${version}`);

// show version and exit
if (options.version) {
exit(0);
}

// invalid args or show help
if (files.length < 2 || options.help) {
console.error(usage);
logger.error(usage);
exit(1);
}

Expand All @@ -541,7 +548,7 @@ const main = async () => {
} else {
// check overwrite before doing any work
if (await fileExists(outputFilename)) {
console.error(`File '${outputFilename}' already exists. Use -w option to overwrite.`);
logger.error(`File '${outputFilename}' already exists. Use -w option to overwrite.`);
exit(1);
}
}
Expand Down Expand Up @@ -580,19 +587,19 @@ const main = async () => {
throw new Error('No splats to write');
}

console.log(`Loaded ${dataTable.numRows} gaussians`);
logger.info(`Loaded ${dataTable.numRows} gaussians`);

// write file
await writeFile(outputFilename, dataTable, options);
} catch (err) {
// handle errors
console.error(err);
logger.error(err);
exit(1);
}

const endTime = hrtime(startTime);

console.log(`done in ${endTime[0] + endTime[1] / 1e9}s`);
logger.info(`done in ${endTime[0] + endTime[1] / 1e9}s`);

// something in webgpu seems to keep the process alive after returning
// from main so force exit
Expand Down
77 changes: 77 additions & 0 deletions src/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
type LogLevel = 'silent' | 'normal';

/**
* Simple logger utility. Currently supports quiet mode. Designed to be extended for multiple log
* levels in the future.
*/
class Logger {
private level: LogLevel = 'normal';

setLevel(level: LogLevel) {
this.level = level;
}

setQuiet(quiet: boolean) {
this.level = quiet ? 'silent' : 'normal';
}

/**
* Log informational messages (file operations, progress, etc.). Suppressed in quiet mode.
* @param {...any} args - The arguments to log.
*/
info(...args: any[]) {
if (this.level !== 'silent') {
console.log(...args);
}
}

/**
* Log warning messages. Suppressed in quiet mode.
* @param {...any} args - The arguments to log.
*/
warn(...args: any[]) {
if (this.level !== 'silent') {
console.warn(...args);
}
}

/**
* Log error messages. Always shown, even in quiet mode.
* @param {...any} args - The arguments to log.
*/
error(...args: any[]) {
console.error(...args);
}

/**
* Log debug/verbose messages. Currently treated the same as info, but can be filtered
* separately in the future.
* @param {...any} args - The arguments to log.
*/
debug(...args: any[]) {
if (this.level !== 'silent') {
console.log(...args);
}
}

/**
* Write progress indicators directly to stdout (without newline). Suppressed in quiet mode.
* @param text - The text to write.
*/
progress(text: string) {
if (this.level !== 'silent') {
process.stdout.write(text);
}
}

/**
* Check if logger is in quiet/silent mode.
* @returns True if the logger is in quiet/silent mode, false otherwise.
*/
isQuiet(): boolean {
return this.level === 'silent';
}
}

// Export singleton instance
export const logger = new Logger();
5 changes: 3 additions & 2 deletions src/ordering.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DataTable } from './data-table.js';
import { logger } from './logger';

// sort the compressed indices into morton order
const generateOrdering = (dataTable: DataTable, indices: Uint32Array) => {
Expand Down Expand Up @@ -51,7 +52,7 @@ const generateOrdering = (dataTable: DataTable, indices: Uint32Array) => {
const zlen = Mz - mz;

if (!isFinite(xlen) || !isFinite(ylen) || !isFinite(zlen)) {
console.log('invalid extents', xlen, ylen, zlen);
logger.debug('invalid extents', xlen, ylen, zlen);
return;
}

Expand Down Expand Up @@ -96,7 +97,7 @@ const generateOrdering = (dataTable: DataTable, indices: Uint32Array) => {
}

if (end - start > 256) {
// console.log('sorting', end - start);
// logger.debug('sorting', end - start);
generate(indices.subarray(start, end));
}

Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ type Options = {
overwrite: boolean;
help: boolean;
version: boolean;
quiet: boolean;
cpu: boolean;
iterations: number;

Expand Down
9 changes: 4 additions & 5 deletions src/utils/k-means.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { stdout } from 'node:process';

import { Column, DataTable } from '../data-table';
import { logger } from '../logger';
import { KdTree } from './kd-tree';
import { GpuClustering } from '../gpu/gpu-clustering';
import { GpuDevice } from '../gpu/gpu-device';
Expand Down Expand Up @@ -159,7 +158,7 @@ const kmeans = async (points: DataTable, k: number, iterations: number, device?:
let converged = false;
let steps = 0;

console.log(`Running k-means clustering: dims=${points.numColumns} points=${points.numRows} clusters=${k} iterations=${iterations}...`);
logger.debug(`Running k-means clustering: dims=${points.numColumns} points=${points.numRows} clusters=${k} iterations=${iterations}...`);

while (!converged) {
if (gpuClustering) {
Expand Down Expand Up @@ -188,14 +187,14 @@ const kmeans = async (points: DataTable, k: number, iterations: number, device?:
converged = true;
}

stdout.write('#');
logger.progress('#');
}

if (gpuClustering) {
gpuClustering.destroy();
}

console.log(' done 🎉');
logger.debug(' done 🎉');

return { centroids, labels };
};
Expand Down
5 changes: 3 additions & 2 deletions src/writers/write-lod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { dirname, resolve } from 'node:path';
import { BoundingBox, Mat4, Quat, Vec3 } from 'playcanvas';

import { TypedArray, DataTable } from '../data-table';
import { logger } from '../logger';
import { generateOrdering } from '../ordering';
import { writeSog } from './write-sog.js';
import { Options } from '../types';
Expand Down Expand Up @@ -87,7 +88,7 @@ const calcBound = (dataTable: DataTable, indices: number[]): Aabb => {
const M = b.getMax();

if (!isFinite(m.x) || !isFinite(m.y) || !isFinite(m.z) || !isFinite(M.x) || !isFinite(M.y) || !isFinite(M.z)) {
console.warn('Skipping invalid bounding box:', { m, M, index });
logger.warn('Skipping invalid bounding box:', { m, M, index });
continue;
}

Expand Down Expand Up @@ -264,7 +265,7 @@ const writeLod = async (fileHandle: FileHandle, dataTable: DataTable, outputFile
// write file unit to sog
const outputFile = await open(pathname, 'w');

console.log(`writing ${pathname}...`);
logger.info(`writing ${pathname}...`);

await writeSog(outputFile, unitDataTable, pathname, options, indices);

Expand Down
3 changes: 2 additions & 1 deletion src/writers/write-sog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { dirname, resolve } from 'node:path';

import { Column, DataTable } from '../data-table';
import { createDevice, GpuDevice } from '../gpu/gpu-device';
import { logger } from '../logger';
import { generateOrdering } from '../ordering';
import { FileWriter } from '../serialize/writer';
import { ZipWriter } from '../serialize/zip-writer';
Expand Down Expand Up @@ -125,7 +126,7 @@ const writeSog = async (fileHandle: FileHandle, dataTable: DataTable, outputFile

const write = async (filename: string, data: Uint8Array, w = width, h = height) => {
const pathname = resolve(dirname(outputFilename), filename);
console.log(`writing '${pathname}'...`);
logger.info(`writing '${pathname}'...`);

// construct the encoder on first use
if (!webPCodec) {
Expand Down