|
|
|
@ -218,31 +218,32 @@ void Voxel::set_custom_mesh(Ref<Mesh> mesh) {
|
|
|
|
|
ERR_FAIL_COND(normals.size() == 0);
|
|
|
|
|
|
|
|
|
|
struct L {
|
|
|
|
|
static bool get_side(Vector3 pos, Cube::SideAxis &out_side) {
|
|
|
|
|
if (Math::is_equal_approx(pos.x, 0.0)) {
|
|
|
|
|
out_side = Cube::SIDE_NEGATIVE_X;
|
|
|
|
|
return true;
|
|
|
|
|
static uint8_t get_sides(Vector3 pos) {
|
|
|
|
|
uint8_t mask = 0;
|
|
|
|
|
const real_t tolerance = 0.001;
|
|
|
|
|
mask |= Math::is_equal_approx(pos.x, 0.0, tolerance) << Cube::SIDE_NEGATIVE_X;
|
|
|
|
|
mask |= Math::is_equal_approx(pos.x, 1.0, tolerance) << Cube::SIDE_POSITIVE_X;
|
|
|
|
|
mask |= Math::is_equal_approx(pos.y, 0.0, tolerance) << Cube::SIDE_NEGATIVE_Y;
|
|
|
|
|
mask |= Math::is_equal_approx(pos.y, 1.0, tolerance) << Cube::SIDE_POSITIVE_Y;
|
|
|
|
|
mask |= Math::is_equal_approx(pos.z, 0.0, tolerance) << Cube::SIDE_NEGATIVE_Z;
|
|
|
|
|
mask |= Math::is_equal_approx(pos.z, 1.0, tolerance) << Cube::SIDE_POSITIVE_Z;
|
|
|
|
|
return mask;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool get_triangle_side(const Vector3 &a, const Vector3 &b, const Vector3 &c, Cube::SideAxis &out_side) {
|
|
|
|
|
const uint8_t m = get_sides(a) & get_sides(b) & get_sides(c);
|
|
|
|
|
if (m == 0) {
|
|
|
|
|
// At least one of the points doesn't belong to a face
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (Math::is_equal_approx(pos.x, 1.0)) {
|
|
|
|
|
out_side = Cube::SIDE_POSITIVE_X;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (Math::is_equal_approx(pos.y, 0.0)) {
|
|
|
|
|
out_side = Cube::SIDE_NEGATIVE_Y;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (Math::is_equal_approx(pos.y, 1.0)) {
|
|
|
|
|
out_side = Cube::SIDE_POSITIVE_Y;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (Math::is_equal_approx(pos.z, 0.0)) {
|
|
|
|
|
out_side = Cube::SIDE_NEGATIVE_Z;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (Math::is_equal_approx(pos.z, 1.0)) {
|
|
|
|
|
out_side = Cube::SIDE_POSITIVE_Z;
|
|
|
|
|
return true;
|
|
|
|
|
for (unsigned int side = 0; side < Cube::SIDE_COUNT; ++side) {
|
|
|
|
|
if (m == (1 << side)) {
|
|
|
|
|
// All points belong to the same face
|
|
|
|
|
out_side = (Cube::SideAxis)side;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// The triangle isn't in one face
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
@ -265,40 +266,39 @@ void Voxel::set_custom_mesh(Ref<Mesh> mesh) {
|
|
|
|
|
|
|
|
|
|
FixedArray<HashMap<int, int>, Cube::SIDE_COUNT> added_side_indices;
|
|
|
|
|
HashMap<int, int> added_regular_indices;
|
|
|
|
|
FixedArray<Vector3, 3> tri_positions;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < indices.size(); i += 3) {
|
|
|
|
|
|
|
|
|
|
Cube::SideAxis side0;
|
|
|
|
|
Cube::SideAxis side1;
|
|
|
|
|
Cube::SideAxis side2;
|
|
|
|
|
Cube::SideAxis side;
|
|
|
|
|
|
|
|
|
|
if (L::get_side(positions_read[indices_read[i]], side0) &&
|
|
|
|
|
L::get_side(positions_read[indices_read[i + 1]], side1) &&
|
|
|
|
|
L::get_side(positions_read[indices_read[i + 2]], side2) &&
|
|
|
|
|
side0 == side1 &&
|
|
|
|
|
side1 == side2) {
|
|
|
|
|
tri_positions[0] = positions_read[indices_read[i]];
|
|
|
|
|
tri_positions[1] = positions_read[indices_read[i + 1]];
|
|
|
|
|
tri_positions[2] = positions_read[indices_read[i + 2]];
|
|
|
|
|
|
|
|
|
|
if (L::get_triangle_side(tri_positions[0], tri_positions[1], tri_positions[2], side)) {
|
|
|
|
|
|
|
|
|
|
// That triangle is on the face
|
|
|
|
|
|
|
|
|
|
int next_side_index = _model_side_positions[side0].size();
|
|
|
|
|
int next_side_index = _model_side_positions[side].size();
|
|
|
|
|
|
|
|
|
|
for (int j = 0; j < 3; ++j) {
|
|
|
|
|
int src_index = indices_read[i + j];
|
|
|
|
|
const int *existing_dst_index = added_side_indices[side0].getptr(src_index);
|
|
|
|
|
const int *existing_dst_index = added_side_indices[side].getptr(src_index);
|
|
|
|
|
|
|
|
|
|
if (existing_dst_index == nullptr) {
|
|
|
|
|
// Add new vertex
|
|
|
|
|
|
|
|
|
|
_model_side_indices[side0].push_back(next_side_index);
|
|
|
|
|
_model_side_positions[side0].push_back(positions_read[indices_read[i + j]]);
|
|
|
|
|
_model_side_uvs[side0].push_back(uvs_read[indices_read[i + j]]);
|
|
|
|
|
_model_side_indices[side].push_back(next_side_index);
|
|
|
|
|
_model_side_positions[side].push_back(tri_positions[j]);
|
|
|
|
|
_model_side_uvs[side].push_back(uvs_read[indices_read[i + j]]);
|
|
|
|
|
|
|
|
|
|
added_side_indices[side0].set(src_index, next_side_index);
|
|
|
|
|
added_side_indices[side].set(src_index, next_side_index);
|
|
|
|
|
++next_side_index;
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
// Vertex was already added, just add index referencing it
|
|
|
|
|
_model_side_indices[side0].push_back(*existing_dst_index);
|
|
|
|
|
_model_side_indices[side].push_back(*existing_dst_index);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -314,7 +314,7 @@ void Voxel::set_custom_mesh(Ref<Mesh> mesh) {
|
|
|
|
|
if (existing_dst_index == nullptr) {
|
|
|
|
|
|
|
|
|
|
_model_indices.push_back(next_regular_index);
|
|
|
|
|
_model_positions.push_back(positions_read[indices_read[i + j]]);
|
|
|
|
|
_model_positions.push_back(tri_positions[j]);
|
|
|
|
|
_model_normals.push_back(normals_read[indices_read[i + j]]);
|
|
|
|
|
_model_uvs.push_back(uvs_read[indices_read[i + j]]);
|
|
|
|
|
|
|
|
|
@ -329,10 +329,7 @@ void Voxel::set_custom_mesh(Ref<Mesh> mesh) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO Expose side masks in the inspector, somehow
|
|
|
|
|
for (unsigned int side = 0; side < Cube::SIDE_COUNT; ++side) {
|
|
|
|
|
_side_culling_masks[side] = 0;
|
|
|
|
|
}
|
|
|
|
|
generate_side_culling_masks();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ref<Voxel> Voxel::set_cube_geometry(float sy) {
|
|
|
|
@ -356,15 +353,13 @@ Ref<Voxel> Voxel::set_cube_geometry(float sy) {
|
|
|
|
|
for (unsigned int i = 0; i < 6; ++i) {
|
|
|
|
|
indices[i] = Cube::g_side_quad_triangles[side][i];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (side != Cube::SIDE_POSITIVE_Y || sy == 1.0) {
|
|
|
|
|
_side_culling_masks[side] = 0xff;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_collision_aabbs.clear();
|
|
|
|
|
_collision_aabbs.push_back(AABB(Vector3(0, 0, 0), Vector3(1, 1, 1)));
|
|
|
|
|
|
|
|
|
|
generate_side_culling_masks();
|
|
|
|
|
|
|
|
|
|
return Ref<Voxel>(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -407,6 +402,105 @@ void Voxel::update_cube_uv_sides() {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Voxel::set_side_culling_mask(int side, uint64_t mask) {
|
|
|
|
|
_side_culling_masks[side] = mask;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename F>
|
|
|
|
|
static void rasterize_triangle_barycentric(Vector2 a, Vector2 b, Vector2 c, F output_func) {
|
|
|
|
|
// Slower than scanline method, but looks better
|
|
|
|
|
|
|
|
|
|
// Grow the triangle a tiny bit, to help against floating point error
|
|
|
|
|
Vector2 m = 0.333333 * (a + b + c);
|
|
|
|
|
a += 0.001 * (a - m);
|
|
|
|
|
b += 0.001 * (b - m);
|
|
|
|
|
c += 0.001 * (c - m);
|
|
|
|
|
|
|
|
|
|
int min_x = (int)Math::floor(min(min(a.x, b.x), c.x));
|
|
|
|
|
int min_y = (int)Math::floor(min(min(a.y, b.y), c.y));
|
|
|
|
|
int max_x = (int)Math::ceil(max(max(a.x, b.x), c.x));
|
|
|
|
|
int max_y = (int)Math::ceil(max(max(a.y, b.y), c.y));
|
|
|
|
|
|
|
|
|
|
// We test against points centered on grid cells
|
|
|
|
|
Vector2 offset(0.5, 0.5);
|
|
|
|
|
|
|
|
|
|
for (int y = min_y; y < max_y; ++y) {
|
|
|
|
|
for (int x = min_x; x < max_x; ++x) {
|
|
|
|
|
if (Geometry::is_point_in_triangle(Vector2(x, y) + offset, a, b, c)) {
|
|
|
|
|
output_func(x, y);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Voxel::generate_side_culling_masks() {
|
|
|
|
|
|
|
|
|
|
// When two blocky voxels are next to each other, they share a side.
|
|
|
|
|
// Geometry of either side can be culled away if covered by the other,
|
|
|
|
|
// but it's very expensive to do a full polygon check when we build the mesh.
|
|
|
|
|
// So instead, we compute which sides occlude which for every voxel type,
|
|
|
|
|
// and generate culling masks ahead of time, using an approximation.
|
|
|
|
|
// It may have a limitation of the number of different side types,
|
|
|
|
|
// so it's a tradeoff to take when designing the models.
|
|
|
|
|
|
|
|
|
|
for (uint16_t side = 0; side < Cube::SIDE_COUNT; ++side) {
|
|
|
|
|
const std::vector<Vector3> &positions = get_model_side_positions(side);
|
|
|
|
|
const std::vector<int> &indices = get_model_side_indices(side);
|
|
|
|
|
ERR_FAIL_COND(indices.size() % 3 != 0);
|
|
|
|
|
|
|
|
|
|
uint64_t raster_mask = 0;
|
|
|
|
|
|
|
|
|
|
for (unsigned int j = 0; j < indices.size(); j += 3) {
|
|
|
|
|
|
|
|
|
|
Vector3 va = positions[indices[j]];
|
|
|
|
|
Vector3 vb = positions[indices[j + 1]];
|
|
|
|
|
Vector3 vc = positions[indices[j + 2]];
|
|
|
|
|
|
|
|
|
|
// Convert 3D vertices into 2D
|
|
|
|
|
Vector2 a, b, c;
|
|
|
|
|
switch (side) {
|
|
|
|
|
case Cube::SIDE_NEGATIVE_X:
|
|
|
|
|
case Cube::SIDE_POSITIVE_X:
|
|
|
|
|
a = Vector2(va.y, va.z);
|
|
|
|
|
b = Vector2(vb.y, vb.z);
|
|
|
|
|
c = Vector2(vc.y, vc.z);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Cube::SIDE_NEGATIVE_Y:
|
|
|
|
|
case Cube::SIDE_POSITIVE_Y:
|
|
|
|
|
a = Vector2(va.x, va.z);
|
|
|
|
|
b = Vector2(vb.x, vb.z);
|
|
|
|
|
c = Vector2(vc.x, vc.z);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Cube::SIDE_NEGATIVE_Z:
|
|
|
|
|
case Cube::SIDE_POSITIVE_Z:
|
|
|
|
|
a = Vector2(va.x, va.y);
|
|
|
|
|
b = Vector2(vb.x, vb.y);
|
|
|
|
|
c = Vector2(vc.x, vc.y);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
CRASH_NOW();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
a *= 8;
|
|
|
|
|
b *= 8;
|
|
|
|
|
c *= 8;
|
|
|
|
|
|
|
|
|
|
// Rasterize triangles into an 8x8 grid
|
|
|
|
|
rasterize_triangle_barycentric(a, b, c, [&raster_mask](uint64_t x, uint64_t y) {
|
|
|
|
|
if (x >= 8 || y >= 8) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
raster_mask |= (uint64_t(1) << (x + uint64_t(8) * y));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set_side_culling_mask(side, raster_mask);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Ref<Voxel> Voxel::set_xquad_geometry(Vector2 atlas_pos) {
|
|
|
|
|
// // TODO
|
|
|
|
|
// return Ref<Voxel>(this);
|
|
|
|
@ -438,8 +532,9 @@ void Voxel::_bind_methods() {
|
|
|
|
|
ClassDB::bind_method(D_METHOD("set_collision_aabbs", "aabbs"), &Voxel::_b_set_collision_aabbs);
|
|
|
|
|
ClassDB::bind_method(D_METHOD("get_collision_aabbs"), &Voxel::_b_get_collision_aabbs);
|
|
|
|
|
|
|
|
|
|
ClassDB::bind_method(D_METHOD("set_face_culling_mask", "side", "mask"), &Voxel::_b_set_face_culling_mask);
|
|
|
|
|
ClassDB::bind_method(D_METHOD("get_face_culling_mask"), &Voxel::_b_get_face_culling_mask);
|
|
|
|
|
ClassDB::bind_method(D_METHOD("set_side_culling_mask", "side", "mask"), &Voxel::_b_set_side_culling_mask);
|
|
|
|
|
ClassDB::bind_method(D_METHOD("get_side_culling_mask"), &Voxel::_b_get_side_culling_mask);
|
|
|
|
|
//ClassDB::bind_method(D_METHOD("generate_side_culling_masks"), &Voxel::generate_side_culling_masks);
|
|
|
|
|
|
|
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::STRING, "voxel_name"), "set_voxel_name", "get_voxel_name");
|
|
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color");
|
|
|
|
@ -484,12 +579,12 @@ void Voxel::_b_set_collision_aabbs(Array array) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Voxel::_b_set_face_culling_mask(Side face_id, uint8_t mask) {
|
|
|
|
|
void Voxel::_b_set_side_culling_mask(Side face_id, uint64_t mask) {
|
|
|
|
|
ERR_FAIL_INDEX(face_id, SIDE_COUNT);
|
|
|
|
|
_side_culling_masks[face_id] = mask;
|
|
|
|
|
set_side_culling_mask(face_id, mask);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t Voxel::_b_get_face_culling_mask(int face_id) const {
|
|
|
|
|
uint64_t Voxel::_b_get_side_culling_mask(int face_id) const {
|
|
|
|
|
ERR_FAIL_INDEX_V(face_id, SIDE_COUNT, 0);
|
|
|
|
|
return _side_culling_masks[face_id];
|
|
|
|
|
return get_side_culling_mask(face_id);
|
|
|
|
|
}
|
|
|
|
|