Skip to content

Model files

github-actions[bot] edited this page Oct 6, 2026 · 23 revisions

Model files

loaders/obj.mojo, loaders/mtl.mojo, loaders/stl.mojo, loaders/ply.mojo, loaders/gltf.mojo, loaders/collada.mojo, loaders/fbx.mojo and loaders/font.mojo. read_obj reads a Wavefront OBJ file into named objects, each with a BufferGeometry. parse_obj reads the text of one.

read_obj_with_materials also reads the OBJ file's material libraries and gives each object a MaterialId. read_stl and read_ply read an STL or a PLY file into one BufferGeometry. read_gltf reads a glTF 2.0 file, .gltf or .glb, into a scene and its assets. read_collada and read_fbx read a Collada or an FBX file into a scene and its assets. read_font reads a typeface.js font, which lays text out as shapes.

A cube loaded from an OBJ file turns under a lamp

three.js: OBJLoader, MTLLoader, STLLoader, PLYLoader, GLTFLoader, ColladaLoader, FBXLoader and FontLoader.

To write these files, see Exporters. To read a scene in three.js JSON, see Scene JSON.

Text encoding

The glTF and FBX byte readers require valid UTF-8 text. This includes the JSON chunk in a GLB file and binary FBX names and string properties. Malformed text raises an error before it becomes a string. Valid Unicode names are preserved. Binary payloads keep their own format rules.

The Gaussian splat glTF readers and PLY header reader use the same checked UTF-8 conversion. Draco metadata names, VTK detection headers and UltraHDR XMP also reject malformed encoding. UsdzFiles.text refuses bytes that are not valid UTF-8. This does not change a format that explicitly uses lossy decoding elsewhere.

Read a file

var model = read_obj("assets/cube.obj")
for index in range(model.count()):
    print(model.objects[index].name, model.objects[index].material)
var shape = assets.geometries.add(model.objects[0].take_geometry())
Function Meaning
read_obj(path) -> ObjModel Read a file.
parse_obj(text) -> ObjModel Read the text of one.

OBJ polygon convexity is checked in translated, widened coordinates so small or large finite model scales do not change whether a face is accepted.

ObjModel and ObjObject

ObjModel.objects holds one ObjObject per object, in file order. count() says how many. ObjModel.material_libraries holds the file name of each mtllib line, in file order.

Field Meaning
name From o or g. Empty when the file has neither. Faces read before the first o or g belong to it, as three.js has it.
material The first name in materials.
materials The names from usemtl, one for each run of faces. It holds one entry, which is empty when no usemtl comes before the faces. See Material libraries.
geometry A non-indexed BufferGeometry with position, and with normal and uv when the faces name them. It has one group for each run when materials has more than one entry. Group i wears entry i.

is_multi_material() is true when the object uses more than one material.

take_geometry() swaps the geometry out for an empty one, so it can go into a store. A BufferGeometry moves and does not copy.

What is read

Line Meaning
v x y z A position. A fourth number is ignored.
vt u v A texture coordinate.
vn x y z A normal.
f a b c ... A face of three or more corners, cut into a fan of triangles. A polygon must be convex. Each corner is v, v/vt, v//vn or v/vt/vn.
o name, g name A new object. One with no faces is dropped.
usemtl name A new run of faces under that material, in the same object. A run with no faces is dropped. The first usemtl also takes the faces before it, as in three.js.
mtllib name A material library. The rest of the line is one file name.
# A comment, to the end of the line.

An index counts from one. A negative index counts back from the last entry so far. s, l, p and unknown lines are skipped.

Every face of one object must agree about normals and texture coordinates. A geometry without normals shades flat. See Renderer.

Errors

The parser raises, naming the line, for:

  • A v, vt or vn line with too few coordinates. A coordinate that is not a number, or is not finite as a Float32.
  • A face with fewer than three corners, or a corner with more than three parts.
  • A face of four or more corners that is not convex, or has no area. A fan covers a convex polygon and only that.
  • An index that is not a whole number, is zero, or names an entry the file does not have.
  • A face that names a normal or a texture coordinate where an earlier face of the object did not, or the other way round.
  • An mtllib line with no file name.

read_obj raises for a file it cannot read.

Example

assets/cube.obj is a unit cube with normals and texture coordinates. tests/test_obj.mojo reads it and draws it.

Material libraries

loaders/mtl.mojo. read_mtl(path, assets) reads a Wavefront .mtl file into an MtlLibrary. It adds one PHONG material for each newmtl to assets.materials, and each image it names to assets.textures. three.js: MTLLoader and OBJLoader.setMaterials.

var assets = Assets()
var read = read_obj_with_materials("assets/cube.obj", assets)
var shape = assets.geometries.add(read.model.objects[0].take_geometry())
scene.add_mesh(obj_mesh(read.materials[0], shape, node))
Function Meaning
read_mtl(path, assets, options) -> MtlLibrary Read a file. Texture file names are relative to its directory.
parse_mtl(text, directory, assets, options) -> MtlLibrary Read the text of one. directory ends in /, or is empty.
set_materials(model, library, assets) -> List[List[MaterialId]] One list for each object of an ObjModel, in its order, with one MaterialId for each entry of the object's materials.
obj_mesh(materials, geometry, node) -> Mesh The mesh three.js builds for an object: one material, or a list when there are more.
read_obj_with_materials(path, assets, options) -> ObjWithMaterials Read an OBJ file and each library its mtllib lines name.

ObjWithMaterials has model, the ObjModel, library, the combined MtlLibrary, and materials, one list of MaterialIds for each object. The loader reads each library relative to the OBJ file. A material in a later library replaces a material of the same name in an earlier one.

Options

MtlOptions holds the options of three.js's MTLLoader.setMaterialOptions. Each option is a keyword. The defaults read a file as three.js reads it with no options.

Field three.js Default Meaning
side side FRONT_SIDE The side of each material, a default material too.
wrap wrap REPEAT The wrap of each texture. A -clamp on still clamps.
normalize_rgb normalizeRGB False Read Kd and Ks from 0 to 255, and divide them by 255.
ignore_zero_rgbs ignoreZeroRGBs False Skip a Kd or Ks of three zeros. The default color stays.
invert_tr_property invertTrProperty False Read Tr as the opacity.

Neither normalize_rgb nor ignore_zero_rgbs changes Ke, as in three.js. The loader refuses a side or a wrap that is none of the named values.

var library = read_mtl(
    "assets/cube.mtl", assets, MtlOptions(side=DOUBLE_SIDE, wrap=MIRROR)
)

MtlLibrary

Member Meaning
names, materials The name and the MaterialId of each material, in the order the names first appear.
count() How many materials the library has.
find(name) -> Int The index of a name, or -1.
get(name) -> MaterialId The material of a name. It raises for a name the library does not have.
add(name, material) Add a material. It replaces a material of the same name.
merge(other) Add each material of another library.
create(name, assets) -> MaterialId The material of a name. For a name the library does not have, it adds a default PHONG material once and keeps it. This is three.js's MaterialCreator.create.

An OBJ object with no usemtl, or with a name that no library has, gets a default material. Each such name gets its own default, as in three.js.

What is read

A keyword can be in any case. Each key keeps the last value that a material gives it. The loader applies the keys in the order they first appear, as three.js does.

Key Meaning
newmtl name A new material. A second newmtl of the same name replaces the first.
Kd r g b The color. Three numbers from zero to one, in sRGB.
Ks r g b The specular color.
Ke r g b The emissive color.
Ns n The shininess. It must not be negative.
d n The opacity, from zero to one. Below one, it sets opacity and transparent.
Tr n The transparency, from zero to one. Above zero, it sets opacity to 1 - n and sets transparent.
illum n The illumination model, a whole number from 0 to 10. The loader checks it and ignores it, as three.js does.
map_Kd file The color map, in sRGB.
map_Ke file The emissive map, in sRGB.
map_d file The alpha map, as data. It also sets transparent.
map_bump file, bump file The bump map, as data. The first of the two is kept.
norm file The normal map, as data.
map_Ks file The specular map, as data.
disp file The displacement map, as data.
# A comment, to the end of the line.

The defaults are those of three.js's MeshPhongMaterial: a white color, a specular of 0x111111, a shininess of 30, no emissive color, and opaque. Lines before the first newmtl are skipped. A key with no value is skipped. Ka, map_Ka and unknown keys are skipped, as three.js skips them.

A material that has a normal map and a bump map keeps the normal map. three.js ignores the bump map in that case, and Material refuses both.

Texture options

A texture line has options, then a file name. The file name can contain spaces.

Option Meaning
-s u v w The texture's repeat. w is ignored. When v is missing, it is one.
-o u v w The texture's offset. w is ignored. When v is missing, it is zero.
-bm n The material's bump_scale, on any texture line, as three.js reads it.
-mm base gain The material's displacement bias and scale, in meters, on any texture line, as three.js reads them. They move nothing without a disp map.
-clamp on, -clamp off CLAMP, or the wrap of the options. three.js always uses the wrap of the options.

A texture repeats by default, as in three.js. The loader decodes PNG, JPEG and TGA images, and tells them apart by their first bytes. A color map is SRGB, and its alpha is coverage. An emissive map is SRGB, and its alpha is IGNORED. An alpha map, a bump map, a normal map, a specular map and a displacement map are LINEAR, and their alpha is IGNORED. One image read the same way twice gives one texture.

Differences from three.js

  • A displacement bias and scale without a disp map are dropped. three.js keeps them, and they move nothing there either.
  • -clamp on clamps a texture. three.js ignores -clamp and uses the wrap option.
  • An unknown texture option is refused. three.js reads it as part of the file name, and then cannot load the file.
  • A color above one is refused. Color holds eight bits for each channel.

Errors

The loader raises, and names the line, for:

  • A newmtl line with no name.
  • A color that is not three numbers from zero to one, or from 0 to 255 with normalize_rgb.
  • A side or a wrap in the options that is none of the named values.
  • An Ns that is not a number, is not finite, or is negative.
  • A d or Tr outside zero to one.
  • An illum that is not a whole number from 0 to 10.
  • A texture option that is not known, or does not have its numbers. A -clamp without on or off.
  • A texture line with no file name.
  • An image that the loader cannot read or decode.

read_mtl and read_obj_with_materials also raise for a file that they cannot read.

Example

assets/cube.mtl is the Brick material that assets/cube.obj names, with the texture assets/brick.png. assets/mtl/ holds an OBJ file with two libraries. tests/test_mtl.mojo reads them and draws the cube.

STL

loaders/stl.mojo. read_stl(path) reads an STL file, binary or ASCII, into an StlModel. three.js: STLLoader.

var model = read_stl("assets/tetrahedron.stl")
print(model.geometry.triangle_count(), model.has_colors(), model.alpha)
var shape = assets.geometries.add(model.take_geometry())
Function Meaning
read_stl(path) -> StlModel Read a file.
parse_stl(bytes) -> StlModel Read the bytes of one, binary or ASCII.
parse_stl_text(text) -> StlModel Read the text of an ASCII file.
is_binary_stl(bytes) -> Bool Tell the two encodings apart, as three.js does.

ASCII input can begin with one UTF-8 byte-order mark. Both byte and text parsers ignore that leading marker; embedded markers remain data.

A file exactly as long as its face count says is binary. Otherwise, a file with solid starting at byte offset zero through four is ASCII. Any other file is binary.

StlModel

Field Meaning
geometry A non-indexed BufferGeometry. It has position and normal, and color when the binary header has COLOR=. Each corner gets the normal of its face.
solids One StlSolid for each solid, in file order: name, start and count. start and count count corners. A binary file has one solid with no name.

An ASCII file's geometry has one group for each solid. Solid i wears material i, as three.js's parseASCII adds them. A binary file's geometry has no group. | alpha | The alpha of COLOR=, from zero to one. It is one when the file has no header color. |

has_colors() is true when the geometry has a color attribute. take_geometry() swaps the geometry out for an empty one.

Binary colors

The binary format has no standard color. This loader reads the convention that three.js reads:

  • A header with COLOR= and four bytes gives a default color and an alpha. The last COLOR= in the header wins.
  • Each face then has a 16-bit color. Red is in bits 0 to 4, green in bits 5 to 9, and blue in bits 10 to 14.
  • A face with bit 15 set uses the default color.

The colors are sRGB. The loader decodes them to linear light, as three.js does. A header without COLOR= gives no color attribute. Most writers put zeros in the 16 bits, and zero is black.

Errors

The loader raises for:

  • A binary file shorter than 84 bytes, or shorter than its face count needs. The loader reads a binary file with bytes after its faces.
  • A coordinate or a normal that is not a number, or is not finite as a Float32.
  • An ASCII file with no solid, or a solid without endsolid. A solid in a solid.
  • A keyword in the wrong place, such as vertex outside a facet.
  • A facet without normal, or with other than three vertices. outer without loop.
  • A word that STL does not have. Text that is not UTF-8.

PLY

loaders/ply.mojo. read_ply(path) reads a PLY file into one indexed BufferGeometry. three.js: PLYLoader.

var shape = assets.geometries.add(read_ply("assets/cube.ply"))
Function Meaning
read_ply(path, options) -> BufferGeometry Read a file.
parse_ply(bytes, options) -> BufferGeometry Read the bytes of one.
ply_format(name) -> PlyFormat The format a format line names.
ply_scalar(name) -> PlyScalar The type a property line names.
decode_ply_scalar(bytes, at, scalar, format) -> Float64 One binary value.

The loader reads the three formats: ascii, binary_little_endian and binary_big_endian. It reads every scalar type: char, uchar, short, ushort, int, uint, float and double. It also reads the names int8 to float64.

PlyFormat and PlyScalar are types. A bare integer does not compile. PlyScalar.size() and decode_ply_scalar refuse a value that is not valid.

What is read

Element and property Attribute
vertex: x, y, z, or px, py, pz, or posx, posy, posz position. A file must have them.
vertex: nx, ny, nz, or normalx, normaly, normalz normal.
vertex: s, t, or u, v, or texture_u, texture_v, or tx, ty uv.
vertex: red, green, blue, or r, g, b, or diffuse_red and so on color, divided by 255 and decoded from sRGB.
vertex: alpha or a The fourth channel of color, divided by 255. three.js does not read it.
face: vertex_indices or vertex_index The index.

A face of more than three corners becomes a fan of triangles from its first corner. Thus the face must be convex, as an OBJ face must be. three.js cuts a quad on the other diagonal, and it does not read a face of five or more corners.

The loader reads past all other elements and properties. A file without a face element is a point cloud, and its geometry has no index.

Options

PlyOptions holds three.js's setPropertyNameMapping and setCustomPropertyNameMapping.

Method three.js Meaning
set_property_name(name, read_as) setPropertyNameMapping Read a property of the file as another name, in every element.
set_custom_attribute(attribute, properties) setCustomPropertyNameMapping Read vertex properties into an attribute of their own. The item size is the count of properties.
var options = PlyOptions()
options.set_property_name("diffuse_red", "red")
options.set_custom_attribute("quality", ["q1", "q2"])
var shape = read_ply("assets/scan.ply", options)

A custom attribute names properties after the renames. It is read after the named attributes, so a custom position replaces the positions, as in three.js. An empty list of properties reads nothing, as in three.js. A property that the vertex element does not have is refused. three.js reads NaN for it.

Errors

The loader raises, and names the element and the row, for:

  • A file that does not start with ply, or has no end_header. A header with no format line.
  • A format or a type that is not known. An element or property line with the wrong fields.
  • A property before any element. An element named twice. A list length that is not an integer type.
  • A header line that is not format, element, property, comment or obj_info.
  • No vertex element, or a vertex without x, y and z. A vertex property that is a list.
  • A custom attribute that names a property the vertex element does not have, or a list.
  • Only some channels of a normal, a texture coordinate or a color. An alpha without a color.
  • A face without a list of integer vertex indices.
  • An ASCII row with too few or too many values. A value that is not a number of its type, or is out of its range.
  • A file that ends before its last row. A list with a negative length.
  • A value of a vertex that is not finite as a Float32.
  • A face with fewer than three corners, or one that names a vertex the file does not have. A face that is not convex, or has no area.

Example

assets/cube.ply is a unit cube in ASCII, with a color at each corner and six quads. assets/tetrahedron.stl is a binary tetrahedron with a header color. tests/test_ply.mojo and tests/test_stl.mojo read them and draw them.

glTF

loaders/gltf.mojo. read_gltf(path, scene, assets) reads a glTF 2.0 file into the scene and the assets it is handed. It reads meshes, points, lines, materials, textures, nodes, skins, morph targets, animations, cameras, sparse accessors and twenty-one extensions. three.js: GLTFLoader.

var model = read_gltf("assets/gltf/box.glb", scene, assets)
print(model.node_names[0], model.mesh_count)
var camera_node = model.nodes[2]

Pass workers to read_gltf or load_gltf to decode core material maps with the bounded image queue. One worker keeps lazy serial decoding. Extension-only maps remain lazy.

A .glb is told by its magic. Anything else is read as JSON. A buffer or an image named by a relative URI is read from the file's own directory. A data: URI must be base64.

Accessor offsets and strides must stay inside their buffer views and meet alignment rules. Matrix columns include their required padding. The final column can omit trailing padding. Integer attributes, indices, and sparse indices keep their exact values; they do not pass through Float32 storage.

Function Meaning
read_gltf(path, scene, assets) -> GltfModel Read a file.
load_gltf(text, bin, directory, scene, assets) -> GltfModel Read the JSON of one, with a .glb's binary chunk or none.
split_glb(bytes) -> (String, List[UInt8]) A .glb's JSON and its binary chunk.
decode_base64(text) -> List[UInt8] The bytes of a base64 text.
decode_image(bytes) -> DecodedImage A PNG or a JPEG, told by its first bytes. Other bytes are read as a TGA, which has no signature.

GltfModel

The model says what went where, by the file's own indices.

Field Meaning
nodes One NodeId per glTF node. NO_PARENT for a node the loaded scene does not reach.
node_names Each node's name, or empty.
geometries One GeometryId per primitive, mesh by mesh. mesh_geometries(mesh) returns one mesh's.
materials One MaterialId per glTF material.
color_textures, data_textures One TextureId per glTF texture, as read for a color map and as read for a data map. NO_TEXTURE when no material read it that way.
first_mesh, mesh_count Where the meshes this file added begin in scene.meshes, and how many.
first_skinned_mesh, skinned_mesh_count Where the skinned meshes this file added begin in scene.skinned_meshes, and how many.
first_instanced_mesh, instanced_mesh_count Where the instanced meshes this file added begin in scene.instanced_meshes, and how many.
first_line, line_count Where the lines this file added begin in scene.lines, and how many.
first_points, points_count Where the points this file added begin in scene.points, and how many.
first_light, light_count Where the lights this file added begin in scene.lights, and how many.
cameras One GltfCamera per node that carries a camera and that the loaded scene reaches.
animations One AnimationClip per animation that drives something the loaded scene reaches, in file order.
mesh_extras One UserData per glTF mesh: its extras, three.js's mesh.userData.
material_extras One UserData per glTF material: its extras, three.js's material.userData.
scene_extras The extras of the loaded scene, three.js's scene.userData.

Extras

The loader reads extras into user data, as three.js's assignExtrasToUserData does. User data is a UserData from core/user_data.mojo, the same map that Scene JSON reads.

  • A node's extras go into its user_data. A named node also gets name in its user_data, as three.js sets userData.name.
  • In three.js, the node of a mesh with one primitive is the mesh itself. Thus such a node takes the mesh's extras first, then name, then its own extras. A node with a camera or a light does not, and a joint does not. three.js makes a group or a bone of it.
  • A primitive's extras go into its geometry's user_data, as three.js reads them into geometry.userData.
  • A Mesh, a Material and a Scene here hold no user data. mesh_extras, material_extras and scene_extras hold their extras.
  • extras that are not an object are skipped, as three.js skips them.

To write the user data back, give it to GltfExportOptions. See Exporters.

What maps to what

glTF ThreeMojo
A primitive of triangles A BufferGeometry with position, and normal, uv, uv1 and color when present, indexed when it is. TEXCOORD_1 becomes uv1.
A primitive of points or lines A Points or a Line. See Points and lines.
A material A STANDARD material, or a BASIC or PHYSICAL one when an extension asks. It reads base color and alpha, base color texture, metallic and roughness factors, the metallic-roughness texture as both roughness_map and metalness_map, normal texture and scale, the occlusion texture, emissive factor and texture, doubleSided. BLEND sets transparent. MASK sets alpha_test to alphaCutoff.
A texture A Texture at its sampler's wrap and filters. A base color or emissive map is read as sRGB. A metallic-roughness, normal or occlusion map is read as linear, with its alpha ignored. One glTF texture read both ways is two textures.
A node An Object3D at its translation, rotation and scale, or at its matrix decomposed, with the node's name. Each primitive of its mesh is a Mesh.
A node with a skin Each primitive of its mesh is a SkinnedMesh. See Skins, morph targets and animations.
A node with a camera A PerspectiveCamera or an OrthographicCamera attached to the node. See Cameras.
A sparse accessor Its values replace the elements that its indices name. The other elements come from the buffer view, or are zero when there is none.
The default scene Its roots and everything under them, each node after its parent. scene picks it.

A primitive without a material draws with one default standard_material. A primitive with COLOR_0 draws with a copy of its material that has vertex_colors on, one copy per material. An accessor without a buffer view reads as zeros. A normalized integer accessor divides by its largest value, as the specification has it.

glTF's texture coordinates run down from an image's top left. Each glTF texture has flip_y off, as three.js sets flipY = false. So v reads down from the first row, and the geometry's coordinates and any KHR_texture_transform are kept as the file has them. The writer writes a texture with flip_y upside down, as three.js's GLTFExporter writes a flipY texture, and one without it as it is.

A texture takes its sampler's wrapS, wrapT, magFilter and minFilter, as three.js's GLTFLoader reads them. A mipmap minFilter builds the chain. The reader refuses a mipmap magFilter and a filter that is none of glTF's six. The writer writes the four, and writes a mipmap minFilter on a texture without a chain as the filter it reads inside a level.

Points and lines

A primitive of points or lines draws as three.js's GLTFLoader.loadMesh draws it.

mode ThreeMojo
POINTS, 0 Points.
LINES, 1 Line with SEGMENTS.
LINE_LOOP, 2 Line with LOOP.
LINE_STRIP, 3 Line with STRIP.
TRIANGLES, 4 Mesh. This is the default.

A point or a line gets a BASIC material of its own, as three.js makes a PointsMaterial or a LineBasicMaterial. It takes the color, the opacity and the transparency of the file's material. It has vertex_colors on when the primitive has COLOR_0. Points also take the base color texture and the alpha test, at a size of one pixel that does not shrink with distance. A line takes no texture, because a line here has none.

An index here holds triangles. Thus the loader copies the vertices of an indexed point or line primitive out in index order, as three.js's toNonIndexed does. The morph targets are copied with them.

A point or a line on a skinned node or an instanced node is refused. A skinned or instanced line is not ported.

Occlusion

A material's occlusionTexture becomes its ao_map. Its strength becomes ao_map_intensity, or one when it is not there. This is what three.js reads into aoMap and aoMapIntensity. The red channel of the map dims the indirect light. See Materials.

Every map can read the second set of texture coordinates, the occlusion texture among them. A texCoord of one gives a copy of the texture the channel UV_CHANNEL_1. The renderer then samples it at the geometry's uv1. A texCoord in the map's KHR_texture_transform replaces the map's own.

An unlit material reads no occlusion, as in three.js.

Skins, morph targets and animations

The loader reads a skin into a Skeleton, a morph target into the geometry, and an animation into an AnimationClip. The names are three.js's, as in Skinning and Animation.

glTF ThreeMojo
JOINTS_0 The skinIndex attribute. The components must be unsigned bytes or unsigned shorts.
WEIGHTS_0 The skinWeight attribute. The loader divides each vertex's four weights by their sum, as three.js's normalizeSkinWeights does. Four zeros become one on the first bone.
A skin A Skeleton with one Bone per joint. The inverse bind matrices come from inverseBindMatrices, or are the identity. The mesh is bound at the identity, in ATTACHED mode.
A primitive's targets Morph targets of the geometry, with morph_relative set, because glTF holds offsets. POSITION, NORMAL and COLOR_0 are read. A geometry can carry any number of targets.
A mesh's extras.targetNames The names of the targets. Each mesh gets a morph_target_dictionary from them.
weights The morph influences of each mesh. A node's weights replace its mesh's weights.
extras.targetNames The geometry's morph_names, one per target, as three.js reads them into morphTargetDictionary. extras of another form names nothing.
An animation An AnimationClip, named by the file or animation_ and its index.
A translation, rotation or scale channel A POSITION, QUATERNION or SCALE track on the node.
A weights channel One morph influence track for each morph target of each mesh, plain or skinned, on the node and below it.
STEP, LINEAR The track's STEP or LINEAR interpolation.
CUBICSPLINE The track's CUBIC_SPLINE interpolation. Each key's in-tangent, value and out-tangent go to in_tangents, values and out_tangents.

A skinned mesh waits until the loader has placed every node. Thus a joint can come after the mesh in the file. Each primitive gets its own copy of the skeleton, because a SkinnedMesh owns its skeleton.

Cameras

A node with a camera gets a camera attached to it. The camera looks down the node's -z axis, with its +y axis up. GltfCamera holds the camera.

Member Meaning
kind GLTF_PERSPECTIVE or GLTF_ORTHOGRAPHIC, a GltfCameraKind. A bare integer does not compile.
index, name The camera's index in the file's cameras, and its name.
node The scene node that the camera rides.
perspective() The PerspectiveCamera. It raises for an orthographic camera, or a kind that is not valid.
orthographic() The OrthographicCamera. It raises for a perspective camera, or a kind that is not valid.

A perspective camera takes yfov in radians and znear. Without aspectRatio, the aspect is one. Without zfar, the far plane is at two million meters. These are three.js's values. An orthographic camera spans xmag and ymag on each side of its axis. One glTF camera on two nodes is two cameras.

Differences from three.js

  • A morph target without POSITION moves no position. three.js adds the base positions to it as offsets.
  • A channel on a node that the default scene does not reach is left out. An animation left with no track is left out.
  • A skin joint that the scene does not reach is refused. three.js puts a new bone in its place.
  • A morph target without COLOR_0 changes no color. three.js adds the base colors to it as offsets.
  • A rotation key must be of unit length. A track refuses one that is not.
  • A map other than the occlusion texture must read the first set of texture coordinates. three.js reads any set. Here, only the ao map and the light map are sampled at a second set.
  • A map that reads the third set of texture coordinates or a later set is refused. A geometry here has only uv and uv1.

glTF extensions

The loader reads twenty-one extensions. Twenty are the ones three.js's GLTFLoader reads that map onto a feature of this renderer. KHR_materials_variants is read as the plugin that three.js names for it reads it. is_supported_extension(name) tells if the loader reads an extension.

Extension ThreeMojo
KHR_materials_unlit A BASIC material: the base color, its texture, doubleSided and the alpha mode. The loader ignores the emissive, normal and metallic-roughness terms and every other material extension, as three.js does.
KHR_materials_emissive_strength emissiveStrength sets emissive_intensity.
KHR_materials_ior A PHYSICAL material. ior sets ior, or 1.5 when it is not there.
KHR_materials_specular A PHYSICAL material. specularFactor sets specular_intensity. specularColorFactor sets specular_color, converted from linear to sRGB. specularTexture sets specular_intensity_map, linear with its alpha kept. specularColorTexture sets specular_color_map, sRGB with its alpha ignored.
KHR_materials_clearcoat A PHYSICAL material. clearcoatFactor and clearcoatRoughnessFactor set clearcoat and clearcoat_roughness. clearcoatTexture, clearcoatRoughnessTexture and clearcoatNormalTexture set the three coat maps, as data. The normal texture's scale sets clearcoat_normal_scale on both axes, as three.js sets it.
KHR_materials_transmission A PHYSICAL material. transmissionFactor sets transmission, and transmissionTexture sets transmission_map, read as data. See Materials.
KHR_materials_volume A PHYSICAL material. thicknessFactor sets thickness, and thicknessTexture sets thickness_map, read as data. attenuationDistance sets attenuation_distance, and a distance of zero or none is infinite, as three.js reads it. attenuationColor sets attenuation_color, converted from linear to sRGB.
KHR_materials_dispersion A PHYSICAL material. dispersion sets dispersion.
KHR_materials_sheen A PHYSICAL material with a sheen of one, as three.js sets it. sheenColorFactor sets sheen_color, converted from linear to sRGB, and sheenRoughnessFactor sets sheen_roughness. Both are zero when they are not there. sheenColorTexture and sheenRoughnessTexture set the two maps. The roughness texture keeps its alpha, because the roughness is stored there. See Materials.
KHR_materials_iridescence A PHYSICAL material. iridescenceFactor, iridescenceIor, iridescenceThicknessMinimum and iridescenceThicknessMaximum set the film, in nanometers. iridescenceTexture and iridescenceThicknessTexture set its maps. See Materials.
KHR_materials_anisotropy A PHYSICAL material. anisotropyStrength and anisotropyRotation set anisotropy and anisotropy_rotation, and anisotropyTexture sets its map. See Materials.
KHR_texture_transform A copy of the texture with its offset, rotation and repeat set. See Texture transforms.
KHR_lights_punctual A directional, point or spot Light on the node. See Punctual lights.
KHR_mesh_quantization Nothing more. The loader reads every attribute at any component type, and a normalized one divides by its largest value.
EXT_mesh_gpu_instancing One InstancedMesh for each primitive of the node's mesh. See Instancing.
KHR_draco_mesh_compression The primitive's Draco data, decoded by loaders/draco.mojo. See Draco primitives.
EXT_materials_bump A PHYSICAL material. bumpTexture sets bump_map, read as data, and bumpFactor sets bump_scale. See Bump maps.
KHR_texture_basisu The texture reads the KTX 2.0 image that the extension names, before its own source. See KTX 2.0 textures.
EXT_meshopt_compression The buffer view's compressed bytes, decoded by loaders/meshopt.mojo. See Compressed buffer views.
EXT_texture_webp The texture reads the WebP image that the extension names, before its own source. See WebP images.
KHR_materials_variants The variant names, and the material that each variant gives each mapped object. select_variant changes the materials. See Material variants.

A file that lists another extension in extensionsRequired is refused, as three.js refuses it. A file that lists an extension only in extensionsUsed is read without that extension.

Texture transforms

The transform is applied first, then the flip of v. So the copy gets an offset of (x, 1 - y), a repeat of (x, -y), and the rotation as the file gives it. The glTF coordinates then go where three.js's matrix puts them. A transform that names only texCoord makes no copy.

The transform's texCoord replaces the texture's own texCoord, as in three.js. It must be zero or one. Each map keeps its own transform and its own set, as in three.js.

Punctual lights

glTF ThreeMojo
directional directional_light. Its target is a new node one meter down the node's -z axis, as three.js adds its target.
point point_light with distance set to range, or zero for no cutoff.
spot spot_light with angle set to outerConeAngle and penumbra set to 1 - innerConeAngle / outerConeAngle. Its target is as for a directional light.

The color is linear in the file and sRGB in the Light. The intensity is one when the file gives none. Point and spot lights use a decay of two, as three.js does.

Instancing

Each instance matrix is TRANSLATION, ROTATION and SCALE composed. An attribute that is not there is the identity's part. _COLOR_0 colors the instances, as three.js reads it into instanceColor. The loader counts every other attribute but does not read it, because it is for a custom shader. A node with an empty attributes object draws plain meshes, as in three.js.

Draco primitives

The loader decodes the buffer view that the extension names, as three.js's DRACOLoader decodes it. See Draco.

  • Each attribute that the extension names comes from the Draco data. The extension gives its Draco unique id.
  • The accessor gives the component type and normalized. A float accessor reads the values as floats. An integer accessor reads them at its type, then the loader divides a normalized one by its largest value, as for any accessor.
  • The Draco attribute gives the number of components of each element.
  • The triangles of a Draco mesh replace the accessor's indices. A Draco point cloud keeps them.
  • An attribute that the extension does not name comes from its accessor.

The loader refuses an id that is not in the Draco data, and a value that does not fit its accessor's type. It also refuses a Draco float attribute that an integer accessor reads. Draco rounds that float. That file is not valid glTF.

Bump maps

EXT_materials_bump makes a PHYSICAL material, as three.js makes one. A material here holds a normal map or a bump map, not both. three.js draws the normal map of a material with both and ignores the bump map. Thus the loader drops the bump map of such a material. A bumpFactor without a bumpTexture draws nothing, and the loader keeps a bump_scale of one.

KTX 2.0 textures

KHR_texture_basisu names a KTX 2.0 image, which render/ktx2.mojo decodes. A texture with the extension reads that image, and the texture's own source is not needed. The loader reads the first level of the first face.

Image Texture
UASTC, ETC1S or another format of bytes A byte texture in the color space that the map needs: sRGB for a color map, linear for data. The loader builds the mip chain when the sampler reads one.
UASTC HDR, or a half or float format A float texture, linear, whatever the map needs.

The loader tells a KTX 2.0 image by its identifier, is_ktx2(bytes). ktx2_texture(bytes, sampling, space, alpha) builds the texture.

Compressed buffer views

EXT_meshopt_compression stores a buffer view in a smaller form. The loader decodes each such view when it reads the buffers, as three.js does with its MeshoptDecoder set. Accessors, images and animations then read the decoded bytes.

Mode What it holds
ATTRIBUTES Elements of any size, as byte deltas from the element before. A filter can follow it.
TRIANGLES A triangle list, as one code for each triangle, from a cache of recent edges and vertices.
INDICES Any list of indices, as deltas from one of two running values.
Filter What it makes
OCTAHEDRAL Unit vectors, from two octahedral coordinates.
QUATERNION Unit quaternions, from their three smallest components.
EXPONENTIAL Floats, from an 8-bit exponent and a 24-bit mantissa.

The decoder is a port of meshoptimizer 0.22, which three.js 0.180 ships. Its output is the same bytes as three.js's decoder, filters included. The filters round each product before they add to it, as WebAssembly does.

A buffer that the extension marks as a fallback is not read. A buffer with no URI after the first is not read either. Only a compressed view can name such a buffer.

decode_gltf_buffer(count, stride, source, mode, filter) decodes one view. decode_vertex_buffer, decode_index_buffer and decode_index_sequence decode one mode each.

The loader refuses these:

  • A compressed stream that is too short, has the wrong header, or does not end where its tail starts. The message has meshoptimizer's code, as three.js's does.
  • A mode or a filter that is not known.
  • A view whose byteLength is not its count times its byteStride, or whose byteStride is not the extension's.
  • A stride, a count or a filter that the specification does not allow for the mode. meshoptimizer does not examine these.

Material variants

KHR_materials_variants gives a model several sets of materials, for example one for each color of a product. three.js's GLTFLoader does not read it. Its documentation names a plugin that does: GLTFMaterialsVariantsExtension of takahirox/three-gltf-extensions. The loader reads the extension as that plugin reads it.

var model = read_gltf("chair.gltf", scene, assets)
select_variant(model, scene, String("red"))
select_variant(model, scene)
  • model.variants holds the variant names. A name that is already taken gets .1, then .2, until it is unique, as the plugin makes it.
  • model.variant_objects holds each drawn object whose primitive has mappings. Each has its scene list, its index there, its original material, and one material for each variant.
  • The loader makes every variant's material when it reads the file, as the plugin's ensureLoadVariants makes them. A material is made final as three.js makes it: a copy with vertex colors, or a points or line material.
  • A later mapping of a variant replaces an earlier one, as in the plugin.
  • select_variant(model, scene, name) gives each mapped object the variant's material. A variant that does not map an object, an unknown name, and no name give the object its original material back.
  • The loader reads no mappings when the root does not have the extension, as the plugin does.

The plugin selects the variant under one object. select_variant selects it for the whole model. The plugin cannot restore the original material of a primitive that names no material, or of points or lines. select_variant restores them.

The loader refuses a variant with no name, a mapping with no material, and a mapping that names a variant that is not there.

WebP images

EXT_texture_webp names a WebP image, which render/webp.mojo decodes. See Read a WebP.

  • A texture with the extension reads that image, before its own source. three.js registers its WebP plugin after its KTX 2.0 plugin, so KHR_texture_basisu comes first.
  • The texture's own source is not needed, and a file can require the extension.

Not ported

  • EXT_texture_avif. It needs an AV1 decoder, and this port has none. A texture with the extension reads its own source.

Differences from three.js

  • A specularColorFactor outside zero to one is refused. A Color cannot hold it. three.js keeps it.
  • An ior outside 1 to 2.333 is refused, because Material refuses it.
  • A clear coat texture with a clearcoatFactor of zero is left out, with the normal texture's scale. three.js keeps them, but draws none.
  • An iridescenceTexture or iridescenceThicknessTexture with an iridescenceFactor of zero is left out. So is an anisotropyTexture with an anisotropyStrength of zero. three.js keeps them, but draws neither.
  • A sheenColorFactor outside zero to one is refused, as a specularColorFactor is.
  • A skinned node with EXT_mesh_gpu_instancing is refused. An instanced skinned mesh is not ported. three.js drops the skin.
  • A KTX 2.0 byte image takes the color space that the map needs. three.js keeps the image's own color space for a data map.
  • A KTX 2.0 image builds its own mip chain from its first level. three.js uploads the chain that the file holds.

Not read

A primitive of triangle strips or triangle fans is refused. three.js turns them into triangles. Only the first two sets of texture coordinates are read.

Errors

The loader raises for:

  • A file it cannot read. A document that is not JSON or not glTF 2. A required extension that the loader does not read.
  • A buffer shorter than its length. A buffer view or accessor that runs past its buffer. A buffer view that reads a buffer with no data.
  • A compressed buffer view that is malformed. See Compressed buffer views.
  • An unknown accessor type or component type. An attribute of the wrong width. Indices that are not unsigned integers.
  • An image that is not PNG, JPEG, WebP, TGA or KTX 2.0. A texture or material that names something the file does not have. A texture with no image.
  • A primitive mode that is not points, lines or triangles. A point or a line on a skinned or instanced node.
  • targetNames that do not hold one name per morph target.
  • An unknown wrap mode or alpha mode.
  • A node reached twice. A node matrix that flattens an axis.
  • A sparse accessor with indices that do not rise, that are not unsigned integers, or that name an element past the accessor.
  • A skin without joints. A joint that the file does not have, or that the scene does not reach. Inverse bind matrices that are not one MAT4 per joint.
  • A skinned primitive without JOINTS_0 and WEIGHTS_0.
  • Primitives of one mesh with different numbers of targets. weights that are not one number per target.
  • A morph target COLOR_0 that is not a VEC3 or a VEC4. An extras.targetNames that is not an array of strings.
  • A camera of an unknown type, or without the values it needs. A camera that PerspectiveCamera or OrthographicCamera refuses.
  • An animation channel with an unknown path, or a sampler with an unknown interpolation. A sampler output that does not hold one value per key, or three for CUBICSPLINE.
  • A track or a clip that KeyframeTrack or AnimationClip refuses.
  • An extensions value, or an extension, that is not an object.
  • A texture reference or a texture transform on a third set of coordinates or past it. A specularColorFactor outside zero to one.
  • A node that names a light the file does not have. A light of an unknown type, a range that is not above zero, or a spot light without spot. A light that Light refuses.
  • Instancing without an attributes object. Instancing attributes of different counts, or of the wrong width. A skinned node that is instanced.

Collada

loaders/collada.mojo. read_collada(path, scene, assets) reads a Collada .dae file into the scene and the assets it is handed. It reads the geometries, the materials and their textures, the node hierarchy, the cameras, the lights, the skins and the animations. three.js: ColladaLoader.

from loaders.collada import read_collada

var model = read_collada("assets/room.dae", scene, assets)
print(model.node_names[0], model.mesh_count)
var camera = model.perspective_cameras[0]

The file is XML. XML reads it. An image is read from the directory of the file.

Function Meaning
read_collada(path, scene, assets) -> ColladaModel Read a file.
load_collada(text, directory, scene, assets) -> ColladaModel Read the text of one. directory ends in /, or is empty.
up_axis_rotation(axis) -> Quaternion The rotation of the root node for an UpAxis.

ColladaModel

The model tells what went where.

Field Meaning
root The node of the visual scene. Each root <node> is a child of it.
up_axis, unit The <up_axis> as an UpAxis, and <unit meter> as a Length.
nodes, node_names One NodeId and one name for each <node> placed, in the order placed. A JOINT node has its sid as its name, as in three.js.
materials, material_ids, material_names One MaterialId, id and name for each <material>, in file order. material(id) finds one.
geometries One GeometryId for each primitive built.
textures One TextureId for each map that a material reads.
perspective_cameras, orthographic_cameras Each camera that an <instance_camera> places. Each camera rides its node.
first_mesh, mesh_count, first_line, line_count, first_light, light_count Where the meshes, lines and lights of the file start in the scene, and how many there are.
first_skinned_mesh, skinned_mesh_count Where the skinned meshes of the file start in scene.skinned_meshes, and how many there are.
animations One AnimationClip for each clip that makes a track. See Skins and animations.

UpAxis is a type: X_UP, Y_UP or Z_UP. A bare integer does not compile. up_axis_rotation refuses a value that is none of the three.

What maps to what

Collada ThreeMojo
<triangles>, <polylist>, <polygons> One BufferGeometry for each kind, with position, and normal, uv and color when the inputs give them. Each primitive of the kind is a group. It is drawn as a Mesh.
<lines>, <linestrips> A geometry of point pairs, drawn as a Line in SEGMENTS mode.
phong, blinn A PHONG material, three.js's MeshPhongMaterial.
lambert A LAMBERT material, three.js's MeshLambertMaterial.
constant A BASIC material, three.js's MeshBasicMaterial.
<node> An Object3D. Its <matrix>, <translate>, <rotate> and <scale> steps are multiplied in file order and decomposed.
<instance_geometry> A mesh for each kind of surface primitive and a line for each line primitive, with the materials that <bind_material> binds to their symbols.

Group i wears material i of the list of symbols that the primitives name. A primitive with no symbol adds a group and no material, as in three.js. A kind with more than one symbol is a mesh that wears a list. See Several materials. A skinned kind with more than one symbol is one SkinnedMesh for each group, because a skinned mesh here wears one material. | <instance_node> | The node from <library_nodes> or the visual scene, placed again. | | <instance_controller> of a <skin> | A SkinnedMesh for each primitive of the skin's geometry. See Skins and animations. | | <animation>, <animation_clip> | An AnimationClip. | | <instance_camera> | A PerspectiveCamera or an OrthographicCamera that rides the node. | | <instance_light> | A directional, point, spot or ambient light at the node. | | <visual_scene> | The root node. A Z_UP file turns it by -90 degrees about x. The root is scaled by <unit meter>. The vertices do not change, as in three.js. |

A quad of a <polylist> or a <polygons> is cut into the triangles (a, b, d) and (b, c, d), as in three.js. A larger polygon is cut into a fan from its first corner. A polygon of fewer than three corners gives no triangle.

A vertex color is sRGB in the file and linear in a geometry, so the loader decodes it. A fourth value is kept as alpha. Only the first two values of a texture coordinate are read.

Materials

A material reads the profile_COMMON technique of its effect. The last constant, lambert, blinn or phong element sets the kind.

Parameter Meaning
diffuse The color, and a color map in sRGB.
specular The specular color, on a PHONG material only.
shininess The shininess, on a PHONG material only. Zero keeps the default of 30, as in three.js.
emission The emissive color and an emissive map, on a PHONG or LAMBERT material only.
bump A normal map, on a PHONG or LAMBERT material only.
transparent, transparency The opacity. See below.
<extra> double_sided DOUBLE_SIDE for 1, FRONT_SIDE for anything else.
<extra> bump A normal map. It replaces the bump parameter.

The opacity comes from the opaque mode of <transparent>, its color and the <transparency> factor:

Mode Opacity
A_ONE, the default alpha times the factor
RGB_ZERO one minus red times the factor
A_ZERO one minus alpha times the factor
RGB_ONE red times the factor

A missing <transparency> is 1. A missing <transparent> is a white of alpha 1 in A_ONE. An opacity below 1 sets transparent. A <transparent> with a texture sets transparent and no alpha map, as in three.js.

A <texture> names a sampler2D, the sampler names a surface, and the surface names an <image>. When no sampler has the name, the name is the image, as in three.js. A texture of an image that the file does not have is left out. The <extra><technique> of a texture can set wrapU, repeatU, repeatV, offsetU and offsetV. wrapU sets one wrap for both axes. A texture repeats by default.

A primitive with no material symbol draws with a white PHONG material, or a white BASIC material for lines. A symbol that <bind_material> does not bind draws with a magenta BASIC material, as in three.js. A line draws with a BASIC copy of a lit material. The copy keeps the color, the opacity and transparent.

Cameras and lights

A perspective camera reads yfov in degrees, aspect_ratio, znear and zfar. A number that is missing takes the three.js default: 50 degrees, an aspect of 1, 0.1 and 2000. An orthographic camera reads xmag and ymag. When one of them is missing, aspect_ratio gives it. The camera spans half of each on each side.

A light reads its color. A point or spot light reads quadratic_attenuation, and its distance is the square root of one over it. A spot light has a cone of 60 degrees, as in three.js, which does not read falloff_angle. A directional or spot light shines toward the world origin.

A node with no child nodes and one object becomes that object in three.js, at the transform of the node. A node with more objects becomes a group, and each object keeps its own transform. Only a directional or spot light shows the difference. When such a light is one object of several, three.js places it one unit up the y axis of the node. This loader does the same.

Skins and animations

A skin controller makes a SkinnedMesh. The loader reads the controller after it places every node, so a bone can come after the mesh in the file. loaders/collada.mojo reads the skins, and loaders/collada_animation.mojo reads the animations. three.js: buildSkin, buildSkeleton, buildAnimation and buildAnimationClip.

Collada ThreeMojo
<bind_shape_matrix> The bind matrix of the mesh. The identity when the skin has none.
<joints> JOINT The bone order. Each name matches the sid of a JOINT node.
<joints> INV_BIND_MATRIX The inverse bind of each bone.
<vertex_weights> skinIndex and skinWeight. Each vertex keeps its four largest weights, largest first. The loader normalizes the weights.
<skeleton> The root of the bones. Each JOINT node under it is a bone. A <skeleton> can also name the visual scene. Then each JOINT node at the top of the scene is a root.
A channel on a <matrix> step A POSITION, a QUATERNION and a SCALE track on the node.
<animation_clip> A clip that plays the animations it names. Its length is end - start. When that is not above zero, the tracks give the length.

A bone that the controller does not name follows the named bones, with the identity as its inverse bind, as in three.js. A JOINT node with no child nodes and one object is not a bone, because three.js makes it that object.

A channel target is node-id/sid. sid(i)(j) gives entry i + 4j of the matrix, row by row. sid.member and sid give all 16 entries.

Each key is decomposed into a position, a rotation and a scale. When a key does not give an entry, the loader interpolates it from the keys around it. When no key gives an entry, the node's own matrix gives it. Every track is LINEAR.

A file with animations and no clips gives one clip, default, of every animation. An <animation> that holds other animations is a folder, and its own channels are not read, as in three.js.

Differences from three.js

  • Each line primitive is its own geometry and its own Line. three.js makes one geometry for each kind, with groups and an array of materials. A line here draws one material.
  • Two surface primitives of one kind that do not agree about an attribute are refused. three.js fills the attribute in part.
  • <polygons> is read. three.js does not read it.
  • A vertex color is decoded from sRGB in each of its three channels. three.js decodes the wrong three values of each color.
  • A texture coordinate keeps its first two values. three.js keeps all the values of the source.
  • A <linestrips> element gives point pairs, so each strip is drawn alone. three.js joins every strip into one line.
  • An image path must be relative. A path with : is refused.
  • A color channel outside zero to one is refused.
  • three.js adds a skin to the geometry, so each instance of the geometry is skinned. Here only the instances of the controller are skinned.
  • A bone inside an <instance_node> copy is not a bone. three.js reads it.
  • A channel on a node that the scene does not reach makes no track. A clip that makes no track is not a clip. three.js makes a clip of no length.
  • A controller joint that names no bone, and a joint index outside the joints, are refused. three.js reads a hole.

Not read

Kinematics and physics are not read. A <morph> controller is refused, because three.js does not read one. A channel on a <translate>, <rotate> or <scale> step makes no track, as in three.js.

<lookat> and <skew> steps are skipped. <trifans>, <tristrips> and polygons with holes are not read. A second set of texture coordinates is skipped. The specular map and the ambient map are skipped. three.js reads them as specularMap and lightMap. Material has both fields, but this loader does not fill them.

Errors

The loader raises for:

  • A text that is not XML, or a root that is not <COLLADA>.
  • A unit that is not one positive number. An up axis other than X_UP, Y_UP and Z_UP.
  • No <scene>, or a scene that names no visual scene. A reference that is not #id.
  • A material with no effect, or an effect with no profile_COMMON technique and shading.
  • A number that is not a number or is not finite as a Float32. A color with too few numbers. An opacity outside zero to one. An opaque mode that is not known.
  • A sampler with no surface, or a surface or an image with no init_from. An image that cannot be read.
  • A geometry that the file does not have, or one with no <mesh>. A source with a stride below one.
  • A primitive with no inputs, an input with no offset, or an input that names no source. A <p> or a <vcount> that does not fill whole corners and faces. An index outside its source. A primitive with corners and no positions, or two inputs that give one attribute.
  • A node that <instance_node> names and the file does not have. Nodes that instance each other more than MAX_INSTANCE_DEPTH deep.
  • A step with the wrong count of numbers. A transform that flattens an axis.
  • A camera or a light that its builder refuses, or a light with no technique.
  • An <instance_controller> that names no controller. A <morph> controller, or a controller with no <skin>.
  • A skin with no <joints> or <vertex_weights>, or an input that is missing or names no source. Joint names that are not a Name_array. A joint with no inverse bind matrix. A <v> that is too short. A joint or weight index outside its source. A position with no weights.
  • A <skeleton> that names no node and no visual scene. A joint that names no bone. A bone that the scene does not reach.
  • A clip that names no animation. A channel that names no sampler or node, a sampler that names no source, or a target with no sid. A matrix index outside the matrix. A key that flattens an axis.

FBX

loaders/fbx.mojo and loaders/fbx_tree.mojo. read_fbx(path, scene, assets) reads an FBX file into the scene and the assets it is handed. The file can be binary or ASCII. It reads the meshes, the materials and their textures, the model hierarchy, the cameras, the lights, the skins, the blend shapes and the animation stacks. three.js: FBXLoader.

from loaders.fbx import read_fbx

var model = read_fbx("assets/robot.fbx", scene, assets)
var arm = model.model("Arm")
print(model.mesh_count, len(model.materials))
Function Meaning
read_fbx(path, scene, assets) -> FbxModel Read a file. A texture is read from the directory of the file, or from the file when it is embedded.
parse_fbx(bytes) -> FbxDocument Read the tree of a file. Bytes that start with the binary magic are binary. Other bytes are ASCII.
parse_fbx_text(text) -> FbxDocument Read the tree of an ASCII file.
load_fbx(document, directory, scene, assets) -> FbxModel Read a tree into a scene.

FbxModel

Field Meaning
root The node that each root model hangs on, three.js's sceneGraph group.
format, version FBX_ASCII or FBX_BINARY, and the version of the file.
models, model_ids, model_names One NodeId, id and name for each Model, parents first. model(name) finds one.
materials, material_ids, material_names One MaterialId, id and name for each Material that has a connection, in file order.
geometries One GeometryId for each material index of each geometry.
textures One TextureId for each texture and each use of it.
cameras Each PerspectiveCamera, riding its model.
unit The length of one unit of the file, a Length: UnitScaleFactor from GlobalSettings, in centimeters. Like three.js, the loader keeps it and does not apply it.
first_mesh, mesh_count, first_light, light_count Where the meshes and lights of the file start in the scene, and how many there are.
first_skinned_mesh, skinned_mesh_count Where the skinned meshes of the file start in scene.skinned_meshes, and how many there are.
animations One AnimationClip for each animation stack that makes a track. See Skins, blend shapes and animation.

A name is sanitized as three.js's PropertyBinding.sanitizeNodeName does it. White space becomes _, and [, ], ., : and / are removed.

The tree

An FBX file is a tree of nodes. Each node has a name, properties and children. FbxDocument holds each node in one list. Node zero has no name, and its children are the top-level nodes. FbxPropertyKind tells what a property holds: FBX_INTEGER, FBX_NUMBER, FBX_STRING, FBX_INTEGERS, FBX_NUMBERS or FBX_BYTES.

Member Meaning
children(node), children_named(node, name), child(node, name) The children of a node. child gives NO_FBX_NODE when there is none.
property_count(node), property(node, index) The properties of a node. property refuses a kind that is none of the six.
integer, number, string One property as a whole number, a number or a string.
numbers(node), integers(node) The array of a node, or its single numbers.

The binary form is read at version 6400 and later, and the ASCII form at version 7000 and later, as in three.js. From version 7500, the three sizes of a record are 64 bits. An array can be raw or compressed with zlib. render/inflate.mojo expands a compressed array, as it does for a PNG. An ASCII array, Name: *count { a: ... }, becomes one array property of its node, so the two forms give the same tree.

A binary file writes an object name as Name\x00\x01Class. three.js stops the string at the zero byte, and so does this reader. An ASCII file writes "Class::Name". object_name(name, format) removes the Class::.

What maps to what

FBX ThreeMojo
Model An Object3D under its parent model.
Geometry of type Mesh A BufferGeometry with position, and normal, uv and color when the layer elements give them. It has a group for each run of polygons of one material index, as three.js's genGeometry adds them. A layer mapped AllSame adds no group.
Material A LAMBERT material for the lambert shading model, and a PHONG material for any other, as in three.js.
Texture and Video A Texture.
NodeAttribute of a Camera model A PerspectiveCamera.
NodeAttribute of a Light model A point, directional or spot light.
GlobalSettings AmbientColor An ambient light, when the color is not black.

The transform of a model is three.js's generateTransform. The local chain is: translation, rotation offset, rotation pivot, pre-rotation, rotation, inverse post-rotation, inverse rotation pivot, scaling offset, scaling pivot, scale, inverse scaling pivot. The rotation and scale then combine with the parent in the order that InheritType names. RotationOrder sets the order of the rotation, and fbx_euler_order converts it. The pre-rotation and the post-rotation always turn in ZYX, as in three.js.

The geometric translation, rotation and scale of the first model of a geometry move its positions and normals, as in three.js.

Geometry

PolygonVertexIndex lists the corners of each polygon. A negative index ends a polygon, and its position is -index - 1. A triangle is kept. A polygon of four or more corners is laid flat on the plane of its Newell normal and cut by ear clipping. A convex polygon is cut into a fan from its first corner. A polygon of fewer than three corners gives no triangle.

FbxLayer holds one layer element: the values, the indices, and how they map onto the corners. The first LayerElementNormal, LayerElementUV, LayerElementColor and LayerElementMaterial with values are read.

Mapping FbxMapping One value for each
ByPolygonVertex BY_POLYGON_VERTEX corner
ByPolygon BY_POLYGON polygon
ByVertice, ByVertex BY_VERTEX position
AllSame ALL_SAME mesh

A reference of Direct reads the values directly. IndexToDirect, or the older Index, reads them through the indices. FbxMapping and FbxReference are types. FbxLayer.start refuses a value that is none of the named constants.

A vertex color is decoded from sRGB. A material index below zero is zero, as in three.js. A mesh with no material draws with a gray PHONG material, 0xcccccc. When the geometry has colors, each material of the mesh gets vertex_colors, as in three.js.

A mesh with one material wears it on every polygon. A mesh with more is one Mesh that wears them as a list, in the order they connect. A group whose index is past the list draws nothing, as in three.js. A skinned mesh with more is one SkinnedMesh for each group, because a skinned mesh here wears one material. A geometry with no polygons gets no mesh. three.js adds a mesh of nothing.

Materials

Property Meaning
Diffuse, or DiffuseColor of type Color or ColorRGB The color.
Emissive, or EmissiveColor of type Color or ColorRGB The emissive color.
EmissiveFactor The emissive intensity.
Specular, or SpecularColor of type Color The specular color, on a PHONG material.
Shininess The shininess, on a PHONG material.
ReflectionFactor The reflectivity.
BumpFactor The bump scale, with a bump map only.
TransparencyFactor, Opacity, TransparentColor The opacity. See below.

The opacity is one minus TransparencyFactor. When that is exactly 0 or 1, Opacity gives the opacity, or else one minus the red of TransparentColor. This is three.js's rule. An opacity below 1 sets transparent.

A texture connects to a material under the name of its map:

Connection Map
DiffuseColor, Maya|TEX_color_map map, sRGB
EmissiveColor emissive_map, sRGB
NormalMap, Maya|TEX_normal_map normal_map, linear
Bump bump_map, linear. A normal map replaces it.
TransparentColor, TransparencyFactor alpha_map, linear. It also sets transparent.

A texture reads the first Video connected to it. The image is the Content of the video: raw bytes in a binary file, base64 in an ASCII file. When the video has no content, another video of the same name can give it, as in three.js. Otherwise the image is the file that RelativeFilename or Filename names, after its last \.

WrapModeU of 0 repeats, and any other value clamps. Scaling and Translation set repeat and offset. A layered texture gives its first layer. A texture with no video is left out.

Cameras and lights

A camera reads FieldOfView in degrees, 45 by default, and AspectWidth over AspectHeight. Without an aspect, three.js uses the shape of the window, and this loader uses a square. FocalLength sets the field of view through a 35 mm film gauge, three.js's setFocalLength.

NearPlane and FarPlane are divided by 1000, as in three.js. Without them, the planes are 1 and 1000.

A light reads LightType: 0 is a point light, 1 a directional light and 2 a spot light. Any other type is a point light with three.js's defaults. Color is sRGB. Intensity is divided by 100. CastLightOnObject of 0 sets the intensity to zero.

FarAttenuationEnd is the distance, unless EnableFarAttenuation is 0. The decay is 1. A spot light reads InnerAngle in degrees, 60 by default. OuterAngle sets the penumbra to 1. CastShadows of 1 casts a shadow from any light, a point light too, as in three.js.

A directional or spot light stands one unit up its own y axis before the model transform applies, as in three.js. It shines toward the world origin.

Skins, blend shapes and animation

A Skin deformer on a geometry makes each mesh of the geometry a SkinnedMesh. loaders/fbx.mojo reads the skins and blend shapes, and loaders/fbx_animation.mojo reads the animation stacks. three.js: parseDeformers, bindSkeleton and AnimationParser.

FBX ThreeMojo
Cluster of a Skin A bone: the model that connects to the cluster. The inverse of TransformLink is its inverse bind.
Indexes, Weights of a Cluster skinIndex and skinWeight. A vertex with more than four weights keeps the four largest. The loader normalizes the weights.
BindPose The matrix of the mesh model is the bind matrix. Without one, the bind matrix is the identity.
BlendShapeChannel of a BlendShape A morph target of the geometry, as offsets, so morph_relative is set.
AnimationStack An AnimationClip, named after the stack. Its first AnimationLayer gives the tracks.
AnimationCurveNode T, S A POSITION or SCALE track on the model.
AnimationCurveNode R A QUATERNION track on the model.
AnimationCurveNode DeformPercent A morph influence track on each mesh of the model. The value is divided by 100.

A geometry can carry any number of blend shape channels, as in three.js. The influences start at zero, as in three.js, which does not read DeformPercent.

A curve time is in FBX ticks, 46186158000 each second. A key comes at each time that any axis has a key. An axis with no key at that time keeps its last value, and the first value is the model's own.

The angles of an R curve node are in degrees and turn in the RotationOrder of the model. The pre-rotation turns first, and the post-rotation is undone last, in ZYX, as generateTransform does. When an angle changes by 180 degrees or more between two keys, three.js adds keys between them by slerp and drops the second key. This loader does the same. Every track is LINEAR, as in three.js.

The stacks come in the order that three.js meets them. A JavaScript object puts an id from 0 to 2^32 - 2 first, in numeric order. js_key_order gives that order.

Differences from three.js

  • A polygon of four or more corners is cut by ear clipping. three.js uses earcut, which picks other diagonals. A flat polygon gives the same surface.
  • The ASCII reader reads tokens, not lines and tabs. A file that three.js reads, this reader reads the same way.
  • three.js sets the penumbra of a spot light to the outer angle in radians, at least 1. This loader uses 1, because a penumbra above 1 is refused.
  • ByVertex and Index are read as ByVertice and IndexToDirect. three.js does not read them.
  • A texture with no image is NO_TEXTURE. three.js makes an empty texture.
  • A bump scale without a bump map is dropped, because Material refuses it.
  • An image path must be relative. A path with : is refused.
  • A track drives the model that its curve node connects to. three.js finds the model by its name, so two models of one name differ.
  • A morph track drives the target of its channel. three.js finds the target by the name of the channel.
  • three.js adds a slerp key at the time of the key before it, a second time. This loader drops a key whose time does not rise. The rotation is the same.
  • An R curve node without a curve for each axis makes no track. three.js makes a track of one number, which is not a rotation.
  • A stack that makes no track is not a clip. three.js makes a clip of no length.
  • A geometric translation moves the offsets of a blend shape, as in three.js.

Not read

Animation layers after the first of a stack are not read, as in three.js. NURBS curves, LookAtProperty, orthographic cameras, a second set of texture coordinates, and the ambient occlusion, displacement, reflection and specular maps are skipped.

Errors

The reader raises for:

  • An ASCII file with no FBXVersion, or one older than 7000. A binary file older than 6400.
  • A binary record or property that runs past the file, or children that run past their record. An unknown property type. An array encoding that is not 0 or 1. A zlib array of the wrong length.
  • An ASCII node without :, a { that is not closed, or a } that closes nothing.
  • An ASCII string that is not closed, or a *count that does not match its a.
  • Nodes nested deeper than MAX_FBX_DEPTH.

The loader raises for:

  • A format that is neither ASCII nor binary. A file with no Objects.
  • A material with no ShadingModel. A color channel outside zero to one.
  • A connection to a texture that is not there. An image path that is not relative, or an image that cannot be read. A Content that is neither bytes nor base64.
  • A mapping or a reference that is not known. A layer index outside its values.
  • A position index outside Vertices. A last polygon with no negative index. A polygon of four or more corners with no area, or one that crosses itself so that no ear is left.
  • A mesh model with no mesh geometry. Models connected in a loop, or nested deeper than MAX_MODEL_DEPTH. A transform that flattens an axis.
  • A camera, a light or a material that its builder refuses.
  • A skin that deforms no geometry. A cluster with no TransformLink, a TransformLink that is not 16 numbers, or a cluster with not one weight for each index. A cluster with no bone.
  • A blend shape that holds something other than a BlendShapeChannel, or a channel with no shape. A shape with not three numbers for each index, or an index outside the positions.
  • An animation curve with no KeyTime or KeyValueFloat, or not one value for each time. A curve on a curve node that is not read. A curve node that drives nothing. A stack with no layer. A blend shape channel with no model. A rotation curve with no keys.

Fonts

from loaders.font import read_font

var font = read_font("assets/fonts/fixture.typeface.json")
var shapes = font.generate_shapes("AO i8\nD", Length(1, METER))

loaders/font.mojo. read_font(path) reads a typeface.js JSON font into a Font. parse_font(text) reads the text of one. Font.generate_shapes(text, size) lays the text out as shapes with holes. Text extrudes them. three.js: FontLoader, Font and ShapePath.

What is read

Key Meaning
resolution The font units in one em. It must be positive.
boundingBox.yMin, boundingBox.yMax The lowest and highest point of a glyph, in font units.
underlineThickness The underline, in font units.
familyName The name of the font. It is optional.
glyphs One object for each character. The key must be one character.
glyphs.X.ha The advance of the glyph: how far the next glyph starts to the right.
glyphs.X.o The outline of the glyph. It is optional. A space has none.

An outline is a string of commands, each with its numbers, in font units. Other keys are ignored.

Command Draws
m x y A new outline that starts at the point.
l x y A straight run to the point.
q x y cx cy A quadratic Bezier curve to x y, with the control point cx cy.
b x y c1x c1y c2x c2y A cubic Bezier curve to x y, with two control points.
z Nothing. three.js's TTFLoader writes it to close an outline, and Font steps over it.

The end point comes first in q and b. That is the order of the file, and three.js reads it the same way.

TrueType fonts

from loaders.ttf import read_ttf

var font = read_ttf("assets/ttf/kenpixel.ttf")

loaders/ttf.mojo. read_ttf(path, reversed) reads a TrueType font into a Font. ttf_json(bytes, reversed) gives the typeface JSON that three.js's TTFLoader makes, which parse_font reads. three.js reads the font with opentype.js. This reader follows opentype.js for TrueType outlines:

  • The cmap subtable is the last for Unicode or Windows Unicode, of format 4 or 12. A format 4 table's last segment is not read, as in opentype.js.
  • A simple glyph gives a line to each point on the curve, and a quadratic curve through each point off it. Two points off the curve in a row meet at their midpoint.
  • A composite glyph places its parts by an offset, a scale, a 2 by 2 matrix or matched points.
  • The advances come from hmtx, and the full name from name, in US English.

The numbers are scaled by 100000 / (unitsPerEm * 72) and rounded. A glyph with no outline has no x_min or x_max. With reversed, three.js reverses a glyph's commands once for each code point that maps to it. So a glyph of two code points comes back in its first order, without its z. The reader keeps this.

The reader refuses a font that is not TrueType, CFF outlines and WOFF. It also refuses a font with no glyf or cmap it can read, and a code point that is a surrogate. It does not write original_font_information: FontLoader does not read it. A lone surrogate in the full name becomes U+FFFD, because a Mojo string cannot hold one.

The tests compare six fonts with three.js 0.180, plain and reversed: kenpixel.ttf from three.js's examples, and five from assets/ttf/make_fonts.py. A font with Apple's true signature must read as its TrueType twin. The reader must refuse fourteen broken fonts.

Layout

One font unit is size / resolution meters. The glyphs run left to right from the origin. Each glyph starts at the advance of the glyph before it. A line break, \n, moves back to x zero and down by one line height:

(boundingBox.yMax - boundingBox.yMin + underlineThickness) * size / resolution

Font.line_height(size) gives it. A character with no glyph takes the ? glyph, as in three.js.

Holes

A font does not mark its holes. A solid runs clockwise and a hole runs counterclockwise. ShapePath.to_shapes sorts the outlines of a glyph by that rule, as three.js does:

  • An outline that runs clockwise is a solid. to_shapes(is_ccw=True) swaps the rule.
  • A hole goes to the solid before it. When the first outline is a hole, each hole goes to the solid after it.
  • With two or more solids, a hole that lies inside a different solid moves to it. No hole moves when one hole lies inside two solids.
  • When no outline is a solid, each outline is a shape of its own.

math/shape_path.mojo holds ShapePath, signed_area, is_clockwise and is_point_inside_polygon. The point test is the three.js ray test, and a point on an edge is inside.

Differences from three.js

  • The file is checked when it is read. three.js reads a glyph when it draws it, and skips a command it does not know.
  • A Shape must be closed. to_shapes adds the closing run when an outline does not end where it started. The surface is the same.
  • A step that draws nothing, such as a line to the current point, is skipped. three.js keeps it and then drops the repeated point.
  • The layout is calculated in Float64, as in three.js. Each point is stored as a Float32.

Errors

parse_font raises, with the glyph when there is one, for:

  • A document that is not a JSON object.
  • A missing or non-positive resolution, or a missing boundingBox, yMin, yMax, underlineThickness or glyphs. A metric that is not a finite Float32.
  • A glyph key that is not one character. A glyph that is not an object, or has no ha. An o that is not a string.
  • An outline command other than m, l, q and b. A command with too few numbers. A number that is not a finite Float32.
  • An outline that draws before its first m. An m that draws nothing before the next m or the end.

generate_shapes raises for a size that is not positive. It raises for a character with no glyph in a font with no ? glyph.

Example

assets/fonts/fixture.typeface.json is a small font made for the tests. It has A, D, O, i, 8, ? and a space. tests/test_font.mojo compares its layout with three.js 0.180.

JSON

loaders/json.mojo. parse_json(text) reads a JSON text into a JsonDocument: one JsonNode per value, the root at node zero. The glTF loader reads with it. It is exactly RFC 8259. No comments, no trailing commas, no bare words.

Member Meaning
kind(node) -> JsonKind OBJECT, ARRAY, STRING, NUMBER, BOOLEAN or NULL.
length(node) How many children an object or an array has.
get(node, key), has(node, key) An object's child by key, or NO_NODE. The last of two equal keys wins.
at(node, index), key(node, index) A child, or an object's key, by position.
number(node), integer(node), string(node), boolean(node), is_null(node) The leaf values. Each raises for a node of another kind. integer refuses a fraction.

A text that bends the grammar is refused with the byte it went wrong at. Nesting past MAX_DEPTH is refused. JsonKind is a type. A bare integer does not compile.

XML

loaders/xml.mojo. parse_xml(text) reads an XML text into an XmlDocument: one XmlElement for each element, with the root element at element zero. The Collada loader reads with it. three.js uses the DOMParser of the browser.

Member Meaning
name(element), parent(element) The name of an element, as written, and the element that holds it. The root has NO_ELEMENT as its parent.
attribute(element, key, default), has_attribute(element, key) An attribute, or default when there is none.
children(element), children_named(element, name), child(element, name) The child elements, in file order. child gives the first, or NO_ELEMENT.
text(element), text_content(element) The text directly inside an element, or all the text inside it.

The reader reads elements, attributes, text, CDATA sections, comments and processing instructions. It resolves the five named entities and character references. It changes each line ending to a line feed. It skips a document type declaration, so an entity that the declaration names is not known. It does not resolve namespaces: a:b is the name of the element.

The reader refuses these malformed forms with their byte position:

  • No root element, or text after it. A declaration, a comment, a CDATA section or an instruction that is not closed.
  • A malformed name or tag. An attribute without a quoted value, a < in a value, or an attribute given twice.
  • An end tag that does not match, or an element that is not closed.
  • An entity that is not known, or a character reference that names no character.
  • Elements nested deeper than MAX_XML_DEPTH.
  • Forbidden XML characters, signed character references, a double hyphen inside a comment, or ]]> outside CDATA.

CRLF and CR become LF in text and CDATA. Raw whitespace in attributes becomes a space; a CRLF pair gives one space. Character references keep the character they name. Quoted delimiters and comments inside a document type do not close it early. No external data is read and declared entities are not expanded.

Names follow XML 1.0 fifth edition NameStartChar and NameChar, including their Unicode limits. A processing instruction needs a name and whitespace before its data. Its target cannot be xml in any letter case.

An XML declaration can appear only at the start, after an optional byte order mark. Its fields are version, optional encoding, then optional standalone. Version syntax accepts 1. followed by one or more digits. Each such version uses XML 1.0 character rules. At most one document type declaration can appear before the root. Its name, external identifier and closing delimiters are checked.

This is a limited reader, not a complete XML validator. It does not validate DTD contents or root-name agreement with the DTD. It does not expand DTD entities or load external data. Namespace constraints are not checked. Input is a UTF-8 String; an encoding declaration does not transcode it.

Home

Tutorials

How-to

Reference

Explanation

Clone this wiki locally