Back to the editor

Roadmap

WebUVs grows in three stages, and we're in the first. What has already shipped is in the changelog. Last updated 2026-10-28.

Stage 1: OBJ (now)

The editor as it stands: open an OBJ with its MTL and textures, or start from a built-in shape or an example model; cut seams, flatten, relax, pack and arrange the UVs; then download the OBJ, or an SVG of the layout to paint over.

Still to come in this stage:

  • Download with the .mtl. Download saves the MTL beside the OBJ, as the text view's MTL tab already writes it.
  • Scale for the Numbers previews. The Scale slider works for Island and Face Numbers too, as it does for the patterns, Direction, Stretch and Texel Density.
  • Texture placement from MTL files. An MTL's -o and -s map options, shown in the File preview.
  • The header at phone width. The view tabs no longer run into the logo, the File button and the light/dark switch.
  • A web manifest, so the site can be installed like an app.
  • More languages. Every word on screen already comes from i18n/locales/en.json, so another language is another file there and a line in nuxt.config.ts. A language switcher comes with the second one.

Stage 2: .3ds files

Open 3D Studio (.3ds) models, edit their UVs and save them back - without disturbing anything else in the file, such as its animation.

How .3ds stores UVs

A .3ds mesh has exactly one UV channel - the format has room for no more, so 3ds Max's extra map channels don't survive export to .3ds - and it stores one UV per vertex. A seam therefore has to be a vertex written twice: the same position with two UVs.

WebUVs keeps UVs per face corner, with positions and UVs indexed separately. Opening a .3ds means merging those doubled vertices to find the seams, and saving means splitting them again.

What else in a .3ds bears on its UVs:

  • Texture placement for each map. Scale, offset, rotation and tiling for each map - diffuse, bump, opacity, specular and more - all over the same UVs, so a diffuse map can repeat twice and turn while the bump map doesn't.
  • Triangles only, with flags marking which edges are hidden diagonals.

Saving: patch the file, never rewrite it

Saving keeps the original file's bytes and replaces only what was edited. A .3ds file is a tree of chunks, each starting with its ID and its own length, so any chunk we don't understand can be skipped when reading and carried over untouched when saving. The animation refers to objects by name, never by vertex number: as long as no object is renamed or reordered and no vertex moves, it comes through byte for byte.

An edit lands at one of two levels:

  1. UVs moved, seams unchanged - Relax, Pack, Align, Transform. Only the numbers inside each UV chunk are overwritten: the file keeps its size and every other byte stays the same. This covers most edits to a model that's already unwrapped.
  2. Seams changed - Split, Weld, Flatten, or unwrapping a mesh that had no UVs. The object's vertex list changes, so its vertices, vertex flags, UVs and face list are rewritten, along with the length fields of the chunks around them.
    • Faces keep their order and their flags, so the material and smoothing lists inside the face chunk, which point at faces by number, are copied as they are.
    • The original vertices keep their order and their exact bytes; new copies go at the end.
    • A mesh that had no UVs gets a UV chunk after its face list.

Everything outside the edited objects' meshes - materials, lights, cameras, the animation, and any chunk we don't recognize - is copied as it is.

Risks and guards

  • Morphs. A morph blends objects vertex by vertex, so every object in one must keep the same vertex count and order. Seam edits are refused on an object that takes part in a morph (the animation's morph tracks say which); moving its UVs is still fine.
  • The 65,535 limit. New seams add vertices and could take an object over it. Saving then stops and names the object - Blender's exporter just drops such an object with a warning.
  • The triangles never change. Quads are rebuilt from the hidden-edge flags for display only; the triangles saved are the original ones, diagonals and all.
  • Positions never change. A .3ds stores its vertices already placed in the scene, with each object's own axes recorded beside them. WebUVs never moves a vertex, so the two stay consistent.
  • Texture names in older files are often short DOS names in capitals, so matching a dropped image to its material ignores case.
  • The mapping gizmo (how 3D Studio first generated the UVs: planar, cylindrical or spherical) goes out of date once the UVs are edited. It only records how they were made, so it's left as it is.

Steps

  1. Reading. The chunk reader; opening .3ds files (meshes, materials, texture file names); and a read-only 3DS tab in the text view, showing the chunk tree with each chunk's bytes in hex.
  2. Saving UV edits (level 1).
  3. Saving seam edits (level 2), with the guards above.
  4. Texture placement: each map's scale, offset and rotation shown in the File preview.

Chunk reference

Every chunk is a 2-byte ID and a 4-byte length (counting those 6 bytes), little-endian, followed by its data and any chunks inside it.

ID Chunk Holds
0x4D4D Main Everything else
0x3D3D Editor Materials and objects
0xAFFF Material Name (0xA000), colors, texture maps
0xA200, 0xA204, 0xA210, 0xA220, 0xA230, 0xA33A, 0xA33C, 0xA33D Texture maps Diffuse, specular, opacity, reflection, bump, second texture, shininess, self-illumination
0xA300 Map file name Text
0xA351 to 0xA35C Map placement Tiling flags (0xA351), U/V scale (0xA354, 0xA356), U/V offset (0xA358, 0xA35A), angle (0xA35C)
0x4000 Object Name, then a mesh, light or camera
0x4100 Mesh The chunks below
0x4110 Vertices Count (u16), then x, y, z (floats)
0x4111 Vertex flags Count (u16), then a u16 per vertex
0x4120 Faces Count (u16), then a, b, c, flags (u16 each); flags 0x1, 0x2, 0x4 mark edges CA, BC, AB visible
0x4130 Face material Material name, count, face numbers - inside 0x4120
0x4150 Smoothing A u32 group mask per face - inside 0x4120
0x4140 UVs Count (u16), then u, v (floats) - one pair per vertex
0x4160 Local axes 4 × 3 floats
0x4170 Mapping gizmo Type (u16) and 21 floats
0x4200 UV channel (.i3d only) See Stage 3
0xB000 Animation Object nodes (0xB002) naming their object (0xB010); tracks for position (0xB020), rotation (0xB021), scale (0xB022), morph (0xB026) and hiding (0xB029)

Sources: Paul Bourke's 3DS notes and Blender's io_scene_3ds importer and exporter.

Stage 3: .i3d files (Hidden & Dangerous)

Extend Stage 2 to the .i3d models of Illusion Softworks' Hidden & Dangerous (1999) and Hidden & Dangerous Deluxe: edit their UVs and save them back so the game still loads them.

An .i3d is a 3DS file with a few changes, so it builds on Stage 2's chunk reader and its saving by patching. The one change that matters for UVs is a single chunk, worked out in our own reverse engineering of the game's files.

The 0x4200 chunk: UVs per face corner

In place of the per-vertex UV chunk (0x4140), which most .i3d meshes don't have at all, a mesh has a 0x4200 chunk for each of its UV channels, inside its 0x4100 mesh chunk:

Bytes Type Holds
4 int32 Channel number - 1 is the main UV channel
2 u16 UV count, N
8 × N 2 floats each The UVs: u, v
2 u16 Face count, M - the same as the mesh's
6 × M 3 u16 each For each face, the UV number at each of its corners

That is how WebUVs stores UVs inside: a list of their own, picked per face corner. So for an .i3d:

  • Seams cost nothing. A seam is two UV numbers at one vertex; the vertex list isn't touched.
  • Any edit, seams included, rewrites only the edited channel's 0x4200 chunk, and the length fields above it. Vertices, faces, materials, the other channels and the animation are all copied as they are, so the morph and vertex-count worries of a .3ds don't come up.
  • Several UV channels. A mesh can have more than one - for detail maps or lightmaps, say. WebUVs edits one at a time, channel 1 unless another is picked, and carries the rest over untouched.
  • V runs up, as in WebUVs, so the UVs need no flipping either way.

Guards

  • The face count stays the mesh's. WebUVs keeps the mesh's triangles in their order, so the UV faces still line up with them one to one.
  • 65,535 UVs per channel, the u16 limit.
  • A mesh without UVs gets a channel 1 chunk at the end of its mesh chunk, keeping the chunks in ascending ID order, as the game's files have them.
  • Chunks only Illusion Softworks' tools wrote (0x2426, 0x948D, 0x9F59 and others) are copied untouched, like any other chunk we don't recognize.

Other differences from .3ds

  • -Y is up, where a .3ds is Z-up. The 3D view turns the model upright, as (x, -y, -z) does; the file's positions are never changed.
  • A mesh can be just 0x4110, 0x4120 and 0x4200, in that order, without the local axes (0x4160) or vertex flags (0x4111) a .3ds usually has, so the reader requires neither.

Steps

  1. Reading: the 0x4200 channels, a choice of channel where a mesh has several, the model turned upright, and 0x4200 decoded in the raw tab.
  2. Saving every kind of UV edit, seams included, from the start - there's no simpler first level to stop at.