# How ArmorPaint Handles UV Unwrapping and Editing: A Technical Deep Dive

> ArmorPaint's UV unwrapping uses a plugin-based algorithm for chart packing. Learn how its UV editing offers real-time 16-bit integer texture coordinate manipulation in an interactive 2D view.

- Repository: [Armory 3D/armorpaint](https://github.com/armory3d/armorpaint)
- Tags: deep-dive
- Published: 2026-09-13

---

**ArmorPaint implements UV unwrapping as a plugin-based chart-packing algorithm that merges selected meshes, generates charts from face adjacency data, and packs them into UV space, while UV editing provides real-time manipulation of 16-bit integer texture coordinates through an interactive 2D view.**

ArmorPaint, the open-source 3D texture painting application developed by the **armory3d/armorpaint** repository, handles UV unwrapping and editing through a custom C-based pipeline. Unlike external dependencies, the software implements a self-contained chart generation algorithm that operates directly on the engine's raw mesh buffers, coupled with an immediate-mode UV editor that modifies texture coordinates in place.

## UV Unwrapping Architecture

The UV unwrapping system in ArmorPaint follows a modular plugin architecture registered in [`paint/plugins/plugins.c`](https://github.com/armory3d/armorpaint/blob/main/paint/plugins/plugins.c). When triggered from the UI, the system merges selected paint objects into a temporary working mesh, processes them through a custom chart-packing algorithm, and writes the results back to the mesh's texture coordinate array.

### Plugin Registration and UI Trigger

The unwrapping functionality is registered as a plugin procedure where the function `proc_uv_unwrap` is mapped to the key `"uv_unwrap"` in the global unwrappers map:

```c
// In paint/plugins/plugins.c
any_map_set(util_mesh_unwrappers, "uv_unwrap", proc_uv_unwrap);

```

In [`paint/sources/ui/tab_plugins.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/ui/tab_plugins.c), the UI button handler `plugin_uv_unwrap_button()` initiates the process. It first calls `util_mesh_merge()` to combine selected objects into `g_context->merged_object`, then constructs a temporary `raw_mesh_t` structure with position (`posa`) and index (`inda`) buffers before invoking the core unwrapping routine.

### Mesh Decoding and Chart Generation

Inside [`paint/plugins/uv_unwrap/uv_unwrap.c`](https://github.com/armory3d/armorpaint/blob/main/paint/plugins/uv_unwrap/uv_unwrap.c), the `proc_uv_unwrap` function decodes vertex positions from the engine's 16-bit integer format to floating-point vectors:

```c
// Decode 16-bit positions to float vectors
pa[i*3] = mesh->posa->buffer[i*4] * inv;

```

The algorithm then computes face normals via cross-product normalization to group faces into charts based on similarity. A **canonical vertex map** using `uv_hash_pos` with linear probing deduplicates identical positions, while an **edge adjacency hash table** (`uv_hash_edge`) builds per-face connectivity lists in the `face_adj` array to determine chart boundaries.

### Chart Packing and UV Assignment

Using the adjacency data, the system generates charts through `chart_find()` and related functions. Each chart is projected, convex-hulled, and packed into UV space using heuristics in `uv_pack_charts` and `uv_min_rect_dir` to minimize wasted texture space. The final packed UV coordinates are written directly into the mesh's `texa` buffer:

```c
mesh->texa->buffer[vi*2] = ...;   // U coordinate
mesh->texa->buffer[vi*2+1] = ...; // V coordinate

```

After completion, `mesh_data_build()` reconstructs the vertex buffers and `util_uv_uvmap_cached` is set to `false` to invalidate cached UV maps.

## UV Editing Workflow

ArmorPaint provides real-time UV editing through [`paint/sources/util/edit_uvmap.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/util/edit_uvmap.c), where users interact directly with the 16-bit integer texture coordinate buffer in a 2D view.

### Selection and Manipulation

The editor supports single-vertex selection and box-selection modes. When the left mouse button initiates a drag, the system calculates UV-space deltas by scaling mouse movement to the 16-bit integer domain:

```c
f32 duv_x = g_ui->input_dx / tw * 32767.0f;
f32 duv_y = g_ui->input_dy / th * 32767.0f;

```

Selected vertices are stored in `_selected_verts`, and during dragging, these deltas are applied to the `texa` buffer with clamping to the valid 16-bit range (-32767 to 32767):

```c
for (i32 i = 0; i < _selected_verts->length; i++) {
    i32 vi = _selected_verts->buffer[i];
    i32 new_x = (i32)texa->buffer[vi*2] + (i32)duv_x;
    i32 new_y = (i32)texa->buffer[vi*2+1] + (i32)duv_y;
    texa->buffer[vi*2]   = (i16)math_min(math_max(new_x, -32767), 32767);
    texa->buffer[vi*2+1] = (i16)math_min(math_max(new_y, -32767), 32767);
}

```

### Mesh Data Synchronization

Upon mouse release, the modified coordinates persist immediately in the mesh structure. The system calls `mesh_data_build_vertices` to rebuild GPU buffers and invalidates cached UV maps, ensuring the 3D viewport and UV view remain synchronized without requiring a full scene reload.

## Integration with Import Pipeline

When importing meshes lacking UV coordinates, [`paint/sources/project.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/project.c) invokes `project_unwrap_mesh_box()` to prompt automatic unwrapping. This hooks into the same `proc_uv_unwrap` pipeline, ensuring consistency between manual and automatic unwrapping workflows:

```c
// In paint/sources/project.c
if (import_mesh_needs_unwrap) {
    import_mesh_needs_unwrap = false;
    project_unwrap_mesh_box();
}

```

## Summary

- ArmorPaint implements UV unwrapping as a **plugin-based system** registered in [`paint/plugins/plugins.c`](https://github.com/armory3d/armorpaint/blob/main/paint/plugins/plugins.c) and triggered via `plugin_uv_unwrap_button()` in the UI layer.
- The **chart-packing algorithm** operates in [`uv_unwrap.c`](https://github.com/armory3d/armorpaint/blob/main/uv_unwrap.c), utilizing hash-based position deduplication (`uv_hash_pos`) and edge adjacency maps to generate optimal charts before packing.
- **16-bit integer precision** is used throughout; UV coordinates are stored in the `texa` buffer and decoded/encoded using a `32767.0f` scaling factor.
- **Real-time editing** occurs in [`edit_uvmap.c`](https://github.com/armory3d/armorpaint/blob/main/edit_uvmap.c), where mouse deltas convert to UV space and modify the texture coordinate buffer directly with immediate GPU buffer updates via `mesh_data_build_vertices`.
- The **import pipeline** automatically triggers unwrapping via `project_unwrap_mesh_box()` when meshes arrive without existing UV data.

## Frequently Asked Questions

### What file format does ArmorPaint use for internal UV storage?

ArmorPaint stores UV coordinates as 16-bit signed integers (`i16`) in the `texa` buffer of the `raw_mesh_t` structure. The engine converts these to floating-point values for processing by multiplying by an inverse constant (typically `1/32767.0f`) and re-encodes them after editing by scaling and clamping to the [-32767, 32767] range.

### Can ArmorPaint unwrap multiple objects simultaneously?

Yes. The `plugin_uv_unwrap_button()` function in [`tab_plugins.c`](https://github.com/armory3d/armorpaint/blob/main/tab_plugins.c) calls `util_mesh_merge()` to combine all selected paint objects into a single temporary mesh before processing. After unwrapping completes, the updated UV coordinates are distributed back to their respective original objects and vertex buffers are rebuilt individually.

### How does the UV editor handle precision in the 2D view?

The editor converts mouse delta movements (`g_ui->input_dx` and `g_ui->input_dy`) into UV-space deltas by dividing by the texture width/height and multiplying by `32767.0f`. This conversion ensures that pixel-precision mouse movements map accurately to the 16-bit integer UV coordinate system used internally.

### Where is the chart-packing algorithm implemented?

The core chart-packing logic resides in [`paint/plugins/uv_unwrap/uv_unwrap.c`](https://github.com/armory3d/armorpaint/blob/main/paint/plugins/uv_unwrap/uv_unwrap.c), specifically within functions like `uv_pack_charts` and `uv_min_rect_dir`. These functions project charts, compute convex hulls, and apply heuristics to minimize wasted space during the packing phase before writing results to the mesh's `texa` buffer.