Data Structure for Storing Mesh Data in ArmorPaint: A Complete Technical Guide

ArmorPaint represents 3D geometry using a hierarchical system of C structs that separate logical mesh description (mesh_data_t), GPU runtime resources (mesh_data_runtime_t), and scene integration handles (mesh_object_t).

The open-source 3D painting software ArmorPaint (armory3d/armorpaint) implements a specialized data structure for storing mesh data that cleanly separates CPU-side geometry definitions from GPU-ready buffers. This design enables efficient memory management and real-time rendering while supporting dynamic mesh import from formats like OBJ and FBX.

Core Mesh Data Structures

The engine defines three primary structs in base/sources/engine.h that work together to represent a complete mesh.

mesh_data_t: Logical Geometry Description

The mesh_data_t struct serves as the authoritative source for mesh geometry, storing all vertex attributes and metadata before GPU upload.

Key fields include:

  • char *name – Human-readable identifier for the mesh.
  • float scale_pos and float scale_tex – Scale factors applied to position and UV coordinates during rendering.
  • vertex_array_t_array_t *vertex_arrays – Dynamic array containing all vertex attributes (positions, normals, UVs, colors).
  • u32_array_t *index_array – Index buffer defining triangle topology.
  • mesh_data_runtime_t *_ – Lazy-initialized pointer to the GPU-side representation.

mesh_data_runtime_t: GPU Resources

When a mesh is first rendered, ArmorPaint creates a mesh_data_runtime_t instance containing the actual GPU buffers.

Important members:

  • gpu_buffer_t *vertex_buffer and gpu_buffer_t *index_buffer – Direct handles to GPU memory.
  • gpu_vertex_structure_t structure – Vertex layout description for the graphics pipeline.
  • bool owns_arrays – Boolean flag indicating whether the engine should free CPU-side arrays when the mesh is destroyed, preventing double-free errors during cleanup.

mesh_object_t: Scene Integration

To place a mesh in the world, the engine wraps mesh_data_t inside mesh_object_t, which binds geometry to materials and transforms.

Critical fields:

  • object_t *base – Generic object header containing transform matrices and hierarchy information.
  • mesh_data_t *data – Pointer to the logical mesh data.
  • shader_data_t *material – Reference to the shader material used for rendering.
  • f32 camera_dist – Cached distance to camera, utilized for level-of-detail (LOD) calculations.

Collection Management Containers

Beyond individual meshes, ArmorPaint manages collections through specialized dynamic-array wrappers defined in paint/sources/types.h.

The mesh_data_t_array_t struct maintains a list of all logical meshes in the project via a mesh_data_t **buffer pointer, while mesh_object_t_array_t tracks all scene instances using mesh_object_t **buffer. The global scene state (scene_t in engine.h) stores these collections in any_array_t *mesh_datas, enabling iteration over all geometry without traversing the scene graph.

Mesh Creation and Import Pipeline

Creating a usable mesh follows a strict three-stage pipeline implemented across paint/sources/util_mesh.c and paint/sources/io/import_mesh.c.

First, construct a raw mesh from application data:

raw_mesh_t *raw = plugin_make_raw_mesh(
    "my_mesh",                // name identifier
    posa,                     // i16_array_t * positions (short4norm format)
    nora,                     // i16_array_t * normals (short4norm format)
    inda,                     // u32_array_t * triangle indices
    1.0f);                    // scale_pos factor

Next, convert the raw data into the logical structure:

mesh_data_t *mesh = import_mesh_raw_mesh(raw);

Finally, instantiate the mesh in the scene with material binding:

mesh_object_t *obj = scene_add_mesh_object(
    mesh,                     // logical mesh data
    default_material,         // shader_data_t * material pointer
    NULL);                    // parent object (NULL for root level)

To modify mesh properties after creation, access the data directly through the scene object:

obj->data->scale_pos = 2.5f;   // Enlarge mesh by 2.5x

For batch operations, iterate the project's mesh registry:

for (int i = 0; i < project->mesh_datas->length; ++i) {
    mesh_data_t *m = project->mesh_datas->buffer[i];
    printf("Mesh %s has %u vertices\n",
           m->name,
           (unsigned)m->vertex_arrays->buffer[0]->values->length);
}

Key Source Files and Implementation Details

The mesh data structure implementation spans multiple files in the base/ and paint/ directories:

Summary

  • ArmorPaint uses a three-tier architecture: mesh_data_t stores logical geometry, mesh_data_runtime_t manages GPU buffers, and mesh_object_t handles scene integration.
  • Lazy initialization defers GPU resource creation until first render via the _ pointer in mesh_data_t.
  • Dynamic array wrappers in paint/sources/types.h provide type-safe collections for mesh management.
  • Explicit ownership flags (owns_arrays) prevent memory leaks during CPU-to-GPU data transitions.
  • Scale factors (scale_pos, scale_tex) allow non-destructive vertex deformation at the data structure level.

Frequently Asked Questions

What distinguishes mesh_data_t from mesh_object_t in ArmorPaint?

mesh_data_t contains the actual geometry data—vertices, indices, and attributes—that can be shared across multiple instances, while mesh_object_t represents a specific instance in the scene with its own transform, material assignment, and camera distance calculation. Multiple mesh_object_t instances can reference the same mesh_data_t to optimize memory usage for repeated geometry.

How does ArmorPaint handle the transition from CPU data to GPU buffers?

The transition occurs lazily through the mesh_data_runtime_t struct. When a mesh is first rendered, the engine checks if the _ pointer in mesh_data_t is null; if so, it creates GPU buffers via util_mesh.c and stores them in mesh_data_runtime_t, including the gpu_vertex_structure_t layout description required by the rendering pipeline.

Where does ArmorPaint store the complete list of meshes in a project?

The global project state maintains a mesh_data_t_array_t accessible through project->mesh_datas, defined in paint/sources/types.h. This array holds pointers to all mesh_data_t structures loaded in the current session, allowing plugins and tools to iterate geometry without traversing the scene hierarchy.

What mesh file formats can populate these data structures?

According to the source in paint/sources/io/import_mesh.c, the engine parses external files including OBJ and FBX formats, converting them into the internal mesh_data_t representation through the import_mesh_raw_mesh() function, which normalizes position and normal data into the engine's native i16_array_t (short4norm) format.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →