npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@baba_hadjsaid/3dgsviewer

v1.2.4

Published

Embeddable 3D Gaussian Splatting viewer (PlayCanvas) with a React component, a framework-free core and a feature plug-in API

Readme

@baba_hadjsaid/3dgsviewer

npm

Embeddable 3D Gaussian Splatting viewer. PlayCanvas under the hood, a React component on top, and a feature plug-in API for everything optional.

Reads .ply, .compressed.ply, .sog and .meta.json from any URL — http(s):, blob: or a custom protocol — and streams level-of-detail ("Streamed SOG") scenes from a lod-meta.json URL.

Install

npm install @baba_hadjsaid/3dgsviewer playcanvas

playcanvas is a peer dependency — the viewer must share the host app's copy, or you end up with two WebGL contexts. react / react-dom are optional peers, needed only for the <GaussianSplatViewer> component.

The viewer creates its gsplat component with unified: false. That is required, not stylistic: under PlayCanvas's unified renderer component.material is null, so there is no per-instance material for features to touch and the reveal intro silently does nothing.

The one exception is streamed LOD. PlayCanvas renders an octree (lod-meta.json) only under the unified renderer, so those scenes get unified: true, skip the reveal intro, and stream their chunk units after the scene is ready. The viewer sets app.scene.gsplat.lodUnderfillLimit to levels - 1, so the coarsest resident level shows while finer ones load, and by default sizes the splat budget to the model's full detail (see splatBudget). The URL's file name must be exactly lod-meta.json; chunk URLs resolve next to it, and its query string is not carried over to them.

Use

import { GaussianSplatViewer } from '@baba_hadjsaid/3dgsviewer';
import '@baba_hadjsaid/3dgsviewer/styles.css';

<GaussianSplatViewer src="/scenes/room.ply" />

Without React, drive the core directly. It needs a root element containing a [data-viewer-element="canvas"] canvas (it appends one if there isn't):

import { createViewer } from '@baba_hadjsaid/3dgsviewer/viewer';

const viewer = createViewer({ root: document.querySelector('#stage'), src: url });
await viewer.ready;
viewer.destroy();

fullScreen

One switch picks between the two ways to present a scene. It defaults to true, so existing code keeps the complete experience.

| | fullScreen: true (default) | fullScreen: false | |---|---|---| | Title / help panel | yes | — | | View toolbar, Axes + BBox toggles | yes | — | | FPS readout | yes | — | | Spinner, progress bar, status text | yes | — | | Reveal intro animation | yes | — | | Orbit camera path (P) | yes | — | | Keyboard (arrows, WASD/QE, P) | yes | — | | Gamepad | yes | — | | Drag-and-drop a file onto the viewer | yes | — | | Mouse: rotate / zoom / pan | yes | yes | | Touch: one-finger rotate, two-finger pan+zoom | yes | yes | | Auto-framing of the loaded scene | yes | yes | | onReady / onError | yes | yes |

<GaussianSplatViewer src={url} fullScreen={false} />

Minimal mode renders the model and nothing else — no chrome, no motion, no keyboard focus. Loading and error UI is yours to draw: onError still fires, and the built-in status text is suppressed rather than hidden, so nothing writes into a DOM node you did not ask for.

Touch stays on because it is the direct-manipulation equivalent of the mouse, not a separate control scheme. If you want it off, say so and it becomes its own flag.

An explicit features, cameraPath, overlays or loadingEffect option always wins over the mode's defaults — so you can run minimal chrome with the orbit path if that is what you want:

<GaussianSplatViewer src={url} fullScreen={false} cameraPath={createOrbitCameraPath()} />

Options

Both the component's props and createViewer(...) take the same names.

| Option | Meaning | |---|---| | src | Scene URL. Required. | | format | 'ply' | 'compressed.ply' | 'sog' | 'meta.json' | 'lod-meta.json' — for extensionless URLs (blob:, custom protocols). | | splatBudget | Streamed LOD only: global splat budget (app.scene.gsplat.splatBudget). Default 'auto': the model's full detail (its level-0 splat count plus the engine's per-chunk padding), capped at 4,000,000 on desktops, 2,000,000 at 4 GB navigator.deviceMemory and 1,000,000 on touch-only or ≤ 2 GB devices. A number sets it; 0 keeps the engine's 1,000,000, which draws a large model's far part at half or a quarter of its splats. | | lodMaxConcurrentLoads | Streamed LOD only: chunk files downloaded at once. Default 6 (the engine's is 2). | | lodRangeMin / lodRangeMax | Streamed LOD only: finest / coarsest level the component may use. | | stats (React: showStats) | A panel of what is on screen: splats drawn, FPS, and for a streamed LOD the full-detail splat count, the splat budget (flagged when it limits detail) and which detail levels are in use. Default true (not shown in embedded mode). A host may supply its own <dl data-viewer-element="stats">. Replaces the former "Controls" help panel. | | lodProgress | Streamed LOD only: show a bar and "Loading detail… n of m parts" across the top while chunks the current view needs are downloading. Default true. A host may place its own element with data-viewer-element="lod-progress" (same inner structure) instead of the one the viewer adds. | | cameraFrustumsVisible | Whether the training-camera pyramids are shown when the scene opens. Default false: hidden until the Cameras toggle is clicked. (showCameraFrustums: false removes the feature and its toggle altogether.) | | fullScreen | See above. Default true. | | initialCameraPose | Starting camera; skips auto-framing. | | root | Core only — the element to mount into. | | onReady(:{app, camera}) / onError(err) | Lifecycle callbacks. | | features | Full replacement for the default feature list. | | cameraPath / overlays / loadingEffect | Override one slot; false disables it. | | revealEffect, revealDurationMs, revealEpsilon, revealExponentMin, revealExponentMax | Reveal intro tuning. | | imuWebSocketUrl | Optional WebSocket feeding live IMU camera orientation. | | title, showTitle, className, style | Component only, full mode only. (showHelp is gone: the Controls panel became the stats panel.) |

Features

src/features/feature-api.js documents the contract. A feature touches the scene only through the SceneApi object — never viewer internals, never another feature — so the orbit path, the bbox/axes overlays and the reveal intro can all be swapped for your own:

import { createViewer } from '@baba_hadjsaid/3dgsviewer/viewer';
import { createOrbitCameraPath } from '@baba_hadjsaid/3dgsviewer/features';

createViewer({ root, src, features: [createOrbitCameraPath({ durationMs: 20000 }), myEffect()] });

Published form

dist/ ships precompiled ESM with sourcemaps: the JSX is already transpiled, the module tree is preserved (so subpath imports stay real modules and the package tree-shakes), and react, react-dom and playcanvas stay external. No consumer needs to transpile node_modules, whatever bundler they use.

src/ ships too, so the sourcemaps resolve to readable code.

Build it locally with npm run build; npm run check then verifies the built output, and prepublishOnly runs both so a hand-typed npm publish cannot skip them.

Releasing

Push to master and CI does the rest: it bundles the package, runs the contract test, bumps the version, tags it, publishes to npm and cuts a GitHub release. The commit message picks the bump level.

| Commit message contains | Result | |---|---| | (nothing special) | patch — 1.0.0 → 1.0.1 | | [minor] | minor — 1.0.0 → 1.1.0 | | [major] | major — 1.0.0 → 2.0.0 | | [skip release] | no publish |

The bump commit is written back as chore(release): v1.0.1 [skip ci], so it cannot retrigger the workflow. Only changes under src/** or to package.json start a release — README and workflow edits alone do not.

You can also trigger it by hand from the Actions tab (Release → Run workflow) and choose the level there.

Publishing uses npm trusted publishing (OIDC): no npm token is stored in this repo. GitHub mints a short-lived credential per run, which also attaches signed provenance to the npm page.

Locally, npm run build && npm run check runs the exact gate CI runs.