Skip to content

Commit

Permalink
Add kvx support [1.3.0]
Browse files Browse the repository at this point in the history
  • Loading branch information
GabeRundlett committed Apr 14, 2024
1 parent 4b5f5dc commit 5c1ca6a
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 2 deletions.
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ if(GVOX_ENABLE_TESTS)
list(APPEND VCPKG_MANIFEST_FEATURES "tests")
endif()

project(gvox VERSION 1.2.11)
project(gvox VERSION 1.3.0)

if(GVOX_ENABLE_STATIC_ANALYSIS)
set(CPPCHECK_TEMPLATE "gcc")
Expand Down Expand Up @@ -50,6 +50,7 @@ set(GVOX_PARSE_ADAPTERS
"gvox_global_palette"
"gvox_brickmap"
"voxlap"
"kvx"
"magicavoxel"
)
set(GVOX_SERIALIZE_ADAPTERS
Expand Down
10 changes: 10 additions & 0 deletions include/gvox/adapters/parse/kvx.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#ifndef GVOX_KVX_PARSE_ADAPTER_H
#define GVOX_KVX_PARSE_ADAPTER_H

typedef struct {
// This determines whether to make the insides of the map hollow or solid
// By default, this is set to 1.
uint8_t mipmaplevels;
} GvoxKvxParseAdapterConfig;

#endif
187 changes: 187 additions & 0 deletions src/adapters/parse/kvx.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
#include <gvox/gvox.h>
#include <gvox/adapters/parse/kvx.h>

#include <cstdlib>
#include <cstdint>

#include <bit>
#include <vector>
#include <array>
#include <new>
#include <limits>

struct KvxParseUserState {
GvoxKvxParseAdapterConfig config{};

size_t voxdata_offset;
uint32_t numbytes;
uint32_t xsiz, ysiz, zsiz, xpivot, ypivot, zpivot;
std::vector<uint32_t> xoffset{};
std::vector<uint16_t> xyoffset{};
std::vector<uint8_t> voxdata{};
std::array<std::array<uint8_t, 3>, 256> palette;
};

// Base
extern "C" void gvox_parse_adapter_kvx_create(GvoxAdapterContext *ctx, void const *config) {
auto *user_state_ptr = malloc(sizeof(KvxParseUserState));
[[maybe_unused]] auto &user_state = *(new (user_state_ptr) KvxParseUserState());
gvox_adapter_set_user_pointer(ctx, user_state_ptr);
if (config != nullptr) {
user_state.config = *static_cast<GvoxKvxParseAdapterConfig const *>(config);
if (user_state.config.mipmaplevels == 0 || user_state.config.mipmaplevels == std::numeric_limits<uint8_t>::max()) {
user_state.config.mipmaplevels = 5;
}
} else {
user_state.config = GvoxKvxParseAdapterConfig{
.mipmaplevels = 5,
};
}
}

extern "C" void gvox_parse_adapter_kvx_destroy(GvoxAdapterContext *ctx) {
auto &user_state = *static_cast<KvxParseUserState *>(gvox_adapter_get_user_pointer(ctx));
user_state.~KvxParseUserState();
free(&user_state);
}

extern "C" void gvox_parse_adapter_kvx_blit_begin(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const * /*unused*/, uint32_t /*unused*/) {
auto &user_state = *static_cast<KvxParseUserState *>(gvox_adapter_get_user_pointer(ctx));

size_t offset = 0;
auto read = [&](void *dst, size_t size) {
gvox_input_read(blit_ctx, offset, size, dst);
offset += size;
};

for (int32_t i = 0; i < user_state.config.mipmaplevels; i++) {
uint32_t numbytes;
read(&numbytes, 4);
if (i == 0) {
user_state.numbytes = numbytes;
read(&user_state.xsiz, 4);
read(&user_state.ysiz, 4);
read(&user_state.zsiz, 4);
read(&user_state.xpivot, 4);
read(&user_state.ypivot, 4);
read(&user_state.zpivot, 4);
user_state.xoffset.resize(user_state.xsiz + 1);
read(user_state.xoffset.data(), (user_state.xsiz + 1) * 4);
user_state.xyoffset.resize(user_state.xsiz * (user_state.ysiz + 1));
read(user_state.xyoffset.data(), (user_state.xsiz * (user_state.ysiz + 1)) * 2);
user_state.voxdata_offset = (user_state.xsiz + 1) * 4 + user_state.xsiz * (user_state.ysiz + 1) * 2;
user_state.voxdata.resize(user_state.numbytes - 24 - user_state.voxdata_offset);
read(user_state.voxdata.data(), user_state.voxdata.size());
} else {
offset += numbytes;
}
}
read(user_state.palette.data(), 768);
}

extern "C" void gvox_parse_adapter_kvx_blit_end(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/) {
}

// General
extern "C" auto gvox_parse_adapter_kvx_query_details() -> GvoxParseAdapterDetails {
return {
.preferred_blit_mode = GVOX_BLIT_MODE_DONT_CARE,
};
}

extern "C" auto gvox_parse_adapter_kvx_query_parsable_range(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx) -> GvoxRegionRange {
auto &user_state = *static_cast<KvxParseUserState *>(gvox_adapter_get_user_pointer(ctx));
return {{0, 0, 0}, {user_state.xsiz, user_state.ysiz, user_state.zsiz}};
}

extern "C" auto gvox_parse_adapter_kvx_sample_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx, GvoxRegion const * /*unused*/, GvoxOffset3D const *offset, uint32_t channel_id) -> GvoxSample {
auto &user_state = *static_cast<KvxParseUserState *>(gvox_adapter_get_user_pointer(ctx));
if (offset->x < 0 || offset->y < 0 || offset->z < 0 ||
static_cast<uint32_t>(offset->x) >= user_state.xsiz ||
static_cast<uint32_t>(offset->y) >= user_state.ysiz ||
static_cast<uint32_t>(offset->z) >= user_state.zsiz) {
return {0u, 0u};
}

auto x = offset->x;
auto y = user_state.ysiz - offset->y - 1;

Check warning on line 107 in src/adapters/parse/kvx.cpp

View workflow job for this annotation

GitHub Actions / Build Linux

implicit conversion changes signedness: 'const int32_t' (aka 'const int') to 'unsigned int' [-Wsign-conversion]
auto z = user_state.zsiz - offset->z - 1;

Check warning on line 108 in src/adapters/parse/kvx.cpp

View workflow job for this annotation

GitHub Actions / Build Linux

implicit conversion changes signedness: 'const int32_t' (aka 'const int') to 'unsigned int' [-Wsign-conversion]

auto start_index = user_state.xoffset[x] - user_state.voxdata_offset + user_state.xyoffset[x * (user_state.ysiz + 1) + y];

Check warning on line 110 in src/adapters/parse/kvx.cpp

View workflow job for this annotation

GitHub Actions / Build Linux

implicit conversion changes signedness: 'int32_t' (aka 'int') to 'unsigned int' [-Wsign-conversion]

Check warning on line 110 in src/adapters/parse/kvx.cpp

View workflow job for this annotation

GitHub Actions / Build Linux

implicit conversion changes signedness: 'int32_t' (aka 'int') to 'std::vector::size_type' (aka 'unsigned long') [-Wsign-conversion]
auto end_index = user_state.xoffset[x] - user_state.voxdata_offset + user_state.xyoffset[x * (user_state.ysiz + 1) + (y + 1)];

Check warning on line 111 in src/adapters/parse/kvx.cpp

View workflow job for this annotation

GitHub Actions / Build Linux

implicit conversion changes signedness: 'int32_t' (aka 'int') to 'unsigned int' [-Wsign-conversion]

Check warning on line 111 in src/adapters/parse/kvx.cpp

View workflow job for this annotation

GitHub Actions / Build Linux

implicit conversion changes signedness: 'int32_t' (aka 'int') to 'std::vector::size_type' (aka 'unsigned long') [-Wsign-conversion]

if (start_index >= user_state.voxdata.size() || end_index > user_state.voxdata.size()) {
return {0u, 0u};
}
auto *startptr = user_state.voxdata.data() + start_index;
auto *endptr = user_state.voxdata.data() + end_index;

bool is_solid = false;
uint32_t color = 0x0;

while (startptr < endptr) {
auto slabztop = startptr[0];
auto slabzleng = startptr[1];
auto slabbackfacecullinfo = startptr[2];

Check warning on line 125 in src/adapters/parse/kvx.cpp

View workflow job for this annotation

GitHub Actions / Build Linux

unused variable 'slabbackfacecullinfo' [-Wunused-variable]

Check warning on line 125 in src/adapters/parse/kvx.cpp

View workflow job for this annotation

GitHub Actions / Build Windows

'slabbackfacecullinfo': local variable is initialized but not referenced

Check warning on line 125 in src/adapters/parse/kvx.cpp

View workflow job for this annotation

GitHub Actions / Build Windows

'slabbackfacecullinfo': local variable is initialized but not referenced
startptr += 3;

if (z < slabztop) {
// missed voxel
break;
}

if (z < slabztop + slabzleng) {

Check warning on line 133 in src/adapters/parse/kvx.cpp

View workflow job for this annotation

GitHub Actions / Build Windows

'<': signed/unsigned mismatch

Check warning on line 133 in src/adapters/parse/kvx.cpp

View workflow job for this annotation

GitHub Actions / Build Windows

'<': signed/unsigned mismatch
// hit voxel
auto palette_index = startptr[z - slabztop];
auto palette_entry = user_state.palette[palette_index];
color = uint32_t(palette_entry[0] * 4) | uint32_t(palette_entry[1] * 4 << 8) | uint32_t(palette_entry[2] * 4 << 16) | (0x1u << 24);
is_solid = true;
}

startptr += slabzleng;
}

switch (channel_id) {
case GVOX_CHANNEL_ID_COLOR: return {color, static_cast<uint8_t>(is_solid)};
case GVOX_CHANNEL_ID_MATERIAL_ID: return {static_cast<uint32_t>(is_solid), static_cast<uint8_t>(is_solid)};
default: break;
}
return {0u, 0u};
}

// Serialize Driven
extern "C" auto gvox_parse_adapter_kvx_query_region_flags(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/, GvoxRegionRange const * /*unused*/, uint32_t /*unused*/) -> uint32_t {
return 0;
}

extern "C" auto gvox_parse_adapter_kvx_load_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) -> GvoxRegion {
uint32_t const available_channels = GVOX_CHANNEL_BIT_COLOR | GVOX_CHANNEL_BIT_MATERIAL_ID;
if ((channel_flags & ~available_channels) != 0) {
gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_REQUESTED_CHANNEL_NOT_PRESENT, "Tried loading a region with a channel that wasn't present in the original data");
}
GvoxRegion const region = {
.range = *range,
.channels = channel_flags & available_channels,
.flags = 0u,
.data = nullptr,
};
return region;
}

extern "C" void gvox_parse_adapter_kvx_unload_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/, GvoxRegion * /*unused*/) {
}

// Parse Driven
extern "C" void gvox_parse_adapter_kvx_parse_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) {
uint32_t const available_channels = GVOX_CHANNEL_BIT_COLOR | GVOX_CHANNEL_BIT_MATERIAL_ID;
if ((channel_flags & ~available_channels) != 0) {
gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_REQUESTED_CHANNEL_NOT_PRESENT, "Tried loading a region with a channel that wasn't present in the original data");
}
GvoxRegion const region = {
.range = *range,
.channels = channel_flags & available_channels,
.flags = 0u,
.data = nullptr,
};
gvox_emit_region(blit_ctx, &region);
}
32 changes: 32 additions & 0 deletions tests/simple/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,37 @@ void test_palette_file_io(void) {
gvox_destroy_context(gvox_ctx);
}

void test_kvx(void) {
GvoxContext *gvox_ctx = gvox_create_context();

{
GvoxFileInputAdapterConfig i_config = {
.filepath = "assets/test.kvx",
.byte_offset = 0,
};
GvoxColoredTextSerializeAdapterConfig s_config = {
.non_color_max_value = 255,
.vertical = 1,
};
GvoxAdapterContext *i_ctx = gvox_create_adapter_context(gvox_ctx, gvox_get_input_adapter(gvox_ctx, "file"), &i_config);
GvoxAdapterContext *o_ctx = gvox_create_adapter_context(gvox_ctx, gvox_get_output_adapter(gvox_ctx, "stdout"), NULL);
GvoxAdapterContext *p_ctx = gvox_create_adapter_context(gvox_ctx, gvox_get_parse_adapter(gvox_ctx, "kvx"), NULL);
GvoxAdapterContext *s_ctx = gvox_create_adapter_context(gvox_ctx, gvox_get_serialize_adapter(gvox_ctx, "colored_text"), &s_config);
gvox_blit_region(
i_ctx, o_ctx, p_ctx, s_ctx,
NULL,
GVOX_CHANNEL_BIT_COLOR |
GVOX_CHANNEL_BIT_MATERIAL_ID);
gvox_destroy_adapter_context(i_ctx);
gvox_destroy_adapter_context(o_ctx);
gvox_destroy_adapter_context(p_ctx);
gvox_destroy_adapter_context(s_ctx);
}
handle_gvox_error(gvox_ctx);

gvox_destroy_context(gvox_ctx);
}

void test_magicavoxel(void) {
GvoxContext *gvox_ctx = gvox_create_context();

Expand Down Expand Up @@ -492,6 +523,7 @@ int main(void) {
test_raw_file_io();
test_palette_buffer_io();
test_palette_file_io();
test_kvx();
test_magicavoxel();
test_voxlap();
// test_speed();
Expand Down
2 changes: 1 addition & 1 deletion vcpkg.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "gvox",
"version-semver": "1.2.11",
"version-semver": "1.3.0",
"description": "GVOX Voxel Format Library",
"homepage": "https://github.com/GabeRundlett/gvox",
"features": {
Expand Down

0 comments on commit 5c1ca6a

Please sign in to comment.