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

3d-file-parsers

v1.0.0

Published

Lightweight, zero-dependency parsers for niche 3D file formats (M3D, X3D, IrrMesh, Terragen, PTS) that major libraries don't support. Returns raw typed arrays — works with Three.js, Babylon.js, or any WebGL framework.

Readme

3d-file-parsers

Lightweight, framework-agnostic parsers for niche 3D file formats that Three.js, Babylon.js, and Assimp.js don't natively support.

All parsers return raw Float32Array / Uint32Array data — plug them into any WebGL/WebGPU renderer with zero adapter code.

Used in production at 3D Tools by FabRapid — a free, browser-based 3D file converter supporting 70+ input formats and 23 output formats with zero server uploads.

Supported Formats

| Parser | Format | Extension | Description | |--------|--------|-----------|-------------| | parseM3D | Model3D | .m3d | Binary mesh format with optional zlib compression. Supports int16/float32/float64 vertex precision and polygon meshes. | | parseX3D | X3D | .x3d | XML-based 3D scene format (ISO standard). Handles IndexedFaceSet, IndexedTriangleSet, and Box primitives with material colors. | | parseIrrMesh | Irrlicht Mesh | .irrmesh | XML mesh format from the Irrlicht Engine. Supports standard, 2tcoords, and tangents vertex types. | | parseTerragen | Terragen | .ter | Binary heightmap terrain format. Automatically triangulates the elevation grid into a renderable mesh. | | parsePTS | Leica PTS | .pts | ASCII point cloud format used by Leica laser scanners. Supports XYZ, XYZ+intensity, and XYZ+intensity+RGB columns. |

Why This Exists

When building 3d-tools.fabrapid.com, we needed to support dozens of 3D formats in the browser. Most common formats (GLTF, OBJ, STL, FBX) are well-covered by Three.js loaders and Assimp WASM. But several formats — M3D, X3D, IrrMesh, Terragen, and PTS — had no JavaScript parser available, or the existing implementations were tightly coupled to a specific rendering engine.

We wrote these parsers from scratch based on the official format specifications, tested them against real-world files, and extracted them into this standalone package so others don't have to.

Design principles:

  • Zero framework dependency — returns plain typed arrays, not Three.js objects
  • Isomorphic — works in browsers and Node.js (XML parsers accept a custom DOMParser)
  • Tiny footprint — only fflate (3KB gzipped) for M3D's zlib decompression; everything else is zero-dep
  • TypeScript-first — full type definitions with documented interfaces

Installation

# pnpm
pnpm add 3d-file-parsers

# npm
npm install 3d-file-parsers

# yarn
yarn add 3d-file-parsers

Quick Start

Import everything

import { parseM3D, parseX3D, parseIrrMesh, parseTerragen, parsePTS } from '3d-file-parsers';

Or import individual parsers (tree-shakeable)

import { parseM3D } from '3d-file-parsers/m3d';
import { parseX3D } from '3d-file-parsers/x3d';
import { parseIrrMesh } from '3d-file-parsers/irrmesh';
import { parseTerragen } from '3d-file-parsers/terragen';
import { parsePTS } from '3d-file-parsers/pts';

Usage

Parse M3D (Model3D)

import { parseM3D } from '3d-file-parsers';

const response = await fetch('model.m3d');
const buffer = await response.arrayBuffer();

const { positions, normals, indices, vertexCount, faceCount } = parseM3D(buffer);

console.log(`Vertices: ${vertexCount}, Faces: ${faceCount}`);
// positions: Float32Array — flat xyz array (length = vertexCount * 3)
// normals:   Float32Array | null — auto-computed face normals
// indices:   Uint32Array — triangle indices

Parse X3D

import { parseX3D } from '3d-file-parsers';

const response = await fetch('scene.x3d');
const text = await response.text();

const { meshes } = parseX3D(text);

for (const mesh of meshes) {
  console.log(`Mesh: ${mesh.indices.length / 3} triangles`);
  // mesh.positions: Float32Array
  // mesh.normals:   Float32Array | null
  // mesh.indices:   Uint32Array
  // mesh.color?:    [r, g, b] — diffuse material color (0-1)
}

Node.js usage (X3D / IrrMesh)

XML-based parsers need a DOMParser. In Node.js, pass one from @xmldom/xmldom:

import { DOMParser } from '@xmldom/xmldom';
import { parseX3D } from '3d-file-parsers';

const { meshes } = parseX3D(xmlString, new DOMParser());

Parse IrrMesh (Irrlicht)

import { parseIrrMesh } from '3d-file-parsers';

const response = await fetch('model.irrmesh');
const text = await response.text();

const { buffers } = parseIrrMesh(text);

for (const buf of buffers) {
  console.log(`Buffer: ${buf.vertexCount} vertices, ${buf.faceCount} faces`);
  // buf.positions: Float32Array
  // buf.normals:   Float32Array
  // buf.indices:   Uint32Array
}

Parse Terragen Terrain

import { parseTerragen } from '3d-file-parsers';

const response = await fetch('terrain.ter');
const buffer = await response.arrayBuffer();

const { positions, indices, normals, xpts, ypts } = parseTerragen(buffer);

console.log(`Terrain grid: ${xpts} x ${ypts} (${xpts * ypts} vertices)`);
// Automatically triangulated — ready to render as a mesh

Parse PTS Point Cloud

import { parsePTS } from '3d-file-parsers';

const response = await fetch('scan.pts');
const buffer = await response.arrayBuffer();

const { positions, colors, pointCount, hasColor } = parsePTS(buffer);

console.log(`Points: ${pointCount}, Has color: ${hasColor}`);
// positions: Float32Array — flat xyz (length = pointCount * 3)
// colors:    Float32Array | null — flat rgb normalized to 0-1

Using with Three.js

The parsers return raw typed arrays. Here's how to create Three.js geometry:

import * as THREE from 'three';
import { parseM3D } from '3d-file-parsers';

const data = parseM3D(buffer);
const geometry = new THREE.BufferGeometry();

geometry.setAttribute('position', new THREE.Float32BufferAttribute(data.positions, 3));

if (data.normals) {
  geometry.setAttribute('normal', new THREE.Float32BufferAttribute(data.normals, 3));
}

if (data.indices.length > 0) {
  geometry.setIndex(new THREE.BufferAttribute(data.indices, 1));
}

const mesh = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial());
scene.add(mesh);

Point cloud with Three.js

import * as THREE from 'three';
import { parsePTS } from '3d-file-parsers';

const data = parsePTS(buffer);
const geometry = new THREE.BufferGeometry();

geometry.setAttribute('position', new THREE.Float32BufferAttribute(data.positions, 3));

if (data.colors) {
  geometry.setAttribute('color', new THREE.Float32BufferAttribute(data.colors, 3));
}

const material = new THREE.PointsMaterial({
  size: 0.01,
  vertexColors: data.hasColor,
});

const points = new THREE.Points(geometry, material);
scene.add(points);

Using with Babylon.js

import * as BABYLON from '@babylonjs/core';
import { parseM3D } from '3d-file-parsers';

const data = parseM3D(buffer);
const mesh = new BABYLON.Mesh('model', scene);
const vertexData = new BABYLON.VertexData();

vertexData.positions = Array.from(data.positions);
vertexData.indices = Array.from(data.indices);

if (data.normals) {
  vertexData.normals = Array.from(data.normals);
}

vertexData.applyToMesh(mesh);

Using with Raw WebGL

import { parseM3D } from '3d-file-parsers';

const data = parseM3D(buffer);

const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, data.positions, gl.STATIC_DRAW);

const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data.indices, gl.STATIC_DRAW);

gl.drawElements(gl.TRIANGLES, data.indices.length, gl.UNSIGNED_INT, 0);

API Reference

parseM3D(buffer: ArrayBuffer): M3DData

| Property | Type | Description | |----------|------|-------------| | positions | Float32Array | Flat xyz vertex positions | | normals | Float32Array \| null | Auto-computed vertex normals | | indices | Uint32Array | Triangle indices | | vertexCount | number | Number of vertices | | faceCount | number | Number of triangles |

parseX3D(xmlText: string, domParser?): X3DData

| Property | Type | Description | |----------|------|-------------| | meshes | X3DMesh[] | Array of parsed meshes | | meshes[].positions | Float32Array | Flat xyz vertex positions | | meshes[].normals | Float32Array \| null | Computed vertex normals | | meshes[].indices | Uint32Array | Triangle indices | | meshes[].color | [r, g, b]? | Diffuse material color (0-1) |

parseIrrMesh(xmlText: string, domParser?): IrrMeshData

| Property | Type | Description | |----------|------|-------------| | buffers | IrrMeshBuffer[] | Array of mesh buffers | | buffers[].positions | Float32Array | Flat xyz vertex positions | | buffers[].normals | Float32Array | Vertex normals from file | | buffers[].indices | Uint32Array | Triangle indices | | buffers[].vertexCount | number | Vertices in this buffer | | buffers[].faceCount | number | Triangles in this buffer |

parseTerragen(buffer: ArrayBuffer): TerragenData

| Property | Type | Description | |----------|------|-------------| | positions | Float32Array | Flat xyz vertex positions | | indices | Uint32Array | Triangle indices (2 per grid quad) | | normals | Float32Array | Computed vertex normals | | xpts | number | Grid points along X axis | | ypts | number | Grid points along Y axis |

parsePTS(buffer: ArrayBuffer): PTSData

| Property | Type | Description | |----------|------|-------------| | positions | Float32Array | Flat xyz point positions | | colors | Float32Array \| null | RGB colors normalized 0-1 (null if no color data) | | pointCount | number | Number of points | | hasColor | boolean | Whether the file contained color data |

Browser Support

Works in all modern browsers with ArrayBuffer and DataView support (Chrome 49+, Firefox 42+, Safari 10+, Edge 14+).

For Node.js, XML-based parsers (X3D, IrrMesh) require a DOM parser like @xmldom/xmldom.

Related Projects

  • 3D Tools by FabRapid — Free online 3D file converter (70+ formats, zero uploads, browser-only)
  • 3D File Viewer — Online 3D model viewer with support for all major formats
  • 3D Model Compressor — Browser-based GLB compression with before/after comparison
  • Assimp — The industry-standard C++ import library (covers most common formats)
  • Three.js — WebGL rendering library with many built-in loaders
  • fflate — Fast JavaScript compression library (used for M3D zlib decompression)

Contributing

Contributions are welcome! If you need support for additional niche 3D formats, please open an issue.

License

MIT