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

metaverse-terrain

v0.4.1

Published

ES module Three.js terrain heightfield with PBR texture blending, animated water, and brush editing

Readme

metaverse-terrain

Three.js terrain library with PBR texture blending, animated water, and heightmap brush editing.

TerrainRegion owns the terrain mesh, TSL node materials, and height data. Your app owns the scene graph, camera, input, and raycasting.

Install

npm

npm install metaverse-terrain three

Requires three >= 0.160 as a peer dependency. The runtime uses Three.js Shading Language (TSL), so host apps must use WebGPURenderer from three/webgpu (with Three's WebGL fallback where available). Use with Vite, Webpack, or any bundler — no import map needed.

jsDelivr

No install step. Load from cdn.jsdelivr.net/npm/metaverse-terrain in the browser using an import map (see below).


Using with jsDelivr

Browsers cannot resolve bare imports like 'metaverse-terrain' on their own. Add an import map in your HTML, then load your app in a <script type="module">.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <script type="importmap">
      {
        "imports": {
          "three": "https://cdn.jsdelivr.net/npm/three/build/three.module.js",
          "three/webgpu": "https://cdn.jsdelivr.net/npm/three/build/three.webgpu.js",
          "three/tsl": "https://cdn.jsdelivr.net/npm/three/build/three.tsl.js",
          "metaverse-terrain": "https://cdn.jsdelivr.net/npm/metaverse-terrain/index.js"
        }
      }
    </script>
  </head>
  <body>
    <script type="module">
      import * as THREE from 'three/webgpu';
      import { TerrainRegion } from 'metaverse-terrain';

      // your app here
    </script>
  </body>
</html>

Serve over HTTP (not file://). The snippets below go inside that <script type="module"> block.

Textures

Provide all five layers: sand, grass, rock, snow, water. Reference PNGs are on jsDelivr:

const TEXTURE_BASE = 'https://cdn.jsdelivr.net/npm/metaverse-terrain/texture';

const textures = {
  sand: `${TEXTURE_BASE}/terrain-sand.png`,
  grass: `${TEXTURE_BASE}/terrain-grass.png`,
  rock: `${TEXTURE_BASE}/terrain-rock.png`,
  snow: `${TEXTURE_BASE}/terrain-snow.png`,
  water: `${TEXTURE_BASE}/terrain-water.png`,
};

PBR textures (optional)

PBR is optional — albedo-only terrain still works. To enable physically based shading, provide per-layer normal, metallic, roughness, and AO maps (or a pre-packed MRAO texture). Reference maps ship with the package:

const pbrTextures = {
  sand: {
    metal: `${TEXTURE_BASE}/terrain-sand_metal.png`,
    roughness: `${TEXTURE_BASE}/terrain-sand_roughness.png`,
    normal: `${TEXTURE_BASE}/terrain-sand_normal.png`,
    ao: `${TEXTURE_BASE}/terrain-sand_ao.png`,
  },
  grass: {
    metal: `${TEXTURE_BASE}/terrain-grass_metal.png`,
    roughness: `${TEXTURE_BASE}/terrain-grass_roughness.png`,
    normal: `${TEXTURE_BASE}/terrain-grass_normal.png`,
    ao: `${TEXTURE_BASE}/terrain-grass_ao.png`,
  },
  rock: {
    metal: `${TEXTURE_BASE}/terrain-rock_metal.png`,
    roughness: `${TEXTURE_BASE}/terrain-rock_roughness.png`,
    normal: `${TEXTURE_BASE}/terrain-rock_normal.png`,
    ao: `${TEXTURE_BASE}/terrain-rock_ao.png`,
  },
  snow: {
    metal: `${TEXTURE_BASE}/terrain-snow_metal.png`,
    roughness: `${TEXTURE_BASE}/terrain-snow_roughness.png`,
    normal: `${TEXTURE_BASE}/terrain-snow_normal.png`,
    ao: `${TEXTURE_BASE}/terrain-snow_ao.png`,
  },
  water: {
    metal: `${TEXTURE_BASE}/terrain-water_metal.png`,
    roughness: `${TEXTURE_BASE}/terrain-water_roughness.png`,
    normal: `${TEXTURE_BASE}/terrain-water_normal.png`,
    ao: `${TEXTURE_BASE}/terrain-water_ao.png`,
  },
};

loadPBRTextureSet() loads the maps and packs metallic, roughness, and AO into a single MRAO texture per layer:

import { TerrainRegion, loadPBRTextureSet } from 'metaverse-terrain';

const packedPBR = await loadPBRTextureSet(pbrTextures);

const region = new TerrainRegion({
  textures,
  pbrTextures: packedPBR,
  normalStrength: 1.0,
  terrainAOIntensity: 1.0,
});

For best results with WebGPURenderer, set scene.environment from an equirectangular HDRI (texture.mapping = THREE.EquirectangularReflectionMapping). The bundled examples load a Venice sunset HDR via Three.js RGBELoader. PBR terrain uses MeshStandardNodeMaterial; water uses a TSL MeshBasicNodeMaterial with fresnel, specular/glint, wave, foam, and normal detail. Disable PBR water at runtime with region.setPBREnabled(false) on low-end devices.

Minimal example

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <script type="importmap">
      {
        "imports": {
          "three": "https://cdn.jsdelivr.net/npm/three/build/three.module.js",
          "three/webgpu": "https://cdn.jsdelivr.net/npm/three/build/three.webgpu.js",
          "three/tsl": "https://cdn.jsdelivr.net/npm/three/build/three.tsl.js",
          "metaverse-terrain": "https://cdn.jsdelivr.net/npm/metaverse-terrain/index.js"
        }
      }
    </script>
    <style>body { margin: 0; } canvas { display: block; }</style>
  </head>
  <body>
    <script type="module">
      import * as THREE from 'three/webgpu';
      import { TerrainRegion } from 'metaverse-terrain';

      const TEXTURE_BASE = 'https://cdn.jsdelivr.net/npm/metaverse-terrain/texture';
      const textures = {
        sand: `${TEXTURE_BASE}/terrain-sand.png`,
        grass: `${TEXTURE_BASE}/terrain-grass.png`,
        rock: `${TEXTURE_BASE}/terrain-rock.png`,
        snow: `${TEXTURE_BASE}/terrain-snow.png`,
        water: `${TEXTURE_BASE}/terrain-water.png`,
      };

      const renderer = new THREE.WebGPURenderer({ antialias: true });
      await renderer.init();
      renderer.setSize(innerWidth, innerHeight);
      document.body.appendChild(renderer.domElement);

      const scene = new THREE.Scene();
      scene.background = new THREE.Color(0x9fb7d5);

      const camera = new THREE.PerspectiveCamera(50, innerWidth / innerHeight, 0.1, 900);
      camera.position.set(132, 108, 168);
      camera.lookAt(0, 10, 0);

      const region = new TerrainRegion({ textures, seed: 42 });
      scene.add(region.group);

      function animate(time) {
        region.update(time);
        renderer.render(scene, camera);
        requestAnimationFrame(animate);
      }

      animate(0);
    </script>
  </body>
</html>

Painting

The library does not handle pointer events. Raycast the terrain, then call paintAt:

import { TerrainRegion, getTerrainHitFromPointer } from 'metaverse-terrain';

const TEXTURE_BASE = 'https://cdn.jsdelivr.net/npm/metaverse-terrain/texture';
const textures = { /* sand, grass, rock, snow, water — see above */ };

const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
const canvas = renderer.domElement;

const region = new TerrainRegion({ textures });
let isPainting = false;

canvas.addEventListener('pointerdown', (event) => {
  const hit = getTerrainHitFromPointer(
    region, canvas, camera, raycaster, pointer, event.clientX, event.clientY,
  );
  if (!hit) return;

  isPainting = true;
  region.beginStroke();
  region.paintAt(hit.point, { temporaryLower: event.shiftKey });
});

canvas.addEventListener('pointermove', (event) => {
  if (!isPainting) return;
  const hit = getTerrainHitFromPointer(
    region, canvas, camera, raycaster, pointer, event.clientX, event.clientY,
  );
  if (hit) region.paintAt(hit.point, { temporaryLower: event.shiftKey });
});

canvas.addEventListener('pointerup', () => {
  region.endStroke();
  isPainting = false;
});

Configuration

import { TerrainRegion } from 'metaverse-terrain';

const region = new TerrainRegion({
  textures,
  regionSize: 256,
  seed: 29,
  waterLevel: 28,
  textureDensity: 10,
});

region.setBrushMode('raise');
region.setBrushRadius(8);
region.setWaterLevel(24);
region.downloadHeightmap();

Optional DOM helpers

import { bindTerrainPainting, bindTextureDrop } from 'metaverse-terrain';

bindTerrainPainting(region, { domElement: canvas, camera, raycaster, pointer });
bindTextureDrop(region); // needs [data-texture-drop="grass"] elements in HTML

Using with npm

import * as THREE from 'three/webgpu';
import { TerrainRegion, loadPBRTextureSet } from 'metaverse-terrain';
import sand from 'metaverse-terrain/texture/terrain-sand.png';
import grass from 'metaverse-terrain/texture/terrain-grass.png';
import rock from 'metaverse-terrain/texture/terrain-rock.png';
import snow from 'metaverse-terrain/texture/terrain-snow.png';
import water from 'metaverse-terrain/texture/terrain-water.png';
import sandMetal from 'metaverse-terrain/texture/terrain-sand_metal.png';
import sandRoughness from 'metaverse-terrain/texture/terrain-sand_roughness.png';
import sandNormal from 'metaverse-terrain/texture/terrain-sand_normal.png';
import sandAO from 'metaverse-terrain/texture/terrain-sand_ao.png';
// ... same pattern for grass, rock, snow, water

const pbrTextures = await loadPBRTextureSet({
  sand: { metal: sandMetal, roughness: sandRoughness, normal: sandNormal, ao: sandAO },
  // grass, rock, snow, water ...
});

const region = new TerrainRegion({
  textures: { sand, grass, rock, snow, water },
  pbrTextures,
  seed: 42,
});

scene.add(region.group);

Omit pbrTextures for albedo-only rendering.


API

| Export | Purpose | |--------|---------| | TerrainRegion | Terrain mesh, water, heightmap, brush state | | loadPBRTextureSet | Load and pack PBR maps (metal, roughness, normal, AO → MRAO) | | getTerrainHitFromPointer | Screen coords → terrain intersection | | bindTerrainPainting | Wire pointer events to brush painting | | bindTextureDrop | Drag-and-drop images onto texture swatches | | TERRAIN_TEXTURE_LAYERS | ['sand', 'grass', 'rock', 'snow', 'water'] | | PBR_CHANNELS | ['metal', 'roughness', 'normal', 'ao'] | | DEFAULT_TEXTURE_HEIGHTS | Default height blend thresholds |

TerrainRegion methods

| Method | Description | |--------|-------------| | raycast(raycaster) | Intersect terrain mesh | | paintAt(point, options?) | Apply brush and emit onHeightmapChange | | setBrushMode / setBrushRadius / setBrushStrength | Brush settings | | setWaterLevel / setWaterEnabled | Water plane | | setTextureDensity | Texture tiling density | | setTextureHeights | Sand/grass/rock/snow height bands | | setNormalStrength / setTerrainAOIntensity | PBR normal and AO intensity | | setPBREnabled / setWaterIOR | PBR water toggle and index of refraction | | setTerrainTexture(layer, source) | Replace a texture at runtime | | randomize(seed?) / level(height?) | Regenerate or flatten heightmap | | drawHeightmapPreview(canvas) / downloadHeightmap() | Export heightmap | | update(elapsedTime) | Animate water | | dispose() | Free GPU resources |

TypeScript types ship in index.d.ts.

Demo apps

git clone https://github.com/richardanaya/metaverse-terrain.git
cd metaverse-terrain
python3 -m http.server 8080
  • Editor — full brush editor with PBR and HDRI lighting
  • Minimal — smallest PBR integration

License

MIT — Richard Anaya. See LICENSE.

Reference textures: MIT — Richard Anaya. See ASSET_LICENSES.md.