/*
reinhard@finalmedia.de
So 16. Aug 21:20:40 CEST 2026

Public Domain


MagicaVoxel Format (vox) zu fsplat konvertieren
Liest von stdin und schreibt nach stdout.
erster parameter ist skalierungsfaktor pro splat.
z.B. 0.02 oder 0.5

Compilieren mittels:
	gcc -O3 vox2fsplat.c -o vox2fsplat

Beispielaufrufe.

./vox2fsplat 0.5 < eingabe.vox > ausgabe.fsplat

./vox2fsplat 0.5 < example.vox | ./fsplat 4<camlines | fdrawterm

*/

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

typedef struct {
    uint8_t x, y, z, c;
} VoxelRaw;

int match_chunk_id(const char* id, const uint8_t* buffer) {
    return memcmp(id, buffer, 4) == 0;
}

int main(int argc, char** argv) {

    float splat_scale = (argc > 1) ? (float)atof(argv[1]) : 0.05f;
    if (splat_scale <= 0.0001f) {
        splat_scale = 0.05f;
    }

    size_t capacity = 2 * 1024 * 1024;
    size_t bytes_read = 0;
    uint8_t* buffer = (uint8_t*)malloc(capacity);

    while (!feof(stdin)) {
        if (bytes_read + 4096 > capacity) {
            capacity *= 2;
            buffer = (uint8_t*)realloc(buffer, capacity);
        }
        size_t n = fread(buffer + bytes_read, 1, 4096, stdin);
        bytes_read += n;
        if (n < 4096 && ferror(stdin)) {
            fprintf(stderr, "Fehler beim Lesen von stdin.\n");
            free(buffer);
            return 1;
        }
    }

    if (bytes_read < 8 || memcmp(buffer, "VOX ", 4) != 0) {
        fprintf(stderr, "Fehler: Ungueltiges .vox Dateiformat.\n");
        free(buffer);
        return 1;
    }

    size_t voxel_capacity = 4096;
    int32_t total_voxels = 0;
    VoxelRaw* all_voxels = (VoxelRaw*)malloc(voxel_capacity * sizeof(VoxelRaw));

    uint32_t palette[256];
    int has_custom_palette = 0;

    // Variablen für die Bounding Box (Zentrierung)
    int32_t min_x = 9999, min_y = 9999, min_z = 9999;
    int32_t max_x = -9999, max_y = -9999, max_z = -9999;

    // 1. Chunks parsen und Voxel sammeln
    for (size_t i = 8; i + 12 <= bytes_read; i++) {
        if (match_chunk_id("XYZ ", buffer + i) || match_chunk_id("XYZI", buffer + i)) {
            int32_t chunk_bytes;
            memcpy(&chunk_bytes, buffer + i + 4, 4);

            size_t count_offset = i + 12;
            if (count_offset + 4 <= bytes_read) {
                int32_t chunk_voxels = 0;
                memcpy(&chunk_voxels, buffer + count_offset, 4);

                if (count_offset + 4 + (chunk_voxels * 4) <= bytes_read) {
                    uint8_t* chunk_data = buffer + count_offset + 4;
                    while (total_voxels + chunk_voxels > voxel_capacity) {
                        voxel_capacity *= 2;
                        all_voxels = (VoxelRaw*)realloc(all_voxels, voxel_capacity * sizeof(VoxelRaw));
                    }

                    for (int32_t v = 0; v < chunk_voxels; v++) {
                        uint8_t vx = chunk_data[v * 4 + 0];
                        uint8_t vy = chunk_data[v * 4 + 1];
                        uint8_t vz = chunk_data[v * 4 + 2];

                        all_voxels[total_voxels].x = vx;
                        all_voxels[total_voxels].y = vy;
                        all_voxels[total_voxels].z = vz;
                        all_voxels[total_voxels].c = chunk_data[v * 4 + 3];

                        // Bounding Box erweitern
                        if (vx < min_x) min_x = vx; if (vx > max_x) max_x = vx;
                        if (vy < min_y) min_y = vy; if (vy > max_y) max_y = vy;
                        if (vz < min_z) min_z = vz; if (vz > max_z) max_z = vz;

                        total_voxels++;
                    }
                    i += 12 + chunk_bytes - 1;
                }
            }
        }
        else if (match_chunk_id("RGBA", buffer + i)) {
            size_t data_offset = i + 12;
            if (data_offset + 1024 <= bytes_read) {
                memcpy(palette, buffer + data_offset, 1024);
                has_custom_palette = 1;
                i += 12 + 1024 - 1;
            }
        }
    }

    if (total_voxels == 0) {
        fprintf(stderr, "Fehler: Keine Voxel-Daten im Stream gefunden.\n");
        free(all_voxels);
        free(buffer);
        return 1;
    }

    if (!has_custom_palette) {
        for (int i = 0; i < 256; i++) {
            palette[i] = (255 << 24) | (i << 16) | (i << 8) | i;
        }
    }

    // Zentrum der Bounding Box berechnen
    float center_x = (min_x + max_x) / 2.0f;
    float center_y = (min_y + max_y) / 2.0f;
    float center_z = (min_z + max_z) / 2.0f;

    // 2. SH-Codebook generieren
    float* codebook = (float*)calloc(256 * 48, sizeof(float));
    const float sh_scale = 0.28209479177387814f;

    for (int i = 0; i < 256; i++) {
        uint8_t r = (palette[i] >> 0)  & 0xFF;
        uint8_t g = (palette[i] >> 8)  & 0xFF;
        uint8_t b = (palette[i] >> 16) & 0xFF;

        codebook[i * 48 + 0]  = (r / 255.0f) / sh_scale;
        codebook[i * 48 + 16] = (g / 255.0f) / sh_scale;
        codebook[i * 48 + 32] = (b / 255.0f) / sh_scale;
    }

    // 3. Header schreiben
    uint32_t num_patterns = 256;
    fwrite(&num_patterns, 4, 1, stdout);
    fwrite(codebook, sizeof(float), 256 * 48, stdout);
    fwrite(&total_voxels, 4, 1, stdout);

    // Neutrale Quaternion (Keine Rotation)
    uint8_t qx = 128, qy = 128, qz = 128, qw = 255;
    uint8_t alpha = 255;

    // 4. Datenblöcke mit Achsen-Remapping (Z-Up zu Y-Up) und Zentrierung schreiben
    for (int32_t i = 0; i < total_voxels; i++) {
        float raw_x = (float)all_voxels[i].x - center_x;
        float raw_y = (float)all_voxels[i].y - center_y;
        float raw_z = (float)all_voxels[i].z - center_z;

        // Achsen-Tausch: Goxel-Y wird zu Z, Goxel-Z wird zu Y (Invertierung falls nötig)
        float pos[3];
        pos[0] = raw_x;
        pos[1] = raw_z;  // Z-Up von Goxel wandert auf die Y-Achse (oben)
        pos[2] = -raw_y; // Y von Goxel wandert invertiert auf die Z-Achse (Tiefe)

        float scale[3] = { splat_scale, splat_scale, splat_scale };

        fwrite(pos, sizeof(float), 3, stdout);
        fwrite(scale, sizeof(float), 3, stdout);
        fwrite(&alpha, 1, 1, stdout);
        fwrite(&qx, 1, 1, stdout);
        fwrite(&qy, 1, 1, stdout);
        fwrite(&qz, 1, 1, stdout);
        fwrite(&qw, 1, 1, stdout);
        fwrite(&all_voxels[i].c, 1, 1, stdout);
    }

    fprintf(stderr, "Erfolgreich %d Voxel konvertiert und zentriert (Splat-Skalierung: %.3f).\n",
            total_voxels, splat_scale);

    free(all_voxels);
    free(codebook);
    free(buffer);
    return 0;
}


