Added project: first working prototype, but lots of TODOs

master
Marc Gilleron 2016-05-01 15:00:02 +02:00
commit 1acabf1307
12 changed files with 690 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.obj
*.pyc

5
SCsub Normal file
View File

@ -0,0 +1,5 @@
Import('env')
env.add_source_files(env.modules_sources,"*.cpp")
env.add_source_files(env.modules_sources,"lib/*.c")

11
config.py Normal file
View File

@ -0,0 +1,11 @@
def can_build(platform):
return True
def configure(env):
pass

16
register_types.cpp Normal file
View File

@ -0,0 +1,16 @@
#include "register_types.h"
#include "voxel_buffer.h"
#include "voxel_mesh_builder.h"
void register_voxel_types() {
ObjectTypeDB::register_type<Voxel>();
ObjectTypeDB::register_type<VoxelBuffer>();
ObjectTypeDB::register_type<VoxelMeshBuilder>();
}
void unregister_voxel_types() {
}

2
register_types.h Normal file
View File

@ -0,0 +1,2 @@
void register_voxel_types();
void unregister_voxel_types();

36
vector3i.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef VOXEL_VECTOR3I_H
#define VOXEL_VECTOR3I_H
struct Vector3i {
int x;
int y;
int z;
Vector3i() : x(0), y(0), z(0) {}
Vector3i(int px, int py, int pz) : x(px), y(py), z(pz) {}
Vector3i(const Vector3i & other) {
*this = other;
}
Vector3i & operator=(const Vector3i & other) {
x = other.x;
y = other.y;
z = other.z;
return *this;
}
bool operator==(const Vector3i & other) {
return x == other.x && y == other.y && z == other.z;
}
bool operator!=(const Vector3i & other) {
return x != other.x && y != other.y && z != other.z;
}
};
#endif // VOXEL_VECTOR3I_H

102
voxel.cpp Normal file
View File

@ -0,0 +1,102 @@
#include "voxel.h"
Voxel::Voxel() : Reference(), _id(0), _material_id(0), _is_transparent(false), _color(1.f, 1.f, 1.f) {
}
void Voxel::set_id(int id) {
ERR_FAIL_COND(id < 0 || id >= 256);
_id = id;
}
void Voxel::set_cube_geometry(float sy) {
const Vector3 vertices[SIDE_COUNT][6] = {
{
// LEFT
Vector3(0, 0, 0),
Vector3(0, sy, 0),
Vector3(0, sy, 1),
Vector3(0, 0, 0),
Vector3(0, sy, 1),
Vector3(0, 0, 1),
},
{
// RIGHT
Vector3(1, 0, 0),
Vector3(1, sy, 1),
Vector3(1, sy, 0),
Vector3(1, 0, 0),
Vector3(1, 0, 1),
Vector3(1, sy, 1)
},
{
// BOTTOM
Vector3(0, 0, 0),
Vector3(1, 0, 1),
Vector3(1, 0, 0),
Vector3(0, 0, 0),
Vector3(0, 0, 1),
Vector3(1, 0, 1)
},
{
// TOP
Vector3(0, sy, 0),
Vector3(1, sy, 0),
Vector3(1, sy, 1),
Vector3(0, sy, 0),
Vector3(1, sy, 1),
Vector3(0, sy, 1)
},
{
// BACK
Vector3(0, 0, 0),
Vector3(1, 0, 0),
Vector3(1, sy, 0),
Vector3(0, 0, 0),
Vector3(1, sy, 0),
Vector3(0, sy, 0),
},
{
// FRONT
Vector3(1, 0, 1),
Vector3(0, 0, 1),
Vector3(1, sy, 1),
Vector3(0, 0, 1),
Vector3(0, sy, 1),
Vector3(1, sy, 1)
}
};
for (unsigned int side = 0; side < SIDE_COUNT; ++side) {
_model_side_vertices[side].resize(6);
DVector<Vector3>::Write w = _model_side_vertices[side].write();
for (unsigned int i = 0; i < 6; ++i) {
w[i] = vertices[side][i];
}
}
}
void Voxel::set_cube_uv_all_sides(Vector3 atlas_pos) {
// TODO
}
void Voxel::set_cube_uv_tbs_sides(Vector3 top_atlas_pos, Vector3 side_atlas_pos, Vector3 bottom_atlas_pos) {
// TODO
}
void Voxel::_bind_methods() {
ObjectTypeDB::bind_method(_MD("set_name", "name"), &Voxel::set_name);
ObjectTypeDB::bind_method(_MD("get_name"), &Voxel::get_name);
ObjectTypeDB::bind_method(_MD("set_id", "id"), &Voxel::set_id);
ObjectTypeDB::bind_method(_MD("get_id"), &Voxel::get_id);
ObjectTypeDB::bind_method(_MD("set_color", "color"), &Voxel::set_color);
ObjectTypeDB::bind_method(_MD("get_color"), &Voxel::get_color);
ObjectTypeDB::bind_method(_MD("set_cube_geometry", "height"), &Voxel::set_cube_geometry, DEFVAL(1.f));
// TODO
}

70
voxel.h Normal file
View File

@ -0,0 +1,70 @@
#ifndef VOXEL_TYPE_H
#define VOXEL_TYPE_H
#include <reference.h>
// Definition of one type of voxel.
// A voxel can be a simple coloured cube, or a more complex model.
class Voxel : public Reference {
OBJ_TYPE(Voxel, Reference)
public:
enum Side {
SIDE_LEFT = 0,
SIDE_RIGHT,
SIDE_BOTTOM,
SIDE_TOP,
SIDE_BACK,
SIDE_FRONT,
SIDE_COUNT
};
private:
int _id;
String _name;
int _material_id;
bool _is_transparent;
Color _color;
DVector<Vector3> _model_vertices;
DVector<Vector3> _model_normals;
DVector<Vector2> _model_uv;
DVector<Vector3> _model_side_vertices[SIDE_COUNT];
DVector<Vector2> _model_side_uv[SIDE_COUNT];
// TODO Child voxel types
public:
Voxel();
_FORCE_INLINE_ void set_name(String name) { _name = name; }
_FORCE_INLINE_ String get_name() const { return _name; }
void set_id(int id);
_FORCE_INLINE_ int get_id() const { return _id; }
_FORCE_INLINE_ void set_color(Color color) { _color = color; }
_FORCE_INLINE_ Color get_color() const { return _color; }
_FORCE_INLINE_ void set_material_id(unsigned int id) { _material_id = id; }
_FORCE_INLINE_ unsigned int get_material_id() const { return _material_id; }
void set_cube_geometry(float sy = 1);
void set_cube_uv_all_sides(Vector3 atlas_pos);
void set_cube_uv_tbs_sides(Vector3 top_atlas_pos, Vector3 side_atlas_pos, Vector3 bottom_atlas_pos);
const DVector<Vector3> & get_model_vertices() const { return _model_vertices; }
const DVector<Vector3> & get_model_normals() const { return _model_normals; }
const DVector<Vector2> & get_model_uv() const { return _model_uv; }
const DVector<Vector3> & get_model_side_vertices(unsigned int side) const { return _model_side_vertices[side]; }
const DVector<Vector2> & get_model_side_uv(unsigned int side) const { return _model_side_uv[side]; }
protected:
static void _bind_methods();
};
#endif // VOXEL_TYPE_H

202
voxel_buffer.cpp Normal file
View File

@ -0,0 +1,202 @@
#include "voxel_buffer.h"
VoxelBuffer::VoxelBuffer() {
}
VoxelBuffer::~VoxelBuffer() {
clear();
}
void VoxelBuffer::create(int sx, int sy, int sz) {
if (sx <= 0 || sy <= 0 || sz <= 0) {
return;
}
Vector3i new_size(sx, sy, sz);
if (new_size != _size) {
for (unsigned int i = 0; i < MAX_CHANNELS; ++i) {
Channel & channel = _channels[i];
if (channel.data) {
// TODO Optimize with realloc
delete_channel(i, _size);
create_channel(i, new_size);
}
}
_size = new_size;
}
}
void VoxelBuffer::clear() {
for (unsigned int i = 0; i < MAX_CHANNELS; ++i) {
Channel & channel = _channels[i];
if (channel.data) {
delete_channel(i, _size);
}
}
}
void VoxelBuffer::clear_channel(unsigned int channel_index, int clear_value) {
ERR_FAIL_INDEX(channel_index, MAX_CHANNELS);
delete_channel(channel_index, _size);
_channels[channel_index].defval = clear_value;
}
int VoxelBuffer::get_voxel(int x, int y, int z, unsigned int channel_index) const {
ERR_FAIL_INDEX_V(channel_index, MAX_CHANNELS, 0);
x -= _offset.x;
y -= _offset.y;
z -= _offset.z;
const Channel & channel = _channels[channel_index];
if (validate_local_pos(x, y, z) && channel.data) {
return channel.data[z][x][y];
}
else {
return channel.defval;
}
}
int VoxelBuffer::get_voxel_local(int x, int y, int z, unsigned int channel_index) const {
ERR_FAIL_INDEX_V(channel_index, MAX_CHANNELS, 0);
const Channel & channel = _channels[channel_index];
if (validate_local_pos(x, y, z) && channel.data) {
return channel.data[z][x][y];
}
else {
return channel.defval;
}
}
void VoxelBuffer::set_voxel(int value, int x, int y, int z, unsigned int channel_index) {
ERR_FAIL_INDEX(channel_index, MAX_CHANNELS);
x -= _offset.x;
y -= _offset.y;
z -= _offset.z;
ERR_FAIL_COND(!validate_local_pos(x, y, z));
Channel & channel = _channels[channel_index];
if (channel.defval != value) {
if (channel.data == NULL) {
create_channel(channel_index, _size);
}
channel.data[z][x][y] = value;
}
}
void VoxelBuffer::set_voxel_v(int value, Vector3 pos, unsigned int channel_index) {
set_voxel(value, pos.x, pos.y, pos.z, channel_index);
}
void VoxelBuffer::fill(int defval, unsigned int channel_index) {
ERR_FAIL_INDEX(channel_index, MAX_CHANNELS);
Channel & channel = _channels[channel_index];
for (unsigned int z = 0; z < _size.z; ++z) {
for (unsigned int x = 0; x < _size.x; ++x) {
uint8_t * column = channel.data[z][x];
for (unsigned int y = 0; y < _size.y; ++y) {
column[y] = defval;
}
}
}
}
bool VoxelBuffer::is_uniform(unsigned int channel_index) {
ERR_FAIL_INDEX_V(channel_index, MAX_CHANNELS, true);
Channel & channel = _channels[channel_index];
if (channel.data == NULL)
return true;
uint8_t voxel = channel.data[0][0][0];
for (unsigned int z = 0; z < _size.z; ++z) {
for (unsigned int x = 0; x < _size.x; ++x) {
uint8_t * column = channel.data[z][x];
for (unsigned int y = 0; y < _size.y; ++y) {
if (column[y] != voxel) {
return false;
}
}
}
}
return true;
}
void VoxelBuffer::optimize() {
for (unsigned int i = 0; i < MAX_CHANNELS; ++i) {
if (_channels[i].data && is_uniform(i)) {
clear_channel(i, _channels[i].data[0][0][0]);
}
}
}
void VoxelBuffer::create_channel(int i, Vector3i size, uint8_t defval) {
Channel & channel = _channels[i];
channel.data = (uint8_t***)memalloc(size.z * sizeof(uint8_t**));
for (unsigned int z = 0; z < size.z; ++z) {
uint8_t ** plane = (uint8_t**)memalloc(size.x * sizeof(uint8_t*));
channel.data[z] = plane;
for (unsigned int x = 0; x < size.x; ++x) {
uint8_t * column = (uint8_t*)memalloc(size.y * sizeof(uint8_t));
plane[x] = column;
for (unsigned int y = 0; y < size.y; ++y) {
column[y] = defval;
}
}
}
}
void VoxelBuffer::delete_channel(int i, Vector3i size) {
Channel & channel = _channels[i];
for (unsigned int z = 0; z < size.z; ++z) {
for (unsigned int x = 0; x < size.x; ++x) {
memfree(channel.data[z][x]);
}
memfree(channel.data[z]);
}
memfree(channel.data);
channel.data = NULL;
}
void VoxelBuffer::_bind_methods() {
ObjectTypeDB::bind_method(_MD("create", "sx", "sy", "sz"), &VoxelBuffer::create);
ObjectTypeDB::bind_method(_MD("clear"), &VoxelBuffer::clear);
ObjectTypeDB::bind_method(_MD("get_size_x"), &VoxelBuffer::get_size_x);
ObjectTypeDB::bind_method(_MD("get_size_y"), &VoxelBuffer::get_size_y);
ObjectTypeDB::bind_method(_MD("get_size_z"), &VoxelBuffer::get_size_z);
ObjectTypeDB::bind_method(_MD("get_offset_x"), &VoxelBuffer::get_offset_x);
ObjectTypeDB::bind_method(_MD("get_offset_y"), &VoxelBuffer::get_offset_y);
ObjectTypeDB::bind_method(_MD("get_offset_z"), &VoxelBuffer::get_offset_z);
ObjectTypeDB::bind_method(_MD("set_offset", "x", "y", "z"), &VoxelBuffer::set_offset);
ObjectTypeDB::bind_method(_MD("set_voxel", "value", "x", "y", "z", "channel"), &VoxelBuffer::set_voxel, DEFVAL(0));
ObjectTypeDB::bind_method(_MD("set_voxel_v", "value", "pos", "channel"), &VoxelBuffer::set_voxel, DEFVAL(0));
ObjectTypeDB::bind_method(_MD("get_voxel", "x", "y", "z", "channel"), &VoxelBuffer::set_voxel, DEFVAL(0));
ObjectTypeDB::bind_method(_MD("fill", "value", "channel"), &VoxelBuffer::fill, DEFVAL(0));
ObjectTypeDB::bind_method(_MD("is_uniform", "channel"), &VoxelBuffer::is_uniform, DEFVAL(0));
ObjectTypeDB::bind_method(_MD("optimize"), &VoxelBuffer::optimize);
}

86
voxel_buffer.h Normal file
View File

@ -0,0 +1,86 @@
#ifndef VOXEL_BUFFER_H
#define VOXEL_BUFFER_H
#include <reference.h>
#include <vector.h>
#include "vector3i.h"
// Dense voxels data storage.
// Organized in 8-bit channels like images, all optional.
// Note: for float storage (marching cubes for example), you can map [0..256] to [0..1] and save 3 bytes per cell
class VoxelBuffer : public Reference {
OBJ_TYPE(VoxelBuffer, Reference)
// Arbitrary value, 8 should be enough. Tweak for your needs.
static const int MAX_CHANNELS = 8;
struct Channel {
// Allocated when the channel is populated.
// Array of array of arrays, in order [z][x][y] because it makes vertical-wise access faster (the engine is Y-up).
uint8_t *** data;
uint8_t defval; // Default value when data is null
Channel() : data(NULL), defval(0) {}
};
// Each channel can store arbitary data.
// For example, you can decide to store colors (R, G, B, A), gameplay types (type, state, light) or both.
Channel _channels[MAX_CHANNELS];
// How many voxels are there in the three directions. All populated channels have the same size.
Vector3i _size;
// Offset applied to coordinates when accessing voxels.
// Use _local versions to bypass this.
Vector3i _offset;
public:
VoxelBuffer();
~VoxelBuffer();
void create(int sx, int sy, int sz);
void clear();
void clear_channel(unsigned int channel_index, int clear_value=0);
_FORCE_INLINE_ int get_size_x() const { return _size.x; }
_FORCE_INLINE_ int get_size_y() const { return _size.y; }
_FORCE_INLINE_ int get_size_z() const { return _size.z; }
_FORCE_INLINE_ int get_offset_x() const { return _offset.x; }
_FORCE_INLINE_ int get_offset_y() const { return _offset.y; }
_FORCE_INLINE_ int get_offset_z() const { return _offset.z; }
_FORCE_INLINE_ void set_offset(int x, int y, int z) { _offset = Vector3i(x,y,z); }
int get_voxel(int x, int y, int z, unsigned int channel_index=0) const;
int get_voxel_local(int x, int y, int z, unsigned int channel_index=0) const;
void set_voxel(int value, int x, int y, int z, unsigned int channel_index=0);
void set_voxel_v(int value, Vector3 pos, unsigned int channel_index = 0);
void fill(int defval, unsigned int channel_index = 0);
bool is_uniform(unsigned int channel_index = 0);
void optimize();
//void copy_from(Ref<VoxelBuffer> other);
_FORCE_INLINE_ bool validate_local_pos(unsigned int x, unsigned int y, unsigned int z) const {
return x < _size.x
&& y < _size.y
&& z < _size.x;
}
private:
void create_channel(int i, Vector3i size, uint8_t defval=0);
void delete_channel(int i, Vector3i size);
protected:
static void _bind_methods();
};
#endif // VOXEL_BUFFER_H

123
voxel_mesh_builder.cpp Normal file
View File

@ -0,0 +1,123 @@
#include "voxel_mesh_builder.h"
static const Vector3i g_side_normals[Voxel::SIDE_COUNT] = {
Vector3i(-1, 0, 0),
Vector3i(1, 0, 0),
Vector3i(0, -1, 0),
Vector3i(0, 1, 0),
Vector3i(0, 0, -1),
Vector3i(0, 0, 1),
};
VoxelMeshBuilder::VoxelMeshBuilder() {
}
void VoxelMeshBuilder::add_voxel_type(Ref<Voxel> voxel) {
ERR_FAIL_COND(voxel.is_null());
ERR_FAIL_COND(voxel->get_id() >= MAX_VOXEL_TYPES);
ERR_FAIL_COND(voxel->get_material_id() >= MAX_MATERIALS);
unsigned int id = voxel->get_id();
_voxel_types[id] = voxel;
}
void VoxelMeshBuilder::set_material(Ref<Material> material, unsigned int id) {
ERR_FAIL_COND(id >= MAX_MATERIALS);
_materials[id] = material;
_surface_tool[id].set_material(material);
}
Ref<Mesh> VoxelMeshBuilder::build(Ref<VoxelBuffer> buffer_ref) {
ERR_FAIL_COND_V(buffer_ref.is_null(), Ref<Mesh>());
const VoxelBuffer & buffer = **buffer_ref;
for (unsigned int i = 0; i < MAX_MATERIALS; ++i) {
_surface_tool[i].begin(Mesh::PRIMITIVE_TRIANGLES);
}
// Iterate 3D padded data to extract voxel faces.
// This is the most intensive job in this class, so all required data should be as fit as possible.
for (unsigned int z = 1; z < buffer.get_size_z()-1; ++z) {
for (unsigned int x = 1; x < buffer.get_size_x()-1; ++x) {
for (unsigned int y = 1; y < buffer.get_size_y()-1; ++y) {
int voxel_id = buffer.get_voxel_local(x, y, z, 0);
if (voxel_id != 0 && !_voxel_types[voxel_id].is_null()) {
const Voxel & voxel = **_voxel_types[voxel_id];
SurfaceTool & st = _surface_tool[voxel.get_material_id()];
// Hybrid approach: extract cube faces and decimate those that aren't visible,
// and still allow voxels to have geometry that is not a cube
// Sides
for (unsigned int side = 0; side < Voxel::SIDE_COUNT; ++side) {
const DVector<Vector3> & vertices = voxel.get_model_side_vertices(side);
if (vertices.size() != 0) {
Vector3i normal = g_side_normals[side];
unsigned nx = x + normal.x;
unsigned ny = y + normal.y;
unsigned nz = z + normal.z;
int neighbor_voxel_id = buffer.get_voxel_local(nx, ny, nz, 0);
// TODO Better face visibility test
if (neighbor_voxel_id == 0) {
DVector<Vector3>::Read r = vertices.read();
Vector3 pos(x - 1, y - 1, z - 1);
for (unsigned int i = 0; i < vertices.size(); ++i) {
st.add_normal(Vector3(normal.x, normal.y, normal.z));
st.add_vertex(r[i] + pos);
}
}
}
}
// Inside
if (voxel.get_model_vertices().size() != 0) {
const DVector<Vector3> & vertices = voxel.get_model_vertices();
DVector<Vector3>::Read rv = voxel.get_model_vertices().read();
DVector<Vector3>::Read rn = voxel.get_model_normals().read();
Vector3 pos(x - 1, y - 1, z - 1);
for (unsigned int i = 0; i < vertices.size(); ++i) {
st.add_normal(rn[i]);
st.add_vertex(rv[i] + pos);
}
}
}
}
}
}
// Commit mesh
Ref<Mesh> mesh_ref = _surface_tool[0].commit();
_surface_tool[0].clear();
for (unsigned int i = 1; i < MAX_MATERIALS; ++i) {
if (_materials[i].is_valid()) {
SurfaceTool & st = _surface_tool[i];
st.commit(mesh_ref);
st.clear();
}
}
return mesh_ref;
}
void VoxelMeshBuilder::_bind_methods() {
ObjectTypeDB::bind_method(_MD("add_voxel_type", "voxel"), &VoxelMeshBuilder::add_voxel_type);
ObjectTypeDB::bind_method(_MD("set_material", "material", "id"), &VoxelMeshBuilder::set_material);
ObjectTypeDB::bind_method(_MD("build", "voxel_buffer"), &VoxelMeshBuilder::build);
}

35
voxel_mesh_builder.h Normal file
View File

@ -0,0 +1,35 @@
#ifndef VOXEL_MESH_BUILDER
#define VOXEL_MESH_BUILDER
#include <reference.h>
#include <scene/resources/mesh.h>
#include <scene/resources/surface_tool.h>
#include "voxel.h"
#include "voxel_buffer.h"
class VoxelMeshBuilder : public Reference {
OBJ_TYPE(VoxelMeshBuilder, Reference);
static const unsigned int MAX_VOXEL_TYPES = 256; // Required limit because voxel types are stored in 8 bits
static const unsigned int MAX_MATERIALS = 8; // Arbitrary. Tweak if needed.
Ref<Voxel> _voxel_types[MAX_VOXEL_TYPES];
Ref<Material> _materials[MAX_MATERIALS];
SurfaceTool _surface_tool[MAX_MATERIALS];
public:
VoxelMeshBuilder();
void add_voxel_type(Ref<Voxel> voxel);
void set_material(Ref<Material> material, unsigned int id);
Ref<Mesh> build(Ref<VoxelBuffer> buffer_ref);
protected:
static void _bind_methods();
};
#endif // VOXEL_MESH_BUILDER