Skip to content

ROTFEAT/3d-file-parsers

Repository files navigation

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

About

Lightweight parsers for niche 3D file formats (M3D, X3D, IrrMesh, Terragen, PTS) — framework-agnostic, returns raw typed arrays. Used in production at 3d-tools.fabrapid.com

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors