Attributes Example

// Writes an OMF file containing all non-texture attributes on a cube surface, then reads
// back some of those attributes.

#include <assert.h>
#include <omf.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static const double VERTICES[][3] = {
    { 0.0, 0.0, 0.0 },
    { 1.0, 0.0, 0.0 },
    { 1.0, 1.0, 0.0 },
    { 0.0, 1.0, 0.0 },
    { 0.0, 0.0, 1.0 },
    { 1.0, 0.0, 1.0 },
    { 1.0, 1.0, 1.0 },
    { 0.0, 1.0, 1.0 },
};

static const uint32_t TRIANGLES[][3] = {
    { 0, 2, 1 },
    { 0, 3, 2 },
    { 0, 1, 5 },
    { 0, 5, 4 },
    { 1, 2, 6 },
    { 1, 6, 5 },
    { 2, 3, 7 },
    { 2, 7, 6 },
    { 3, 0, 4 },
    { 3, 4, 7 },
    { 4, 5, 6 },
    { 4, 6, 7 },
};

static const double PATH_VECTORS_3D[][3] = {
    {  1.0,  0.0,  0.0 },
    {  0.0,  1.0,  0.0 },
    { -1.0,  0.0,  0.0 },
    {  0.0,  0.0,  1.0 },
    {  0.0,  0.0, -1.0 },
    { -1.0,  0.0,  0.0 },
    {  0.0, -1.0,  0.0 },
    {  1.0,  0.0,  0.0 },
};

static const double OUTWARD_VECTORS_2D[][2] = {
    {  0.0,  0.0 },
    {  0.0,  0.0 },
    {  0.0, -1.0 },
    {  0.0, -1.0 },
    {  1.0,  0.0 },
    {  1.0,  0.0 },
    {  0.0,  1.0 },
    {  0.0,  1.0 },
    { -1.0,  0.0 },
    { -1.0,  0.0 },
    {  0.0,  0.0 },
    {  0.0,  0.0 },
};

static const bool OUTWARD_VECTORS_2D_MASK[] = {
    true, true, false, false, false, false, false, false, false, false, true, true,
};

static const bool FIRST_TRIANGLE[] = {
    true, false, true, false, true, false, true, false, true, false, true, false,
};

static const uint8_t COLORS[][4] = {
    {   0,   0,   0, 255 },
    { 255,   0,   0, 255 },
    { 255, 255,   0, 255 },
    {   0, 255,   0, 255 },
    {   0,   0, 255, 255 },
    { 255,   0, 255, 255 },
    { 255, 255, 255, 255 },
    {   0, 255, 255, 255 },
};

static const char *FACE_STRINGS[] = {
    "down", "down",
    "south", "south",
    "east", "east",
    "north", "north",
    "west", "west",
    "up", "up",
};

static const char *VERTEX_STRINGS[] = {
    "origin", NULL, NULL, NULL,
    NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL,
};

static const uint32_t CATEGORY_VALUES[] = {
    1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0,
};

static const char *CATEGORY_NAMES[] = {
    "ceiling",
    "floor",
    "wall",
};

static const int64_t CATEGORY_IDS[] = {
    1024,
    1025,
    -1,
};

static const uint8_t CATEGORY_COLORS[][4] = {
    { 255, 0, 0, 255 },
    { 0, 255, 0, 255 },
    { 0, 0, 255, 255 },
};

static const float NUMBERS[] = {
    0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0,
};

static const uint8_t GRADIENT[][4] = {
    { 255, 0, 0, 255 },
    { 255, 255, 0, 255 },
};

static const float DISCRETE_BOUNDARIES[] = {
    1.0, 4.0, 5.5, 7.5,
};
static const bool DISCRETE_INCLUSIVE[] = {
    true, // Includes the 1.0 value.
    false, // Excludes the 4.0 value.
    false,
    false,
};
static const uint8_t DISCRETE_COLORS[][4] = {
    { 255, 0, 0, 255 },
    { 255, 85, 0, 255 },
    { 255, 170, 0, 255 },
    { 255, 255, 0, 255 },
};

static const int64_t DATETIMES_MS[] = {
    -93706495806958LL,      // -1000-07-24T01:49:53.042
    -1465596606958LL,       //  1923-07-24T01:49:53.042
    1690163393042LL,        //  2023-07-24T01:49:53.042
    4845836993042LL,        //  2123-07-24T01:49:53.042
    32521312193042LL,       //  3000-07-24T01:49:53.042
    253388396993042LL,      //  9999-07-24T01:49:53.042
    0LL,                    //  1970-01-01T00:00:00.000 (the epoch)
    -2051264047219200000LL, // -65000000-01-01T00:00:00.000 (65 million years ago)
};

static bool write(const char *path) {
    OmfError *error;
    OmfWriter *writer;
    OmfProject project;
    OmfElement element;
    OmfSurface surface;
    OmfAttribute attribute;
    OmfCategoryData cat_data;
    OmfNumberData num_data;
    OmfContinuousColormap c_cmap;
    OmfDiscreteColormap d_cmap;
    OmfHandle *proj_handle, *cube_handle, *attr_handle;

    // Open the file and create a project.
    writer = omf_writer_open(path);
    project = omf_project_init("attributes.omf");
    proj_handle = omf_writer_project(writer, &project);

    // Create the cube element and keep that handle too.
    surface = omf_surface_init(
        omf_writer_array_vertices64(writer, VERTICES, 8),
        omf_writer_array_triangles(writer, TRIANGLES, 12)
    );
    element = omf_element_init("Cube");
    element.surface = &surface;
    cube_handle = omf_writer_element(writer, proj_handle, &element);

    // Masked 2D vectors on the faces. The attribute data is the array.
    attribute = omf_attribute_init("Outward", OMF_LOCATION_PRIMITIVES);
    attribute.description = "A vector on each face pointing outward in the XY plane, or null "
        "if the face is parallel to the XY plane.";
    attribute.vector_data = omf_writer_array_vectors64x2(
        writer, OUTWARD_VECTORS_2D, OUTWARD_VECTORS_2D_MASK, 12);
    omf_writer_attribute(writer, cube_handle, &attribute);

    // 3D vectors on the vertices. The attribute data is just the array for this type.
    attribute = omf_attribute_init("Path", OMF_LOCATION_VERTICES);
    attribute.description = "From each vertex, points toward the next vertex in a closed and "
        "non-intersecting path around the cube";
    attribute.vector_data = omf_writer_array_vectors64x3(writer, PATH_VECTORS_3D, NULL, 8);
    omf_writer_attribute(writer, cube_handle, &attribute);

    // Boolean values on faces. The attribute data is the array.
    attribute = omf_attribute_init("First triangle", OMF_LOCATION_PRIMITIVES);
    attribute.description = "Filter that selects the first triangle of each square face.";
    attribute.boolean_data = omf_writer_array_booleans(writer, FIRST_TRIANGLE, NULL, 12);
    omf_writer_attribute(writer, cube_handle, &attribute);

    // Color values on vertices. The attribute data is the array.
    attribute = omf_attribute_init("Position", OMF_LOCATION_VERTICES);
    attribute.description = "Transforms the vertex positions into RGB colors.";
    attribute.color_data = omf_writer_array_colors(writer, COLORS, NULL, 8);
    omf_writer_attribute(writer, cube_handle, &attribute);

    // Text values on faces. The attribute data is the string array.
    attribute = omf_attribute_init("Directions", OMF_LOCATION_PRIMITIVES);
    attribute.description = "Strings giving the direction of each face.";
    attribute.text_data = omf_writer_array_text(writer, FACE_STRINGS, 12);
    omf_writer_attribute(writer, cube_handle, &attribute);

    // Masked string values on vertices. The attribute data is the string array.
    attribute = omf_attribute_init("Origin", OMF_LOCATION_PRIMITIVES);
    attribute.description = "A string on just the origin vertex.";
    attribute.text_data = omf_writer_array_text(writer, VERTEX_STRINGS, 12);
    omf_writer_attribute(writer, cube_handle, &attribute);

    // Category values on faces. This is more complicated because we need to store the legend
    // as well.
    cat_data = omf_category_data_init();
    cat_data.values = omf_writer_array_indices(writer, CATEGORY_VALUES, NULL, 12);
    cat_data.names = omf_writer_array_names(writer, CATEGORY_NAMES, 3);
    cat_data.gradient = omf_writer_array_gradient(writer, CATEGORY_COLORS, 3);
    attribute = omf_attribute_init("Face type", OMF_LOCATION_PRIMITIVES);
    attribute.description = "The type of each face: wall, floor, or ceiling.";
    attribute.category_data = &cat_data;
    attr_handle = omf_writer_attribute(writer, cube_handle, &attribute);

    /// Add an integer sub-attribute to that category attribute.
    num_data = omf_number_data_init();
    num_data.values = omf_writer_array_numbers_int64(writer, CATEGORY_IDS, NULL, 3);
    attribute = omf_attribute_init("Discrete", OMF_LOCATION_CATEGORIES);
    attribute.description = "Category ids.";
    attribute.number_data = &num_data;
    omf_writer_attribute(writer, attr_handle, &attribute);

    // Number values on vertices with a discrete colormap.
    c_cmap = omf_continuous_colormap_init(
        0.0, 7.0, omf_writer_array_gradient(writer, GRADIENT, 2));
    num_data = omf_number_data_init();
    num_data.continuous_colormap = &c_cmap;
    num_data.values = omf_writer_array_numbers_float32(writer, NUMBERS, NULL, 8);
    attribute = omf_attribute_init("Continuous", OMF_LOCATION_VERTICES);
    attribute.description = "Numbers with a continuous colormap, shading from red to yellow.";
    attribute.number_data = &num_data;
    omf_writer_attribute(writer, cube_handle, &attribute);

    // Number values on vertices with a discrete colormap.
    d_cmap = omf_discrete_colormap_init();
    d_cmap.boundaries = omf_writer_array_boundaries_float32(
        writer, DISCRETE_BOUNDARIES, DISCRETE_INCLUSIVE, 4);
    d_cmap.gradient = omf_writer_array_gradient(writer, DISCRETE_COLORS, 5);
    num_data = omf_number_data_init();
    num_data.discrete_colormap = &d_cmap;
    num_data.values = omf_writer_array_numbers_float32(writer, NUMBERS, NULL, 8);
    attribute = omf_attribute_init("Discrete", OMF_LOCATION_VERTICES);
    attribute.description = "Numbers with a discrete colormap, shading from red to yellow with "
        "each color applied to two vertices.";
    attribute.number_data = &num_data;
    omf_writer_attribute(writer, cube_handle, &attribute);

    // Datetime values on vertices with no colormap.
    num_data = omf_number_data_init();
    num_data.values = omf_writer_array_numbers_date_time(writer, DATETIMES_MS, NULL, 8);
    attribute = omf_attribute_init("Date-times", OMF_LOCATION_VERTICES);
    attribute.description = "A scattering of date-time values as milliseconds since the epoch.";
    attribute.units = "datetime[ms]";
    attribute.number_data = &num_data;
    omf_writer_attribute(writer, cube_handle, &attribute);

    // Finish writing and close the file.
    omf_writer_finish(writer, NULL);

    // Check for errors.
    if ((error = omf_error()) != NULL) {
        fprintf(stderr, "[write failed] %s (%d)\n", error->message, error->code);
        omf_error_free(error);
        return false;
    }
    return true;
}

static void print_numbers_float32(OmfReader *reader, const OmfArray *array) {
    OmfArrayInfo info;
    OmfNumbersFloat32 *iter;
    float data;
    bool is_null;

    info = omf_reader_array_info(reader, array);
    assert(info.array_type == OMF_ARRAY_TYPE_NUMBERS_FLOAT32);
    iter = omf_reader_array_numbers_float32_iter(reader, array);
    while (omf_numbers_float32_next(iter, &data, &is_null)) {
        assert(!is_null);
        printf("    %g\n", data);
    }
    omf_numbers_float32_free(iter);
}

static void print_vectors64x2(OmfReader *reader, const OmfArray *array) {
    OmfArrayInfo info;
    OmfVectors64x2 *iter;
    double data[2];
    bool is_null;

    info = omf_reader_array_info(reader, array);
    assert(info.array_type == OMF_ARRAY_TYPE_VECTORS64X2);
    iter = omf_reader_array_vectors64x2_iter(reader, array);
    while (omf_vectors64x2_next(iter, data, &is_null)) {
        if (is_null) {
            printf("    null\n");
        } else {
            printf("    { %g, %g }\n", data[0], data[1]);
        }
    }
    omf_vectors64x2_free(iter);
}

static void print_text(OmfReader *reader, const OmfArray *array) {
    OmfArrayInfo info;
    OmfText *iter;
    const char *data;
    size_t len;

    info = omf_reader_array_info(reader, array);
    assert(info.array_type == OMF_ARRAY_TYPE_TEXT);
    iter = omf_reader_array_text_iter(reader, array);
    while (omf_text_next(iter, &data, &len)) {
        if (data == NULL) {
            assert(len == 0);
            printf("    null\n");
        } else {
            assert(len == strlen(data));
            printf("    \"%s\"\n", data);
        }
    }
    omf_text_free(iter);
}

static bool read(const char *path) {
    OmfReader *reader;
    const OmfProject *project;
    OmfError *error;
    const OmfAttribute *attribute;

    // Open the file and read the project.
    reader = omf_reader_open(path);
    project = omf_reader_project(reader, NULL);
    if (!project) {
        error = omf_error();
        fprintf(stderr, "[read failed] %s (%d)\n", error->message, error->code);
        omf_error_free(error);
        return false;
    }
    printf("name: %s\n", project->name);

    // Read a few of those attributes back and print them out.

    // Masked vector attribute.
    attribute = &project->elements[0].attributes[0];
    printf("%s:\n", attribute->name);
    print_vectors64x2(reader, attribute->vector_data);

    // String attribute.
    attribute = &project->elements[0].attributes[4];
    printf("%s:\n", attribute->name);
    print_text(reader, attribute->text_data);

    // Masked string attribute.
    attribute = &project->elements[0].attributes[5];
    printf("%s:\n", attribute->name);
    print_text(reader, attribute->text_data);

    // Number attribute.
    attribute = &project->elements[0].attributes[7];
    printf("%s:\n", attribute->name);
    print_numbers_float32(reader, attribute->number_data->values);

    // Close the reader only once we're done with `project`.
    omf_reader_close(reader);

    // Check for errors.
    if ((error = omf_error()) != NULL) {
        fprintf(stderr, "[read failed] %s (%d)\n", error->message,
                error->code);
        omf_error_free(error);
        return false;
    }
    return true;
}

int main() {
    if (!write("attributes.omf")) return 1;
    if (!read("attributes.omf")) return 1;
    return 0;
}