# 3DTilesRendererJS: the cache that stops refining, and the export map behind it

> One npm package carrying four renderer entry points and three plugin bundles for three.js, Babylon.js and r3f. The interesting parts are not the example list. They are an LRU cache that hard-stops refinement, a main field pointing at source while every export points at a build, and a library build script that does not name the library config.

**NASA-AMMOS/3DTilesRendererJS** — Renderer for 3D Tiles in Javascript using three.js, Babylon.js, and r3f

- Repository: https://github.com/NASA-AMMOS/3DTilesRendererJS
- Website: https://nasa-ammos.github.io/3DTilesRendererJS/three/mars.html
- Stars: 2,477 · Forks: 419
- Language: JavaScript
- License: Apache-2.0
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/nasa-ammos-3dtilesrendererjs

## Cache limits are hard caps and refinement stops when one is hit

The Gotchas section opens on the one behavior in this renderer that silently degrades output rather than failing loudly. No new tiles load once the `LRUCache` reaches `maxSize` or `maxBytesSize`. If the tiles needed for the current view do not fit, coarser tiles are displayed and refinement stops. That is the whole failure mode in two sentences: no error, no empty scene, just a permanently blurrier surface than the tileset can deliver.

The guidance that follows is to monitor `lruCache.cachedBytes` and `lruCache.isFull()` and raise the byte cap. That byte cap is spelled out only as far as `maxBytesSi` before the sentence ends, so the property name is left half written while the two monitoring calls above it are complete. Anyone configuring this is expected to watch cache occupancy as a running value rather than pick a size once, which puts the real work in a poll on `isFull()`.

The distinction between a cap and a budget is the thing to hold onto. A budget that the renderer spends differently leaves you with a slower scene. A cap that it refuses to exceed leaves you with a different scene, permanently, and the only lever is raising the number. Both `maxSize` (tile count) and `maxBytesSize` (tile bytes) exist, so a tileset with few large tiles and a tileset with many small ones fail in different ways.

## main points at source while every export resolves to a build

The package manifest carries two parallel descriptions of the same entry point:

```json
"main": "src/index.js",
"module": "src/index.js",
"files": [
  "src/*",
  "build/*"
],
```

`files` ships both directories, so the tarball contains raw source and built output. But the root `exports` entry sends `types` to `./src/index.d.ts` and `import` to `./build/index.js`. Every other subpath does the same split, with declarations read from source and code loaded from `build/`. So a resolver that honours `exports` gets the bundled file, and a tool that falls back to `main` loads `src/index.js` instead, which is the unbundled tree with its own bare import specifiers.

The export map also exposes `./src/*` as a first-class subpath, mapping to `./src/*` at runtime. That makes any file inside the source tree addressable as `3d-tiles-renderer/src/...`. Handy while you are reading the source, and a promise nobody intends to keep, because an internal file path is not an API surface.

The root entry point is also the only one that has this ambiguity in a useful direction. `core` exists as a separate subpath with its own bundle and its own API document, which is the engine-independent half; the three engine bindings and the three plugin bundles each get their own file on top of it. Nine entry points, nine bundles, one package name, and no single import that reaches all of them.

## The package description is a link to the 3D Tiles specification

In package.json the `description` field holds a URL:

```
https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification
```

That is the specification the renderer implements, not a sentence describing the package. It is the text npm shows on the package page and in search results, so anyone deciding whether to install 0.5.3 reads a link to someone else's specification document instead of a description of what they are installing.

The rest of the manifest is more careful. `name` is `3d-tiles-renderer` and `version` is `0.5.3`, matching the v0.5.3 tag. `type` is `module` and `sideEffects` is false, so bundlers may drop unreferenced modules from the import graph. The keyword list carries both `3d-tiles` and `3dtiles`, plus `b3dm`, `gltf`, `threejs`, `babylonjs`, `r3f`, `terrain` and `cesium`.

The export map itself is the largest part of the manifest and the clearest statement of what the package considers public. It names nine subpaths besides the root, each resolving to its own distinct build file: `build/index.js`, `build/index.plugins.js`, `build/index.core.js`, `build/index.three.js`, `build/index.r3f.js`, `build/index.babylonjs.js`, `build/index.babylonjs-plugins.js`, `build/index.core-plugins.js` and `build/index.three-plugins.js`. Nine bundles, one install.

## Three entry points, three different places their guides live

The API table presents four renderer entry points in one uniform shape, and then the documentation paths underneath them are not uniform at all.

| Package | Reference |
| --- | --- |
| `3d-tiles-renderer/core` | [API Reference](./src/core/renderer/API.md) |
| `3d-tiles-renderer/three` | [Usage Guide](./USAGE.md) · [API Reference](./src/three/renderer/API.md) |
| `3d-tiles-renderer/babylonjs` | [Usage Guide](./src/babylonjs/renderer/README.md) · [API Reference](./src/babylonjs/renderer/API.md) |
| `3d-tiles-renderer/r3f` | [Usage Guide](./src/r3f/README.md) · [API Reference](./src/r3f/API.md) |

The three.js usage guide sits at the repository root as `USAGE.md`. The Babylon.js and r3f usage guides sit inside their own source trees. The plugins table repeats the pattern and then breaks it: `3d-tiles-renderer/three/plugins` and `3d-tiles-renderer/babylonjs/plugins` each get a Plugin Guide alongside the API reference, while `3d-tiles-renderer/core/plugins` gets only an API reference.

So the entry point you choose decides which document you read, where it lives, and for the plugins bundle whether you get a guide at all. The package itself makes no such distinction: all four subpaths resolve from one install.

## The r3f binding ships with docs but has no example row

The examples table is explicitly Three.js based, and says so. Babylon.js gets two demos, Mars and Google Photorealistic Tiles, linked at the top of the section. r3f gets nothing in the table at all.

That is a gap against the rest of the material rather than a hole in the project. `3d-tiles-renderer/r3f` is a real export in the manifest, it has both a Usage Guide and an API Reference, and `example/r3f/` is one of the directories in the example folder alongside `example/three/` and `example/babylonjs/`. So the binding is documented, example code exists, and the curated table that everything else in the README points you at skips it.

The table is otherwise organized by what you are trying to do rather than by engine: Core (multiple tilesets, all options and features, VR), External Tiles Providers, Customization, and Plugins. A reader working in r3f has to infer from the directory names that the same examples exist behind the table.

## build-lib names a different config than the other two scripts

The visible npm scripts read:

```json
"start": "vite --config ./vite.config.js",
"build-examples": "vite build --config ./vite.config.js",
"build-lib": "vite build --config .
```

Two of the three pass `./vite.config.js` explicitly. The third passes a single dot. Meanwhile the repository has both `vite.config.js` and `vite.lib-config.js` at the top level, and the library build script does not mention the second file by name. Reconciling that is left to whoever builds the package.

The rest of the toolchain explains why there are two configs and more: `vite.config.js` for examples, `vite.lib-config.js` for the library, `vitest.config.js` with `vitest.setup.js` for tests, `babel.config.json`, `eslint.config.js`, `jsdoc2md.config.js` for generating the API markdown, and both `tsconfig.json` and `tsconfig.test.json` for a codebase whose primary language is JavaScript. There is also a TESTCASES.md at the top level and no test fixtures named in the docs.

The example site has its own shape: `example/three/`, `example/babylonjs/` and `example/r3f/` for the three engine bindings, plus `example/public/`, `example/logos/` and `example/styles.css`. A `start` script wired to the single `vite.config.js` therefore serves the whole example set, which is why the library build needed a config of its own in the first place.

## Repository name, package name and branch are three different strings

The repository is NASA-AMMOS/3DTilesRendererJS, the npm package is `3d-tiles-renderer`, and the default branch is `master` rather than `main`. The README heading is the package name, so a reader arriving from a repository URL has to notice that the prose is written for someone who already installed the npm artifact.

The releases track a 0.5 line rather than a 1.0 line: v0.5.1 on 2026-08-07, v0.5.2 on 2026-08-24 and v0.5.3 on 2026-09-18, three releases in six weeks. The most recent push was on 2026-10-02 and the repository is not archived, so the code is moving faster than the docs, which is consistent with the project's own framing that most of the 3D Tiles spec is supported with a few exceptions.

What is not supported is tracked rather than listed. The README says that if a tileset or geometry does not load or render properly you should open an issue with example data, because example data is needed for adding and testing features, and it points at a Feature Complete Milestone as the place that records which features are not yet implemented. That milestone, not the feature table, is the honest answer to how complete this renderer is.

## Conclusion

Pick this renderer when you want 3D Tiles in a three.js, Babylon.js or r3f project and you would rather not adopt Cesium. Before wiring it up, read the LRUCache section first, because the caps are the failure mode you will hit under memory pressure and the fix is a number you set rather than a flag you pass. Then decide which entry point you are importing and commit to it, since main and the exports map resolve to different files. Budget time for the gaps the project itself points at: the Feature Complete Milestone lists unimplemented spec features, the r3f binding has documentation but no example row, and external tileset demos need a Cesium Ion or Google Tiles key.

## FAQ

### Which rendering engines does 3DTilesRendererJS support?

three.js, Babylon.js and r3f, all from a single npm package. The manifest exposes 3d-tiles-renderer/three, 3d-tiles-renderer/babylonjs and 3d-tiles-renderer/r3f as separate export subpaths alongside 3d-tiles-renderer/core, each resolving to its own built bundle, plus plugins bundles for three, Babylon.js and core.

### How do I install 3DTilesRendererJS?

One command: npm install 3d-tiles-renderer --save. The package is version 0.5.3, declares type module and sideEffects false, and publishes both its src tree and a build directory, so the import you choose from its exports map decides which of the two your bundler loads.

### Why does my 3D Tiles view stay coarse instead of refining?

The cache limits are hard caps. Once the LRUCache reaches maxSize or maxBytesSize no new tiles load, and if the tiles needed for the current view do not fit then coarser tiles are displayed and refinement stops. The documented response is to watch lruCache.cachedBytes and lruCache.isFull() and raise the byte cap.

### Do the 3DTilesRendererJS external tileset examples need API keys?

Yes, for the External Tiles Providers group. The examples covering Cesium Ion 3D Tiles, Cesium Ion Lunar, Cesium Ion Mars, Google Photorealistic, Google Globe and PLATEAU are marked as requiring either a Google Tiles API Key or a Cesium Ion API Key. The Core, Customization and Plugins examples carry no such note.

### How can I tell which 3D Tiles features are not implemented yet?

The project tracks that on a Feature Complete Milestone rather than in the README, which states that most of the 3D Tiles spec is supported with a few exceptions. It also asks that you open an issue with example data when a tileset or geometry does not load or render properly, since example data is needed for adding and testing features.

## Sources

- [License: Apache-2.0](https://github.com/NASA-AMMOS/3DTilesRendererJS/blob/master/LICENSE)
- [NASA-AMMOS/3DTilesRendererJS on GitHub](https://github.com/NASA-AMMOS/3DTilesRendererJS)
- [Project website](https://nasa-ammos.github.io/3DTilesRendererJS/three/mars.html)
- [README](https://github.com/NASA-AMMOS/3DTilesRendererJS/blob/master/README.md)
- [Releases](https://github.com/NASA-AMMOS/3DTilesRendererJS/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/nasa-ammos-3dtilesrendererjs
