How ArmorPaint Handles UV Unwrapping and Editing: A Technical Deep Dive
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. 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:
// In paint/plugins/plugins.c
any_map_set(util_mesh_unwrappers, "uv_unwrap", proc_uv_unwrap);
In 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, the proc_uv_unwrap function decodes vertex positions from the engine's 16-bit integer format to floating-point vectors:
// 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:
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, 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:
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):
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 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:
// 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.cand triggered viaplugin_uv_unwrap_button()in the UI layer. - The chart-packing algorithm operates in
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
texabuffer and decoded/encoded using a32767.0fscaling factor. - Real-time editing occurs in
edit_uvmap.c, where mouse deltas convert to UV space and modify the texture coordinate buffer directly with immediate GPU buffer updates viamesh_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 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, 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →