Repository navigation
OBJ loader
import { loadObj, ObjError } from '@modelshot/formats';
const { scene, warnings } = loadObj(fileBytesOrText, {
fileName: 'crate.obj', // optional: for messages and the name of faces before any o/g
files: new Map([
['crate.mtl', mtlBytes],
['crate.png', pngBytes],
]), // optional: files picked next to the model
limits: { maxTriangles: 100_000 }, // optional: overrides of DEFAULT_OBJ_LIMITS
unitScale: 16, // optional: scene units per OBJ unit (default DEFAULT_OBJ_UNIT_SCALE)
});loadObj(input: string | Uint8Array, options?) loads a Wavefront .obj with its .mtl material libraries and PNG or
JPEG textures into a scene format v1 document plus warnings. It is synchronous and bounded (one
linear scan over the text, limits checked as counts grow) and reads nothing but the files the caller passed. It throws
only ObjError (and a RangeError for invalid options).
-
v(x y z; a fourthwis ignored),vt(u [v]),vn;fwithv,v/vt,v//vnorv/vt/vncorners, 1-based or negative (relative) indices; polygons of 3 tomaxFaceVertices(64) vertices, concave ones included. -
oandg(each starts an object),usemtl,mtllib(each distinct name read once; names resolving to the same picked file parse it once);#comments, blank lines, CRLF line ends, a byte-order mark, and lines continued with a trailing backslash. The text is decoded as UTF-8 with invalid bytes replaced. - MTL:
newmtl,Kd(r g b, or one grey value),d,Tr(when there is nod),map_Kd(PNG or JPEG, decided by the bytes and sized from the header as in glTF; Exif / eXIf orientation and ICC profiles ignored).
| feature | result |
|---|---|
l, p, curves and surfaces (curv, curv2, surf, cstype, deg, vp, …), lod, usemap, any other keyword |
warning UNSUPPORTED_STATEMENT, one per keyword with its count and first line, for the first 64 distinct keywords; one more counts the lines of all others |
colour components after v x y z
|
warning UNSUPPORTED_VERTEX_COLOR once; positions load |
s smoothing groups in a file without vn
|
warning SMOOTHING_IGNORED once; faces are flat |
a mtllib file not picked, or a URL |
warning MISSING_RESOURCE; its materials' faces are drawn plain (−1) |
a usemtl name no loaded library defines |
warning UNKNOWN_MATERIAL; drawn plain (−1) |
map_Kd options (-o, -s, -t, -clamp, -bm, -mm, …) |
warning UNSUPPORTED_MAP_OPTION, one per material; the file is still read |
other maps (map_Ka, map_Ks, map_Bump, map_d, bump, norm, disp, decal, refl, …) |
warning UNSUPPORTED_MATERIAL_FEATURE, one per material listing them |
Ka, Ks, Ke, Ns, Ni, illum, Tf, PBR parameters (Pr, Pm, …), Kd spectral / Kd xyz
|
warning MATERIAL_PARAMS_IGNORED, one per material listing them |
Kd ≠ white or d < 1 beside map_Kd
|
warning BASE_COLOR_FACTOR_IGNORED; the texture is drawn as it is |
a map_Kd file not picked, a URL, or not a PNG or JPEG (the reason named) |
warning MISSING_RESOURCE or UNSUPPORTED_IMAGE; the texture is missing
|
-
Units and axes: positions ×
unitScale(default 16: 1 OBJ unit = 1 block, the scale of Blockbench's OBJ export); OBJ's y-up right-handed axes are the scene's. -
Objects: every
o/gstarts an object; faces before any of them belong to an object named after the file. Each object with faces is one rootSceneNodewith oneSceneMesh; empty objects are dropped. -
Groups: one
SceneMeshGroupper material, in order of firstusemtl, faces without a known material last; every group isdoubleSided(OBJ has no sidedness, and exporters wind inconsistently). - Triangulation: polygons are triangulated in their plane (ear clipping; a self-intersecting polygon falls back to a fan), counter-clockwise about the face's Newell normal; a face with zero area is dropped.
-
Vertices: corners with a
vnare shared per group by theirv/vt/vntriple; a face withoutvnon every corner gets its own corners with its flat face normal. A zerovntakes the face normal. -
UVs:
vtv is flipped (1 − v: OBJ's v grows upwards, the scene's downwards). Faces of amap_Kdmaterial withoutvtsample texel (0, 0);uvRectsare the union of the triangle UV boxes per vertex, as for glTF. -
Materials:
map_Kd→ that PNG or JPEG,blendwithBLEND_ALPHA_CUTOFF(only alpha byte 0 is invisible, as Blockbench and most OBJ viewers draw textures). Withoutmap_Kd: a generated 1×1 PNG ofKd(read as sRGB, as three.js's MTLLoader does) with alphad;blendwhend< 1, elseopaque. -
Filter: every texture is nearest (no
SceneTexture.filter, MS-38): MTL has no filter statement, and its-blendu/-blendvoptions (on by default) would turn every OBJ smooth, against the pixel-art default; they stayUNSUPPORTED_MAP_OPTION. -
source:{ format: 'obj', formatVersion: '', modelFormat: 'obj' }.
Override per call with options.limits (a positive integer or Infinity, else a RangeError).
| limit | default | error |
|---|---|---|
maxFileBytes |
64 MiB |
FILE_TOO_LARGE (the .obj, and each .mtl), checked before decoding |
maxLineChars |
4096 |
LINE_TOO_LONG (a logical line, continuations joined), checked before splitting |
maxFaceVertices |
64 | TOO_MANY_FACE_VERTICES |
maxTriangles |
200 000 |
TOO_MANY_TRIANGLES (n − 2 per face, counted while parsing) |
maxVertices |
400 000 |
TOO_MANY_VERTICES (v, vt, vn statements each, and built vertices) |
maxObjects |
10 000 |
TOO_MANY_OBJECTS (o and g statements) |
maxMaterials |
256 |
TOO_MANY_MATERIALS (usemtl names, distinct newmtl names, distinct mtllib names) |
maxTextures |
64 |
TOO_MANY_TEXTURES (map_Kd images; generated colour textures are bounded by the materials, as in glTF) |
maxTextureSide |
4096 px | TEXTURE_TOO_LARGE |
maxTexturePixels |
16 777 216 | TEXTURE_TOO_LARGE |
maxStringChars |
1024 | names and file references are cut to this length |
Ear clipping is O(n³) per face; at the defaults a file of 64-vertex concave faces up to the triangle limit loads in
well under a second on the dev machine (bounded.test.ts).
ObjError has a code, a path <file>:<line> (crate.obj:12, crate.mtl:3; the file name alone for the whole
file) and a message quoting at most 32 characters of file content.
| code | when |
|---|---|
FILE_TOO_LARGE |
the .obj or a .mtl exceeds maxFileBytes
|
LINE_TOO_LONG |
a logical line exceeds maxLineChars
|
INVALID_STATEMENT |
too few numbers for v, vt or vn; a face of fewer than 3 corners; a malformed corner or index; input that is neither text nor bytes |
INDEX_OUT_OF_RANGE |
index 0, or an index beyond the v, vt or vn statements read so far |
NON_FINITE_DATA |
a number that is not finite (NaN, Infinity, 1e999), or not finite in float32 (a position times unitScale, a vt or vn value) |
TOO_MANY_* |
a count over its limit (see Limits) |
TEXTURE_TOO_LARGE |
a map_Kd PNG or JPEG over the side or pixel limit |
At most 100 are kept (MAX_OBJ_WARNINGS); each has code, path, message. The codes are those of the table in
Not supported.
mtllib and map_Kd are only matched, by relative path and then by case-insensitive base name, against the files the
caller passed in files; a URL is reported as not fetched. Nothing else is read.
decodeObjText(input, fileName, maxFileBytes) decodes an .obj once (size check, UTF-8 with invalid bytes
replaced, byte-order mark dropped) into a DecodedObjText that loadObj and listObjReferences take in place of the
file; it keeps the file's own size, so a limit is checked against the bytes, not the decoded text. Only text this
function returned is taken.
listObjReferences(input, { maxReferences }) lists the mtllib names of an .obj, and listMtlReferences (the
same arguments) the map_Kd file of each material of an .mtl (its last one, options skipped, the first definition
of a name), as the loader reads them: in order, each distinct spelling once, at most maxReferences + 1, nothing for a
file the loader would refuse. The embed uses them to download an .obj's libraries and then their textures (relative
to each library) from the model's own server (Embed).
Repository · This wiki is generated from docs/: change the pages there.