virtualx-engine/modules/csg/csg.cpp
Rémi Verschelde a7f49ac9a1 Update copyright statements to 2020
Happy new year to the wonderful Godot community!

We're starting a new decade with a well-established, non-profit, free
and open source game engine, and tons of further improvements in the
pipeline from hundreds of contributors.

Godot will keep getting better, and we're looking forward to all the
games that the community will keep developing and releasing with it.
2020-01-01 11:16:22 +01:00

1520 lines
43 KiB
C++

/*************************************************************************/
/* csg.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "csg.h"
#include "core/math/face3.h"
#include "core/math/geometry.h"
#include "core/os/os.h"
#include "core/sort_array.h"
#include "thirdparty/misc/triangulator.h"
void CSGBrush::clear() {
faces.clear();
}
void CSGBrush::build_from_faces(const PoolVector<Vector3> &p_vertices, const PoolVector<Vector2> &p_uvs, const PoolVector<bool> &p_smooth, const PoolVector<Ref<Material> > &p_materials, const PoolVector<bool> &p_invert_faces) {
clear();
int vc = p_vertices.size();
ERR_FAIL_COND((vc % 3) != 0);
PoolVector<Vector3>::Read rv = p_vertices.read();
int uvc = p_uvs.size();
PoolVector<Vector2>::Read ruv = p_uvs.read();
int sc = p_smooth.size();
PoolVector<bool>::Read rs = p_smooth.read();
int mc = p_materials.size();
PoolVector<Ref<Material> >::Read rm = p_materials.read();
int ic = p_invert_faces.size();
PoolVector<bool>::Read ri = p_invert_faces.read();
Map<Ref<Material>, int> material_map;
faces.resize(p_vertices.size() / 3);
for (int i = 0; i < faces.size(); i++) {
Face &f = faces.write[i];
f.vertices[0] = rv[i * 3 + 0];
f.vertices[1] = rv[i * 3 + 1];
f.vertices[2] = rv[i * 3 + 2];
if (uvc == vc) {
f.uvs[0] = ruv[i * 3 + 0];
f.uvs[1] = ruv[i * 3 + 1];
f.uvs[2] = ruv[i * 3 + 2];
}
if (sc == vc / 3) {
f.smooth = rs[i];
} else {
f.smooth = false;
}
if (ic == vc / 3) {
f.invert = ri[i];
} else {
f.invert = false;
}
if (mc == vc / 3) {
Ref<Material> mat = rm[i];
if (mat.is_valid()) {
const Map<Ref<Material>, int>::Element *E = material_map.find(mat);
if (E) {
f.material = E->get();
} else {
f.material = material_map.size();
material_map[mat] = f.material;
}
} else {
f.material = -1;
}
}
}
materials.resize(material_map.size());
for (Map<Ref<Material>, int>::Element *E = material_map.front(); E; E = E->next()) {
materials.write[E->get()] = E->key();
}
_regen_face_aabbs();
}
void CSGBrush::_regen_face_aabbs() {
for (int i = 0; i < faces.size(); i++) {
faces.write[i].aabb.position = faces[i].vertices[0];
faces.write[i].aabb.expand_to(faces[i].vertices[1]);
faces.write[i].aabb.expand_to(faces[i].vertices[2]);
faces.write[i].aabb.grow_by(faces[i].aabb.get_longest_axis_size() * 0.001); //make it a tad bigger to avoid num precision errors
}
}
void CSGBrush::copy_from(const CSGBrush &p_brush, const Transform &p_xform) {
faces = p_brush.faces;
materials = p_brush.materials;
for (int i = 0; i < faces.size(); i++) {
for (int j = 0; j < 3; j++) {
faces.write[i].vertices[j] = p_xform.xform(p_brush.faces[i].vertices[j]);
}
}
_regen_face_aabbs();
}
////////////////////////
void CSGBrushOperation::BuildPoly::create(const CSGBrush *p_brush, int p_face, MeshMerge &mesh_merge, bool p_for_B) {
//creates the initial face that will be used for clipping against the other faces
Vector3 va[3] = {
p_brush->faces[p_face].vertices[0],
p_brush->faces[p_face].vertices[1],
p_brush->faces[p_face].vertices[2],
};
plane = Plane(va[0], va[1], va[2]);
to_world.origin = va[0];
to_world.basis.set_axis(2, plane.normal);
to_world.basis.set_axis(0, (va[1] - va[2]).normalized());
to_world.basis.set_axis(1, to_world.basis.get_axis(0).cross(to_world.basis.get_axis(2)).normalized());
to_poly = to_world.affine_inverse();
face_index = p_face;
for (int i = 0; i < 3; i++) {
Point p;
Vector3 localp = to_poly.xform(va[i]);
p.point.x = localp.x;
p.point.y = localp.y;
p.uv = p_brush->faces[p_face].uvs[i];
points.push_back(p);
///edge
Edge e;
e.points[0] = i;
e.points[1] = (i + 1) % 3;
e.outer = true;
edges.push_back(e);
}
smooth = p_brush->faces[p_face].smooth;
invert = p_brush->faces[p_face].invert;
if (p_brush->faces[p_face].material != -1) {
material = p_brush->materials[p_brush->faces[p_face].material];
}
base_edges = 3;
}
static Vector2 interpolate_uv(const Vector2 &p_vertex_a, const Vector2 &p_vertex_b, const Vector2 &p_vertex_c, const Vector2 &p_uv_a, const Vector2 &p_uv_c) {
float len_a_c = (p_vertex_c - p_vertex_a).length();
if (len_a_c < CMP_EPSILON) {
return p_uv_a;
}
float len_a_b = (p_vertex_b - p_vertex_a).length();
float c = len_a_b / len_a_c;
return p_uv_a.linear_interpolate(p_uv_c, c);
}
static Vector2 interpolate_triangle_uv(const Vector2 &p_pos, const Vector2 *p_vtx, const Vector2 *p_uv) {
if (p_pos.distance_squared_to(p_vtx[0]) < CMP_EPSILON2) {
return p_uv[0];
}
if (p_pos.distance_squared_to(p_vtx[1]) < CMP_EPSILON2) {
return p_uv[1];
}
if (p_pos.distance_squared_to(p_vtx[2]) < CMP_EPSILON2) {
return p_uv[2];
}
Vector2 v0 = p_vtx[1] - p_vtx[0];
Vector2 v1 = p_vtx[2] - p_vtx[0];
Vector2 v2 = p_pos - p_vtx[0];
float d00 = v0.dot(v0);
float d01 = v0.dot(v1);
float d11 = v1.dot(v1);
float d20 = v2.dot(v0);
float d21 = v2.dot(v1);
float denom = (d00 * d11 - d01 * d01);
if (denom == 0) {
return p_uv[0];
}
float v = (d11 * d20 - d01 * d21) / denom;
float w = (d00 * d21 - d01 * d20) / denom;
float u = 1.0f - v - w;
return p_uv[0] * u + p_uv[1] * v + p_uv[2] * w;
}
void CSGBrushOperation::BuildPoly::_clip_segment(const CSGBrush *p_brush, int p_face, const Vector2 *segment, MeshMerge &mesh_merge, bool p_for_B) {
//keep track of what was inserted
Vector<int> inserted_points;
//keep track of point indices for what was inserted, allowing reuse of points.
int segment_idx[2] = { -1, -1 };
//check if edge and poly share a vertex, of so, assign it to segment_idx
for (int i = 0; i < points.size(); i++) {
for (int j = 0; j < 2; j++) {
if (segment[j].is_equal_approx(points[i].point)) {
segment_idx[j] = i;
inserted_points.push_back(i);
break;
}
}
}
//check if both segment points are shared with other vertices
if (segment_idx[0] != -1 && segment_idx[1] != -1) {
if (segment_idx[0] == segment_idx[1]) {
return; //segment was too tiny, both mapped to same point
}
bool found = false;
//check if the segment already exists
for (int i = 0; i < edges.size(); i++) {
if (
(edges[i].points[0] == segment_idx[0] && edges[i].points[1] == segment_idx[1]) ||
(edges[i].points[0] == segment_idx[1] && edges[i].points[1] == segment_idx[0])) {
found = true;
break;
}
}
if (found) {
//it does already exist, do nothing
return;
}
//directly add the new segment
Edge new_edge;
new_edge.points[0] = segment_idx[0];
new_edge.points[1] = segment_idx[1];
edges.push_back(new_edge);
return;
}
//check edge by edge against the segment points to see if intersects
for (int i = 0; i < base_edges; i++) {
//if a point is shared with one of the edge points, then this edge must not be tested, as it will result in a numerical precision error.
bool edge_valid = true;
for (int j = 0; j < 2; j++) {
if (edges[i].points[0] == segment_idx[0] || edges[i].points[1] == segment_idx[1] || edges[i].points[0] == segment_idx[1] || edges[i].points[1] == segment_idx[0]) {
edge_valid = false; //segment has this point, can't check against this
break;
}
}
if (!edge_valid) //already hit a point in this edge, so don't test it
continue;
//see if either points are within the edge isntead of crossing it
Vector2 res;
bool found = false;
int assign_segment_id = -1;
for (int j = 0; j < 2; j++) {
Vector2 edgeseg[2] = { points[edges[i].points[0]].point, points[edges[i].points[1]].point };
Vector2 closest = Geometry::get_closest_point_to_segment_2d(segment[j], edgeseg);
if (closest.is_equal_approx(segment[j])) {
//point rest of this edge
res = closest;
found = true;
assign_segment_id = j;
}
}
//test if the point crosses the edge
if (!found && Geometry::segment_intersects_segment_2d(segment[0], segment[1], points[edges[i].points[0]].point, points[edges[i].points[1]].point, &res)) {
//point does cross the edge
found = true;
}
//check whether an intersection against the segment happened
if (found) {
//It did! so first, must slice the segment
Point new_point;
new_point.point = res;
//make sure to interpolate UV too
new_point.uv = interpolate_uv(points[edges[i].points[0]].point, new_point.point, points[edges[i].points[1]].point, points[edges[i].points[0]].uv, points[edges[i].points[1]].uv);
int point_idx = points.size();
points.push_back(new_point);
//split the edge in 2
Edge new_edge;
new_edge.points[0] = edges[i].points[0];
new_edge.points[1] = point_idx;
new_edge.outer = edges[i].outer;
edges.write[i].points[0] = point_idx;
edges.insert(i, new_edge);
i++; //skip newly inserted edge
base_edges++; //will need an extra one in the base triangle
if (assign_segment_id >= 0) {
//point did split a segment, so make sure to remember this
segment_idx[assign_segment_id] = point_idx;
}
inserted_points.push_back(point_idx);
}
}
//final step: after cutting the original triangle, try to see if we can still insert
//this segment
//if already inserted two points, just use them for a segment
if (inserted_points.size() >= 2) { //should never be >2 on non-manifold geometry, but cope with error
//two points were inserted, create the new edge
Edge new_edge;
new_edge.points[0] = inserted_points[0];
new_edge.points[1] = inserted_points[1];
edges.push_back(new_edge);
return;
}
// One or no points were inserted (besides splitting), so try to see if extra points can be placed inside the triangle.
// This needs to be done here, after the previous tests were exhausted
for (int i = 0; i < 2; i++) {
if (segment_idx[i] != -1)
continue; //already assigned to something, so skip
//check whether one of the segment endpoints is inside the triangle. If it is, this points needs to be inserted
if (Geometry::is_point_in_triangle(segment[i], points[0].point, points[1].point, points[2].point)) {
Point new_point;
new_point.point = segment[i];
Vector2 point3[3] = { points[0].point, points[1].point, points[2].point };
Vector2 uv3[3] = { points[0].uv, points[1].uv, points[2].uv };
new_point.uv = interpolate_triangle_uv(new_point.point, point3, uv3);
int point_idx = points.size();
points.push_back(new_point);
inserted_points.push_back(point_idx);
}
}
//check again whether two points were inserted, if so then create the new edge
if (inserted_points.size() >= 2) { //should never be >2 on non-manifold geometry, but cope with error
Edge new_edge;
new_edge.points[0] = inserted_points[0];
new_edge.points[1] = inserted_points[1];
edges.push_back(new_edge);
}
}
void CSGBrushOperation::BuildPoly::clip(const CSGBrush *p_brush, int p_face, MeshMerge &mesh_merge, bool p_for_B) {
//Clip function.. find triangle points that will be mapped to the plane and form a segment
Vector2 segment[3]; //2D
int src_points = 0;
for (int i = 0; i < 3; i++) {
Vector3 p = p_brush->faces[p_face].vertices[i];
if (plane.has_point(p)) {
Vector3 pp = plane.project(p);
pp = to_poly.xform(pp);
segment[src_points++] = Vector2(pp.x, pp.y);
} else {
Vector3 q = p_brush->faces[p_face].vertices[(i + 1) % 3];
if (plane.has_point(q))
continue; //next point is in plane, will be added eventually
if (plane.is_point_over(p) == plane.is_point_over(q))
continue; // both on same side of the plane, don't add
Vector3 res;
if (plane.intersects_segment(p, q, &res)) {
res = to_poly.xform(res);
segment[src_points++] = Vector2(res.x, res.y);
}
}
}
//all above or all below, nothing to do. Should not happen though since a precheck was done before.
if (src_points == 0)
return;
//just one point in plane is not worth doing anything
if (src_points == 1)
return;
//transform A points to 2D
if (segment[0].is_equal_approx(segment[1]))
return; //too small
_clip_segment(p_brush, p_face, segment, mesh_merge, p_for_B);
}
void CSGBrushOperation::_collision_callback(const CSGBrush *A, int p_face_a, Map<int, BuildPoly> &build_polys_a, const CSGBrush *B, int p_face_b, Map<int, BuildPoly> &build_polys_b, MeshMerge &mesh_merge) {
//construct a frame of reference for both transforms, in order to do intersection test
Vector3 va[3] = {
A->faces[p_face_a].vertices[0],
A->faces[p_face_a].vertices[1],
A->faces[p_face_a].vertices[2],
};
Vector3 vb[3] = {
B->faces[p_face_b].vertices[0],
B->faces[p_face_b].vertices[1],
B->faces[p_face_b].vertices[2],
};
{
//check if either is a degenerate
if (va[0].is_equal_approx(va[1]) || va[0].is_equal_approx(va[2]) || va[1].is_equal_approx(va[2]))
return;
if (vb[0].is_equal_approx(vb[1]) || vb[0].is_equal_approx(vb[2]) || vb[1].is_equal_approx(vb[2]))
return;
}
{
//check if points are the same
int equal_count = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (va[i].distance_to(vb[j]) < mesh_merge.vertex_snap) {
equal_count++;
break;
}
}
}
//if 2 or 3 points are the same, there is no point in doing anything. They can't
//be clipped either, so add both.
if (equal_count == 2 || equal_count == 3) {
return;
}
}
// do a quick pre-check for no-intersection using the SAT theorem
{
//b under or over a plane
int over_count = 0, in_plane_count = 0, under_count = 0;
Plane plane_a(va[0], va[1], va[2]);
if (plane_a.normal == Vector3()) {
return; //degenerate
}
for (int i = 0; i < 3; i++) {
if (plane_a.has_point(vb[i]))
in_plane_count++;
else if (plane_a.is_point_over(vb[i]))
over_count++;
else
under_count++;
}
if (over_count == 0 || under_count == 0)
return; //no intersection, something needs to be under AND over
//a under or over b plane
over_count = 0;
under_count = 0;
in_plane_count = 0;
Plane plane_b(vb[0], vb[1], vb[2]);
if (plane_b.normal == Vector3())
return; //degenerate
for (int i = 0; i < 3; i++) {
if (plane_b.has_point(va[i]))
in_plane_count++;
else if (plane_b.is_point_over(va[i]))
over_count++;
else
under_count++;
}
if (over_count == 0 || under_count == 0)
return; //no intersection, something needs to be under AND over
//edge pairs (cross product combinations), see SAT theorem
for (int i = 0; i < 3; i++) {
Vector3 axis_a = (va[i] - va[(i + 1) % 3]).normalized();
for (int j = 0; j < 3; j++) {
Vector3 axis_b = (vb[j] - vb[(j + 1) % 3]).normalized();
Vector3 sep_axis = axis_a.cross(axis_b);
if (sep_axis == Vector3())
continue; //colineal
sep_axis.normalize();
real_t min_a = 1e20, max_a = -1e20;
real_t min_b = 1e20, max_b = -1e20;
for (int k = 0; k < 3; k++) {
real_t d = sep_axis.dot(va[k]);
min_a = MIN(min_a, d);
max_a = MAX(max_a, d);
d = sep_axis.dot(vb[k]);
min_b = MIN(min_b, d);
max_b = MAX(max_b, d);
}
min_b -= (max_a - min_a) * 0.5;
max_b += (max_a - min_a) * 0.5;
real_t dmin = min_b - (min_a + max_a) * 0.5;
real_t dmax = max_b - (min_a + max_a) * 0.5;
if (dmin > CMP_EPSILON || dmax < -CMP_EPSILON) {
return; //does not contain zero, so they don't overlap
}
}
}
}
//if we are still here, it means they most likely intersect, so create BuildPolys if they don't exist
BuildPoly *poly_a = NULL;
if (!build_polys_a.has(p_face_a)) {
BuildPoly bp;
bp.create(A, p_face_a, mesh_merge, false);
build_polys_a[p_face_a] = bp;
}
poly_a = &build_polys_a[p_face_a];
BuildPoly *poly_b = NULL;
if (!build_polys_b.has(p_face_b)) {
BuildPoly bp;
bp.create(B, p_face_b, mesh_merge, true);
build_polys_b[p_face_b] = bp;
}
poly_b = &build_polys_b[p_face_b];
//clip each other, this could be improved by using vertex unique IDs (more vertices may be shared instead of using snap)
poly_a->clip(B, p_face_b, mesh_merge, false);
poly_b->clip(A, p_face_a, mesh_merge, true);
}
void CSGBrushOperation::_add_poly_points(const BuildPoly &p_poly, int p_edge, int p_from_point, int p_to_point, const Vector<Vector<int> > &vertex_process, Vector<bool> &edge_process, Vector<PolyPoints> &r_poly) {
//this function follows the polygon points counter clockwise and adds them. It creates lists of unique polygons
//every time an unused edge is found, it's pushed to a stack and continues from there.
List<EdgeSort> edge_stack;
{
EdgeSort es;
es.angle = 0; //won't be checked here
es.edge = p_edge;
es.prev_point = p_from_point;
es.edge_point = p_to_point;
edge_stack.push_back(es);
}
//attempt to empty the stack.
while (edge_stack.size()) {
EdgeSort e = edge_stack.front()->get();
edge_stack.pop_front();
if (edge_process[e.edge]) {
//nothing to do here
continue;
}
Vector<int> points;
points.push_back(e.prev_point);
int prev_point = e.prev_point;
int to_point = e.edge_point;
int current_edge = e.edge;
edge_process.write[e.edge] = true; //mark as processed
int limit = p_poly.points.size() * 4; //avoid infinite recursion
while (to_point != e.prev_point && limit) {
Vector2 segment[2] = { p_poly.points[prev_point].point, p_poly.points[to_point].point };
//construct a basis transform from the segment, which will be used to check the angle
Transform2D t2d;
t2d[0] = (segment[1] - segment[0]).normalized(); //use as Y
t2d[1] = Vector2(-t2d[0].y, t2d[0].x); // use as tangent
t2d[2] = segment[1]; //origin
if (t2d.basis_determinant() == 0)
break; //abort poly
t2d.affine_invert();
//push all edges found here, they will be sorted by minimum angle later.
Vector<EdgeSort> next_edges;
for (int i = 0; i < vertex_process[to_point].size(); i++) {
int edge = vertex_process[to_point][i];
int opposite_point = p_poly.edges[edge].points[0] == to_point ? p_poly.edges[edge].points[1] : p_poly.edges[edge].points[0];
if (opposite_point == prev_point)
continue; //not going back
EdgeSort e2;
Vector2 local_vec = t2d.xform(p_poly.points[opposite_point].point);
e2.angle = -local_vec.angle(); //negate so we can sort by minimum angle
e2.edge = edge;
e2.edge_point = opposite_point;
e2.prev_point = to_point;
next_edges.push_back(e2);
}
//finally, sort by minimum angle
next_edges.sort();
int next_point = -1;
int next_edge = -1;
for (int i = 0; i < next_edges.size(); i++) {
if (i == 0) {
//minimum angle found is the next point
next_point = next_edges[i].edge_point;
next_edge = next_edges[i].edge;
} else {
//the rest are pushed to the stack IF they were not processed yet.
if (!edge_process[next_edges[i].edge]) {
edge_stack.push_back(next_edges[i]);
}
}
}
if (next_edge == -1) {
//did not find anything, may be a dead-end edge (this should normally not happen)
//just flip the direction and go back
next_point = prev_point;
next_edge = current_edge;
}
points.push_back(to_point);
prev_point = to_point;
to_point = next_point;
edge_process.write[next_edge] = true; //mark this edge as processed
current_edge = next_edge;
limit--;
}
//if more than 2 points were added to the polygon, add it to the list of polygons.
if (points.size() > 2) {
PolyPoints pp;
pp.points = points;
r_poly.push_back(pp);
}
}
}
void CSGBrushOperation::_add_poly_outline(const BuildPoly &p_poly, int p_from_point, int p_to_point, const Vector<Vector<int> > &vertex_process, Vector<int> &r_outline) {
//this is the opposite of the function above. It adds polygon outlines instead.
//this is used for triangulating holes.
//no stack is used here because only the bigger outline is interesting.
r_outline.push_back(p_from_point);
int prev_point = p_from_point;
int to_point = p_to_point;
int limit = p_poly.points.size() * 4; //avoid infinite recursion
while (to_point != p_from_point && limit) {
Vector2 segment[2] = { p_poly.points[prev_point].point, p_poly.points[to_point].point };
//again create a transform to compute the angle.
Transform2D t2d;
t2d[0] = (segment[1] - segment[0]).normalized(); //use as Y
t2d[1] = Vector2(-t2d[0].y, t2d[0].x); // use as tangent
t2d[2] = segment[1]; //origin
if (t2d.basis_determinant() == 0)
break; //abort poly
t2d.affine_invert();
float max_angle = 0;
int next_point_angle = -1;
for (int i = 0; i < vertex_process[to_point].size(); i++) {
int edge = vertex_process[to_point][i];
int opposite_point = p_poly.edges[edge].points[0] == to_point ? p_poly.edges[edge].points[1] : p_poly.edges[edge].points[0];
if (opposite_point == prev_point)
continue; //not going back
float angle = -t2d.xform(p_poly.points[opposite_point].point).angle();
if (next_point_angle == -1 || angle > max_angle) { //same as before but use greater to check.
max_angle = angle;
next_point_angle = opposite_point;
}
}
if (next_point_angle == -1) {
//go back because no route found
next_point_angle = prev_point;
}
r_outline.push_back(to_point);
prev_point = to_point;
to_point = next_point_angle;
limit--;
}
}
void CSGBrushOperation::_merge_poly(MeshMerge &mesh, int p_face_idx, const BuildPoly &p_poly, bool p_from_b) {
//finally, merge the 2D polygon back to 3D
Vector<Vector<int> > vertex_process;
Vector<bool> edge_process;
vertex_process.resize(p_poly.points.size());
edge_process.resize(p_poly.edges.size());
//none processed by default
for (int i = 0; i < edge_process.size(); i++) {
edge_process.write[i] = false;
}
//put edges in points, so points can go through them
for (int i = 0; i < p_poly.edges.size(); i++) {
vertex_process.write[p_poly.edges[i].points[0]].push_back(i);
vertex_process.write[p_poly.edges[i].points[1]].push_back(i);
}
Vector<PolyPoints> polys;
//process points that were not processed
for (int i = 0; i < edge_process.size(); i++) {
if (edge_process[i])
continue; //already processed
int intersect_poly = -1;
if (i > 0) {
//this is disconnected, so it's clearly a hole. lets find where it belongs
Vector2 ref_point = p_poly.points[p_poly.edges[i].points[0]].point;
for (int j = 0; j < polys.size(); j++) {
//find a point outside poly
Vector2 out_point(-1e20, -1e20);
const PolyPoints &pp = polys[j];
for (int k = 0; k < pp.points.size(); k++) {
Vector2 p = p_poly.points[pp.points[k]].point;
out_point.x = MAX(out_point.x, p.x);
out_point.y = MAX(out_point.y, p.y);
}
out_point += Vector2(0.12341234, 0.4123412); // move to a random place to avoid direct edge-point chances
int intersections = 0;
for (int k = 0; k < pp.points.size(); k++) {
Vector2 p1 = p_poly.points[pp.points[k]].point;
Vector2 p2 = p_poly.points[pp.points[(k + 1) % pp.points.size()]].point;
if (Geometry::segment_intersects_segment_2d(ref_point, out_point, p1, p2, NULL)) {
intersections++;
}
}
if (intersections % 2 == 1) {
//hole is inside this poly
intersect_poly = j;
break;
}
}
}
if (intersect_poly != -1) {
//must add this as a hole
Vector<int> outline;
_add_poly_outline(p_poly, p_poly.edges[i].points[0], p_poly.edges[i].points[1], vertex_process, outline);
if (outline.size() > 1) {
polys.write[intersect_poly].holes.push_back(outline);
}
}
_add_poly_points(p_poly, i, p_poly.edges[i].points[0], p_poly.edges[i].points[1], vertex_process, edge_process, polys);
}
//get rid of holes, not the most optiomal way, but also not a common case at all to be inoptimal
for (int i = 0; i < polys.size(); i++) {
if (!polys[i].holes.size())
continue;
//repeat until no more holes are left to be merged
while (polys[i].holes.size()) {
//try to merge a hole with the outline
bool added_hole = false;
for (int j = 0; j < polys[i].holes.size(); j++) {
//try hole vertices
int with_outline_vertex = -1;
int from_hole_vertex = -1;
bool found = false;
for (int k = 0; k < polys[i].holes[j].size(); k++) {
int from_idx = polys[i].holes[j][k];
Vector2 from = p_poly.points[from_idx].point;
//try a segment from hole vertex to outline vertices
from_hole_vertex = k;
bool valid = true;
for (int l = 0; l < polys[i].points.size(); l++) {
int to_idx = polys[i].points[l];
Vector2 to = p_poly.points[to_idx].point;
with_outline_vertex = l;
//try against outline (other points) first
valid = true;
for (int m = 0; m < polys[i].points.size(); m++) {
int m_next = (m + 1) % polys[i].points.size();
if (m == with_outline_vertex || m_next == with_outline_vertex) //do not test with edges that share this point
continue;
if (Geometry::segment_intersects_segment_2d(from, to, p_poly.points[polys[i].points[m]].point, p_poly.points[polys[i].points[m_next]].point, NULL)) {
valid = false;
break;
}
}
if (!valid)
continue;
//try against all holes including self
for (int m = 0; m < polys[i].holes.size(); m++) {
for (int n = 0; n < polys[i].holes[m].size(); n++) {
int n_next = (n + 1) % polys[i].holes[m].size();
if (m == j && (n == from_hole_vertex || n_next == from_hole_vertex)) //contains vertex being tested from current hole, skip
continue;
if (Geometry::segment_intersects_segment_2d(from, to, p_poly.points[polys[i].holes[m][n]].point, p_poly.points[polys[i].holes[m][n_next]].point, NULL)) {
valid = false;
break;
}
}
if (!valid)
break;
}
if (valid) //all passed! exit loop
break;
else
continue; //something went wrong, go on.
}
if (valid) {
found = true; //if in the end this was valid, use it
break;
}
}
if (found) {
//hook this hole with outline, and remove from list of holes
//duplicate point
int insert_at = with_outline_vertex;
int point = polys[i].points[insert_at];
polys.write[i].points.insert(insert_at, point);
insert_at++;
//insert all others, outline should be backwards (must check)
int holesize = polys[i].holes[j].size();
for (int k = 0; k <= holesize; k++) {
int idx = (from_hole_vertex + k) % holesize;
int point2 = polys[i].holes[j][idx];
polys.write[i].points.insert(insert_at, point2);
insert_at++;
}
added_hole = true;
polys.write[i].holes.remove(j);
break; //got rid of hole, break and continue
}
}
ERR_BREAK(!added_hole);
}
}
//triangulate polygons
for (int i = 0; i < polys.size(); i++) {
Vector<Vector2> vertices;
vertices.resize(polys[i].points.size());
for (int j = 0; j < vertices.size(); j++) {
vertices.write[j] = p_poly.points[polys[i].points[j]].point;
}
Vector<int> indices = Geometry::triangulate_polygon(vertices);
for (int j = 0; j < indices.size(); j += 3) {
//obtain the vertex
Vector3 face[3];
Vector2 uv[3];
float cp = Geometry::vec2_cross(p_poly.points[polys[i].points[indices[j + 0]]].point, p_poly.points[polys[i].points[indices[j + 1]]].point, p_poly.points[polys[i].points[indices[j + 2]]].point);
if (Math::abs(cp) < CMP_EPSILON)
continue;
for (int k = 0; k < 3; k++) {
Vector2 p = p_poly.points[polys[i].points[indices[j + k]]].point;
face[k] = p_poly.to_world.xform(Vector3(p.x, p.y, 0));
uv[k] = p_poly.points[polys[i].points[indices[j + k]]].uv;
}
mesh.add_face(face[0], face[1], face[2], uv[0], uv[1], uv[2], p_poly.smooth, p_poly.invert, p_poly.material, p_from_b);
}
}
}
//use a limit to speed up bvh and limit the depth
#define BVH_LIMIT 8
int CSGBrushOperation::MeshMerge::_create_bvh(BVH *p_bvh, BVH **p_bb, int p_from, int p_size, int p_depth, int &max_depth, int &max_alloc) {
if (p_depth > max_depth) {
max_depth = p_depth;
}
if (p_size == 0) {
return -1;
} else if (p_size <= BVH_LIMIT) {
for (int i = 0; i < p_size - 1; i++) {
p_bb[p_from + i]->next = p_bb[p_from + i + 1] - p_bvh;
}
return p_bb[p_from] - p_bvh;
}
AABB aabb;
aabb = p_bb[p_from]->aabb;
for (int i = 1; i < p_size; i++) {
aabb.merge_with(p_bb[p_from + i]->aabb);
}
int li = aabb.get_longest_axis_index();
switch (li) {
case Vector3::AXIS_X: {
SortArray<BVH *, BVHCmpX> sort_x;
sort_x.nth_element(0, p_size, p_size / 2, &p_bb[p_from]);
//sort_x.sort(&p_bb[p_from],p_size);
} break;
case Vector3::AXIS_Y: {
SortArray<BVH *, BVHCmpY> sort_y;
sort_y.nth_element(0, p_size, p_size / 2, &p_bb[p_from]);
//sort_y.sort(&p_bb[p_from],p_size);
} break;
case Vector3::AXIS_Z: {
SortArray<BVH *, BVHCmpZ> sort_z;
sort_z.nth_element(0, p_size, p_size / 2, &p_bb[p_from]);
//sort_z.sort(&p_bb[p_from],p_size);
} break;
}
int left = _create_bvh(p_bvh, p_bb, p_from, p_size / 2, p_depth + 1, max_depth, max_alloc);
int right = _create_bvh(p_bvh, p_bb, p_from + p_size / 2, p_size - p_size / 2, p_depth + 1, max_depth, max_alloc);
int index = max_alloc++;
BVH *_new = &p_bvh[index];
_new->aabb = aabb;
_new->center = aabb.position + aabb.size * 0.5;
_new->face = -1;
_new->left = left;
_new->right = right;
_new->next = -1;
return index;
}
int CSGBrushOperation::MeshMerge::_bvh_count_intersections(BVH *bvhptr, int p_max_depth, int p_bvh_first, const Vector3 &p_begin, const Vector3 &p_end, int p_exclude) const {
uint32_t *stack = (uint32_t *)alloca(sizeof(int) * p_max_depth);
enum {
TEST_AABB_BIT = 0,
VISIT_LEFT_BIT = 1,
VISIT_RIGHT_BIT = 2,
VISIT_DONE_BIT = 3,
VISITED_BIT_SHIFT = 29,
NODE_IDX_MASK = (1 << VISITED_BIT_SHIFT) - 1,
VISITED_BIT_MASK = ~NODE_IDX_MASK,
};
int intersections = 0;
int level = 0;
const Vector3 *vertexptr = points.ptr();
const Face *facesptr = faces.ptr();
AABB segment_aabb;
segment_aabb.position = p_begin;
segment_aabb.expand_to(p_end);
int pos = p_bvh_first;
stack[0] = pos;
while (true) {
uint32_t node = stack[level] & NODE_IDX_MASK;
const BVH &b = bvhptr[node];
bool done = false;
switch (stack[level] >> VISITED_BIT_SHIFT) {
case TEST_AABB_BIT: {
if (b.face >= 0) {
const BVH *bp = &b;
while (bp) {
bool valid = segment_aabb.intersects(bp->aabb) && bp->aabb.intersects_segment(p_begin, p_end);
if (valid && p_exclude != bp->face) {
const Face &s = facesptr[bp->face];
Face3 f3(vertexptr[s.points[0]], vertexptr[s.points[1]], vertexptr[s.points[2]]);
Vector3 res;
if (f3.intersects_segment(p_begin, p_end, &res)) {
intersections++;
}
}
if (bp->next != -1) {
bp = &bvhptr[bp->next];
} else {
bp = NULL;
}
}
stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node;
} else {
bool valid = segment_aabb.intersects(b.aabb) && b.aabb.intersects_segment(p_begin, p_end);
if (!valid) {
stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node;
} else {
stack[level] = (VISIT_LEFT_BIT << VISITED_BIT_SHIFT) | node;
}
}
continue;
}
case VISIT_LEFT_BIT: {
stack[level] = (VISIT_RIGHT_BIT << VISITED_BIT_SHIFT) | node;
stack[level + 1] = b.left | TEST_AABB_BIT;
level++;
continue;
}
case VISIT_RIGHT_BIT: {
stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node;
stack[level + 1] = b.right | TEST_AABB_BIT;
level++;
continue;
}
case VISIT_DONE_BIT: {
if (level == 0) {
done = true;
break;
} else
level--;
continue;
}
}
if (done)
break;
}
return intersections;
}
void CSGBrushOperation::MeshMerge::mark_inside_faces() {
// mark faces that are inside. This helps later do the boolean ops when merging.
// this approach is very brute force (with a bunch of optimizatios, such as BVH and pre AABB intersection test)
AABB aabb;
for (int i = 0; i < points.size(); i++) {
if (i == 0) {
aabb.position = points[i];
} else {
aabb.expand_to(points[i]);
}
}
float max_distance = aabb.size.length() * 1.2;
Vector<BVH> bvhvec;
bvhvec.resize(faces.size() * 3); //will never be larger than this (todo make better)
BVH *bvh = bvhvec.ptrw();
AABB faces_a;
AABB faces_b;
bool first_a = true;
bool first_b = true;
for (int i = 0; i < faces.size(); i++) {
bvh[i].left = -1;
bvh[i].right = -1;
bvh[i].face = i;
bvh[i].aabb.position = points[faces[i].points[0]];
bvh[i].aabb.expand_to(points[faces[i].points[1]]);
bvh[i].aabb.expand_to(points[faces[i].points[2]]);
bvh[i].center = bvh[i].aabb.position + bvh[i].aabb.size * 0.5;
bvh[i].next = -1;
if (faces[i].from_b) {
if (first_b) {
faces_b = bvh[i].aabb;
first_b = false;
} else {
faces_b.merge_with(bvh[i].aabb);
}
} else {
if (first_a) {
faces_a = bvh[i].aabb;
first_a = false;
} else {
faces_a.merge_with(bvh[i].aabb);
}
}
}
AABB intersection_aabb = faces_a.intersection(faces_b);
intersection_aabb.grow_by(intersection_aabb.get_longest_axis_size() * 0.01); //grow a little, avoid numerical error
if (intersection_aabb.size == Vector3()) //AABB do not intersect, so neither do shapes.
return;
Vector<BVH *> bvhtrvec;
bvhtrvec.resize(faces.size());
BVH **bvhptr = bvhtrvec.ptrw();
for (int i = 0; i < faces.size(); i++) {
bvhptr[i] = &bvh[i];
}
int max_depth = 0;
int max_alloc = faces.size();
_create_bvh(bvh, bvhptr, 0, faces.size(), 1, max_depth, max_alloc);
for (int i = 0; i < faces.size(); i++) {
if (!intersection_aabb.intersects(bvh[i].aabb))
continue; //not in AABB intersection, so not in face intersection
Vector3 center = points[faces[i].points[0]];
center += points[faces[i].points[1]];
center += points[faces[i].points[2]];
center /= 3.0;
Plane plane(points[faces[i].points[0]], points[faces[i].points[1]], points[faces[i].points[2]]);
Vector3 target = center + plane.normal * max_distance + Vector3(0.0001234, 0.000512, 0.00013423); //reduce chance of edge hits by doing a small increment
int intersections = _bvh_count_intersections(bvh, max_depth, max_alloc - 1, center, target, i);
if (intersections & 1) {
faces.write[i].inside = true;
}
}
}
void CSGBrushOperation::MeshMerge::add_face(const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_c, const Vector2 &p_uv_a, const Vector2 &p_uv_b, const Vector2 &p_uv_c, bool p_smooth, bool p_invert, const Ref<Material> &p_material, bool p_from_b) {
Vector3 src_points[3] = { p_a, p_b, p_c };
Vector2 src_uvs[3] = { p_uv_a, p_uv_b, p_uv_c };
int indices[3];
for (int i = 0; i < 3; i++) {
VertexKey vk;
vk.x = int((double(src_points[i].x) + double(vertex_snap) * 0.31234) / double(vertex_snap));
vk.y = int((double(src_points[i].y) + double(vertex_snap) * 0.31234) / double(vertex_snap));
vk.z = int((double(src_points[i].z) + double(vertex_snap) * 0.31234) / double(vertex_snap));
int res;
if (snap_cache.lookup(vk, res)) {
indices[i] = res;
} else {
indices[i] = points.size();
points.push_back(src_points[i]);
snap_cache.set(vk, indices[i]);
}
}
if (indices[0] == indices[2] || indices[0] == indices[1] || indices[1] == indices[2])
return; //not adding degenerate
MeshMerge::Face face;
face.from_b = p_from_b;
face.inside = false;
face.smooth = p_smooth;
face.invert = p_invert;
if (p_material.is_valid()) {
if (!materials.has(p_material)) {
face.material_idx = materials.size();
materials[p_material] = face.material_idx;
} else {
face.material_idx = materials[p_material];
}
} else {
face.material_idx = -1;
}
for (int k = 0; k < 3; k++) {
face.points[k] = indices[k];
face.uvs[k] = src_uvs[k];
;
}
faces.push_back(face);
}
void CSGBrushOperation::merge_brushes(Operation p_operation, const CSGBrush &p_A, const CSGBrush &p_B, CSGBrush &result, float p_snap) {
CallbackData cd;
cd.self = this;
cd.A = &p_A;
cd.B = &p_B;
MeshMerge mesh_merge;
mesh_merge.vertex_snap = p_snap;
//check intersections between faces. Use AABB to speed up precheck
//this generates list of buildpolys and clips them.
//this was originally BVH optimized, but its not really worth it.
for (int i = 0; i < p_A.faces.size(); i++) {
cd.face_a = i;
for (int j = 0; j < p_B.faces.size(); j++) {
if (p_A.faces[i].aabb.intersects(p_B.faces[j].aabb)) {
_collision_callback(&p_A, i, cd.build_polys_A, &p_B, j, cd.build_polys_B, mesh_merge);
}
}
}
//merge the already cliped polys back to 3D
for (Map<int, BuildPoly>::Element *E = cd.build_polys_A.front(); E; E = E->next()) {
_merge_poly(mesh_merge, E->key(), E->get(), false);
}
for (Map<int, BuildPoly>::Element *E = cd.build_polys_B.front(); E; E = E->next()) {
_merge_poly(mesh_merge, E->key(), E->get(), true);
}
//merge the non clipped faces back
for (int i = 0; i < p_A.faces.size(); i++) {
if (cd.build_polys_A.has(i))
continue; //made from buildpoly, skipping
Vector3 points[3];
Vector2 uvs[3];
for (int j = 0; j < 3; j++) {
points[j] = p_A.faces[i].vertices[j];
uvs[j] = p_A.faces[i].uvs[j];
}
Ref<Material> material;
if (p_A.faces[i].material != -1) {
material = p_A.materials[p_A.faces[i].material];
}
mesh_merge.add_face(points[0], points[1], points[2], uvs[0], uvs[1], uvs[2], p_A.faces[i].smooth, p_A.faces[i].invert, material, false);
}
for (int i = 0; i < p_B.faces.size(); i++) {
if (cd.build_polys_B.has(i))
continue; //made from buildpoly, skipping
Vector3 points[3];
Vector2 uvs[3];
for (int j = 0; j < 3; j++) {
points[j] = p_B.faces[i].vertices[j];
uvs[j] = p_B.faces[i].uvs[j];
}
Ref<Material> material;
if (p_B.faces[i].material != -1) {
material = p_B.materials[p_B.faces[i].material];
}
mesh_merge.add_face(points[0], points[1], points[2], uvs[0], uvs[1], uvs[2], p_B.faces[i].smooth, p_B.faces[i].invert, material, true);
}
//mark faces that ended up inside the intersection
mesh_merge.mark_inside_faces();
//regen new brush to start filling it again
result.clear();
switch (p_operation) {
case OPERATION_UNION: {
int outside_count = 0;
for (int i = 0; i < mesh_merge.faces.size(); i++) {
if (mesh_merge.faces[i].inside)
continue;
outside_count++;
}
result.faces.resize(outside_count);
outside_count = 0;
for (int i = 0; i < mesh_merge.faces.size(); i++) {
if (mesh_merge.faces[i].inside)
continue;
for (int j = 0; j < 3; j++) {
result.faces.write[outside_count].vertices[j] = mesh_merge.points[mesh_merge.faces[i].points[j]];
result.faces.write[outside_count].uvs[j] = mesh_merge.faces[i].uvs[j];
}
result.faces.write[outside_count].smooth = mesh_merge.faces[i].smooth;
result.faces.write[outside_count].invert = mesh_merge.faces[i].invert;
result.faces.write[outside_count].material = mesh_merge.faces[i].material_idx;
outside_count++;
}
result._regen_face_aabbs();
} break;
case OPERATION_INTERSECTION: {
int inside_count = 0;
for (int i = 0; i < mesh_merge.faces.size(); i++) {
if (!mesh_merge.faces[i].inside)
continue;
inside_count++;
}
result.faces.resize(inside_count);
inside_count = 0;
for (int i = 0; i < mesh_merge.faces.size(); i++) {
if (!mesh_merge.faces[i].inside)
continue;
for (int j = 0; j < 3; j++) {
result.faces.write[inside_count].vertices[j] = mesh_merge.points[mesh_merge.faces[i].points[j]];
result.faces.write[inside_count].uvs[j] = mesh_merge.faces[i].uvs[j];
}
result.faces.write[inside_count].smooth = mesh_merge.faces[i].smooth;
result.faces.write[inside_count].invert = mesh_merge.faces[i].invert;
result.faces.write[inside_count].material = mesh_merge.faces[i].material_idx;
inside_count++;
}
result._regen_face_aabbs();
} break;
case OPERATION_SUBSTRACTION: {
int face_count = 0;
for (int i = 0; i < mesh_merge.faces.size(); i++) {
if (mesh_merge.faces[i].from_b && !mesh_merge.faces[i].inside)
continue;
if (!mesh_merge.faces[i].from_b && mesh_merge.faces[i].inside)
continue;
face_count++;
}
result.faces.resize(face_count);
face_count = 0;
for (int i = 0; i < mesh_merge.faces.size(); i++) {
if (mesh_merge.faces[i].from_b && !mesh_merge.faces[i].inside)
continue;
if (!mesh_merge.faces[i].from_b && mesh_merge.faces[i].inside)
continue;
for (int j = 0; j < 3; j++) {
result.faces.write[face_count].vertices[j] = mesh_merge.points[mesh_merge.faces[i].points[j]];
result.faces.write[face_count].uvs[j] = mesh_merge.faces[i].uvs[j];
}
if (mesh_merge.faces[i].from_b) {
//invert facing of insides of B
SWAP(result.faces.write[face_count].vertices[1], result.faces.write[face_count].vertices[2]);
SWAP(result.faces.write[face_count].uvs[1], result.faces.write[face_count].uvs[2]);
}
result.faces.write[face_count].smooth = mesh_merge.faces[i].smooth;
result.faces.write[face_count].invert = mesh_merge.faces[i].invert;
result.faces.write[face_count].material = mesh_merge.faces[i].material_idx;
face_count++;
}
result._regen_face_aabbs();
} break;
}
//updatelist of materials
result.materials.resize(mesh_merge.materials.size());
for (const Map<Ref<Material>, int>::Element *E = mesh_merge.materials.front(); E; E = E->next()) {
result.materials.write[E->get()] = E->key();
}
}