mirror of
https://github.com/Relintai/godot_voxel.git
synced 2025-03-11 21:53:24 +01:00
Spaces to tabs
This commit is contained in:
parent
8acf40d338
commit
8d1c8cc339
@ -7,12 +7,12 @@
|
||||
|
||||
void register_voxel_types() {
|
||||
|
||||
ObjectTypeDB::register_type<Voxel>();
|
||||
ObjectTypeDB::register_type<VoxelBuffer>();
|
||||
ObjectTypeDB::register_type<VoxelMesher>();
|
||||
ObjectTypeDB::register_type<VoxelLibrary>();
|
||||
ObjectTypeDB::register_type<VoxelMap>();
|
||||
ObjectTypeDB::register_type<VoxelTerrain>();
|
||||
ObjectTypeDB::register_type<Voxel>();
|
||||
ObjectTypeDB::register_type<VoxelBuffer>();
|
||||
ObjectTypeDB::register_type<VoxelMesher>();
|
||||
ObjectTypeDB::register_type<VoxelLibrary>();
|
||||
ObjectTypeDB::register_type<VoxelMap>();
|
||||
ObjectTypeDB::register_type<VoxelTerrain>();
|
||||
|
||||
}
|
||||
|
||||
|
176
vector3i.h
176
vector3i.h
@ -5,134 +5,134 @@
|
||||
|
||||
struct Vector3i {
|
||||
|
||||
union {
|
||||
struct {
|
||||
int x;
|
||||
int y;
|
||||
int z;
|
||||
};
|
||||
int coords[3];
|
||||
};
|
||||
union {
|
||||
struct {
|
||||
int x;
|
||||
int y;
|
||||
int z;
|
||||
};
|
||||
int coords[3];
|
||||
};
|
||||
|
||||
_FORCE_INLINE_ Vector3i() : x(0), y(0), z(0) {}
|
||||
_FORCE_INLINE_ Vector3i() : x(0), y(0), z(0) {}
|
||||
|
||||
_FORCE_INLINE_ Vector3i(int px, int py, int pz) : x(px), y(py), z(pz) {}
|
||||
_FORCE_INLINE_ Vector3i(int px, int py, int pz) : x(px), y(py), z(pz) {}
|
||||
|
||||
_FORCE_INLINE_ Vector3i(const Vector3i & other) {
|
||||
*this = other;
|
||||
}
|
||||
_FORCE_INLINE_ Vector3i(const Vector3i & other) {
|
||||
*this = other;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ Vector3i(const Vector3 & f) {
|
||||
x = Math::floor(f.x);
|
||||
y = Math::floor(f.y);
|
||||
z = Math::floor(f.z);
|
||||
}
|
||||
_FORCE_INLINE_ Vector3i(const Vector3 & f) {
|
||||
x = Math::floor(f.x);
|
||||
y = Math::floor(f.y);
|
||||
z = Math::floor(f.z);
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ Vector3 to_vec3() const {
|
||||
return Vector3(x, y, z);
|
||||
}
|
||||
_FORCE_INLINE_ Vector3 to_vec3() const {
|
||||
return Vector3(x, y, z);
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ int volume() const {
|
||||
return x*y*z;
|
||||
}
|
||||
_FORCE_INLINE_ int volume() const {
|
||||
return x*y*z;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ int length_sq() const {
|
||||
return x*x + y*y + z*z;
|
||||
}
|
||||
_FORCE_INLINE_ int length_sq() const {
|
||||
return x*x + y*y + z*z;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ float length() const {
|
||||
return Math::sqrt(length_sq());
|
||||
}
|
||||
_FORCE_INLINE_ float length() const {
|
||||
return Math::sqrt(length_sq());
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ Vector3i & operator=(const Vector3i & other) {
|
||||
x = other.x;
|
||||
y = other.y;
|
||||
z = other.z;
|
||||
return *this;
|
||||
}
|
||||
_FORCE_INLINE_ Vector3i & operator=(const Vector3i & other) {
|
||||
x = other.x;
|
||||
y = other.y;
|
||||
z = other.z;
|
||||
return *this;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ void operator+=(const Vector3i & other) {
|
||||
x += other.x;
|
||||
y += other.y;
|
||||
z += other.z;
|
||||
}
|
||||
_FORCE_INLINE_ void operator+=(const Vector3i & other) {
|
||||
x += other.x;
|
||||
y += other.y;
|
||||
z += other.z;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ void operator-=(const Vector3i & other) {
|
||||
x -= other.x;
|
||||
y -= other.y;
|
||||
z -= other.z;
|
||||
}
|
||||
_FORCE_INLINE_ void operator-=(const Vector3i & other) {
|
||||
x -= other.x;
|
||||
y -= other.y;
|
||||
z -= other.z;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ int & operator[](unsigned int i) {
|
||||
return coords[i];
|
||||
}
|
||||
_FORCE_INLINE_ int & operator[](unsigned int i) {
|
||||
return coords[i];
|
||||
}
|
||||
|
||||
void clamp_to(const Vector3i min, const Vector3i max) {
|
||||
if (x < min.x) x = min.x;
|
||||
if (y < min.y) y = min.y;
|
||||
if (z < min.z) z = min.z;
|
||||
void clamp_to(const Vector3i min, const Vector3i max) {
|
||||
if (x < min.x) x = min.x;
|
||||
if (y < min.y) y = min.y;
|
||||
if (z < min.z) z = min.z;
|
||||
|
||||
if (x >= max.x) x = max.x - 1;
|
||||
if (y >= max.y) y = max.y - 1;
|
||||
if (z >= max.z) z = max.z - 1;
|
||||
}
|
||||
if (x >= max.x) x = max.x - 1;
|
||||
if (y >= max.y) y = max.y - 1;
|
||||
if (z >= max.z) z = max.z - 1;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ bool is_contained_in(const Vector3i & min, const Vector3i & max) {
|
||||
return x >= min.x && y >= min.y && z >= min.z
|
||||
&& x < max.x && y < max.y && z < max.z;
|
||||
}
|
||||
_FORCE_INLINE_ bool is_contained_in(const Vector3i & min, const Vector3i & max) {
|
||||
return x >= min.x && y >= min.y && z >= min.z
|
||||
&& x < max.x && y < max.y && z < max.z;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ Vector3i wrap(const Vector3i & size) {
|
||||
return Vector3i(
|
||||
x % size.x,
|
||||
y % size.y,
|
||||
z % size.z
|
||||
);
|
||||
}
|
||||
_FORCE_INLINE_ Vector3i wrap(const Vector3i & size) {
|
||||
return Vector3i(
|
||||
x % size.x,
|
||||
y % size.y,
|
||||
z % size.z
|
||||
);
|
||||
}
|
||||
|
||||
static void sort_min_max(Vector3i & a, Vector3i & b) {
|
||||
sort_min_max(a.x, b.x);
|
||||
sort_min_max(a.y, b.y);
|
||||
sort_min_max(a.z, b.z);
|
||||
}
|
||||
static void sort_min_max(Vector3i & a, Vector3i & b) {
|
||||
sort_min_max(a.x, b.x);
|
||||
sort_min_max(a.y, b.y);
|
||||
sort_min_max(a.z, b.z);
|
||||
}
|
||||
|
||||
private:
|
||||
static _FORCE_INLINE_ void sort_min_max(int & a, int & b) {
|
||||
if (a > b) {
|
||||
int temp = a;
|
||||
a = b;
|
||||
b = temp;
|
||||
}
|
||||
}
|
||||
static _FORCE_INLINE_ void sort_min_max(int & a, int & b) {
|
||||
if (a > b) {
|
||||
int temp = a;
|
||||
a = b;
|
||||
b = temp;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
_FORCE_INLINE_ Vector3i operator+(const Vector3i a, const Vector3i & b) {
|
||||
return Vector3i(a.x + b.x, a.y + b.y, a.z + b.z);
|
||||
return Vector3i(a.x + b.x, a.y + b.y, a.z + b.z);
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ Vector3i operator-(const Vector3i & a, const Vector3i & b) {
|
||||
return Vector3i(a.x - b.x, a.y - b.y, a.z - b.z);
|
||||
return Vector3i(a.x - b.x, a.y - b.y, a.z - b.z);
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ Vector3i operator*(const Vector3i & a, int n) {
|
||||
return Vector3i(a.x * n, a.y * n, a.z * n);
|
||||
return Vector3i(a.x * n, a.y * n, a.z * n);
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ Vector3i operator*(int n, const Vector3i & a) {
|
||||
return Vector3i(a.x * n, a.y * n, a.z * n);
|
||||
return Vector3i(a.x * n, a.y * n, a.z * n);
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ Vector3i operator/(const Vector3i & a, int n) {
|
||||
return Vector3i(a.x / n, a.y / n, a.z / n);
|
||||
return Vector3i(a.x / n, a.y / n, a.z / n);
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ bool operator==(const Vector3i & a, const Vector3i & b) {
|
||||
return a.x == b.x && a.y == b.y && a.z == b.z;
|
||||
return a.x == b.x && a.y == b.y && a.z == b.z;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ bool operator!=(const Vector3i & a, const Vector3i & b) {
|
||||
return a.x != b.x && a.y != b.y && a.z != b.z;
|
||||
return a.x != b.x && a.y != b.y && a.z != b.z;
|
||||
}
|
||||
|
||||
struct Vector3iHasher {
|
||||
@ -140,7 +140,7 @@ struct Vector3iHasher {
|
||||
uint32_t hash = hash_djb2_one_32(v.x);
|
||||
hash = hash_djb2_one_32(v.y, hash);
|
||||
return hash_djb2_one_32(v.z, hash);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif // VOXEL_VECTOR3I_H
|
||||
|
280
voxel.cpp
280
voxel.cpp
@ -2,160 +2,160 @@
|
||||
#include "voxel_library.h"
|
||||
#include "voxel_mesher.h"
|
||||
|
||||
Voxel::Voxel() : Reference(),
|
||||
_id(-1),
|
||||
_material_id(0),
|
||||
_is_transparent(false),
|
||||
_library(NULL),
|
||||
_color(1.f, 1.f, 1.f)
|
||||
Voxel::Voxel() : Reference(),
|
||||
_id(-1),
|
||||
_material_id(0),
|
||||
_is_transparent(false),
|
||||
_library(NULL),
|
||||
_color(1.f, 1.f, 1.f)
|
||||
{}
|
||||
|
||||
Ref<Voxel> Voxel::set_id(int id) {
|
||||
ERR_FAIL_COND_V(id < 0 || id >= 256, Ref<Voxel>(this));
|
||||
// Cannot modify ID after creation
|
||||
ERR_FAIL_COND_V(_id != -1, Ref<Voxel>(this));
|
||||
ERR_FAIL_COND_V(id < 0 || id >= 256, Ref<Voxel>(this));
|
||||
// Cannot modify ID after creation
|
||||
ERR_FAIL_COND_V(_id != -1, Ref<Voxel>(this));
|
||||
|
||||
_id = id;
|
||||
_id = id;
|
||||
|
||||
return Ref<Voxel>(this);
|
||||
return Ref<Voxel>(this);
|
||||
}
|
||||
|
||||
Ref<Voxel> Voxel::set_material_id(unsigned int id) {
|
||||
ERR_FAIL_COND_V(id >= VoxelMesher::MAX_MATERIALS, Ref<Voxel>(this));
|
||||
_material_id = id;
|
||||
return Ref<Voxel>(this);
|
||||
ERR_FAIL_COND_V(id >= VoxelMesher::MAX_MATERIALS, Ref<Voxel>(this));
|
||||
_material_id = id;
|
||||
return Ref<Voxel>(this);
|
||||
}
|
||||
|
||||
Ref<Voxel> 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)
|
||||
}
|
||||
};
|
||||
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];
|
||||
}
|
||||
}
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
return Ref<Voxel>(this);
|
||||
return Ref<Voxel>(this);
|
||||
}
|
||||
|
||||
Ref<Voxel> Voxel::_set_cube_uv_sides(const Vector2 atlas_pos[6]) {
|
||||
ERR_FAIL_COND_V(_library == NULL, Ref<Voxel>());
|
||||
ERR_FAIL_COND_V(_library == NULL, Ref<Voxel>());
|
||||
|
||||
float e = 0.001;
|
||||
const Vector2 uv[4] = {
|
||||
Vector2(e, e),
|
||||
Vector2(1.f - e, e),
|
||||
Vector2(e, 1.f - e),
|
||||
Vector2(1.f - e, 1.f - e),
|
||||
};
|
||||
float e = 0.001;
|
||||
const Vector2 uv[4] = {
|
||||
Vector2(e, e),
|
||||
Vector2(1.f - e, e),
|
||||
Vector2(e, 1.f - e),
|
||||
Vector2(1.f - e, 1.f - e),
|
||||
};
|
||||
|
||||
const int uv6[SIDE_COUNT][6] = {
|
||||
// LEFT
|
||||
{ 2,0,1,2,1,3 },
|
||||
// RIGHT
|
||||
{ 2,1,0,2,3,1 },
|
||||
// BOTTOM
|
||||
{ 0,3,1,0,2,3 },
|
||||
// TOP
|
||||
{ 0,1,3,0,3,2 },
|
||||
// BACK
|
||||
{ 2,3,1,2,1,0 },
|
||||
// FRONT
|
||||
{ 3,2,1,2,0,1 }
|
||||
};
|
||||
const int uv6[SIDE_COUNT][6] = {
|
||||
// LEFT
|
||||
{ 2,0,1,2,1,3 },
|
||||
// RIGHT
|
||||
{ 2,1,0,2,3,1 },
|
||||
// BOTTOM
|
||||
{ 0,3,1,0,2,3 },
|
||||
// TOP
|
||||
{ 0,1,3,0,3,2 },
|
||||
// BACK
|
||||
{ 2,3,1,2,1,0 },
|
||||
// FRONT
|
||||
{ 3,2,1,2,0,1 }
|
||||
};
|
||||
|
||||
float s = 1.0 / (float)_library->get_atlas_size();
|
||||
float s = 1.0 / (float)_library->get_atlas_size();
|
||||
|
||||
for (unsigned int side = 0; side < SIDE_COUNT; ++side) {
|
||||
_model_side_uv[side].resize(6);
|
||||
DVector<Vector2>::Write w = _model_side_uv[side].write();
|
||||
for (unsigned int i = 0; i < 6; ++i) {
|
||||
w[i] = (atlas_pos[side] + uv[uv6[side][i]]) * s;
|
||||
}
|
||||
}
|
||||
for (unsigned int side = 0; side < SIDE_COUNT; ++side) {
|
||||
_model_side_uv[side].resize(6);
|
||||
DVector<Vector2>::Write w = _model_side_uv[side].write();
|
||||
for (unsigned int i = 0; i < 6; ++i) {
|
||||
w[i] = (atlas_pos[side] + uv[uv6[side][i]]) * s;
|
||||
}
|
||||
}
|
||||
|
||||
return Ref<Voxel>(this);
|
||||
return Ref<Voxel>(this);
|
||||
}
|
||||
|
||||
Ref<Voxel> Voxel::set_cube_uv_all_sides(Vector2 atlas_pos) {
|
||||
const Vector2 positions[6] = {
|
||||
atlas_pos,
|
||||
atlas_pos,
|
||||
atlas_pos,
|
||||
atlas_pos,
|
||||
atlas_pos,
|
||||
atlas_pos
|
||||
};
|
||||
return _set_cube_uv_sides(positions);
|
||||
const Vector2 positions[6] = {
|
||||
atlas_pos,
|
||||
atlas_pos,
|
||||
atlas_pos,
|
||||
atlas_pos,
|
||||
atlas_pos,
|
||||
atlas_pos
|
||||
};
|
||||
return _set_cube_uv_sides(positions);
|
||||
}
|
||||
|
||||
Ref<Voxel> Voxel::set_cube_uv_tbs_sides(Vector2 top_atlas_pos, Vector2 side_atlas_pos, Vector2 bottom_atlas_pos) {
|
||||
const Vector2 positions[6] = {
|
||||
side_atlas_pos,
|
||||
side_atlas_pos,
|
||||
bottom_atlas_pos,
|
||||
top_atlas_pos,
|
||||
side_atlas_pos,
|
||||
side_atlas_pos,
|
||||
};
|
||||
return _set_cube_uv_sides(positions);
|
||||
const Vector2 positions[6] = {
|
||||
side_atlas_pos,
|
||||
side_atlas_pos,
|
||||
bottom_atlas_pos,
|
||||
top_atlas_pos,
|
||||
side_atlas_pos,
|
||||
side_atlas_pos,
|
||||
};
|
||||
return _set_cube_uv_sides(positions);
|
||||
}
|
||||
|
||||
//Ref<Voxel> Voxel::set_xquad_geometry(Vector2 atlas_pos) {
|
||||
@ -165,24 +165,24 @@ Ref<Voxel> Voxel::set_cube_uv_tbs_sides(Vector2 top_atlas_pos, Vector2 side_atla
|
||||
|
||||
void Voxel::_bind_methods() {
|
||||
|
||||
ObjectTypeDB::bind_method(_MD("set_name:Voxel", "name"), &Voxel::set_name);
|
||||
ObjectTypeDB::bind_method(_MD("get_name"), &Voxel::get_name);
|
||||
ObjectTypeDB::bind_method(_MD("set_name:Voxel", "name"), &Voxel::set_name);
|
||||
ObjectTypeDB::bind_method(_MD("get_name"), &Voxel::get_name);
|
||||
|
||||
ObjectTypeDB::bind_method(_MD("set_id:Voxel", "id"), &Voxel::set_id);
|
||||
ObjectTypeDB::bind_method(_MD("get_id"), &Voxel::get_id);
|
||||
ObjectTypeDB::bind_method(_MD("set_id:Voxel", "id"), &Voxel::set_id);
|
||||
ObjectTypeDB::bind_method(_MD("get_id"), &Voxel::get_id);
|
||||
|
||||
ObjectTypeDB::bind_method(_MD("set_color:Voxel", "color"), &Voxel::set_color);
|
||||
ObjectTypeDB::bind_method(_MD("get_color"), &Voxel::get_color);
|
||||
ObjectTypeDB::bind_method(_MD("set_color:Voxel", "color"), &Voxel::set_color);
|
||||
ObjectTypeDB::bind_method(_MD("get_color"), &Voxel::get_color);
|
||||
|
||||
ObjectTypeDB::bind_method(_MD("set_transparent:Voxel", "color"), &Voxel::set_transparent, DEFVAL(true));
|
||||
ObjectTypeDB::bind_method(_MD("is_transparent"), &Voxel::is_transparent);
|
||||
ObjectTypeDB::bind_method(_MD("set_transparent:Voxel", "color"), &Voxel::set_transparent, DEFVAL(true));
|
||||
ObjectTypeDB::bind_method(_MD("is_transparent"), &Voxel::is_transparent);
|
||||
|
||||
ObjectTypeDB::bind_method(_MD("set_material_id", "id"), &Voxel::set_material_id);
|
||||
ObjectTypeDB::bind_method(_MD("get_material_id"), &Voxel::get_material_id);
|
||||
ObjectTypeDB::bind_method(_MD("set_material_id", "id"), &Voxel::set_material_id);
|
||||
ObjectTypeDB::bind_method(_MD("get_material_id"), &Voxel::get_material_id);
|
||||
|
||||
ObjectTypeDB::bind_method(_MD("set_cube_geometry:Voxel", "height"), &Voxel::set_cube_geometry, DEFVAL(1.f));
|
||||
ObjectTypeDB::bind_method(_MD("set_cube_uv_all_sides:Voxel", "atlas_pos"), &Voxel::set_cube_uv_all_sides);
|
||||
ObjectTypeDB::bind_method(_MD("set_cube_uv_tbs_sides:Voxel", "top_atlas_pos", "side_atlas_pos", "bottom_atlas_pos"), &Voxel::set_cube_uv_tbs_sides);
|
||||
ObjectTypeDB::bind_method(_MD("set_cube_geometry:Voxel", "height"), &Voxel::set_cube_geometry, DEFVAL(1.f));
|
||||
ObjectTypeDB::bind_method(_MD("set_cube_uv_all_sides:Voxel", "atlas_pos"), &Voxel::set_cube_uv_all_sides);
|
||||
ObjectTypeDB::bind_method(_MD("set_cube_uv_tbs_sides:Voxel", "top_atlas_pos", "side_atlas_pos", "bottom_atlas_pos"), &Voxel::set_cube_uv_tbs_sides);
|
||||
|
||||
}
|
||||
|
||||
|
102
voxel.h
102
voxel.h
@ -9,82 +9,82 @@ class VoxelLibrary;
|
||||
// A voxel can be a simple coloured cube, or a more complex model.
|
||||
// Important: it is recommended that you create voxels from a library rather than using new().
|
||||
class Voxel : public Reference {
|
||||
OBJ_TYPE(Voxel, Reference)
|
||||
OBJ_TYPE(Voxel, Reference)
|
||||
|
||||
public:
|
||||
enum Side {
|
||||
SIDE_LEFT = 0,
|
||||
SIDE_RIGHT,
|
||||
SIDE_BOTTOM,
|
||||
SIDE_TOP,
|
||||
SIDE_BACK,
|
||||
SIDE_FRONT,
|
||||
enum Side {
|
||||
SIDE_LEFT = 0,
|
||||
SIDE_RIGHT,
|
||||
SIDE_BOTTOM,
|
||||
SIDE_TOP,
|
||||
SIDE_BACK,
|
||||
SIDE_FRONT,
|
||||
|
||||
SIDE_COUNT
|
||||
};
|
||||
SIDE_COUNT
|
||||
};
|
||||
|
||||
private:
|
||||
VoxelLibrary * _library;
|
||||
VoxelLibrary * _library;
|
||||
|
||||
// Identifiers
|
||||
int _id;
|
||||
String _name;
|
||||
// Identifiers
|
||||
int _id;
|
||||
String _name;
|
||||
|
||||
// Properties
|
||||
int _material_id;
|
||||
bool _is_transparent;
|
||||
// Properties
|
||||
int _material_id;
|
||||
bool _is_transparent;
|
||||
|
||||
// Model
|
||||
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];
|
||||
// Model
|
||||
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
|
||||
// TODO Child voxel types
|
||||
|
||||
public:
|
||||
Voxel();
|
||||
Voxel();
|
||||
|
||||
// Properties
|
||||
// Properties
|
||||
|
||||
_FORCE_INLINE_ Ref<Voxel> set_name(String name) { _name = name; return Ref<Voxel>(this); }
|
||||
_FORCE_INLINE_ String get_name() const { return _name; }
|
||||
_FORCE_INLINE_ Ref<Voxel> set_name(String name) { _name = name; return Ref<Voxel>(this); }
|
||||
_FORCE_INLINE_ String get_name() const { return _name; }
|
||||
|
||||
Ref<Voxel> set_id(int id);
|
||||
_FORCE_INLINE_ int get_id() const { return _id; }
|
||||
Ref<Voxel> set_id(int id);
|
||||
_FORCE_INLINE_ int get_id() const { return _id; }
|
||||
|
||||
_FORCE_INLINE_ Ref<Voxel> set_color(Color color) { _color = color; return Ref<Voxel>(this); }
|
||||
_FORCE_INLINE_ Color get_color() const { return _color; }
|
||||
_FORCE_INLINE_ Ref<Voxel> set_color(Color color) { _color = color; return Ref<Voxel>(this); }
|
||||
_FORCE_INLINE_ Color get_color() const { return _color; }
|
||||
|
||||
Ref<Voxel> set_material_id(unsigned int id);
|
||||
_FORCE_INLINE_ unsigned int get_material_id() const { return _material_id; }
|
||||
Ref<Voxel> set_material_id(unsigned int id);
|
||||
_FORCE_INLINE_ unsigned int get_material_id() const { return _material_id; }
|
||||
|
||||
_FORCE_INLINE_ Ref<Voxel> set_transparent(bool t = true) { _is_transparent = t; return Ref<Voxel>(this); }
|
||||
_FORCE_INLINE_ bool is_transparent() const { return _is_transparent; }
|
||||
_FORCE_INLINE_ Ref<Voxel> set_transparent(bool t = true) { _is_transparent = t; return Ref<Voxel>(this); }
|
||||
_FORCE_INLINE_ bool is_transparent() const { return _is_transparent; }
|
||||
|
||||
// Built-in geometry generators
|
||||
// Built-in geometry generators
|
||||
|
||||
Ref<Voxel> set_cube_geometry(float sy = 1);
|
||||
Ref<Voxel> set_cube_uv_all_sides(Vector2 atlas_pos);
|
||||
Ref<Voxel> set_cube_uv_tbs_sides(Vector2 top_atlas_pos, Vector2 side_atlas_pos, Vector2 bottom_atlas_pos);
|
||||
//Ref<Voxel> set_xquad_geometry(Vector2 atlas_pos);
|
||||
Ref<Voxel> set_cube_geometry(float sy = 1);
|
||||
Ref<Voxel> set_cube_uv_all_sides(Vector2 atlas_pos);
|
||||
Ref<Voxel> set_cube_uv_tbs_sides(Vector2 top_atlas_pos, Vector2 side_atlas_pos, Vector2 bottom_atlas_pos);
|
||||
//Ref<Voxel> set_xquad_geometry(Vector2 atlas_pos);
|
||||
|
||||
// Getters for native usage only
|
||||
// Getters for native usage only
|
||||
|
||||
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]; }
|
||||
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]; }
|
||||
|
||||
void set_library_ptr(VoxelLibrary * lib) { _library = lib; }
|
||||
void set_library_ptr(VoxelLibrary * lib) { _library = lib; }
|
||||
|
||||
protected:
|
||||
Ref<Voxel> _set_cube_uv_sides(const Vector2 atlas_pos[6]);
|
||||
Ref<Voxel> _set_cube_uv_sides(const Vector2 atlas_pos[6]);
|
||||
|
||||
static void _bind_methods();
|
||||
static void _bind_methods();
|
||||
|
||||
};
|
||||
|
||||
|
342
voxel_buffer.cpp
342
voxel_buffer.cpp
@ -10,254 +10,254 @@ VoxelBuffer::VoxelBuffer() {
|
||||
}
|
||||
|
||||
VoxelBuffer::~VoxelBuffer() {
|
||||
clear();
|
||||
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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
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;
|
||||
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);
|
||||
|
||||
const Channel & channel = _channels[channel_index];
|
||||
ERR_FAIL_INDEX_V(channel_index, MAX_CHANNELS, 0);
|
||||
|
||||
if (validate_pos(x, y, z) && channel.data) {
|
||||
return VOXEL_AT(channel.data, x,y,z);
|
||||
}
|
||||
else {
|
||||
return channel.defval;
|
||||
}
|
||||
const Channel & channel = _channels[channel_index];
|
||||
|
||||
if (validate_pos(x, y, z) && channel.data) {
|
||||
return VOXEL_AT(channel.data, x,y,z);
|
||||
}
|
||||
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);
|
||||
ERR_FAIL_COND(!validate_pos(x, y, z));
|
||||
ERR_FAIL_INDEX(channel_index, MAX_CHANNELS);
|
||||
ERR_FAIL_COND(!validate_pos(x, y, z));
|
||||
|
||||
Channel & channel = _channels[channel_index];
|
||||
Channel & channel = _channels[channel_index];
|
||||
|
||||
if (channel.defval != value) {
|
||||
if (channel.data == NULL) {
|
||||
create_channel(channel_index, _size);
|
||||
}
|
||||
VOXEL_AT(channel.data, x, y, z) = value;
|
||||
}
|
||||
if (channel.defval != value) {
|
||||
if (channel.data == NULL) {
|
||||
create_channel(channel_index, _size);
|
||||
}
|
||||
VOXEL_AT(channel.data, x, y, z) = 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);
|
||||
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);
|
||||
ERR_FAIL_INDEX(channel_index, MAX_CHANNELS);
|
||||
|
||||
Channel & channel = _channels[channel_index];
|
||||
if (channel.data == NULL && channel.defval == defval)
|
||||
return;
|
||||
else
|
||||
create_channel_noinit(channel_index, _size);
|
||||
Channel & channel = _channels[channel_index];
|
||||
if (channel.data == NULL && channel.defval == defval)
|
||||
return;
|
||||
else
|
||||
create_channel_noinit(channel_index, _size);
|
||||
|
||||
unsigned int volume = get_volume();
|
||||
memset(channel.data, defval, volume);
|
||||
unsigned int volume = get_volume();
|
||||
memset(channel.data, defval, volume);
|
||||
}
|
||||
|
||||
void VoxelBuffer::fill_area(int defval, Vector3i min, Vector3i max, unsigned int channel_index) {
|
||||
ERR_FAIL_INDEX(channel_index, MAX_CHANNELS);
|
||||
ERR_FAIL_INDEX(channel_index, MAX_CHANNELS);
|
||||
|
||||
Vector3i::sort_min_max(min, max);
|
||||
Vector3i::sort_min_max(min, max);
|
||||
|
||||
min.clamp_to(Vector3i(0, 0, 0), _size);
|
||||
max.clamp_to(Vector3i(0, 0, 0), _size + Vector3i(1,1,1));
|
||||
Vector3i area_size = max - min;
|
||||
min.clamp_to(Vector3i(0, 0, 0), _size);
|
||||
max.clamp_to(Vector3i(0, 0, 0), _size + Vector3i(1,1,1));
|
||||
Vector3i area_size = max - min;
|
||||
|
||||
Channel & channel = _channels[channel_index];
|
||||
if (channel.data == NULL) {
|
||||
if (channel.defval == defval)
|
||||
return;
|
||||
else
|
||||
create_channel(channel_index, _size);
|
||||
}
|
||||
Channel & channel = _channels[channel_index];
|
||||
if (channel.data == NULL) {
|
||||
if (channel.defval == defval)
|
||||
return;
|
||||
else
|
||||
create_channel(channel_index, _size);
|
||||
}
|
||||
|
||||
Vector3i pos;
|
||||
for (pos.z = min.z; pos.z < max.z; ++pos.z) {
|
||||
for (pos.x = min.x; pos.x < max.x; ++pos.x) {
|
||||
unsigned int dst_ri = index(pos.x, pos.y + min.y, pos.z);
|
||||
memset(&channel.data[dst_ri], defval, area_size.y * sizeof(uint8_t));
|
||||
}
|
||||
}
|
||||
Vector3i pos;
|
||||
for (pos.z = min.z; pos.z < max.z; ++pos.z) {
|
||||
for (pos.x = min.x; pos.x < max.x; ++pos.x) {
|
||||
unsigned int dst_ri = index(pos.x, pos.y + min.y, pos.z);
|
||||
memset(&channel.data[dst_ri], defval, area_size.y * sizeof(uint8_t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool VoxelBuffer::is_uniform(unsigned int channel_index) {
|
||||
ERR_FAIL_INDEX_V(channel_index, MAX_CHANNELS, true);
|
||||
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];
|
||||
unsigned int volume = get_volume();
|
||||
for (unsigned int i = 0; i < volume; ++i) {
|
||||
if (channel.data[i] != voxel) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Channel & channel = _channels[channel_index];
|
||||
if (channel.data == NULL)
|
||||
return true;
|
||||
|
||||
return true;
|
||||
uint8_t voxel = channel.data[0];
|
||||
unsigned int volume = get_volume();
|
||||
for (unsigned int i = 0; i < volume; ++i) {
|
||||
if (channel.data[i] != 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]);
|
||||
}
|
||||
}
|
||||
for (unsigned int i = 0; i < MAX_CHANNELS; ++i) {
|
||||
if (_channels[i].data && is_uniform(i)) {
|
||||
clear_channel(i, _channels[i].data[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelBuffer::copy_from(const VoxelBuffer & other, unsigned int channel_index) {
|
||||
ERR_FAIL_INDEX(channel_index, MAX_CHANNELS);
|
||||
ERR_FAIL_COND(other._size == _size);
|
||||
ERR_FAIL_INDEX(channel_index, MAX_CHANNELS);
|
||||
ERR_FAIL_COND(other._size == _size);
|
||||
|
||||
Channel & channel = _channels[channel_index];
|
||||
const Channel & other_channel = other._channels[channel_index];
|
||||
Channel & channel = _channels[channel_index];
|
||||
const Channel & other_channel = other._channels[channel_index];
|
||||
|
||||
if (other_channel.data) {
|
||||
if (channel.data == NULL) {
|
||||
create_channel_noinit(channel_index, _size);
|
||||
}
|
||||
memcpy(channel.data, other_channel.data, get_volume() * sizeof(uint8_t));
|
||||
}
|
||||
else if(channel.data) {
|
||||
delete_channel(channel_index, _size);
|
||||
}
|
||||
if (other_channel.data) {
|
||||
if (channel.data == NULL) {
|
||||
create_channel_noinit(channel_index, _size);
|
||||
}
|
||||
memcpy(channel.data, other_channel.data, get_volume() * sizeof(uint8_t));
|
||||
}
|
||||
else if(channel.data) {
|
||||
delete_channel(channel_index, _size);
|
||||
}
|
||||
|
||||
channel.defval = other_channel.defval;
|
||||
channel.defval = other_channel.defval;
|
||||
}
|
||||
|
||||
void VoxelBuffer::copy_from(const VoxelBuffer & other, Vector3i src_min, Vector3i src_max, Vector3i dst_min, unsigned int channel_index) {
|
||||
ERR_FAIL_INDEX(channel_index, MAX_CHANNELS);
|
||||
ERR_FAIL_INDEX(channel_index, MAX_CHANNELS);
|
||||
|
||||
Channel & channel = _channels[channel_index];
|
||||
const Channel & other_channel = other._channels[channel_index];
|
||||
Channel & channel = _channels[channel_index];
|
||||
const Channel & other_channel = other._channels[channel_index];
|
||||
|
||||
Vector3i::sort_min_max(src_min, src_max);
|
||||
Vector3i::sort_min_max(src_min, src_max);
|
||||
|
||||
src_min.clamp_to(Vector3i(0, 0, 0), other._size);
|
||||
src_max.clamp_to(Vector3i(0, 0, 0), other._size + Vector3i(1,1,1));
|
||||
src_min.clamp_to(Vector3i(0, 0, 0), other._size);
|
||||
src_max.clamp_to(Vector3i(0, 0, 0), other._size + Vector3i(1,1,1));
|
||||
|
||||
dst_min.clamp_to(Vector3i(0, 0, 0), _size);
|
||||
Vector3i area_size = src_max - src_min;
|
||||
//Vector3i dst_max = dst_min + area_size;
|
||||
dst_min.clamp_to(Vector3i(0, 0, 0), _size);
|
||||
Vector3i area_size = src_max - src_min;
|
||||
//Vector3i dst_max = dst_min + area_size;
|
||||
|
||||
if (area_size == _size) {
|
||||
copy_from(other, channel_index);
|
||||
}
|
||||
else {
|
||||
if (other_channel.data) {
|
||||
if (channel.data == NULL) {
|
||||
create_channel(channel_index, _size);
|
||||
}
|
||||
// Copy row by row
|
||||
Vector3i pos;
|
||||
for (pos.z = 0; pos.z < area_size.z; ++pos.z) {
|
||||
for (pos.x = 0; pos.x < area_size.x; ++pos.x) {
|
||||
// Row direction is Y
|
||||
unsigned int src_ri = other.index(pos.x + src_min.x, pos.y + src_min.y, pos.z + src_min.z);
|
||||
unsigned int dst_ri = index(pos.x + dst_min.x, pos.y + dst_min.y, pos.z + dst_min.z);
|
||||
memcpy(&channel.data[dst_ri], &other_channel.data[src_ri], area_size.y * sizeof(uint8_t));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (channel.defval != other_channel.defval) {
|
||||
if (channel.data == NULL) {
|
||||
create_channel(channel_index, _size);
|
||||
}
|
||||
// Set row by row
|
||||
Vector3i pos;
|
||||
for (pos.z = 0; pos.z < area_size.z; ++pos.z) {
|
||||
for (pos.x = 0; pos.x < area_size.x; ++pos.x) {
|
||||
unsigned int dst_ri = index(pos.x + dst_min.x, pos.y + dst_min.y, pos.z + dst_min.z);
|
||||
memset(&channel.data[dst_ri], other_channel.defval, area_size.y * sizeof(uint8_t));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (area_size == _size) {
|
||||
copy_from(other, channel_index);
|
||||
}
|
||||
else {
|
||||
if (other_channel.data) {
|
||||
if (channel.data == NULL) {
|
||||
create_channel(channel_index, _size);
|
||||
}
|
||||
// Copy row by row
|
||||
Vector3i pos;
|
||||
for (pos.z = 0; pos.z < area_size.z; ++pos.z) {
|
||||
for (pos.x = 0; pos.x < area_size.x; ++pos.x) {
|
||||
// Row direction is Y
|
||||
unsigned int src_ri = other.index(pos.x + src_min.x, pos.y + src_min.y, pos.z + src_min.z);
|
||||
unsigned int dst_ri = index(pos.x + dst_min.x, pos.y + dst_min.y, pos.z + dst_min.z);
|
||||
memcpy(&channel.data[dst_ri], &other_channel.data[src_ri], area_size.y * sizeof(uint8_t));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (channel.defval != other_channel.defval) {
|
||||
if (channel.data == NULL) {
|
||||
create_channel(channel_index, _size);
|
||||
}
|
||||
// Set row by row
|
||||
Vector3i pos;
|
||||
for (pos.z = 0; pos.z < area_size.z; ++pos.z) {
|
||||
for (pos.x = 0; pos.x < area_size.x; ++pos.x) {
|
||||
unsigned int dst_ri = index(pos.x + dst_min.x, pos.y + dst_min.y, pos.z + dst_min.z);
|
||||
memset(&channel.data[dst_ri], other_channel.defval, area_size.y * sizeof(uint8_t));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelBuffer::create_channel(int i, Vector3i size, uint8_t defval) {
|
||||
create_channel_noinit(i, size);
|
||||
memset(_channels[i].data, defval, get_volume() * sizeof(uint8_t));
|
||||
create_channel_noinit(i, size);
|
||||
memset(_channels[i].data, defval, get_volume() * sizeof(uint8_t));
|
||||
}
|
||||
|
||||
void VoxelBuffer::create_channel_noinit(int i, Vector3i size) {
|
||||
Channel & channel = _channels[i];
|
||||
unsigned int volume = size.x * size.y * size.z;
|
||||
channel.data = (uint8_t*)memalloc(volume * sizeof(uint8_t));
|
||||
Channel & channel = _channels[i];
|
||||
unsigned int volume = size.x * size.y * size.z;
|
||||
channel.data = (uint8_t*)memalloc(volume * sizeof(uint8_t));
|
||||
}
|
||||
|
||||
void VoxelBuffer::delete_channel(int i, Vector3i size) {
|
||||
Channel & channel = _channels[i];
|
||||
memfree(channel.data);
|
||||
channel.data = NULL;
|
||||
Channel & channel = _channels[i];
|
||||
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("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_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("set_voxel", "value", "x", "y", "z", "channel"), &VoxelBuffer::_set_voxel_binding, DEFVAL(0));
|
||||
ObjectTypeDB::bind_method(_MD("set_voxel_v", "value", "pos", "channel"), &VoxelBuffer::set_voxel_v, DEFVAL(0));
|
||||
ObjectTypeDB::bind_method(_MD("get_voxel", "x", "y", "z", "channel"), &VoxelBuffer::_get_voxel_binding, DEFVAL(0));
|
||||
ObjectTypeDB::bind_method(_MD("set_voxel", "value", "x", "y", "z", "channel"), &VoxelBuffer::_set_voxel_binding, DEFVAL(0));
|
||||
ObjectTypeDB::bind_method(_MD("set_voxel_v", "value", "pos", "channel"), &VoxelBuffer::set_voxel_v, DEFVAL(0));
|
||||
ObjectTypeDB::bind_method(_MD("get_voxel", "x", "y", "z", "channel"), &VoxelBuffer::_get_voxel_binding, DEFVAL(0));
|
||||
|
||||
ObjectTypeDB::bind_method(_MD("fill", "value", "channel"), &VoxelBuffer::fill, DEFVAL(0));
|
||||
ObjectTypeDB::bind_method(_MD("fill_area", "value", "min", "max", "channel"), &VoxelBuffer::_fill_area_binding, DEFVAL(0));
|
||||
ObjectTypeDB::bind_method(_MD("copy_from", "other:VoxelBuffer", "channel"), &VoxelBuffer::_copy_from_binding, DEFVAL(0));
|
||||
ObjectTypeDB::bind_method(_MD("copy_from_area", "other:VoxelBuffer", "src_min", "src_max", "dst_min", "channel"), &VoxelBuffer::_copy_from_area_binding, DEFVAL(0));
|
||||
ObjectTypeDB::bind_method(_MD("fill", "value", "channel"), &VoxelBuffer::fill, DEFVAL(0));
|
||||
ObjectTypeDB::bind_method(_MD("fill_area", "value", "min", "max", "channel"), &VoxelBuffer::_fill_area_binding, DEFVAL(0));
|
||||
ObjectTypeDB::bind_method(_MD("copy_from", "other:VoxelBuffer", "channel"), &VoxelBuffer::_copy_from_binding, DEFVAL(0));
|
||||
ObjectTypeDB::bind_method(_MD("copy_from_area", "other:VoxelBuffer", "src_min", "src_max", "dst_min", "channel"), &VoxelBuffer::_copy_from_area_binding, DEFVAL(0));
|
||||
|
||||
ObjectTypeDB::bind_method(_MD("is_uniform", "channel"), &VoxelBuffer::is_uniform, DEFVAL(0));
|
||||
ObjectTypeDB::bind_method(_MD("optimize"), &VoxelBuffer::optimize);
|
||||
ObjectTypeDB::bind_method(_MD("is_uniform", "channel"), &VoxelBuffer::is_uniform, DEFVAL(0));
|
||||
ObjectTypeDB::bind_method(_MD("optimize"), &VoxelBuffer::optimize);
|
||||
|
||||
}
|
||||
|
||||
void VoxelBuffer::_copy_from_binding(Ref<VoxelBuffer> other, unsigned int channel) {
|
||||
ERR_FAIL_COND(other.is_null());
|
||||
copy_from(**other, channel);
|
||||
ERR_FAIL_COND(other.is_null());
|
||||
copy_from(**other, channel);
|
||||
}
|
||||
|
||||
void VoxelBuffer::_copy_from_area_binding(Ref<VoxelBuffer> other, Vector3 src_min, Vector3 src_max, Vector3 dst_min, unsigned int channel) {
|
||||
ERR_FAIL_COND(other.is_null());
|
||||
copy_from(**other, Vector3i(src_min), Vector3i(src_max), Vector3i(dst_min), channel);
|
||||
ERR_FAIL_COND(other.is_null());
|
||||
copy_from(**other, Vector3i(src_min), Vector3i(src_max), Vector3i(dst_min), channel);
|
||||
}
|
||||
|
122
voxel_buffer.h
122
voxel_buffer.h
@ -10,93 +10,93 @@
|
||||
// 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)
|
||||
OBJ_TYPE(VoxelBuffer, Reference)
|
||||
|
||||
public:
|
||||
// Arbitrary value, 8 should be enough. Tweak for your needs.
|
||||
static const int MAX_CHANNELS = 8;
|
||||
// Arbitrary value, 8 should be enough. Tweak for your needs.
|
||||
static const int MAX_CHANNELS = 8;
|
||||
|
||||
private:
|
||||
|
||||
struct Channel {
|
||||
// Allocated when the channel is populated.
|
||||
// Flat array, in order [z][x][y] because it allows faster vertical-wise access (the engine is Y-up).
|
||||
uint8_t * data;
|
||||
struct Channel {
|
||||
// Allocated when the channel is populated.
|
||||
// Flat array, in order [z][x][y] because it allows faster vertical-wise access (the engine is Y-up).
|
||||
uint8_t * data;
|
||||
|
||||
// Default value when data is null
|
||||
uint8_t defval;
|
||||
|
||||
Channel() : data(NULL), defval(0) {}
|
||||
};
|
||||
// Default value when data is null
|
||||
uint8_t defval;
|
||||
|
||||
// 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];
|
||||
Channel() : data(NULL), defval(0) {}
|
||||
};
|
||||
|
||||
// How many voxels are there in the three directions. All populated channels have the same size.
|
||||
Vector3i _size;
|
||||
// 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;
|
||||
|
||||
public:
|
||||
VoxelBuffer();
|
||||
~VoxelBuffer();
|
||||
VoxelBuffer();
|
||||
~VoxelBuffer();
|
||||
|
||||
void create(int sx, int sy, int sz);
|
||||
void clear();
|
||||
void clear_channel(unsigned int channel_index, int clear_value=0);
|
||||
void create(int sx, int sy, int sz);
|
||||
void clear();
|
||||
void clear_channel(unsigned int channel_index, int clear_value=0);
|
||||
|
||||
_FORCE_INLINE_ Vector3i get_size() const { return _size; }
|
||||
_FORCE_INLINE_ Vector3i get_size() const { return _size; }
|
||||
|
||||
int get_voxel(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);
|
||||
_FORCE_INLINE_ int get_voxel(const Vector3i pos, unsigned int channel_index = 0) const { return get_voxel(pos.x, pos.y, pos.z, channel_index); }
|
||||
_FORCE_INLINE_ void set_voxel(int value, const Vector3i pos, unsigned int channel_index = 0) { set_voxel(value, pos.x, pos.y, pos.z, channel_index); }
|
||||
int get_voxel(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);
|
||||
_FORCE_INLINE_ int get_voxel(const Vector3i pos, unsigned int channel_index = 0) const { return get_voxel(pos.x, pos.y, pos.z, channel_index); }
|
||||
_FORCE_INLINE_ void set_voxel(int value, const Vector3i pos, unsigned int channel_index = 0) { set_voxel(value, pos.x, pos.y, pos.z, channel_index); }
|
||||
|
||||
void fill(int defval, unsigned int channel_index = 0);
|
||||
void fill_area(int defval, Vector3i min, Vector3i max, unsigned int channel_index = 0);
|
||||
void fill(int defval, unsigned int channel_index = 0);
|
||||
void fill_area(int defval, Vector3i min, Vector3i max, unsigned int channel_index = 0);
|
||||
|
||||
bool is_uniform(unsigned int channel_index = 0);
|
||||
bool is_uniform(unsigned int channel_index = 0);
|
||||
|
||||
void optimize();
|
||||
void optimize();
|
||||
|
||||
void copy_from(const VoxelBuffer & other, unsigned int channel_index=0);
|
||||
void copy_from(const VoxelBuffer & other, Vector3i src_min, Vector3i src_max, Vector3i dst_min, unsigned int channel_index = 0);
|
||||
void copy_from(const VoxelBuffer & other, unsigned int channel_index=0);
|
||||
void copy_from(const VoxelBuffer & other, Vector3i src_min, Vector3i src_max, Vector3i dst_min, unsigned int channel_index = 0);
|
||||
|
||||
_FORCE_INLINE_ bool validate_pos(unsigned int x, unsigned int y, unsigned int z) const {
|
||||
return x < _size.x
|
||||
&& y < _size.y
|
||||
&& z < _size.x;
|
||||
}
|
||||
_FORCE_INLINE_ bool validate_pos(unsigned int x, unsigned int y, unsigned int z) const {
|
||||
return x < _size.x
|
||||
&& y < _size.y
|
||||
&& z < _size.x;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ unsigned int index(unsigned int x, unsigned int y, unsigned int z) const {
|
||||
return (z * _size.z + x) * _size.x + y;
|
||||
}
|
||||
_FORCE_INLINE_ unsigned int index(unsigned int x, unsigned int y, unsigned int z) const {
|
||||
return (z * _size.z + x) * _size.x + y;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ unsigned int row_index(unsigned int x, unsigned int y, unsigned int z) const {
|
||||
return (z * _size.z + x) * _size.x;
|
||||
}
|
||||
_FORCE_INLINE_ unsigned int row_index(unsigned int x, unsigned int y, unsigned int z) const {
|
||||
return (z * _size.z + x) * _size.x;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ unsigned int get_volume() {
|
||||
return _size.x * _size.y * _size.z;
|
||||
}
|
||||
_FORCE_INLINE_ unsigned int get_volume() {
|
||||
return _size.x * _size.y * _size.z;
|
||||
}
|
||||
|
||||
private:
|
||||
void create_channel_noinit(int i, Vector3i size);
|
||||
void create_channel(int i, Vector3i size, uint8_t defval=0);
|
||||
void delete_channel(int i, Vector3i size);
|
||||
void create_channel_noinit(int i, Vector3i size);
|
||||
void create_channel(int i, Vector3i size, uint8_t defval=0);
|
||||
void delete_channel(int i, Vector3i size);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
_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; }
|
||||
static void _bind_methods();
|
||||
|
||||
_FORCE_INLINE_ int _get_voxel_binding(int x, int y, int z, unsigned int channel) const { return get_voxel(x, y, z, channel); }
|
||||
_FORCE_INLINE_ void _set_voxel_binding(int value, int x, int y, int z, unsigned int channel) { set_voxel(value, x, y, z, channel); }
|
||||
void _copy_from_binding(Ref<VoxelBuffer> other, unsigned int channel);
|
||||
void _copy_from_area_binding(Ref<VoxelBuffer> other, Vector3 src_min, Vector3 src_max, Vector3 dst_min, unsigned int channel);
|
||||
_FORCE_INLINE_ void _fill_area_binding(int defval, Vector3 min, Vector3 max, unsigned int channel_index) { fill_area(defval, Vector3i(min), Vector3i(max), channel_index); }
|
||||
_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_voxel_binding(int x, int y, int z, unsigned int channel) const { return get_voxel(x, y, z, channel); }
|
||||
_FORCE_INLINE_ void _set_voxel_binding(int value, int x, int y, int z, unsigned int channel) { set_voxel(value, x, y, z, channel); }
|
||||
void _copy_from_binding(Ref<VoxelBuffer> other, unsigned int channel);
|
||||
void _copy_from_area_binding(Ref<VoxelBuffer> other, Vector3 src_min, Vector3 src_max, Vector3 dst_min, unsigned int channel);
|
||||
_FORCE_INLINE_ void _fill_area_binding(int defval, Vector3 min, Vector3 max, unsigned int channel_index) { fill_area(defval, Vector3i(min), Vector3i(max), channel_index); }
|
||||
|
||||
};
|
||||
|
||||
|
@ -1,36 +1,36 @@
|
||||
#include "voxel_library.h"
|
||||
|
||||
VoxelLibrary::VoxelLibrary() : Reference(), _atlas_size(1) {
|
||||
create_voxel(0, "air")->set_transparent(true);
|
||||
create_voxel(0, "air")->set_transparent(true);
|
||||
}
|
||||
|
||||
VoxelLibrary::~VoxelLibrary() {
|
||||
for (unsigned int i = 0; i < MAX_VOXEL_TYPES; ++i) {
|
||||
if (_voxel_types[i].is_valid()) {
|
||||
_voxel_types[i]->set_library_ptr(NULL);
|
||||
}
|
||||
}
|
||||
for (unsigned int i = 0; i < MAX_VOXEL_TYPES; ++i) {
|
||||
if (_voxel_types[i].is_valid()) {
|
||||
_voxel_types[i]->set_library_ptr(NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelLibrary::set_atlas_size(int s) {
|
||||
ERR_FAIL_COND(s <= 0);
|
||||
_atlas_size = s;
|
||||
ERR_FAIL_COND(s <= 0);
|
||||
_atlas_size = s;
|
||||
}
|
||||
|
||||
Ref<Voxel> VoxelLibrary::create_voxel(int id, String name) {
|
||||
ERR_FAIL_COND_V(id < 0 || id >= MAX_VOXEL_TYPES, Ref<Voxel>());
|
||||
Ref<Voxel> voxel(memnew(Voxel));
|
||||
voxel->set_library_ptr(this);
|
||||
voxel->set_id(id);
|
||||
voxel->set_name(name);
|
||||
_voxel_types[id] = voxel;
|
||||
return voxel;
|
||||
ERR_FAIL_COND_V(id < 0 || id >= MAX_VOXEL_TYPES, Ref<Voxel>());
|
||||
Ref<Voxel> voxel(memnew(Voxel));
|
||||
voxel->set_library_ptr(this);
|
||||
voxel->set_id(id);
|
||||
voxel->set_name(name);
|
||||
_voxel_types[id] = voxel;
|
||||
return voxel;
|
||||
}
|
||||
|
||||
void VoxelLibrary::_bind_methods() {
|
||||
|
||||
ObjectTypeDB::bind_method(_MD("create_voxel:Voxel", "id", "name"), &VoxelLibrary::create_voxel);
|
||||
ObjectTypeDB::bind_method(_MD("set_atlas_size", "square_size"), &VoxelLibrary::set_atlas_size);
|
||||
ObjectTypeDB::bind_method(_MD("create_voxel:Voxel", "id", "name"), &VoxelLibrary::create_voxel);
|
||||
ObjectTypeDB::bind_method(_MD("set_atlas_size", "square_size"), &VoxelLibrary::set_atlas_size);
|
||||
|
||||
}
|
||||
|
||||
|
@ -5,31 +5,31 @@
|
||||
#include "voxel.h"
|
||||
|
||||
class VoxelLibrary : public Reference {
|
||||
OBJ_TYPE(VoxelLibrary, Reference)
|
||||
OBJ_TYPE(VoxelLibrary, Reference)
|
||||
|
||||
public:
|
||||
static const unsigned int MAX_VOXEL_TYPES = 256; // Required limit because voxel types are stored in 8 bits
|
||||
static const unsigned int MAX_VOXEL_TYPES = 256; // Required limit because voxel types are stored in 8 bits
|
||||
|
||||
VoxelLibrary();
|
||||
~VoxelLibrary();
|
||||
VoxelLibrary();
|
||||
~VoxelLibrary();
|
||||
|
||||
int get_atlas_size() const { return _atlas_size; }
|
||||
void set_atlas_size(int s);
|
||||
int get_atlas_size() const { return _atlas_size; }
|
||||
void set_atlas_size(int s);
|
||||
|
||||
// Use this factory rather than creating voxels from scratch
|
||||
Ref<Voxel> create_voxel(int id, String name);
|
||||
// Use this factory rather than creating voxels from scratch
|
||||
Ref<Voxel> create_voxel(int id, String name);
|
||||
|
||||
// Internal getters
|
||||
// Internal getters
|
||||
|
||||
_FORCE_INLINE_ bool has_voxel(int id) const { return _voxel_types[id].is_valid(); }
|
||||
_FORCE_INLINE_ const Voxel & get_voxel_const(int id) const { return **_voxel_types[id]; }
|
||||
_FORCE_INLINE_ bool has_voxel(int id) const { return _voxel_types[id].is_valid(); }
|
||||
_FORCE_INLINE_ const Voxel & get_voxel_const(int id) const { return **_voxel_types[id]; }
|
||||
|
||||
private:
|
||||
Ref<Voxel> _voxel_types[MAX_VOXEL_TYPES];
|
||||
int _atlas_size;
|
||||
Ref<Voxel> _voxel_types[MAX_VOXEL_TYPES];
|
||||
int _atlas_size;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
static void _bind_methods();
|
||||
|
||||
};
|
||||
|
||||
|
300
voxel_map.cpp
300
voxel_map.cpp
@ -1,9 +1,9 @@
|
||||
#include "voxel_map.h"
|
||||
|
||||
VoxelMap::VoxelMap() : _last_accessed_block(NULL) {
|
||||
for (unsigned int i = 0; i < VoxelBuffer::MAX_CHANNELS; ++i) {
|
||||
_default_voxel[i] = 0;
|
||||
}
|
||||
for (unsigned int i = 0; i < VoxelBuffer::MAX_CHANNELS; ++i) {
|
||||
_default_voxel[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
VoxelMap::~VoxelMap() {
|
||||
@ -11,21 +11,21 @@ VoxelMap::~VoxelMap() {
|
||||
}
|
||||
|
||||
int VoxelMap::get_voxel(Vector3i pos, unsigned int c) {
|
||||
Vector3i bpos = voxel_to_block(pos);
|
||||
VoxelBlock * block = get_block(bpos);
|
||||
if (block == NULL) {
|
||||
return _default_voxel[c];
|
||||
}
|
||||
return block->voxels->get_voxel(pos - block_to_voxel(bpos), c);
|
||||
Vector3i bpos = voxel_to_block(pos);
|
||||
VoxelBlock * block = get_block(bpos);
|
||||
if (block == NULL) {
|
||||
return _default_voxel[c];
|
||||
}
|
||||
return block->voxels->get_voxel(pos - block_to_voxel(bpos), c);
|
||||
}
|
||||
|
||||
MeshInstance * VoxelBlock::get_mesh_instance(const Node & root) {
|
||||
if (mesh_instance_path.is_empty())
|
||||
return NULL;
|
||||
Node * n = root.get_node(mesh_instance_path);
|
||||
if (n == NULL)
|
||||
return NULL;
|
||||
return n->cast_to<MeshInstance>();
|
||||
if (mesh_instance_path.is_empty())
|
||||
return NULL;
|
||||
Node * n = root.get_node(mesh_instance_path);
|
||||
if (n == NULL)
|
||||
return NULL;
|
||||
return n->cast_to<MeshInstance>();
|
||||
}
|
||||
|
||||
VoxelBlock::~VoxelBlock() {
|
||||
@ -34,204 +34,204 @@ VoxelBlock::~VoxelBlock() {
|
||||
|
||||
// Helper
|
||||
VoxelBlock * VoxelBlock::create(Vector3i bpos, VoxelBuffer * buffer) {
|
||||
VoxelBlock * block = memnew(VoxelBlock);
|
||||
block->pos = bpos;
|
||||
if (buffer) {
|
||||
const int bs = VoxelBlock::SIZE;
|
||||
ERR_FAIL_COND_V(buffer->get_size() != Vector3i(bs, bs, bs), NULL);
|
||||
}
|
||||
else {
|
||||
buffer = memnew(VoxelBuffer);
|
||||
}
|
||||
ERR_FAIL_COND_V(buffer == NULL, NULL);
|
||||
block->voxels = Ref<VoxelBuffer>(buffer);
|
||||
//block->map = ↦
|
||||
return block;
|
||||
VoxelBlock * block = memnew(VoxelBlock);
|
||||
block->pos = bpos;
|
||||
if (buffer) {
|
||||
const int bs = VoxelBlock::SIZE;
|
||||
ERR_FAIL_COND_V(buffer->get_size() != Vector3i(bs, bs, bs), NULL);
|
||||
}
|
||||
else {
|
||||
buffer = memnew(VoxelBuffer);
|
||||
}
|
||||
ERR_FAIL_COND_V(buffer == NULL, NULL);
|
||||
block->voxels = Ref<VoxelBuffer>(buffer);
|
||||
//block->map = ↦
|
||||
return block;
|
||||
}
|
||||
|
||||
void VoxelMap::set_voxel(int value, Vector3i pos, unsigned int c) {
|
||||
Vector3i bpos = voxel_to_block(pos);
|
||||
Vector3i bpos = voxel_to_block(pos);
|
||||
VoxelBlock * block = get_block(bpos);
|
||||
if (block == NULL) {
|
||||
if (block == NULL) {
|
||||
block = VoxelBlock::create(bpos);
|
||||
set_block(bpos, block);
|
||||
}
|
||||
block->voxels->set_voxel(value, pos - block_to_voxel(bpos), c);
|
||||
}
|
||||
block->voxels->set_voxel(value, pos - block_to_voxel(bpos), c);
|
||||
}
|
||||
|
||||
void VoxelMap::set_default_voxel(int value, unsigned int channel) {
|
||||
ERR_FAIL_INDEX(channel, VoxelBuffer::MAX_CHANNELS);
|
||||
_default_voxel[channel] = value;
|
||||
ERR_FAIL_INDEX(channel, VoxelBuffer::MAX_CHANNELS);
|
||||
_default_voxel[channel] = value;
|
||||
}
|
||||
|
||||
int VoxelMap::get_default_voxel(unsigned int channel) {
|
||||
ERR_FAIL_INDEX_V(channel, VoxelBuffer::MAX_CHANNELS, 0);
|
||||
return _default_voxel[channel];
|
||||
ERR_FAIL_INDEX_V(channel, VoxelBuffer::MAX_CHANNELS, 0);
|
||||
return _default_voxel[channel];
|
||||
}
|
||||
|
||||
VoxelBlock * VoxelMap::get_block(Vector3i bpos) {
|
||||
if (_last_accessed_block && _last_accessed_block->pos == bpos) {
|
||||
return _last_accessed_block;
|
||||
}
|
||||
Ref<VoxelBlock> * p = _blocks.getptr(bpos);
|
||||
if (p) {
|
||||
_last_accessed_block = p->ptr();
|
||||
return _last_accessed_block;
|
||||
}
|
||||
return NULL;
|
||||
if (_last_accessed_block && _last_accessed_block->pos == bpos) {
|
||||
return _last_accessed_block;
|
||||
}
|
||||
Ref<VoxelBlock> * p = _blocks.getptr(bpos);
|
||||
if (p) {
|
||||
_last_accessed_block = p->ptr();
|
||||
return _last_accessed_block;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void VoxelMap::set_block(Vector3i bpos, VoxelBlock * block) {
|
||||
if (_last_accessed_block == NULL || _last_accessed_block->pos == bpos) {
|
||||
_last_accessed_block = block;
|
||||
}
|
||||
_blocks.set(bpos, block);
|
||||
if (_last_accessed_block == NULL || _last_accessed_block->pos == bpos) {
|
||||
_last_accessed_block = block;
|
||||
}
|
||||
_blocks.set(bpos, block);
|
||||
}
|
||||
|
||||
void VoxelMap::set_block_buffer(Vector3i bpos, Ref<VoxelBuffer> buffer) {
|
||||
ERR_FAIL_COND(buffer.is_null());
|
||||
VoxelBlock * block = get_block(bpos);
|
||||
if (block == NULL) {
|
||||
ERR_FAIL_COND(buffer.is_null());
|
||||
VoxelBlock * block = get_block(bpos);
|
||||
if (block == NULL) {
|
||||
block = VoxelBlock::create(bpos, *buffer);
|
||||
set_block(bpos, block);
|
||||
}
|
||||
else {
|
||||
block->voxels = buffer;
|
||||
}
|
||||
set_block(bpos, block);
|
||||
}
|
||||
else {
|
||||
block->voxels = buffer;
|
||||
}
|
||||
}
|
||||
|
||||
bool VoxelMap::has_block(Vector3i pos) const {
|
||||
return /*(_last_accessed_block != NULL && _last_accessed_block->pos == pos) ||*/ _blocks.has(pos);
|
||||
return /*(_last_accessed_block != NULL && _last_accessed_block->pos == pos) ||*/ _blocks.has(pos);
|
||||
}
|
||||
|
||||
Vector3i g_moore_neighboring_3d[26] = {
|
||||
Vector3i(-1,-1,-1),
|
||||
Vector3i(0,-1,-1),
|
||||
Vector3i(1,-1,-1),
|
||||
Vector3i(-1,-1,0),
|
||||
Vector3i(0,-1,0),
|
||||
Vector3i(1,-1,0),
|
||||
Vector3i(-1,-1,1),
|
||||
Vector3i(0,-1,1),
|
||||
Vector3i(1,-1,1),
|
||||
Vector3i(-1,-1,-1),
|
||||
Vector3i(0,-1,-1),
|
||||
Vector3i(1,-1,-1),
|
||||
Vector3i(-1,-1,0),
|
||||
Vector3i(0,-1,0),
|
||||
Vector3i(1,-1,0),
|
||||
Vector3i(-1,-1,1),
|
||||
Vector3i(0,-1,1),
|
||||
Vector3i(1,-1,1),
|
||||
|
||||
Vector3i(-1,0,-1),
|
||||
Vector3i(0,0,-1),
|
||||
Vector3i(1,0,-1),
|
||||
Vector3i(-1,0,0),
|
||||
//Vector3i(0,0,0),
|
||||
Vector3i(1,0,0),
|
||||
Vector3i(-1,0,1),
|
||||
Vector3i(0,0,1),
|
||||
Vector3i(1,0,1),
|
||||
Vector3i(-1,0,-1),
|
||||
Vector3i(0,0,-1),
|
||||
Vector3i(1,0,-1),
|
||||
Vector3i(-1,0,0),
|
||||
//Vector3i(0,0,0),
|
||||
Vector3i(1,0,0),
|
||||
Vector3i(-1,0,1),
|
||||
Vector3i(0,0,1),
|
||||
Vector3i(1,0,1),
|
||||
|
||||
Vector3i(-1,1,-1),
|
||||
Vector3i(0,1,-1),
|
||||
Vector3i(1,1,-1),
|
||||
Vector3i(-1,1,0),
|
||||
Vector3i(0,1,0),
|
||||
Vector3i(1,1,0),
|
||||
Vector3i(-1,1,1),
|
||||
Vector3i(0,1,1),
|
||||
Vector3i(1,1,1),
|
||||
Vector3i(-1,1,-1),
|
||||
Vector3i(0,1,-1),
|
||||
Vector3i(1,1,-1),
|
||||
Vector3i(-1,1,0),
|
||||
Vector3i(0,1,0),
|
||||
Vector3i(1,1,0),
|
||||
Vector3i(-1,1,1),
|
||||
Vector3i(0,1,1),
|
||||
Vector3i(1,1,1),
|
||||
};
|
||||
|
||||
bool VoxelMap::is_block_surrounded(Vector3i pos) const {
|
||||
for (unsigned int i = 0; i < 26; ++i) {
|
||||
Vector3i bpos = pos + g_moore_neighboring_3d[i];
|
||||
if (!has_block(bpos)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
for (unsigned int i = 0; i < 26; ++i) {
|
||||
Vector3i bpos = pos + g_moore_neighboring_3d[i];
|
||||
if (!has_block(bpos)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void VoxelMap::get_buffer_copy(Vector3i min_pos, VoxelBuffer & dst_buffer, unsigned int channel) {
|
||||
ERR_FAIL_INDEX(channel, VoxelBuffer::MAX_CHANNELS);
|
||||
ERR_FAIL_INDEX(channel, VoxelBuffer::MAX_CHANNELS);
|
||||
|
||||
Vector3i max_pos = min_pos + dst_buffer.get_size();
|
||||
Vector3i max_pos = min_pos + dst_buffer.get_size();
|
||||
|
||||
Vector3i min_block_pos = voxel_to_block(min_pos);
|
||||
Vector3i max_block_pos = voxel_to_block(max_pos - Vector3i(1,1,1)) + Vector3i(1,1,1);
|
||||
ERR_FAIL_COND((max_block_pos - min_block_pos) != Vector3(3, 3, 3));
|
||||
Vector3i min_block_pos = voxel_to_block(min_pos);
|
||||
Vector3i max_block_pos = voxel_to_block(max_pos - Vector3i(1,1,1)) + Vector3i(1,1,1);
|
||||
ERR_FAIL_COND((max_block_pos - min_block_pos) != Vector3(3, 3, 3));
|
||||
|
||||
Vector3i bpos;
|
||||
for (bpos.z = min_block_pos.z; bpos.z < max_block_pos.z; ++bpos.z) {
|
||||
for (bpos.x = min_block_pos.x; bpos.x < max_block_pos.x; ++bpos.x) {
|
||||
for (bpos.y = min_block_pos.y; bpos.y < max_block_pos.y; ++bpos.y) {
|
||||
Vector3i bpos;
|
||||
for (bpos.z = min_block_pos.z; bpos.z < max_block_pos.z; ++bpos.z) {
|
||||
for (bpos.x = min_block_pos.x; bpos.x < max_block_pos.x; ++bpos.x) {
|
||||
for (bpos.y = min_block_pos.y; bpos.y < max_block_pos.y; ++bpos.y) {
|
||||
|
||||
VoxelBlock * block = get_block(bpos);
|
||||
if (block) {
|
||||
VoxelBlock * block = get_block(bpos);
|
||||
if (block) {
|
||||
|
||||
VoxelBuffer & src_buffer = **block->voxels;
|
||||
Vector3i offset = block_to_voxel(bpos);
|
||||
// Note: copy_from takes care of clamping the area if it's on an edge
|
||||
dst_buffer.copy_from(src_buffer, min_pos - offset, max_pos - offset, offset - min_pos, channel);
|
||||
}
|
||||
else {
|
||||
Vector3i offset = block_to_voxel(bpos);
|
||||
dst_buffer.fill_area(
|
||||
_default_voxel[channel],
|
||||
offset - min_pos,
|
||||
offset - min_pos + Vector3i(VoxelBlock::SIZE,VoxelBlock::SIZE, VoxelBlock::SIZE)
|
||||
);
|
||||
}
|
||||
VoxelBuffer & src_buffer = **block->voxels;
|
||||
Vector3i offset = block_to_voxel(bpos);
|
||||
// Note: copy_from takes care of clamping the area if it's on an edge
|
||||
dst_buffer.copy_from(src_buffer, min_pos - offset, max_pos - offset, offset - min_pos, channel);
|
||||
}
|
||||
else {
|
||||
Vector3i offset = block_to_voxel(bpos);
|
||||
dst_buffer.fill_area(
|
||||
_default_voxel[channel],
|
||||
offset - min_pos,
|
||||
offset - min_pos + Vector3i(VoxelBlock::SIZE,VoxelBlock::SIZE, VoxelBlock::SIZE)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelMap::remove_blocks_not_in_area(Vector3i min, Vector3i max) {
|
||||
|
||||
Vector3i::sort_min_max(min, max);
|
||||
Vector3i::sort_min_max(min, max);
|
||||
|
||||
Vector<Vector3i> to_remove;
|
||||
Vector3i * key = NULL;
|
||||
Vector<Vector3i> to_remove;
|
||||
Vector3i * key = NULL;
|
||||
|
||||
while (_blocks.next(key)) {
|
||||
while (_blocks.next(key)) {
|
||||
|
||||
Ref<VoxelBlock> & block_ref = _blocks.get(*key);
|
||||
ERR_FAIL_COND(block_ref.is_null()); // Should never trigger
|
||||
VoxelBlock & block = **block_ref;
|
||||
Ref<VoxelBlock> & block_ref = _blocks.get(*key);
|
||||
ERR_FAIL_COND(block_ref.is_null()); // Should never trigger
|
||||
VoxelBlock & block = **block_ref;
|
||||
|
||||
if (!block.pos.is_contained_in(min, max)) {
|
||||
if (!block.pos.is_contained_in(min, max)) {
|
||||
|
||||
//if (_observer)
|
||||
// _observer->block_removed(block);
|
||||
//if (_observer)
|
||||
// _observer->block_removed(block);
|
||||
|
||||
to_remove.push_back(*key);
|
||||
to_remove.push_back(*key);
|
||||
|
||||
if (&block == _last_accessed_block)
|
||||
_last_accessed_block = NULL;
|
||||
}
|
||||
}
|
||||
if (&block == _last_accessed_block)
|
||||
_last_accessed_block = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < to_remove.size(); ++i) {
|
||||
_blocks.erase(to_remove[i]);
|
||||
}
|
||||
for (unsigned int i = 0; i < to_remove.size(); ++i) {
|
||||
_blocks.erase(to_remove[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelMap::_bind_methods() {
|
||||
|
||||
ObjectTypeDB::bind_method(_MD("get_voxel", "x", "y", "z", "c"), &VoxelMap::_get_voxel_binding, DEFVAL(0));
|
||||
ObjectTypeDB::bind_method(_MD("set_voxel", "value", "x", "y", "z", "c"), &VoxelMap::_set_voxel_binding, DEFVAL(0));
|
||||
ObjectTypeDB::bind_method(_MD("get_default_voxel", "channel"), &VoxelMap::get_default_voxel, DEFVAL(0));
|
||||
ObjectTypeDB::bind_method(_MD("set_default_voxel", "value", "channel"), &VoxelMap::set_default_voxel, DEFVAL(0));
|
||||
ObjectTypeDB::bind_method(_MD("has_block", "x", "y", "z"), &VoxelMap::_has_block_binding);
|
||||
ObjectTypeDB::bind_method(_MD("get_buffer_copy", "min_pos", "out_buffer:VoxelBuffer", "channel"), &VoxelMap::_get_buffer_copy_binding, DEFVAL(0));
|
||||
ObjectTypeDB::bind_method(_MD("set_block_buffer", "block_pos", "buffer:VoxelBuffer"), &VoxelMap::_set_block_buffer_binding);
|
||||
ObjectTypeDB::bind_method(_MD("voxel_to_block", "voxel_pos"), &VoxelMap::_voxel_to_block_binding);
|
||||
ObjectTypeDB::bind_method(_MD("block_to_voxel", "block_pos"), &VoxelMap::_block_to_voxel_binding);
|
||||
ObjectTypeDB::bind_method(_MD("get_block_size"), &VoxelMap::get_block_size);
|
||||
ObjectTypeDB::bind_method(_MD("get_voxel", "x", "y", "z", "c"), &VoxelMap::_get_voxel_binding, DEFVAL(0));
|
||||
ObjectTypeDB::bind_method(_MD("set_voxel", "value", "x", "y", "z", "c"), &VoxelMap::_set_voxel_binding, DEFVAL(0));
|
||||
ObjectTypeDB::bind_method(_MD("get_default_voxel", "channel"), &VoxelMap::get_default_voxel, DEFVAL(0));
|
||||
ObjectTypeDB::bind_method(_MD("set_default_voxel", "value", "channel"), &VoxelMap::set_default_voxel, DEFVAL(0));
|
||||
ObjectTypeDB::bind_method(_MD("has_block", "x", "y", "z"), &VoxelMap::_has_block_binding);
|
||||
ObjectTypeDB::bind_method(_MD("get_buffer_copy", "min_pos", "out_buffer:VoxelBuffer", "channel"), &VoxelMap::_get_buffer_copy_binding, DEFVAL(0));
|
||||
ObjectTypeDB::bind_method(_MD("set_block_buffer", "block_pos", "buffer:VoxelBuffer"), &VoxelMap::_set_block_buffer_binding);
|
||||
ObjectTypeDB::bind_method(_MD("voxel_to_block", "voxel_pos"), &VoxelMap::_voxel_to_block_binding);
|
||||
ObjectTypeDB::bind_method(_MD("block_to_voxel", "block_pos"), &VoxelMap::_block_to_voxel_binding);
|
||||
ObjectTypeDB::bind_method(_MD("get_block_size"), &VoxelMap::get_block_size);
|
||||
|
||||
//ADD_PROPERTY(PropertyInfo(Variant::INT, "iterations"), _SCS("set_iterations"), _SCS("get_iterations"));
|
||||
//ADD_PROPERTY(PropertyInfo(Variant::INT, "iterations"), _SCS("set_iterations"), _SCS("get_iterations"));
|
||||
|
||||
}
|
||||
|
||||
|
||||
void VoxelMap::_get_buffer_copy_binding(Vector3 pos, Ref<VoxelBuffer> dst_buffer_ref, unsigned int channel) {
|
||||
ERR_FAIL_COND(dst_buffer_ref.is_null());
|
||||
get_buffer_copy(Vector3i(pos), **dst_buffer_ref, channel);
|
||||
ERR_FAIL_COND(dst_buffer_ref.is_null());
|
||||
get_buffer_copy(Vector3i(pos), **dst_buffer_ref, channel);
|
||||
}
|
||||
|
||||
|
122
voxel_map.h
122
voxel_map.h
@ -10,24 +10,24 @@ class VoxelMap;
|
||||
|
||||
// Fixed-size voxel container used in VoxelMap. Used internally.
|
||||
class VoxelBlock : public Reference {
|
||||
OBJ_TYPE(VoxelBlock, Reference)
|
||||
OBJ_TYPE(VoxelBlock, Reference)
|
||||
public:
|
||||
static const int SIZE_POW2 = 4; // 3=>8, 4=>16, 5=>32...
|
||||
static const int SIZE = 1 << SIZE_POW2;
|
||||
static const int SIZE_POW2 = 4; // 3=>8, 4=>16, 5=>32...
|
||||
static const int SIZE = 1 << SIZE_POW2;
|
||||
|
||||
Ref<VoxelBuffer> voxels; // SIZE*SIZE*SIZE voxels
|
||||
Vector3i pos;
|
||||
NodePath mesh_instance_path;
|
||||
//VoxelMap * map;
|
||||
Ref<VoxelBuffer> voxels; // SIZE*SIZE*SIZE voxels
|
||||
Vector3i pos;
|
||||
NodePath mesh_instance_path;
|
||||
//VoxelMap * map;
|
||||
|
||||
MeshInstance * get_mesh_instance(const Node & root);
|
||||
MeshInstance * get_mesh_instance(const Node & root);
|
||||
|
||||
static VoxelBlock * create(Vector3i bpos, VoxelBuffer * buffer = 0);
|
||||
|
||||
~VoxelBlock();
|
||||
~VoxelBlock();
|
||||
|
||||
private:
|
||||
VoxelBlock() : Reference(), voxels(NULL) {}
|
||||
VoxelBlock() : Reference(), voxels(NULL) {}
|
||||
|
||||
};
|
||||
|
||||
@ -38,78 +38,78 @@ private:
|
||||
|
||||
// Infinite voxel storage by means of octants like Gridmap
|
||||
class VoxelMap : public Reference {
|
||||
OBJ_TYPE(VoxelMap, Reference)
|
||||
OBJ_TYPE(VoxelMap, Reference)
|
||||
|
||||
// Voxel values that will be returned if access is out of map bounds
|
||||
uint8_t _default_voxel[VoxelBuffer::MAX_CHANNELS];
|
||||
// Voxel values that will be returned if access is out of map bounds
|
||||
uint8_t _default_voxel[VoxelBuffer::MAX_CHANNELS];
|
||||
|
||||
// Blocks stored with a spatial hash in all 3D directions
|
||||
HashMap<Vector3i, Ref<VoxelBlock>, Vector3iHasher> _blocks;
|
||||
// Blocks stored with a spatial hash in all 3D directions
|
||||
HashMap<Vector3i, Ref<VoxelBlock>, Vector3iHasher> _blocks;
|
||||
|
||||
// Voxel access will most frequently be in contiguous areas, so the same blocks are accessed.
|
||||
// To prevent too much hashing, this reference is checked before.
|
||||
VoxelBlock * _last_accessed_block;
|
||||
// Voxel access will most frequently be in contiguous areas, so the same blocks are accessed.
|
||||
// To prevent too much hashing, this reference is checked before.
|
||||
VoxelBlock * _last_accessed_block;
|
||||
|
||||
//IVoxelMapObserver * _observer;
|
||||
//IVoxelMapObserver * _observer;
|
||||
|
||||
public:
|
||||
VoxelMap();
|
||||
~VoxelMap();
|
||||
VoxelMap();
|
||||
~VoxelMap();
|
||||
|
||||
int get_voxel(Vector3i pos, unsigned int c = 0);
|
||||
void set_voxel(int value, Vector3i pos, unsigned int c = 0);
|
||||
int get_voxel(Vector3i pos, unsigned int c = 0);
|
||||
void set_voxel(int value, Vector3i pos, unsigned int c = 0);
|
||||
|
||||
void set_default_voxel(int value, unsigned int channel=0);
|
||||
int get_default_voxel(unsigned int channel=0);
|
||||
void set_default_voxel(int value, unsigned int channel=0);
|
||||
int get_default_voxel(unsigned int channel=0);
|
||||
|
||||
// Converts voxel coodinates into block coordinates
|
||||
static _FORCE_INLINE_ Vector3i voxel_to_block(Vector3i pos) {
|
||||
return Vector3i(
|
||||
//pos.x < 0 ? (pos.x + 1) / VoxelBlock::SIZE - 1 : pos.x / VoxelBlock::SIZE,
|
||||
//pos.y < 0 ? (pos.y + 1) / VoxelBlock::SIZE - 1 : pos.y / VoxelBlock::SIZE,
|
||||
//pos.z < 0 ? (pos.z + 1) / VoxelBlock::SIZE - 1 : pos.z / VoxelBlock::SIZE
|
||||
pos.x >> VoxelBlock::SIZE_POW2,
|
||||
pos.y >> VoxelBlock::SIZE_POW2,
|
||||
pos.z >> VoxelBlock::SIZE_POW2
|
||||
);
|
||||
}
|
||||
// Converts voxel coodinates into block coordinates
|
||||
static _FORCE_INLINE_ Vector3i voxel_to_block(Vector3i pos) {
|
||||
return Vector3i(
|
||||
//pos.x < 0 ? (pos.x + 1) / VoxelBlock::SIZE - 1 : pos.x / VoxelBlock::SIZE,
|
||||
//pos.y < 0 ? (pos.y + 1) / VoxelBlock::SIZE - 1 : pos.y / VoxelBlock::SIZE,
|
||||
//pos.z < 0 ? (pos.z + 1) / VoxelBlock::SIZE - 1 : pos.z / VoxelBlock::SIZE
|
||||
pos.x >> VoxelBlock::SIZE_POW2,
|
||||
pos.y >> VoxelBlock::SIZE_POW2,
|
||||
pos.z >> VoxelBlock::SIZE_POW2
|
||||
);
|
||||
}
|
||||
|
||||
// Converts block coodinates into voxel coordinates
|
||||
static _FORCE_INLINE_ Vector3i block_to_voxel(Vector3i bpos) {
|
||||
return bpos * VoxelBlock::SIZE;
|
||||
}
|
||||
// Converts block coodinates into voxel coordinates
|
||||
static _FORCE_INLINE_ Vector3i block_to_voxel(Vector3i bpos) {
|
||||
return bpos * VoxelBlock::SIZE;
|
||||
}
|
||||
|
||||
// Gets a copy of all voxels in the area starting at min_pos having the same size as dst_buffer.
|
||||
void get_buffer_copy(Vector3i min_pos, VoxelBuffer & dst_buffer, unsigned int channel = 0);
|
||||
// Gets a copy of all voxels in the area starting at min_pos having the same size as dst_buffer.
|
||||
void get_buffer_copy(Vector3i min_pos, VoxelBuffer & dst_buffer, unsigned int channel = 0);
|
||||
|
||||
// Moves the given buffer into a block of the map. The buffer is referenced, no copy is made.
|
||||
void set_block_buffer(Vector3i bpos, Ref<VoxelBuffer> buffer);
|
||||
// Moves the given buffer into a block of the map. The buffer is referenced, no copy is made.
|
||||
void set_block_buffer(Vector3i bpos, Ref<VoxelBuffer> buffer);
|
||||
|
||||
void remove_blocks_not_in_area(Vector3i min, Vector3i max);
|
||||
void remove_blocks_not_in_area(Vector3i min, Vector3i max);
|
||||
|
||||
_FORCE_INLINE_ Ref<VoxelBlock> get_block_ref(Vector3i bpos) { return get_block(bpos); }
|
||||
_FORCE_INLINE_ Ref<VoxelBlock> get_block_ref(Vector3i bpos) { return get_block(bpos); }
|
||||
|
||||
bool has_block(Vector3i pos) const;
|
||||
bool is_block_surrounded(Vector3i pos) const;
|
||||
bool has_block(Vector3i pos) const;
|
||||
bool is_block_surrounded(Vector3i pos) const;
|
||||
|
||||
//void set_observer(IVoxelMapObserver * observer) { _observer = observer; }
|
||||
//void set_observer(IVoxelMapObserver * observer) { _observer = observer; }
|
||||
|
||||
private:
|
||||
VoxelBlock * get_block(Vector3i bpos);
|
||||
void set_block(Vector3i bpos, VoxelBlock * block);
|
||||
VoxelBlock * get_block(Vector3i bpos);
|
||||
void set_block(Vector3i bpos, VoxelBlock * block);
|
||||
|
||||
_FORCE_INLINE_ int get_block_size() const { return VoxelBlock::SIZE; }
|
||||
_FORCE_INLINE_ int get_block_size() const { return VoxelBlock::SIZE; }
|
||||
|
||||
static void _bind_methods();
|
||||
static void _bind_methods();
|
||||
|
||||
_FORCE_INLINE_ int _get_voxel_binding(int x, int y, int z, unsigned int c = 0) { return get_voxel(Vector3i(x, y, z), c); }
|
||||
_FORCE_INLINE_ void _set_voxel_binding(int value, int x, int y, int z, unsigned int c = 0) { set_voxel(value, Vector3i(x, y, z), c); }
|
||||
_FORCE_INLINE_ bool _has_block_binding(int x, int y, int z) { return has_block(Vector3i(x, y, z)); }
|
||||
_FORCE_INLINE_ Vector3 _voxel_to_block_binding(Vector3 pos) const { return voxel_to_block(Vector3i(pos)).to_vec3(); }
|
||||
_FORCE_INLINE_ Vector3 _block_to_voxel_binding(Vector3 pos) const { return block_to_voxel(Vector3i(pos)).to_vec3(); }
|
||||
bool _is_block_surrounded(Vector3 pos) const { return is_block_surrounded(Vector3i(pos)); }
|
||||
void _get_buffer_copy_binding(Vector3 pos, Ref<VoxelBuffer> dst_buffer_ref, unsigned int channel = 0);
|
||||
void _set_block_buffer_binding(Vector3 bpos, Ref<VoxelBuffer> buffer) { set_block_buffer(Vector3i(bpos), buffer); }
|
||||
_FORCE_INLINE_ int _get_voxel_binding(int x, int y, int z, unsigned int c = 0) { return get_voxel(Vector3i(x, y, z), c); }
|
||||
_FORCE_INLINE_ void _set_voxel_binding(int value, int x, int y, int z, unsigned int c = 0) { set_voxel(value, Vector3i(x, y, z), c); }
|
||||
_FORCE_INLINE_ bool _has_block_binding(int x, int y, int z) { return has_block(Vector3i(x, y, z)); }
|
||||
_FORCE_INLINE_ Vector3 _voxel_to_block_binding(Vector3 pos) const { return voxel_to_block(Vector3i(pos)).to_vec3(); }
|
||||
_FORCE_INLINE_ Vector3 _block_to_voxel_binding(Vector3 pos) const { return block_to_voxel(Vector3i(pos)).to_vec3(); }
|
||||
bool _is_block_surrounded(Vector3 pos) const { return is_block_surrounded(Vector3i(pos)); }
|
||||
void _get_buffer_copy_binding(Vector3 pos, Ref<VoxelBuffer> dst_buffer_ref, unsigned int channel = 0);
|
||||
void _set_block_buffer_binding(Vector3 bpos, Ref<VoxelBuffer> buffer) { set_block_buffer(Vector3i(bpos), buffer); }
|
||||
|
||||
};
|
||||
|
||||
|
@ -9,35 +9,35 @@
|
||||
#include "voxel_library.h"
|
||||
|
||||
class VoxelMesher : public Reference {
|
||||
OBJ_TYPE(VoxelMesher, Reference);
|
||||
OBJ_TYPE(VoxelMesher, Reference);
|
||||
|
||||
public:
|
||||
static const unsigned int MAX_MATERIALS = 8; // Arbitrary. Tweak if needed.
|
||||
static const unsigned int MAX_MATERIALS = 8; // Arbitrary. Tweak if needed.
|
||||
|
||||
private:
|
||||
Ref<VoxelLibrary> _library;
|
||||
Ref<Material> _materials[MAX_MATERIALS];
|
||||
SurfaceTool _surface_tool[MAX_MATERIALS];
|
||||
float _baked_occlusion_darkness;
|
||||
bool _bake_occlusion;
|
||||
Ref<VoxelLibrary> _library;
|
||||
Ref<Material> _materials[MAX_MATERIALS];
|
||||
SurfaceTool _surface_tool[MAX_MATERIALS];
|
||||
float _baked_occlusion_darkness;
|
||||
bool _bake_occlusion;
|
||||
|
||||
public:
|
||||
VoxelMesher();
|
||||
VoxelMesher();
|
||||
|
||||
void set_material(Ref<Material> material, unsigned int id);
|
||||
void set_material(Ref<Material> material, unsigned int id);
|
||||
|
||||
void set_library(Ref<VoxelLibrary> library);
|
||||
void set_library(Ref<VoxelLibrary> library);
|
||||
|
||||
void set_occlusion_darkness(float darkness);
|
||||
|
||||
void set_occlusion_enabled(bool enable);
|
||||
void set_occlusion_darkness(float darkness);
|
||||
|
||||
void set_occlusion_enabled(bool enable);
|
||||
|
||||
Ref<Mesh> build(const VoxelBuffer & buffer_ref);
|
||||
Ref<Mesh> build_ref(Ref<VoxelBuffer> buffer_ref);
|
||||
|
||||
Ref<Mesh> build(const VoxelBuffer & buffer_ref);
|
||||
Ref<Mesh> build_ref(Ref<VoxelBuffer> buffer_ref);
|
||||
|
||||
protected:
|
||||
|
||||
static void _bind_methods();
|
||||
static void _bind_methods();
|
||||
|
||||
};
|
||||
|
||||
|
@ -4,175 +4,175 @@
|
||||
|
||||
VoxelTerrain::VoxelTerrain(): Node(), _min_y(-4), _max_y(4) {
|
||||
|
||||
_map = Ref<VoxelMap>(memnew(VoxelMap));
|
||||
_mesher = Ref<VoxelMesher>(memnew(VoxelMesher));
|
||||
_map = Ref<VoxelMap>(memnew(VoxelMap));
|
||||
_mesher = Ref<VoxelMesher>(memnew(VoxelMesher));
|
||||
}
|
||||
|
||||
struct BlockUpdateComparator0 {
|
||||
inline bool operator()(const Vector3i & a, const Vector3i & b) const {
|
||||
return a.length_sq() > b.length_sq();
|
||||
}
|
||||
return a.length_sq() > b.length_sq();
|
||||
}
|
||||
};
|
||||
|
||||
void VoxelTerrain::force_load_blocks(Vector3i center, Vector3i extents) {
|
||||
//Vector3i min = center - extents;
|
||||
//Vector3i max = center + extents + Vector3i(1,1,1);
|
||||
//Vector3i size = max - min;
|
||||
//Vector3i min = center - extents;
|
||||
//Vector3i max = center + extents + Vector3i(1,1,1);
|
||||
//Vector3i size = max - min;
|
||||
|
||||
_block_update_queue.clear();
|
||||
_block_update_queue.clear();
|
||||
|
||||
Vector3i pos;
|
||||
for (pos.z = -extents.z; pos.z <= extents.z; ++pos.z) {
|
||||
for (pos.x = -extents.x; pos.x <= extents.x; ++pos.x) {
|
||||
for (pos.y = -extents.y; pos.y <= extents.y; ++pos.y) {
|
||||
_block_update_queue.push_back(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vector3i pos;
|
||||
for (pos.z = -extents.z; pos.z <= extents.z; ++pos.z) {
|
||||
for (pos.x = -extents.x; pos.x <= extents.x; ++pos.x) {
|
||||
for (pos.y = -extents.y; pos.y <= extents.y; ++pos.y) {
|
||||
_block_update_queue.push_back(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_block_update_queue.sort_custom<BlockUpdateComparator0>();
|
||||
_block_update_queue.sort_custom<BlockUpdateComparator0>();
|
||||
|
||||
}
|
||||
|
||||
int VoxelTerrain::get_block_update_count() {
|
||||
return _block_update_queue.size();
|
||||
return _block_update_queue.size();
|
||||
}
|
||||
|
||||
void VoxelTerrain::_notification(int p_what) {
|
||||
|
||||
switch (p_what) {
|
||||
|
||||
case NOTIFICATION_ENTER_TREE:
|
||||
set_process(true);
|
||||
break;
|
||||
switch (p_what) {
|
||||
|
||||
case NOTIFICATION_PROCESS:
|
||||
_process();
|
||||
break;
|
||||
case NOTIFICATION_ENTER_TREE:
|
||||
set_process(true);
|
||||
break;
|
||||
|
||||
case NOTIFICATION_EXIT_TREE:
|
||||
break;
|
||||
case NOTIFICATION_PROCESS:
|
||||
_process();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
case NOTIFICATION_EXIT_TREE:
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelTerrain::_process() {
|
||||
update_blocks();
|
||||
update_blocks();
|
||||
}
|
||||
|
||||
void VoxelTerrain::update_blocks() {
|
||||
OS & os = *OS::get_singleton();
|
||||
|
||||
uint32_t time_before = os.get_ticks_msec();
|
||||
uint32_t max_time = 1000 / 60;
|
||||
OS & os = *OS::get_singleton();
|
||||
|
||||
while (!_block_update_queue.empty() && (os.get_ticks_msec() - time_before) < max_time) {
|
||||
uint32_t time_before = os.get_ticks_msec();
|
||||
uint32_t max_time = 1000 / 60;
|
||||
|
||||
// TODO Move this to a thread
|
||||
// TODO Have VoxelTerrainGenerator in C++
|
||||
// TODO Keep track of MeshInstances!
|
||||
while (!_block_update_queue.empty() && (os.get_ticks_msec() - time_before) < max_time) {
|
||||
|
||||
// Get request
|
||||
Vector3i block_pos = _block_update_queue[_block_update_queue.size() - 1];
|
||||
// TODO Move this to a thread
|
||||
// TODO Have VoxelTerrainGenerator in C++
|
||||
// TODO Keep track of MeshInstances!
|
||||
|
||||
if (!_map->has_block(block_pos)) {
|
||||
// Get request
|
||||
Vector3i block_pos = _block_update_queue[_block_update_queue.size() - 1];
|
||||
|
||||
// Get script
|
||||
ScriptInstance * script = get_script_instance();
|
||||
if (script == NULL) {
|
||||
return;
|
||||
}
|
||||
if (!_map->has_block(block_pos)) {
|
||||
|
||||
// Create buffer
|
||||
Ref<VoxelBuffer> buffer_ref = Ref<VoxelBuffer>(memnew(VoxelBuffer));
|
||||
const Vector3i block_size(VoxelBlock::SIZE, VoxelBlock::SIZE, VoxelBlock::SIZE);
|
||||
buffer_ref->create(block_size.x, block_size.y, block_size.y);
|
||||
// Get script
|
||||
ScriptInstance * script = get_script_instance();
|
||||
if (script == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Call script to generate buffer
|
||||
Variant arg1 = buffer_ref;
|
||||
Variant arg2 = block_pos.to_vec3();
|
||||
const Variant * args[2] = { &arg1, &arg2 };
|
||||
Variant::CallError err; // wut
|
||||
script->call_multilevel("_generate_block", args, 2);
|
||||
// Create buffer
|
||||
Ref<VoxelBuffer> buffer_ref = Ref<VoxelBuffer>(memnew(VoxelBuffer));
|
||||
const Vector3i block_size(VoxelBlock::SIZE, VoxelBlock::SIZE, VoxelBlock::SIZE);
|
||||
buffer_ref->create(block_size.x, block_size.y, block_size.y);
|
||||
|
||||
// Check script return
|
||||
ERR_FAIL_COND(buffer_ref->get_size() != block_size);
|
||||
// Call script to generate buffer
|
||||
Variant arg1 = buffer_ref;
|
||||
Variant arg2 = block_pos.to_vec3();
|
||||
const Variant * args[2] = { &arg1, &arg2 };
|
||||
Variant::CallError err; // wut
|
||||
script->call_multilevel("_generate_block", args, 2);
|
||||
|
||||
// Store buffer
|
||||
_map->set_block_buffer(block_pos, buffer_ref);
|
||||
// Check script return
|
||||
ERR_FAIL_COND(buffer_ref->get_size() != block_size);
|
||||
|
||||
// Update meshes
|
||||
Vector3i ndir;
|
||||
for (ndir.z = -1; ndir.z < 2; ++ndir.z) {
|
||||
for (ndir.x = -1; ndir.x < 2; ++ndir.x) {
|
||||
for (ndir.y = -1; ndir.y < 2; ++ndir.y) {
|
||||
Vector3i npos = block_pos + ndir;
|
||||
if (_map->is_block_surrounded(npos)) {
|
||||
update_block_mesh(npos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//update_block_mesh(block_pos);
|
||||
// Store buffer
|
||||
_map->set_block_buffer(block_pos, buffer_ref);
|
||||
|
||||
}
|
||||
// Update meshes
|
||||
Vector3i ndir;
|
||||
for (ndir.z = -1; ndir.z < 2; ++ndir.z) {
|
||||
for (ndir.x = -1; ndir.x < 2; ++ndir.x) {
|
||||
for (ndir.y = -1; ndir.y < 2; ++ndir.y) {
|
||||
Vector3i npos = block_pos + ndir;
|
||||
if (_map->is_block_surrounded(npos)) {
|
||||
update_block_mesh(npos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//update_block_mesh(block_pos);
|
||||
|
||||
// Pop request
|
||||
_block_update_queue.resize(_block_update_queue.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Pop request
|
||||
_block_update_queue.resize(_block_update_queue.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelTerrain::update_block_mesh(Vector3i block_pos) {
|
||||
Ref<VoxelBlock> block_ref = _map->get_block_ref(block_pos);
|
||||
if (block_ref.is_null()) {
|
||||
return;
|
||||
}
|
||||
if (block_ref->voxels->is_uniform(0) && block_ref->voxels->get_voxel(0, 0, 0, 0) == 0) {
|
||||
return;
|
||||
}
|
||||
Ref<VoxelBlock> block_ref = _map->get_block_ref(block_pos);
|
||||
if (block_ref.is_null()) {
|
||||
return;
|
||||
}
|
||||
if (block_ref->voxels->is_uniform(0) && block_ref->voxels->get_voxel(0, 0, 0, 0) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create buffer padded with neighbor voxels
|
||||
VoxelBuffer nbuffer;
|
||||
nbuffer.create(VoxelBlock::SIZE + 2, VoxelBlock::SIZE + 2, VoxelBlock::SIZE + 2);
|
||||
_map->get_buffer_copy(VoxelMap::block_to_voxel(block_pos) - Vector3i(1, 1, 1), nbuffer);
|
||||
// Create buffer padded with neighbor voxels
|
||||
VoxelBuffer nbuffer;
|
||||
nbuffer.create(VoxelBlock::SIZE + 2, VoxelBlock::SIZE + 2, VoxelBlock::SIZE + 2);
|
||||
_map->get_buffer_copy(VoxelMap::block_to_voxel(block_pos) - Vector3i(1, 1, 1), nbuffer);
|
||||
|
||||
// TEST
|
||||
//if (block_pos == Vector3i(0, 0, 0)) {
|
||||
// printf(">>>\n");
|
||||
// String os;
|
||||
// for (unsigned int y = 0; y < nbuffer.get_size().y; ++y) {
|
||||
// for (unsigned int z = 0; z < nbuffer.get_size().z; ++z) {
|
||||
// for (unsigned int x = 0; x < nbuffer.get_size().x; ++x) {
|
||||
// if (nbuffer.get_voxel(x, y, z) == 0)
|
||||
// os += '-';
|
||||
// else
|
||||
// os += 'O';
|
||||
// }
|
||||
// os += '\n';
|
||||
// }
|
||||
// os += '\n';
|
||||
// }
|
||||
// wprintf(os.c_str());
|
||||
//}
|
||||
// TEST
|
||||
//if (block_pos == Vector3i(0, 0, 0)) {
|
||||
// printf(">>>\n");
|
||||
// String os;
|
||||
// for (unsigned int y = 0; y < nbuffer.get_size().y; ++y) {
|
||||
// for (unsigned int z = 0; z < nbuffer.get_size().z; ++z) {
|
||||
// for (unsigned int x = 0; x < nbuffer.get_size().x; ++x) {
|
||||
// if (nbuffer.get_voxel(x, y, z) == 0)
|
||||
// os += '-';
|
||||
// else
|
||||
// os += 'O';
|
||||
// }
|
||||
// os += '\n';
|
||||
// }
|
||||
// os += '\n';
|
||||
// }
|
||||
// wprintf(os.c_str());
|
||||
//}
|
||||
|
||||
// Build mesh (that part is the most CPU-intensive)
|
||||
Ref<Mesh> mesh = _mesher->build(nbuffer);
|
||||
// Build mesh (that part is the most CPU-intensive)
|
||||
Ref<Mesh> mesh = _mesher->build(nbuffer);
|
||||
|
||||
MeshInstance * mesh_instance = block_ref->get_mesh_instance(*this);
|
||||
if (mesh_instance == NULL) {
|
||||
// Create and spawn mesh
|
||||
mesh_instance = memnew(MeshInstance);
|
||||
mesh_instance->set_mesh(mesh);
|
||||
mesh_instance->set_translation(VoxelMap::block_to_voxel(block_pos).to_vec3());
|
||||
add_child(mesh_instance);
|
||||
block_ref->mesh_instance_path = mesh_instance->get_path();
|
||||
}
|
||||
else {
|
||||
// Update mesh
|
||||
mesh_instance->set_mesh(mesh);
|
||||
}
|
||||
MeshInstance * mesh_instance = block_ref->get_mesh_instance(*this);
|
||||
if (mesh_instance == NULL) {
|
||||
// Create and spawn mesh
|
||||
mesh_instance = memnew(MeshInstance);
|
||||
mesh_instance->set_mesh(mesh);
|
||||
mesh_instance->set_translation(VoxelMap::block_to_voxel(block_pos).to_vec3());
|
||||
add_child(mesh_instance);
|
||||
block_ref->mesh_instance_path = mesh_instance->get_path();
|
||||
}
|
||||
else {
|
||||
// Update mesh
|
||||
mesh_instance->set_mesh(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
//void VoxelTerrain::block_removed(VoxelBlock & block) {
|
||||
@ -184,14 +184,14 @@ void VoxelTerrain::update_block_mesh(Vector3i block_pos) {
|
||||
|
||||
void VoxelTerrain::_bind_methods() {
|
||||
|
||||
ObjectTypeDB::bind_method(_MD("get_block_update_count"), &VoxelTerrain::get_block_update_count);
|
||||
ObjectTypeDB::bind_method(_MD("get_mesher:VoxelMesher"), &VoxelTerrain::get_mesher);
|
||||
ObjectTypeDB::bind_method(_MD("get_block_update_count"), &VoxelTerrain::get_block_update_count);
|
||||
ObjectTypeDB::bind_method(_MD("get_mesher:VoxelMesher"), &VoxelTerrain::get_mesher);
|
||||
|
||||
// TODO Make those two static in VoxelMap?
|
||||
ObjectTypeDB::bind_method(_MD("voxel_to_block", "voxel_pos"), &VoxelTerrain::_voxel_to_block_binding);
|
||||
ObjectTypeDB::bind_method(_MD("block_to_voxel", "block_pos"), &VoxelTerrain::_block_to_voxel_binding);
|
||||
// TODO Make those two static in VoxelMap?
|
||||
ObjectTypeDB::bind_method(_MD("voxel_to_block", "voxel_pos"), &VoxelTerrain::_voxel_to_block_binding);
|
||||
ObjectTypeDB::bind_method(_MD("block_to_voxel", "block_pos"), &VoxelTerrain::_block_to_voxel_binding);
|
||||
|
||||
ObjectTypeDB::bind_method(_MD("force_load_blocks", "center", "extents"), &VoxelTerrain::_force_load_blocks_binding);
|
||||
ObjectTypeDB::bind_method(_MD("force_load_blocks", "center", "extents"), &VoxelTerrain::_force_load_blocks_binding);
|
||||
|
||||
}
|
||||
|
||||
|
@ -8,43 +8,43 @@
|
||||
// Infinite static terrain made of voxels.
|
||||
// It is loaded around VoxelTerrainStreamers.
|
||||
class VoxelTerrain : public Node /*, public IVoxelMapObserver*/ {
|
||||
OBJ_TYPE(VoxelTerrain, Node)
|
||||
OBJ_TYPE(VoxelTerrain, Node)
|
||||
|
||||
// Parameters
|
||||
int _min_y; // In blocks, not voxels
|
||||
int _max_y;
|
||||
// Parameters
|
||||
int _min_y; // In blocks, not voxels
|
||||
int _max_y;
|
||||
|
||||
// Voxel storage
|
||||
Ref<VoxelMap> _map;
|
||||
// Voxel storage
|
||||
Ref<VoxelMap> _map;
|
||||
|
||||
Vector<Vector3i> _block_update_queue;
|
||||
Ref<VoxelMesher> _mesher;
|
||||
Vector<Vector3i> _block_update_queue;
|
||||
Ref<VoxelMesher> _mesher;
|
||||
|
||||
public:
|
||||
VoxelTerrain();
|
||||
VoxelTerrain();
|
||||
|
||||
void force_load_blocks(Vector3i center, Vector3i extents);
|
||||
void force_load_blocks(Vector3i center, Vector3i extents);
|
||||
|
||||
int get_block_update_count();
|
||||
int get_block_update_count();
|
||||
|
||||
Ref<VoxelMesher> get_mesher() { return _mesher; }
|
||||
Ref<VoxelMesher> get_mesher() { return _mesher; }
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
void _process();
|
||||
void _notification(int p_what);
|
||||
void _process();
|
||||
|
||||
void update_blocks();
|
||||
void update_block_mesh(Vector3i block_pos);
|
||||
void update_blocks();
|
||||
void update_block_mesh(Vector3i block_pos);
|
||||
|
||||
// Observer events
|
||||
//void block_removed(VoxelBlock & block);
|
||||
// Observer events
|
||||
//void block_removed(VoxelBlock & block);
|
||||
|
||||
static void _bind_methods();
|
||||
static void _bind_methods();
|
||||
|
||||
// Convenience
|
||||
Vector3 _voxel_to_block_binding(Vector3 pos) { return Vector3i(VoxelMap::voxel_to_block(pos)).to_vec3(); }
|
||||
Vector3 _block_to_voxel_binding(Vector3 pos) { return Vector3i(VoxelMap::block_to_voxel(pos)).to_vec3(); }
|
||||
void _force_load_blocks_binding(Vector3 center, Vector3 extents) { force_load_blocks(center, extents); }
|
||||
// Convenience
|
||||
Vector3 _voxel_to_block_binding(Vector3 pos) { return Vector3i(VoxelMap::voxel_to_block(pos)).to_vec3(); }
|
||||
Vector3 _block_to_voxel_binding(Vector3 pos) { return Vector3i(VoxelMap::block_to_voxel(pos)).to_vec3(); }
|
||||
void _force_load_blocks_binding(Vector3 center, Vector3 extents) { force_load_blocks(center, extents); }
|
||||
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user