The idea is to have an octree that stores a 32x32x32 region of 15 bit values more efficiently than a 32x32x32 array, because if all 8 leaf nodes of a given branch are the same, the branch does not need to be allocated and can just be stored as a single value instead of 8+. This recursively means that if all values are the same in the octree, only one object needs to be stored. Each node is a 16 bit object, where the MSB determines if the remaining 15 bits are an pointer (relative to the start of the octree) to a set of 8 subnodes, or a value.
The provided code is a basic implementation of this idea, but does not include more advanced features such as detecting and merging branches whose values are all identical, or multiple parents referencing a single child set if they are the same (i.e. an octree DAG). The Set
function resizes the octree via realloc
when more space is needed, to the smallest power of 2 large enough to contain all the nodes.
One issue is then a full octree would have 65536 allocated objects but only 37448 will ever be needed at any point (excluding the root node0). A recommendation for a better reallocation scheme, along with general advice for improvement, would be heavily appreciated <3.
A valid all-0 octree could be initialized as follows:
Octree *o0 = memset(malloc(sizeof(Octree)), 0, sizeof(Octree));
The code:
typedef union Node8 {
uint64_t y[2], c[2];
uint32_t z[2][2], b[4];
uint16_t x[2][2][2], a[8];
} Node8;
typedef struct Octree {
uint64_t *data; // data* fields are reserved for future use
uint16_t datasize, nodesize;
uint8_t dataalloc, nodealloc;
uint16_t node0; // root node
Node8 node[]; // node[-1] == node0
} Octree;
uint16_t *Get(Octree *const o, const uint8_t x, const uint8_t z, const uint8_t y, uint_fast8_t *const p) {
uint16_t *n = &o->node0;
uint_fast8_t i = 4;
while (*n&0x8000 && (n = &o->node[*n&0x7FFF].x[y>>i&1][z>>i&1][x>>i&1], i))
--i;
if (p)
*p = i;
return n;
}
Octree *Set(Octree *o, const uint8_t x, const uint8_t z, const uint8_t y, const uint16_t v) {
uint_fast8_t i;
uint16_t *n = Get(o, x, z, y, &i);
if (i) {
const uint16_t p = *n;
const unsigned size = o->nodesize+i;
if (size >= 1U<<o->nodealloc) {
const ptrdiff_t off = n-(uint16_t *)o->node;
n = (uint16_t *)(o = realloc(o, sizeof(Octree)+(sizeof(Node8)<<(o->nodealloc = 32-__builtin_clz(size)))))->node+off;
}
do {
*n = o->nodesize|0x8000;
Node8 *const s = &o->node[o->nodesize++];
n = &s->x[y>>i&1][z>>i&1][x>>i&1];
s->a[1] = s->a[0] = p; // copy 8 objects in 3 steps instead of 8
s->b[1] = s->b[0];
s->c[1] = s->c[0];
} while (i--);
}
*n = v;
return o;
}
void Iter(const Octree *const o) {
const uint16_t *node[6] = {&o->node0};
int_fast8_t index[6] = {0};
for (uint_fast8_t i = 0;;) {
const int_fast8_t j = index[i]--;
if (j == -1) {
if (i--)
continue;
break;
}
const uint16_t n = node[i][j];
if (n&0x8000 && i < 5) {
node[++i] = o->node[n&0x7FFF].a;
index[i] = 7;
continue;
}
printf("%d\n", n);
}
}