diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index d6e1a91245d..870d0dc5d9e 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -5858,7 +5858,7 @@ EditorNode::EditorNode() { add_editor_plugin(memnew(ParticlesEditorPlugin(this))); add_editor_plugin(memnew(ResourcePreloaderEditorPlugin(this))); add_editor_plugin(memnew(ItemListEditorPlugin(this))); - add_editor_plugin(memnew(CollisionPolygonEditorPlugin(this))); + add_editor_plugin(memnew(Polygon3DEditorPlugin(this))); add_editor_plugin(memnew(CollisionPolygon2DEditorPlugin(this))); add_editor_plugin(memnew(TileSetEditorPlugin(this))); add_editor_plugin(memnew(TileMapEditorPlugin(this))); diff --git a/editor/plugins/collision_polygon_editor_plugin.cpp b/editor/plugins/collision_polygon_editor_plugin.cpp index 4410242d9ce..d308cfbbf7d 100644 --- a/editor/plugins/collision_polygon_editor_plugin.cpp +++ b/editor/plugins/collision_polygon_editor_plugin.cpp @@ -36,7 +36,7 @@ #include "scene/3d/camera.h" #include "spatial_editor_plugin.h" -void CollisionPolygonEditor::_notification(int p_what) { +void Polygon3DEditor::_notification(int p_what) { switch (p_what) { @@ -53,15 +53,15 @@ void CollisionPolygonEditor::_notification(int p_what) { return; } - if (node->get_depth() != prev_depth) { + if (_get_depth() != prev_depth) { _polygon_draw(); - prev_depth = node->get_depth(); + prev_depth = _get_depth(); } } break; } } -void CollisionPolygonEditor::_node_removed(Node *p_node) { +void Polygon3DEditor::_node_removed(Node *p_node) { if (p_node == node) { node = NULL; @@ -72,7 +72,7 @@ void CollisionPolygonEditor::_node_removed(Node *p_node) { } } -void CollisionPolygonEditor::_menu_option(int p_option) { +void Polygon3DEditor::_menu_option(int p_option) { switch (p_option) { @@ -91,10 +91,10 @@ void CollisionPolygonEditor::_menu_option(int p_option) { } } -void CollisionPolygonEditor::_wip_close() { +void Polygon3DEditor::_wip_close() { undo_redo->create_action(TTR("Create Poly3D")); - undo_redo->add_undo_method(node, "set_polygon", node->get_polygon()); + undo_redo->add_undo_method(node, "set_polygon", node->call("get_polygon")); undo_redo->add_do_method(node, "set_polygon", wip); undo_redo->add_do_method(this, "_polygon_draw"); undo_redo->add_undo_method(this, "_polygon_draw"); @@ -107,14 +107,14 @@ void CollisionPolygonEditor::_wip_close() { undo_redo->commit_action(); } -bool CollisionPolygonEditor::forward_spatial_gui_input(Camera *p_camera, const Ref &p_event) { +bool Polygon3DEditor::forward_spatial_gui_input(Camera *p_camera, const Ref &p_event) { if (!node) return false; Transform gt = node->get_global_transform(); Transform gi = gt.affine_inverse(); - float depth = node->get_depth() * 0.5; + float depth = _get_depth() * 0.5; Vector3 n = gt.basis.get_axis(2).normalized(); Plane p(gt.origin + n * depth, n); @@ -137,7 +137,7 @@ bool CollisionPolygonEditor::forward_spatial_gui_input(Camera *p_camera, const R cpoint = CanvasItemEditor::get_singleton()->snap_point(cpoint); - Vector poly = node->get_polygon(); + Vector poly = node->call("get_polygon"); //first check if a point is to be added (segment split) real_t grab_threshold = EDITOR_DEF("editors/poly_editor/point_grab_radius", 8); @@ -226,7 +226,7 @@ bool CollisionPolygonEditor::forward_spatial_gui_input(Camera *p_camera, const R poly.insert(closest_idx + 1, cpoint); edited_point = closest_idx + 1; edited_point_pos = cpoint; - node->set_polygon(poly); + node->call("set_polygon", poly); _polygon_draw(); return true; } @@ -341,7 +341,16 @@ bool CollisionPolygonEditor::forward_spatial_gui_input(Camera *p_camera, const R return false; } -void CollisionPolygonEditor::_polygon_draw() { + +float Polygon3DEditor::_get_depth() { + + if (bool(node->call("_has_editable_3d_polygon_no_depth"))) + return 0; + + return float(node->call("get_depth")); +} + +void Polygon3DEditor::_polygon_draw() { if (!node) return; @@ -351,9 +360,9 @@ void CollisionPolygonEditor::_polygon_draw() { if (wip_active) poly = wip; else - poly = node->get_polygon(); + poly = node->call("get_polygon"); - float depth = node->get_depth() * 0.5; + float depth = _get_depth() * 0.5; imgeom->clear(); imgeom->set_material_override(line_material); @@ -464,13 +473,13 @@ void CollisionPolygonEditor::_polygon_draw() { m->surface_set_material(0, handle_material); } -void CollisionPolygonEditor::edit(Node *p_collision_polygon) { +void Polygon3DEditor::edit(Node *p_collision_polygon) { if (p_collision_polygon) { - node = Object::cast_to(p_collision_polygon); + node = Object::cast_to(p_collision_polygon); //Enable the pencil tool if the polygon is empty - if (node->get_polygon().size() == 0) { + if (Vector(node->call("get_polygon")).size() == 0) { _menu_option(MODE_CREATE); } wip.clear(); @@ -491,14 +500,14 @@ void CollisionPolygonEditor::edit(Node *p_collision_polygon) { } } -void CollisionPolygonEditor::_bind_methods() { +void Polygon3DEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("_menu_option"), &CollisionPolygonEditor::_menu_option); - ClassDB::bind_method(D_METHOD("_polygon_draw"), &CollisionPolygonEditor::_polygon_draw); - ClassDB::bind_method(D_METHOD("_node_removed"), &CollisionPolygonEditor::_node_removed); + ClassDB::bind_method(D_METHOD("_menu_option"), &Polygon3DEditor::_menu_option); + ClassDB::bind_method(D_METHOD("_polygon_draw"), &Polygon3DEditor::_polygon_draw); + ClassDB::bind_method(D_METHOD("_node_removed"), &Polygon3DEditor::_node_removed); } -CollisionPolygonEditor::CollisionPolygonEditor(EditorNode *p_editor) { +Polygon3DEditor::Polygon3DEditor(EditorNode *p_editor) { node = NULL; editor = p_editor; @@ -545,22 +554,22 @@ CollisionPolygonEditor::CollisionPolygonEditor(EditorNode *p_editor) { pointsm->set_transform(Transform(Basis(), Vector3(0, 0, 0.00001))); } -CollisionPolygonEditor::~CollisionPolygonEditor() { +Polygon3DEditor::~Polygon3DEditor() { memdelete(imgeom); } -void CollisionPolygonEditorPlugin::edit(Object *p_object) { +void Polygon3DEditorPlugin::edit(Object *p_object) { collision_polygon_editor->edit(Object::cast_to(p_object)); } -bool CollisionPolygonEditorPlugin::handles(Object *p_object) const { +bool Polygon3DEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("CollisionPolygon"); + return Object::cast_to(p_object) && bool(p_object->call("_is_editable_3d_polygon")); } -void CollisionPolygonEditorPlugin::make_visible(bool p_visible) { +void Polygon3DEditorPlugin::make_visible(bool p_visible) { if (p_visible) { collision_polygon_editor->show(); @@ -571,14 +580,14 @@ void CollisionPolygonEditorPlugin::make_visible(bool p_visible) { } } -CollisionPolygonEditorPlugin::CollisionPolygonEditorPlugin(EditorNode *p_node) { +Polygon3DEditorPlugin::Polygon3DEditorPlugin(EditorNode *p_node) { editor = p_node; - collision_polygon_editor = memnew(CollisionPolygonEditor(p_node)); + collision_polygon_editor = memnew(Polygon3DEditor(p_node)); SpatialEditor::get_singleton()->add_control_to_menu_panel(collision_polygon_editor); collision_polygon_editor->hide(); } -CollisionPolygonEditorPlugin::~CollisionPolygonEditorPlugin() { +Polygon3DEditorPlugin::~Polygon3DEditorPlugin() { } diff --git a/editor/plugins/collision_polygon_editor_plugin.h b/editor/plugins/collision_polygon_editor_plugin.h index f1f215b092e..2202dd25f4d 100644 --- a/editor/plugins/collision_polygon_editor_plugin.h +++ b/editor/plugins/collision_polygon_editor_plugin.h @@ -44,9 +44,9 @@ class CanvasItemEditor; -class CollisionPolygonEditor : public HBoxContainer { +class Polygon3DEditor : public HBoxContainer { - GDCLASS(CollisionPolygonEditor, HBoxContainer); + GDCLASS(Polygon3DEditor, HBoxContainer); UndoRedo *undo_redo; enum Mode { @@ -66,7 +66,7 @@ class CollisionPolygonEditor : public HBoxContainer { EditorNode *editor; Panel *panel; - CollisionPolygon *node; + Spatial *node; ImmediateGeometry *imgeom; MeshInstance *pointsm; Ref m; @@ -85,6 +85,8 @@ class CollisionPolygonEditor : public HBoxContainer { void _polygon_draw(); void _menu_option(int p_option); + float _get_depth(); + protected: void _notification(int p_what); void _node_removed(Node *p_node); @@ -93,28 +95,28 @@ protected: public: virtual bool forward_spatial_gui_input(Camera *p_camera, const Ref &p_event); void edit(Node *p_collision_polygon); - CollisionPolygonEditor(EditorNode *p_editor); - ~CollisionPolygonEditor(); + Polygon3DEditor(EditorNode *p_editor); + ~Polygon3DEditor(); }; -class CollisionPolygonEditorPlugin : public EditorPlugin { +class Polygon3DEditorPlugin : public EditorPlugin { - GDCLASS(CollisionPolygonEditorPlugin, EditorPlugin); + GDCLASS(Polygon3DEditorPlugin, EditorPlugin); - CollisionPolygonEditor *collision_polygon_editor; + Polygon3DEditor *collision_polygon_editor; EditorNode *editor; public: virtual bool forward_spatial_gui_input(Camera *p_camera, const Ref &p_event) { return collision_polygon_editor->forward_spatial_gui_input(p_camera, p_event); } - virtual String get_name() const { return "CollisionPolygon"; } + virtual String get_name() const { return "Polygon3DEditor"; } bool has_main_screen() const { return false; } virtual void edit(Object *p_object); virtual bool handles(Object *p_object) const; virtual void make_visible(bool p_visible); - CollisionPolygonEditorPlugin(EditorNode *p_node); - ~CollisionPolygonEditorPlugin(); + Polygon3DEditorPlugin(EditorNode *p_node); + ~Polygon3DEditorPlugin(); }; #endif // COLLISION_POLYGON_EDITOR_PLUGIN_H diff --git a/modules/csg/SCsub b/modules/csg/SCsub new file mode 100644 index 00000000000..57c504efd88 --- /dev/null +++ b/modules/csg/SCsub @@ -0,0 +1,9 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +env_csg = env_modules.Clone() + +# Godot's own source files +env_csg.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/csg/config.py b/modules/csg/config.py new file mode 100644 index 00000000000..5f133eba90f --- /dev/null +++ b/modules/csg/config.py @@ -0,0 +1,5 @@ +def can_build(platform): + return True + +def configure(env): + pass diff --git a/modules/csg/csg.cpp b/modules/csg/csg.cpp new file mode 100644 index 00000000000..f399466fe5f --- /dev/null +++ b/modules/csg/csg.cpp @@ -0,0 +1,1472 @@ +#include "csg.h" +#include "face3.h" +#include "geometry.h" +#include "os/os.h" +#include "sort.h" +#include "thirdparty/misc/triangulator.h" + +void CSGBrush::clear() { + faces.clear(); +} + +void CSGBrush::build_from_faces(const PoolVector &p_vertices, const PoolVector &p_uvs, const PoolVector &p_smooth, const PoolVector > &p_materials, const PoolVector &p_invert_faces) { + + clear(); + + int vc = p_vertices.size(); + + ERR_FAIL_COND((vc % 3) != 0) + + PoolVector::Read rv = p_vertices.read(); + int uvc = p_uvs.size(); + PoolVector::Read ruv = p_uvs.read(); + int sc = p_smooth.size(); + PoolVector::Read rs = p_smooth.read(); + int mc = p_materials.size(); + PoolVector >::Read rm = p_materials.read(); + int ic = p_invert_faces.size(); + PoolVector::Read ri = p_invert_faces.read(); + + Map, int> material_map; + + faces.resize(p_vertices.size() / 3); + + for (int i = 0; i < faces.size(); i++) { + Face &f = faces[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 mat = rm[i]; + if (mat.is_valid()) { + const Map, 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, int>::Element *E = material_map.front(); E; E = E->next()) { + materials[E->get()] = E->key(); + } + + _regen_face_aabbs(); +} + +void CSGBrush::_regen_face_aabbs() { + + for (int i = 0; i < faces.size(); i++) { + + faces[i].aabb.position = faces[i].vertices[0]; + faces[i].aabb.expand_to(faces[i].vertices[1]); + faces[i].aabb.expand_to(faces[i].vertices[2]); + faces[i].aabb.grow_by(faces[i].aabb.get_longest_axis_size() * 0.001); //make it a tad bigger to avoid num precision erros + } +} + +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[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 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].distance_to(points[i].point) < CMP_EPSILON) { + 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, cant check against this + break; + } + } + + if (!edge_valid) //already hit a point in this edge, so dont 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.distance_to(segment[j]) < CMP_EPSILON) { + //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[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 + + _clip_segment(p_brush, p_face, segment, mesh_merge, p_for_B); +} + +void CSGBrushOperation::_collision_callback(const CSGBrush *A, int p_face_a, Map &build_polys_a, const CSGBrush *B, int p_face_b, Map &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].distance_to(va[1]) < CMP_EPSILON || va[0].distance_to(va[2]) < CMP_EPSILON || va[1].distance_to(va[2]) < CMP_EPSILON) + return; + + if (vb[0].distance_to(vb[1]) < CMP_EPSILON || vb[0].distance_to(vb[2]) < CMP_EPSILON || vb[1].distance_to(vb[2]) < CMP_EPSILON) + 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 dont existy + + 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 > &vertex_process, Vector &edge_process, Vector &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 edge_stack; + + { + EdgeSort es; + es.angle = 0; //wont be checked here + es.edge = p_edge; + es.prev_point = p_from_point; + es.edge_point = p_to_point; + + edge_stack.push_back(es); + } + + int limit = p_poly.points.size() * 4; + + //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 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[e.edge] = true; //mark as processed + + while (to_point != e.prev_point) { + + 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 + + t2d.affine_invert(); + + //push all edges found here, they will be sorted by minimum angle later. + Vector 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 e; + Vector2 local_vec = t2d.xform(p_poly.points[opposite_point].point); + e.angle = -local_vec.angle(); //negate so we can sort by minimum angle + e.edge = edge; + e.edge_point = opposite_point; + e.prev_point = to_point; + + next_edges.push_back(e); + } + + //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[next_edge] = true; //mark this edge as processed + current_edge = next_edge; + } + + //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 > &vertex_process, Vector &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; + + while (to_point != p_from_point) { + + 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 + t2d.affine_invert(); + + float max_angle; + 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; + } +} + +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 > vertex_process; + Vector 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[i] = false; + } + + //put edges in points, so points can go through them + for (int i = 0; i < p_poly.edges.size(); i++) { + vertex_process[p_poly.edges[i].points[0]].push_back(i); + vertex_process[p_poly.edges[i].points[1]].push_back(i); + } + + Vector polys; + + //process points that were not processed + for (int i = 0; i < edge_process.size(); i++) { + if (edge_process[i] == true) + 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 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[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 agaisnt 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 agaisnt 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; + polys[i].points.insert(insert_at, polys[i].points[insert_at]); + 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; + polys[i].points.insert(insert_at, polys[i].holes[j][idx]); + insert_at++; + } + + added_hole = true; + polys[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 vertices; + vertices.resize(polys[i].points.size()); + for (int j = 0; j < vertices.size(); j++) { + vertices[j] = p_poly.points[polys[i].points[j]].point; + } + + Vector 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 <= 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; + } else if (p_size == 0) { + + return -1; + } + + 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 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 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 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 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 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; + + int intersections = _bvh_count_intersections(bvh, max_depth, max_alloc - 1, center, target, i); + + if (intersections & 1) { + faces[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 &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::Element *E = cd.build_polys_A.front(); E; E = E->next()) { + _merge_poly(mesh_merge, E->key(), E->get(), false); + } + + for (Map::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; + 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; + 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[outside_count].vertices[j] = mesh_merge.points[mesh_merge.faces[i].points[j]]; + result.faces[outside_count].uvs[j] = mesh_merge.faces[i].uvs[j]; + } + + result.faces[outside_count].smooth = mesh_merge.faces[i].smooth; + result.faces[outside_count].invert = mesh_merge.faces[i].invert; + result.faces[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[inside_count].vertices[j] = mesh_merge.points[mesh_merge.faces[i].points[j]]; + result.faces[inside_count].uvs[j] = mesh_merge.faces[i].uvs[j]; + } + + result.faces[inside_count].smooth = mesh_merge.faces[i].smooth; + result.faces[inside_count].invert = mesh_merge.faces[i].invert; + result.faces[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[face_count].vertices[j] = mesh_merge.points[mesh_merge.faces[i].points[j]]; + result.faces[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[face_count].vertices[1], result.faces[face_count].vertices[2]); + SWAP(result.faces[face_count].uvs[1], result.faces[face_count].uvs[2]); + } + + result.faces[face_count].smooth = mesh_merge.faces[i].smooth; + result.faces[face_count].invert = mesh_merge.faces[i].invert; + result.faces[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, int>::Element *E = mesh_merge.materials.front(); E; E = E->next()) { + result.materials[E->get()] = E->key(); + } +} diff --git a/modules/csg/csg.h b/modules/csg/csg.h new file mode 100644 index 00000000000..d89e542b5eb --- /dev/null +++ b/modules/csg/csg.h @@ -0,0 +1,206 @@ +#ifndef CSG_H +#define CSG_H + +#include "aabb.h" +#include "dvector.h" +#include "map.h" +#include "math_2d.h" +#include "oa_hash_map.h" +#include "plane.h" +#include "scene/resources/material.h" +#include "transform.h" +#include "vector3.h" + +struct CSGBrush { + + struct Face { + + Vector3 vertices[3]; + Vector2 uvs[3]; + AABB aabb; + bool smooth; + bool invert; + int material; + }; + + Vector faces; + Vector > materials; + + void _regen_face_aabbs(); + //create a brush from faces + void build_from_faces(const PoolVector &p_vertices, const PoolVector &p_uvs, const PoolVector &p_smooth, const PoolVector > &p_materials, const PoolVector &p_invert_faces); + void copy_from(const CSGBrush &p_brush, const Transform &p_xform); + + void clear(); +}; + +struct CSGBrushOperation { + + enum Operation { + OPERATION_UNION, + OPERATION_INTERSECTION, + OPERATION_SUBSTRACTION, + + }; + + struct MeshMerge { + + struct BVH { + int face; + int left; + int right; + int next; + Vector3 center; + AABB aabb; + }; + + struct BVHCmpX { + + bool operator()(const BVH *p_left, const BVH *p_right) const { + + return p_left->center.x < p_right->center.x; + } + }; + + struct BVHCmpY { + + bool operator()(const BVH *p_left, const BVH *p_right) const { + + return p_left->center.y < p_right->center.y; + } + }; + struct BVHCmpZ { + + bool operator()(const BVH *p_left, const BVH *p_right) const { + + return p_left->center.z < p_right->center.z; + } + }; + + int _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; + int _create_bvh(BVH *p_bvh, BVH **p_bb, int p_from, int p_size, int p_depth, int &max_depth, int &max_alloc); + + struct VertexKey { + int32_t x, y, z; + _FORCE_INLINE_ bool operator<(const VertexKey &p_key) const { + if (x == p_key.x) { + if (y == p_key.y) { + return z < p_key.z; + } else { + return y < p_key.y; + } + } else { + return x < p_key.x; + } + } + + _FORCE_INLINE_ bool operator==(const VertexKey &p_key) const { + return (x == p_key.x && y == p_key.y && z == p_key.z); + } + }; + + struct VertexKeyHash { + static _FORCE_INLINE_ uint32_t hash(const VertexKey &p_vk) { + uint32_t h = hash_djb2_one_32(p_vk.x); + h = hash_djb2_one_32(p_vk.y, h); + h = hash_djb2_one_32(p_vk.z, h); + return h; + } + }; + + OAHashMap snap_cache; + + Vector points; + + struct Face { + bool from_b; + bool inside; + int points[3]; + Vector2 uvs[3]; + bool smooth; + bool invert; + int material_idx; + }; + + Vector faces; + + Map, int> materials; + + Map vertex_map; + void 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 &p_material, bool p_from_b); + // void add_face(const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_c, bool p_from_b); + + float vertex_snap; + void mark_inside_faces(); + }; + + struct BuildPoly { + + Plane plane; + Transform to_poly; + Transform to_world; + int face_index; + + struct Point { + Vector2 point; + Vector2 uv; + }; + + Vector points; + + struct Edge { + bool outer; + int points[2]; + Edge() { + outer = false; + } + }; + + Vector edges; + Ref material; + bool smooth; + bool invert; + + int base_edges; //edges from original triangle, even if split + + void _clip_segment(const CSGBrush *p_brush, int p_face, const Vector2 *segment, MeshMerge &mesh_merge, bool p_for_B); + + void create(const CSGBrush *p_brush, int p_face, MeshMerge &mesh_merge, bool p_for_B); + void clip(const CSGBrush *p_brush, int p_face, MeshMerge &mesh_merge, bool p_for_B); + }; + + struct PolyPoints { + + Vector points; + + Vector > holes; + }; + + struct EdgeSort { + int edge; + int prev_point; + int edge_point; + float angle; + bool operator<(const EdgeSort &p_edge) const { return angle < p_edge.angle; } + }; + + struct CallbackData { + const CSGBrush *A; + const CSGBrush *B; + int face_a; + CSGBrushOperation *self; + Map build_polys_A; + Map build_polys_B; + }; + + void _add_poly_points(const BuildPoly &p_poly, int p_edge, int p_from_point, int p_to_point, const Vector > &vertex_process, Vector &edge_process, Vector &r_poly); + void _add_poly_outline(const BuildPoly &p_poly, int p_from_point, int p_to_point, const Vector > &vertex_process, Vector &r_outline); + void _merge_poly(MeshMerge &mesh, int p_face_idx, const BuildPoly &p_poly, bool p_from_b); + + void _collision_callback(const CSGBrush *A, int p_face_a, Map &build_polys_a, const CSGBrush *B, int p_face_b, Map &build_polys_b, MeshMerge &mesh_merge); + + static void _collision_callbacks(void *ud, int p_face_b); + void merge_brushes(Operation p_operation, const CSGBrush &p_A, const CSGBrush &p_B, CSGBrush &result, float p_snap = 0.001); +}; + +#endif // CSG_H diff --git a/modules/csg/csg_gizmos.cpp b/modules/csg/csg_gizmos.cpp new file mode 100644 index 00000000000..06cbaab3b01 --- /dev/null +++ b/modules/csg/csg_gizmos.cpp @@ -0,0 +1,315 @@ +#include "csg_gizmos.h" + +/////////// + +String CSGShapeSpatialGizmo::get_handle_name(int p_idx) const { + + if (Object::cast_to(cs)) { + + return "Radius"; + } + + if (Object::cast_to(cs)) { + + static const char *hname[3] = { "Width", "Height", "Depth" }; + return hname[p_idx]; + } + + if (Object::cast_to(cs)) { + + return p_idx == 0 ? "Radius" : "Height"; + } + + if (Object::cast_to(cs)) { + + return p_idx == 0 ? "InnerRadius" : "OuterRadius"; + } + + return ""; +} +Variant CSGShapeSpatialGizmo::get_handle_value(int p_idx) const { + + if (Object::cast_to(cs)) { + + CSGSphere *s = Object::cast_to(cs); + return s->get_radius(); + } + + if (Object::cast_to(cs)) { + + CSGBox *s = Object::cast_to(cs); + switch (p_idx) { + case 0: return s->get_width(); + case 1: return s->get_height(); + case 2: return s->get_depth(); + } + } + + if (Object::cast_to(cs)) { + + CSGCylinder *s = Object::cast_to(cs); + return p_idx == 0 ? s->get_radius() : s->get_height(); + } + + if (Object::cast_to(cs)) { + + CSGTorus *s = Object::cast_to(cs); + return p_idx == 0 ? s->get_inner_radius() : s->get_outer_radius(); + } + + return Variant(); +} +void CSGShapeSpatialGizmo::set_handle(int p_idx, Camera *p_camera, const Point2 &p_point) { + + Transform gt = cs->get_global_transform(); + gt.orthonormalize(); + Transform gi = gt.affine_inverse(); + + Vector3 ray_from = p_camera->project_ray_origin(p_point); + Vector3 ray_dir = p_camera->project_ray_normal(p_point); + + Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 16384) }; + + if (Object::cast_to(cs)) { + + CSGSphere *s = Object::cast_to(cs); + + Vector3 ra, rb; + Geometry::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), sg[0], sg[1], ra, rb); + float d = ra.x; + if (d < 0.001) + d = 0.001; + + s->set_radius(d); + } + + if (Object::cast_to(cs)) { + + CSGBox *s = Object::cast_to(cs); + + Vector3 axis; + axis[p_idx] = 1.0; + Vector3 ra, rb; + Geometry::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); + float d = ra[p_idx]; + if (d < 0.001) + d = 0.001; + + switch (p_idx) { + case 0: s->set_width(d); break; + case 1: s->set_height(d); break; + case 2: s->set_depth(d); break; + } + } + + if (Object::cast_to(cs)) { + + CSGCylinder *s = Object::cast_to(cs); + + Vector3 axis; + axis[p_idx == 0 ? 0 : 1] = 1.0; + Vector3 ra, rb; + Geometry::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); + float d = axis.dot(ra); + + if (d < 0.001) + d = 0.001; + + if (p_idx == 0) + s->set_radius(d); + else if (p_idx == 1) + s->set_height(d * 2.0); + } + + if (Object::cast_to(cs)) { + + CSGTorus *s = Object::cast_to(cs); + + Vector3 axis; + axis[0] = 1.0; + Vector3 ra, rb; + Geometry::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); + float d = axis.dot(ra); + + if (d < 0.001) + d = 0.001; + + if (p_idx == 0) + s->set_inner_radius(d); + else if (p_idx == 1) + s->set_outer_radius(d); + } +} +void CSGShapeSpatialGizmo::commit_handle(int p_idx, const Variant &p_restore, bool p_cancel) { + + if (Object::cast_to(cs)) { + CSGSphere *s = Object::cast_to(cs); + if (p_cancel) { + s->set_radius(p_restore); + return; + } + + UndoRedo *ur = SpatialEditor::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Change Sphere Shape Radius")); + ur->add_do_method(s, "set_radius", s->get_radius()); + ur->add_undo_method(s, "set_radius", p_restore); + ur->commit_action(); + } + + if (Object::cast_to(cs)) { + CSGBox *s = Object::cast_to(cs); + if (p_cancel) { + switch (p_idx) { + case 0: s->set_width(p_restore); break; + case 1: s->set_height(p_restore); break; + case 2: s->set_depth(p_restore); break; + } + return; + } + + UndoRedo *ur = SpatialEditor::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Change Box Shape Extents")); + static const char *method[3] = { "set_width", "set_height", "set_depth" }; + float current; + switch (p_idx) { + case 0: current = s->get_width(); break; + case 1: current = s->get_height(); break; + case 2: current = s->get_depth(); break; + } + + ur->add_do_method(s, method[p_idx], current); + ur->add_undo_method(s, method[p_idx], p_restore); + ur->commit_action(); + } + + if (Object::cast_to(cs)) { + CSGCylinder *s = Object::cast_to(cs); + if (p_cancel) { + if (p_idx == 0) + s->set_radius(p_restore); + else + s->set_height(p_restore); + return; + } + + UndoRedo *ur = SpatialEditor::get_singleton()->get_undo_redo(); + if (p_idx == 0) { + ur->create_action(TTR("Change Cylinder Radius")); + ur->add_do_method(s, "set_radius", s->get_radius()); + ur->add_undo_method(s, "set_radius", p_restore); + } else { + ur->create_action(TTR("Change Cylinder Height")); + ur->add_do_method(s, "set_height", s->get_height()); + ur->add_undo_method(s, "set_height", p_restore); + } + + ur->commit_action(); + } + + if (Object::cast_to(cs)) { + CSGTorus *s = Object::cast_to(cs); + if (p_cancel) { + if (p_idx == 0) + s->set_inner_radius(p_restore); + else + s->set_outer_radius(p_restore); + return; + } + + UndoRedo *ur = SpatialEditor::get_singleton()->get_undo_redo(); + if (p_idx == 0) { + ur->create_action(TTR("Change Torus Inner Radius")); + ur->add_do_method(s, "set_inner_radius", s->get_inner_radius()); + ur->add_undo_method(s, "set_inner_radius", p_restore); + } else { + ur->create_action(TTR("Change Torus Outer Radius")); + ur->add_do_method(s, "set_outer_radius", s->get_outer_radius()); + ur->add_undo_method(s, "set_outer_radius", p_restore); + } + + ur->commit_action(); + } +} +void CSGShapeSpatialGizmo::redraw() { + + clear(); + + Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/csg"); + Ref material = create_material("shape_material", gizmo_color); + + PoolVector faces = cs->get_brush_faces(); + + Vector lines; + lines.resize(faces.size() * 2); + { + PoolVector::Read r = faces.read(); + + for (int i = 0; i < lines.size(); i += 6) { + int f = i / 6; + for (int j = 0; j < 3; j++) { + int j_n = (j + 1) % 3; + lines[i + j * 2 + 0] = r[f * 3 + j]; + lines[i + j * 2 + 1] = r[f * 3 + j_n]; + } + } + } + + add_lines(lines, material); + add_collision_segments(lines); + + if (Object::cast_to(cs)) { + CSGSphere *s = Object::cast_to(cs); + + float r = s->get_radius(); + Vector handles; + handles.push_back(Vector3(r, 0, 0)); + add_handles(handles); + } + + if (Object::cast_to(cs)) { + CSGBox *s = Object::cast_to(cs); + + Vector handles; + handles.push_back(Vector3(s->get_width(), 0, 0)); + handles.push_back(Vector3(0, s->get_height(), 0)); + handles.push_back(Vector3(0, 0, s->get_depth())); + add_handles(handles); + } + + if (Object::cast_to(cs)) { + CSGCylinder *s = Object::cast_to(cs); + + Vector handles; + handles.push_back(Vector3(s->get_radius(), 0, 0)); + handles.push_back(Vector3(0, s->get_height() * 0.5, 0)); + add_handles(handles); + } + + if (Object::cast_to(cs)) { + CSGTorus *s = Object::cast_to(cs); + + Vector handles; + handles.push_back(Vector3(s->get_inner_radius(), 0, 0)); + handles.push_back(Vector3(s->get_outer_radius(), 0, 0)); + add_handles(handles); + } +} +CSGShapeSpatialGizmo::CSGShapeSpatialGizmo(CSGShape *p_cs) { + + cs = p_cs; + set_spatial_node(p_cs); +} + +Ref EditorPluginCSG::create_spatial_gizmo(Spatial *p_spatial) { + if (Object::cast_to(p_spatial) || Object::cast_to(p_spatial) || Object::cast_to(p_spatial) || Object::cast_to(p_spatial) || Object::cast_to(p_spatial) || Object::cast_to(p_spatial)) { + Ref csg = memnew(CSGShapeSpatialGizmo(Object::cast_to(p_spatial))); + return csg; + } + + return Ref(); +} + +EditorPluginCSG::EditorPluginCSG(EditorNode *p_editor) { + + EDITOR_DEF("editors/3d_gizmos/gizmo_colors/csg", Color(0.2, 0.5, 1, 0.1)); +} diff --git a/modules/csg/csg_gizmos.h b/modules/csg/csg_gizmos.h new file mode 100644 index 00000000000..b5e394ecad9 --- /dev/null +++ b/modules/csg/csg_gizmos.h @@ -0,0 +1,30 @@ +#ifndef CSG_GIZMOS_H +#define CSG_GIZMOS_H + +#include "csg_shape.h" +#include "editor/editor_plugin.h" +#include "editor/spatial_editor_gizmos.h" + +class CSGShapeSpatialGizmo : public EditorSpatialGizmo { + + GDCLASS(CSGShapeSpatialGizmo, EditorSpatialGizmo); + + CSGShape *cs; + +public: + virtual String get_handle_name(int p_idx) const; + virtual Variant get_handle_value(int p_idx) const; + virtual void set_handle(int p_idx, Camera *p_camera, const Point2 &p_point); + virtual void commit_handle(int p_idx, const Variant &p_restore, bool p_cancel = false); + void redraw(); + CSGShapeSpatialGizmo(CSGShape *p_cs = NULL); +}; + +class EditorPluginCSG : public EditorPlugin { + GDCLASS(EditorPluginCSG, EditorPlugin) +public: + virtual Ref create_spatial_gizmo(Spatial *p_spatial); + EditorPluginCSG(EditorNode *p_editor); +}; + +#endif // CSG_GIZMOS_H diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp new file mode 100644 index 00000000000..4da94c3baaa --- /dev/null +++ b/modules/csg/csg_shape.cpp @@ -0,0 +1,2152 @@ +#include "csg_shape.h" +#include "scene/3d/path.h" + +void CSGShape::set_use_collision(bool p_enable) { + + if (use_collision == p_enable) + return; + + use_collision = p_enable; + + if (!is_inside_tree() || !is_root_shape()) + return; + + if (use_collision) { + root_collision_shape.instance(); + root_collision_instance = PhysicsServer::get_singleton()->body_create(PhysicsServer::BODY_MODE_STATIC); + PhysicsServer::get_singleton()->body_set_state(root_collision_instance, PhysicsServer::BODY_STATE_TRANSFORM, get_global_transform()); + PhysicsServer::get_singleton()->body_add_shape(root_collision_instance, root_collision_shape->get_rid()); + PhysicsServer::get_singleton()->body_set_space(root_collision_instance, get_world()->get_space()); + _make_dirty(); //force update + } else { + PhysicsServer::get_singleton()->free(root_collision_instance); + root_collision_instance = RID(); + root_collision_shape.unref(); + } +} + +bool CSGShape::is_using_collision() const { + return use_collision; +} + +bool CSGShape::is_root_shape() const { + + return !parent; +} + +void CSGShape::_make_dirty() { + + if (!is_inside_tree()) + return; + + if (dirty) { + return; + } + + dirty = true; + + if (parent) { + parent->_make_dirty(); + } else { + //only parent will do + call_deferred("_update_shape"); + } +} + +CSGBrush *CSGShape::_get_brush() { + + if (dirty) { + if (brush) { + memdelete(brush); + } + brush = NULL; + brush = _build_brush(&node_aabb); + dirty = false; + } + + return brush; +} + +void CSGShape::_update_shape() { + + //print_line("updating shape for " + String(get_path())); + set_base(RID()); + root_mesh.unref(); //byebye root mesh + + CSGBrush *n = _get_brush(); + ERR_FAIL_COND(!n); + + OAHashMap vec_map; + + Vector face_count; + face_count.resize(n->materials.size() + 1); + for (int i = 0; i < face_count.size(); i++) { + face_count[i] = 0; + } + + for (int i = 0; i < n->faces.size(); i++) { + int mat = n->faces[i].material; + ERR_CONTINUE(mat < -1 || mat >= face_count.size()); + int idx = mat == -1 ? face_count.size() - 1 : mat; + if (n->faces[i].smooth) { + + Plane p(n->faces[i].vertices[0], n->faces[i].vertices[1], n->faces[i].vertices[2]); + + for (int j = 0; j < 3; j++) { + Vector3 v = n->faces[i].vertices[j]; + Vector3 add; + if (vec_map.lookup(v, &add)) { + add += p.normal; + } else { + add = p.normal; + } + vec_map.set(v, add); + } + } + + face_count[idx]++; + } + + Vector surfaces; + + surfaces.resize(face_count.size()); + + //create arrays + for (int i = 0; i < surfaces.size(); i++) { + + surfaces[i].vertices.resize(face_count[i] * 3); + surfaces[i].normals.resize(face_count[i] * 3); + surfaces[i].uvs.resize(face_count[i] * 3); + surfaces[i].last_added = 0; + + if (i != surfaces.size() - 1) { + surfaces[i].material = n->materials[i]; + } + + surfaces[i].verticesw = surfaces[i].vertices.write(); + surfaces[i].normalsw = surfaces[i].normals.write(); + surfaces[i].uvsw = surfaces[i].uvs.write(); + } + + //fill arrays + PoolVector physics_faces; + bool fill_physics_faces = false; + if (root_collision_shape.is_valid()) { + physics_faces.resize(n->faces.size() * 3); + fill_physics_faces = true; + } + + { + PoolVector::Write physicsw; + + if (fill_physics_faces) { + physicsw = physics_faces.write(); + } + + for (int i = 0; i < n->faces.size(); i++) { + + int order[3] = { 0, 1, 2 }; + + if (n->faces[i].invert) { + SWAP(order[1], order[2]); + } + + if (fill_physics_faces) { + physicsw[i * 3 + 0] = n->faces[i].vertices[order[0]]; + physicsw[i * 3 + 1] = n->faces[i].vertices[order[1]]; + physicsw[i * 3 + 2] = n->faces[i].vertices[order[2]]; + } + + int mat = n->faces[i].material; + ERR_CONTINUE(mat < -1 || mat >= face_count.size()); + int idx = mat == -1 ? face_count.size() - 1 : mat; + + int last = surfaces[idx].last_added; + + Plane p(n->faces[i].vertices[0], n->faces[i].vertices[1], n->faces[i].vertices[2]); + + for (int j = 0; j < 3; j++) { + + Vector3 v = n->faces[i].vertices[j]; + + Vector3 normal = p.normal; + + if (n->faces[i].smooth && vec_map.lookup(v, &normal)) { + normal.normalize(); + } + + if (n->faces[i].invert) { + + normal = -normal; + } + + surfaces[idx].verticesw[last + order[j]] = v; + surfaces[idx].uvsw[last + order[j]] = n->faces[i].uvs[j]; + surfaces[idx].normalsw[last + order[j]] = normal; + } + + surfaces[idx].last_added += 3; + } + } + + root_mesh.instance(); + //create surfaces + + for (int i = 0; i < surfaces.size(); i++) { + + surfaces[i].verticesw = PoolVector::Write(); + surfaces[i].normalsw = PoolVector::Write(); + surfaces[i].uvsw = PoolVector::Write(); + + if (surfaces[i].last_added == 0) + continue; + + Array array; + array.resize(Mesh::ARRAY_MAX); + + array[Mesh::ARRAY_VERTEX] = surfaces[i].vertices; + array[Mesh::ARRAY_NORMAL] = surfaces[i].normals; + array[Mesh::ARRAY_TEX_UV] = surfaces[i].uvs; + + int idx = root_mesh->get_surface_count(); + root_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, array); + root_mesh->surface_set_material(idx, surfaces[i].material); + } + + if (root_collision_shape.is_valid()) { + root_collision_shape->set_faces(physics_faces); + } + + set_base(root_mesh->get_rid()); +} +AABB CSGShape::get_aabb() const { + return node_aabb; +} + +PoolVector CSGShape::get_brush_faces() { + ERR_FAIL_COND_V(!is_inside_tree(), PoolVector()); + CSGBrush *b = _get_brush(); + + PoolVector faces; + int fc = b->faces.size(); + faces.resize(fc * 3); + { + PoolVector::Write w = faces.write(); + for (int i = 0; i < fc; i++) { + w[i * 3 + 0] = b->faces[i].vertices[0]; + w[i * 3 + 1] = b->faces[i].vertices[1]; + w[i * 3 + 2] = b->faces[i].vertices[2]; + } + } + + return faces; +} + +PoolVector CSGShape::get_faces(uint32_t p_usage_flags) const { + + return PoolVector(); +} + +void CSGShape::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE) { + + Node *parentn = get_parent(); + if (parentn) { + parent = Object::cast_to(parentn); + } + + if (use_collision && is_root_shape()) { + root_collision_shape.instance(); + root_collision_instance = PhysicsServer::get_singleton()->body_create(PhysicsServer::BODY_MODE_STATIC); + PhysicsServer::get_singleton()->body_set_state(root_collision_instance, PhysicsServer::BODY_STATE_TRANSFORM, get_global_transform()); + PhysicsServer::get_singleton()->body_add_shape(root_collision_instance, root_collision_shape->get_rid()); + PhysicsServer::get_singleton()->body_set_space(root_collision_instance, get_world()->get_space()); + } + + _make_dirty(); + } + + if (p_what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED) { + + //print_line("local xform changed"); + if (parent) { + parent->_make_dirty(); + } + } + + if (p_what == NOTIFICATION_EXIT_TREE) { + if (parent) + parent->_make_dirty(); + parent = NULL; + + if (use_collision && is_root_shape()) { + PhysicsServer::get_singleton()->free(root_collision_instance); + root_collision_instance = RID(); + root_collision_shape.unref(); + } + _make_dirty(); + } +} + +void CSGShape::set_operation(Operation p_operation) { + + operation = p_operation; + _make_dirty(); +} + +CSGShape::Operation CSGShape::get_operation() const { + return operation; +} + +void CSGShape::_validate_property(PropertyInfo &property) const { + if (is_inside_tree() && property.name.begins_with("use_collision") && !is_root_shape()) { + //hide collision if not root + property.usage = PROPERTY_USAGE_NOEDITOR; + } + if (is_inside_tree() && property.name.begins_with("operation")) { + //hide operation for first node or root + if (is_root_shape()) { + property.usage = PROPERTY_USAGE_NOEDITOR; + } else { + for (int i = 0; i < get_parent()->get_child_count(); i++) { + CSGShape *s = Object::cast_to(get_parent()->get_child(i)); + if (!s) + continue; + + if (s == this) { + property.usage = PROPERTY_USAGE_NOEDITOR; + } + break; + } + } + } +} + +void CSGShape::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_update_shape"), &CSGShape::_update_shape); + ClassDB::bind_method(D_METHOD("is_root_shape"), &CSGShape::is_root_shape); + + ClassDB::bind_method(D_METHOD("set_operation", "operation"), &CSGShape::set_operation); + ClassDB::bind_method(D_METHOD("get_operation"), &CSGShape::get_operation); + + ClassDB::bind_method(D_METHOD("set_use_collision", "operation"), &CSGShape::set_use_collision); + ClassDB::bind_method(D_METHOD("is_using_collision"), &CSGShape::is_using_collision); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "operation", PROPERTY_HINT_ENUM, "Union,Intersection,Subtraction"), "set_operation", "get_operation"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_collision"), "set_use_collision", "is_using_collision"); + + BIND_CONSTANT(OPERATION_UNION); + BIND_CONSTANT(OPERATION_INTERSECTION); + BIND_CONSTANT(OPERATION_SUBTRACTION); +} + +CSGShape::CSGShape() { + brush = NULL; + set_notify_local_transform(true); + dirty = false; + parent = NULL; + use_collision = false; + operation = OPERATION_UNION; +} + +CSGShape::~CSGShape() { + if (brush) { + memdelete(brush); + brush = NULL; + } +} +////////////////////////////////// + +CSGBrush *CSGCombiner::_build_brush(AABB *r_aabb) { + + CSGBrush *n = NULL; + + for (int i = 0; i < get_child_count(); i++) { + + CSGShape *child = Object::cast_to(get_child(i)); + if (!child) + continue; + if (!child->is_visible_in_tree()) + continue; + + CSGBrush *n2 = child->_get_brush(); + if (!n2) + continue; + if (!n) { + n = memnew(CSGBrush); + + n->copy_from(*n2, child->get_transform()); + + } else { + + CSGBrush *nn = memnew(CSGBrush); + CSGBrush *nn2 = memnew(CSGBrush); + nn2->copy_from(*n2, child->get_transform()); + + CSGBrushOperation bop; + + switch (child->get_operation()) { + case CSGShape::OPERATION_UNION: bop.merge_brushes(CSGBrushOperation::OPERATION_UNION, *n, *nn2, *nn, snap); break; + case CSGShape::OPERATION_INTERSECTION: bop.merge_brushes(CSGBrushOperation::OPERATION_INTERSECTION, *n, *nn2, *nn, snap); break; + case CSGShape::OPERATION_SUBTRACTION: bop.merge_brushes(CSGBrushOperation::OPERATION_SUBSTRACTION, *n, *nn2, *nn, snap); break; + } + memdelete(n); + memdelete(nn2); + n = nn; + } + } + + if (n) { + AABB aabb; + for (int i = 0; i < n->faces.size(); i++) { + for (int j = 0; j < 3; j++) { + if (i == 0 && j == 0) + aabb.position = n->faces[i].vertices[j]; + else + aabb.expand_to(n->faces[i].vertices[j]); + } + } + *r_aabb = aabb; + } else { + *r_aabb = AABB(); + } + return n; +} + +void CSGCombiner::set_snap(float p_snap) { + snap = p_snap; +} + +float CSGCombiner::get_snap() const { + return snap; +} + +void CSGCombiner::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_snap", "snap"), &CSGCombiner::set_snap); + ClassDB::bind_method(D_METHOD("get_snap"), &CSGCombiner::get_snap); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "snap", PROPERTY_HINT_RANGE, "0.0001,1,0.001"), "set_snap", "get_snap"); +} + +CSGCombiner::CSGCombiner() { + snap = 0.001; +} + +///////////////////// + +CSGBrush *CSGPrimitive::_create_brush_from_arrays(const PoolVector &p_vertices, const PoolVector &p_uv, const PoolVector &p_smooth, const PoolVector > &p_materials) { + + CSGBrush *brush = memnew(CSGBrush); + + PoolVector invert; + invert.resize(p_vertices.size() / 3); + { + int ic = invert.size(); + PoolVector::Write w = invert.write(); + for (int i = 0; i < ic; i++) { + w[i] = invert_faces; + } + } + brush->build_from_faces(p_vertices, p_uv, p_smooth, p_materials, invert); + + return brush; +} + +void CSGPrimitive::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_invert_faces", "invert_faces"), &CSGPrimitive::set_invert_faces); + ClassDB::bind_method(D_METHOD("is_inverting_faces"), &CSGPrimitive::is_inverting_faces); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "invert_faces"), "set_invert_faces", "is_inverting_faces"); +} + +void CSGPrimitive::set_invert_faces(bool p_invert) { + if (invert_faces == p_invert) + return; + + invert_faces = p_invert; + + _make_dirty(); +} + +bool CSGPrimitive::is_inverting_faces() { + return invert_faces; +} + +CSGPrimitive::CSGPrimitive() { + invert_faces = false; +} + +///////////////////// + +CSGBrush *CSGMesh::_build_brush(AABB *r_aabb) { + + if (!mesh.is_valid()) + return NULL; + + PoolVector vertices; + PoolVector smooth; + PoolVector > materials; + PoolVector uvs; + + *r_aabb = AABB(); + + for (int i = 0; i < mesh->get_surface_count(); i++) { + + if (mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { + continue; + } + + Array arrays = mesh->surface_get_arrays(i); + + PoolVector avertices = arrays[Mesh::ARRAY_VERTEX]; + if (avertices.size() == 0) + continue; + + PoolVector::Read vr = avertices.read(); + + PoolVector anormals = arrays[Mesh::ARRAY_NORMAL]; + PoolVector::Read nr; + bool nr_used = false; + if (anormals.size()) { + nr = anormals.read(); + nr_used = true; + } + + PoolVector auvs = arrays[Mesh::ARRAY_TEX_UV]; + PoolVector::Read uvr; + bool uvr_used = false; + if (auvs.size()) { + uvr = auvs.read(); + uvr_used = true; + } + + Ref mat = mesh->surface_get_material(i); + + PoolVector aindices = arrays[Mesh::ARRAY_INDEX]; + if (aindices.size()) { + int as = vertices.size(); + int is = aindices.size(); + + vertices.resize(as + is); + smooth.resize((as + is) / 3); + materials.resize((as + is) / 3); + uvs.resize(as + is); + + PoolVector::Write vw = vertices.write(); + PoolVector::Write sw = smooth.write(); + PoolVector::Write uvw = uvs.write(); + PoolVector >::Write mw = materials.write(); + + PoolVector::Read ir = aindices.read(); + + for (int j = 0; j < is; j += 3) { + + Vector3 vertex[3]; + Vector3 normal[3]; + Vector2 uv[3]; + + for (int k = 0; k < 3; k++) { + int idx = ir[j + k]; + vertex[k] = vr[idx]; + if (nr_used) { + normal[k] = nr[idx]; + } + if (uvr_used) { + uv[k] = uvr[idx]; + } + } + + bool flat = normal[0].distance_to(normal[1]) < CMP_EPSILON && normal[0].distance_to(normal[2]) < CMP_EPSILON; + + vw[as + j + 0] = vertex[0]; + vw[as + j + 1] = vertex[1]; + vw[as + j + 2] = vertex[2]; + + uvw[as + j + 0] = uv[0]; + uvw[as + j + 1] = uv[1]; + uvw[as + j + 2] = uv[2]; + + sw[j / 3] = !flat; + mw[j / 3] = mat; + } + } else { + int is = vertices.size(); + int as = avertices.size(); + + vertices.resize(as + is); + smooth.resize((as + is) / 3); + uvs.resize(as + is); + materials.resize((as + is) / 3); + + PoolVector::Write vw = vertices.write(); + PoolVector::Write sw = smooth.write(); + PoolVector::Write uvw = uvs.write(); + PoolVector >::Write mw = materials.write(); + + for (int j = 0; j < is; j += 3) { + + Vector3 vertex[3]; + Vector3 normal[3]; + Vector2 uv[3]; + + for (int k = 0; k < 3; k++) { + vertex[k] = vr[j + k]; + if (nr_used) { + normal[k] = nr[j + k]; + } + if (uvr_used) { + uv[k] = uvr[j + k]; + } + } + + bool flat = normal[0].distance_to(normal[1]) < CMP_EPSILON && normal[0].distance_to(normal[2]) < CMP_EPSILON; + + vw[as + j + 0] = vertex[0]; + vw[as + j + 1] = vertex[1]; + vw[as + j + 2] = vertex[2]; + + uvw[as + j + 0] = uv[0]; + uvw[as + j + 1] = uv[1]; + uvw[as + j + 2] = uv[2]; + + sw[j / 3] = !flat; + mw[j / 3] = mat; + } + } + } + + //print_line("total vertices? " + itos(vertices.size())); + if (vertices.size() == 0) + return NULL; + + return _create_brush_from_arrays(vertices, uvs, smooth, materials); +} + +void CSGMesh::_mesh_changed() { + _make_dirty(); + update_gizmo(); +} + +void CSGMesh::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &CSGMesh::set_mesh); + ClassDB::bind_method(D_METHOD("get_mesh"), &CSGMesh::get_mesh); + + ClassDB::bind_method(D_METHOD("_mesh_changed"), &CSGMesh::_mesh_changed); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh"); +} + +void CSGMesh::set_mesh(const Ref &p_mesh) { + + if (mesh == p_mesh) + return; + if (mesh.is_valid()) { + mesh->disconnect("changed", this, "_mesh_changed"); + } + mesh = p_mesh; + + if (mesh.is_valid()) { + mesh->connect("changed", this, "_mesh_changed"); + } + + _make_dirty(); +} + +Ref CSGMesh::get_mesh() { + return mesh; +} + +//////////////////////////////// + +CSGBrush *CSGSphere::_build_brush(AABB *r_aabb) { + + // set our bounding box + + CSGBrush *brush = memnew(CSGBrush); + + int face_count = rings * radial_segments * 2 - radial_segments * 2; + + bool invert_val = is_inverting_faces(); + Ref material = get_material(); + + PoolVector faces; + PoolVector uvs; + PoolVector smooth; + PoolVector > materials; + PoolVector invert; + + faces.resize(face_count * 3); + uvs.resize(face_count * 3); + + smooth.resize(face_count); + materials.resize(face_count); + invert.resize(face_count); + + { + + PoolVector::Write facesw = faces.write(); + PoolVector::Write uvsw = uvs.write(); + PoolVector::Write smoothw = smooth.write(); + PoolVector >::Write materialsw = materials.write(); + PoolVector::Write invertw = invert.write(); + + int face = 0; + + for (int i = 1; i <= rings; i++) { + double lat0 = Math_PI * (-0.5 + (double)(i - 1) / rings); + double z0 = Math::sin(lat0); + double zr0 = Math::cos(lat0); + double u0 = double(i - 1) / rings; + + double lat1 = Math_PI * (-0.5 + (double)i / rings); + double z1 = Math::sin(lat1); + double zr1 = Math::cos(lat1); + double u1 = double(i) / rings; + + for (int j = radial_segments; j >= 1; j--) { + + double lng0 = 2 * Math_PI * (double)(j - 1) / radial_segments; + double x0 = Math::cos(lng0); + double y0 = Math::sin(lng0); + double v0 = double(i - 1) / radial_segments; + + double lng1 = 2 * Math_PI * (double)(j) / radial_segments; + double x1 = Math::cos(lng1); + double y1 = Math::sin(lng1); + double v1 = double(i) / radial_segments; + + Vector3 v[4] = { + Vector3(x1 * zr0, z0, y1 * zr0) * radius, + Vector3(x1 * zr1, z1, y1 * zr1) * radius, + Vector3(x0 * zr1, z1, y0 * zr1) * radius, + Vector3(x0 * zr0, z0, y0 * zr0) * radius + }; + + Vector2 u[4] = { + Vector2(v1, u0), + Vector2(v1, u1), + Vector2(v0, u1), + Vector2(v0, u0), + + }; + + if (i < rings) { + + //face 1 + facesw[face * 3 + 0] = v[0]; + facesw[face * 3 + 1] = v[1]; + facesw[face * 3 + 2] = v[2]; + + uvsw[face * 3 + 0] = u[0]; + uvsw[face * 3 + 1] = u[1]; + uvsw[face * 3 + 2] = u[2]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + } + + if (i > 1) { + //face 2 + facesw[face * 3 + 0] = v[2]; + facesw[face * 3 + 1] = v[3]; + facesw[face * 3 + 2] = v[0]; + + uvsw[face * 3 + 0] = u[2]; + uvsw[face * 3 + 1] = u[3]; + uvsw[face * 3 + 2] = u[0]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + } + } + } + + if (face != face_count) { + ERR_PRINT("Face mismatch bug! fix code"); + } + } + + brush->build_from_faces(faces, uvs, smooth, materials, invert); + + if (r_aabb) { + *r_aabb = AABB(Vector3(-radius, -radius, -radius), Vector3(radius * 2, radius * 2, radius * 2)); + } + return brush; +} + +void CSGSphere::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CSGSphere::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &CSGSphere::get_radius); + + ClassDB::bind_method(D_METHOD("set_radial_segments", "radial_segments"), &CSGSphere::set_radial_segments); + ClassDB::bind_method(D_METHOD("get_radial_segments"), &CSGSphere::get_radial_segments); + ClassDB::bind_method(D_METHOD("set_rings", "rings"), &CSGSphere::set_rings); + ClassDB::bind_method(D_METHOD("get_rings"), &CSGSphere::get_rings); + + ClassDB::bind_method(D_METHOD("set_smooth_faces", "smooth_faces"), &CSGSphere::set_smooth_faces); + ClassDB::bind_method(D_METHOD("get_smooth_faces"), &CSGSphere::get_smooth_faces); + + ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGSphere::set_material); + ClassDB::bind_method(D_METHOD("get_material"), &CSGSphere::get_material); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1"), "set_radial_segments", "get_radial_segments"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1"), "set_rings", "get_rings"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_faces"), "set_smooth_faces", "get_smooth_faces"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "SpatialMaterial,ShaderMaterial"), "set_material", "get_material"); +} + +void CSGSphere::set_radius(const float p_radius) { + ERR_FAIL_COND(p_radius <= 0); + radius = p_radius; + _make_dirty(); + update_gizmo(); +} + +float CSGSphere::get_radius() const { + return radius; +} + +void CSGSphere::set_radial_segments(const int p_radial_segments) { + radial_segments = p_radial_segments > 4 ? p_radial_segments : 4; + _make_dirty(); + update_gizmo(); +} + +int CSGSphere::get_radial_segments() const { + return radial_segments; +} + +void CSGSphere::set_rings(const int p_rings) { + rings = p_rings > 1 ? p_rings : 1; + _make_dirty(); + update_gizmo(); +} + +int CSGSphere::get_rings() const { + return rings; +} + +void CSGSphere::set_smooth_faces(const bool p_smooth_faces) { + smooth_faces = p_smooth_faces; + _make_dirty(); +} + +bool CSGSphere::get_smooth_faces() const { + return smooth_faces; +} + +void CSGSphere::set_material(const Ref &p_material) { + + material = p_material; + _make_dirty(); +} + +Ref CSGSphere::get_material() const { + + return material; +} + +CSGSphere::CSGSphere() { + // defaults + radius = 1.0; + radial_segments = 12; + rings = 6; + smooth_faces = true; +} + +/////////////// + +CSGBrush *CSGBox::_build_brush(AABB *r_aabb) { + + // set our bounding box + + CSGBrush *brush = memnew(CSGBrush); + + int face_count = 12; //it's a cube.. + + bool invert_val = is_inverting_faces(); + Ref material = get_material(); + + PoolVector faces; + PoolVector uvs; + PoolVector smooth; + PoolVector > materials; + PoolVector invert; + + faces.resize(face_count * 3); + uvs.resize(face_count * 3); + + smooth.resize(face_count); + materials.resize(face_count); + invert.resize(face_count); + + { + + PoolVector::Write facesw = faces.write(); + PoolVector::Write uvsw = uvs.write(); + PoolVector::Write smoothw = smooth.write(); + PoolVector >::Write materialsw = materials.write(); + PoolVector::Write invertw = invert.write(); + + int face = 0; + + Vector3 vertex_mul(width, height, depth); + + { + + for (int i = 0; i < 6; i++) { + + Vector3 face_points[4]; + float uv_points[8] = { 0, 0, 0, 1, 1, 1, 1, 0 }; + + for (int j = 0; j < 4; j++) { + + float v[3]; + v[0] = 1.0; + v[1] = 1 - 2 * ((j >> 1) & 1); + v[2] = v[1] * (1 - 2 * (j & 1)); + + for (int k = 0; k < 3; k++) { + + if (i < 3) + face_points[j][(i + k) % 3] = v[k] * (i >= 3 ? -1 : 1); + else + face_points[3 - j][(i + k) % 3] = v[k] * (i >= 3 ? -1 : 1); + } + } + + Vector2 u[4]; + for (int j = 0; j < 4; j++) { + u[j] = Vector2(uv_points[j * 2 + 0], uv_points[j * 2 + 1]); + } + + //face 1 + facesw[face * 3 + 0] = face_points[0] * vertex_mul; + facesw[face * 3 + 1] = face_points[1] * vertex_mul; + facesw[face * 3 + 2] = face_points[2] * vertex_mul; + + uvsw[face * 3 + 0] = u[0]; + uvsw[face * 3 + 1] = u[1]; + uvsw[face * 3 + 2] = u[2]; + + smoothw[face] = false; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + //face 1 + facesw[face * 3 + 0] = face_points[2] * vertex_mul; + facesw[face * 3 + 1] = face_points[3] * vertex_mul; + facesw[face * 3 + 2] = face_points[0] * vertex_mul; + + uvsw[face * 3 + 0] = u[2]; + uvsw[face * 3 + 1] = u[3]; + uvsw[face * 3 + 2] = u[0]; + + smoothw[face] = false; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + } + } + + if (face != face_count) { + ERR_PRINT("Face mismatch bug! fix code"); + } + } + + brush->build_from_faces(faces, uvs, smooth, materials, invert); + + if (r_aabb) { + *r_aabb = AABB(Vector3(-width / 2, -height / 2, -depth / 2), Vector3(width, height, depth)); + } + return brush; +} + +void CSGBox::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_width", "width"), &CSGBox::set_width); + ClassDB::bind_method(D_METHOD("get_width"), &CSGBox::get_width); + + ClassDB::bind_method(D_METHOD("set_height", "height"), &CSGBox::set_height); + ClassDB::bind_method(D_METHOD("get_height"), &CSGBox::get_height); + + ClassDB::bind_method(D_METHOD("set_depth", "depth"), &CSGBox::set_depth); + ClassDB::bind_method(D_METHOD("get_depth"), &CSGBox::get_depth); + + ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGBox::set_material); + ClassDB::bind_method(D_METHOD("get_material"), &CSGBox::get_material); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "width", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001"), "set_width", "get_width"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "height", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "depth", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001"), "set_depth", "get_depth"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "SpatialMaterial,ShaderMaterial"), "set_material", "get_material"); +} + +void CSGBox::set_width(const float p_width) { + width = p_width; + _make_dirty(); + update_gizmo(); +} + +float CSGBox::get_width() const { + return width; +} + +void CSGBox::set_height(const float p_height) { + height = p_height; + _make_dirty(); + update_gizmo(); +} + +float CSGBox::get_height() const { + return height; +} + +void CSGBox::set_depth(const float p_depth) { + depth = p_depth; + _make_dirty(); + update_gizmo(); +} + +float CSGBox::get_depth() const { + return depth; +} + +void CSGBox::set_material(const Ref &p_material) { + + material = p_material; + _make_dirty(); + update_gizmo(); +} + +Ref CSGBox::get_material() const { + + return material; +} + +CSGBox::CSGBox() { + // defaults + width = 1.0; + height = 1.0; + depth = 1.0; +} + +/////////////// + +CSGBrush *CSGCylinder::_build_brush(AABB *r_aabb) { + + // set our bounding box + + CSGBrush *brush = memnew(CSGBrush); + + int face_count = sides * (cone ? 1 : 2) + sides + (cone ? 0 : sides); + + bool invert_val = is_inverting_faces(); + Ref material = get_material(); + + PoolVector faces; + PoolVector uvs; + PoolVector smooth; + PoolVector > materials; + PoolVector invert; + + faces.resize(face_count * 3); + uvs.resize(face_count * 3); + + smooth.resize(face_count); + materials.resize(face_count); + invert.resize(face_count); + + { + + PoolVector::Write facesw = faces.write(); + PoolVector::Write uvsw = uvs.write(); + PoolVector::Write smoothw = smooth.write(); + PoolVector >::Write materialsw = materials.write(); + PoolVector::Write invertw = invert.write(); + + int face = 0; + + Vector3 vertex_mul(radius, height * 0.5, radius); + + { + + for (int i = 0; i < sides; i++) { + + float inc = float(i) / sides; + float inc_n = float((i + 1)) / sides; + + float ang = inc * Math_PI * 2.0; + float ang_n = inc_n * Math_PI * 2.0; + + Vector3 base(Math::cos(ang), 0, Math::sin(ang)); + Vector3 base_n(Math::cos(ang_n), 0, Math::sin(ang_n)); + + Vector3 face_points[4] = { + base + Vector3(0, -1, 0), + base_n + Vector3(0, -1, 0), + base_n * (cone ? 0.0 : 1.0) + Vector3(0, 1, 0), + base * (cone ? 0.0 : 1.0) + Vector3(0, 1, 0), + }; + + Vector2 u[4] = { + Vector2(inc, 0), + Vector2(inc_n, 0), + Vector2(inc_n, 1), + Vector2(inc, 1), + }; + + //side face 1 + facesw[face * 3 + 0] = face_points[0] * vertex_mul; + facesw[face * 3 + 1] = face_points[1] * vertex_mul; + facesw[face * 3 + 2] = face_points[2] * vertex_mul; + + uvsw[face * 3 + 0] = u[0]; + uvsw[face * 3 + 1] = u[1]; + uvsw[face * 3 + 2] = u[2]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + + if (!cone) { + //side face 2 + facesw[face * 3 + 0] = face_points[2] * vertex_mul; + facesw[face * 3 + 1] = face_points[3] * vertex_mul; + facesw[face * 3 + 2] = face_points[0] * vertex_mul; + + uvsw[face * 3 + 0] = u[2]; + uvsw[face * 3 + 1] = u[3]; + uvsw[face * 3 + 2] = u[0]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_val; + materialsw[face] = material; + face++; + } + + //bottom face 1 + facesw[face * 3 + 0] = face_points[1] * vertex_mul; + facesw[face * 3 + 1] = face_points[0] * vertex_mul; + facesw[face * 3 + 2] = Vector3(0, -1, 0) * vertex_mul; + + uvsw[face * 3 + 0] = Vector2(face_points[1].x, face_points[1].y) * 0.5 + Vector2(0.5, 0.5); + uvsw[face * 3 + 1] = Vector2(face_points[0].x, face_points[0].y) * 0.5 + Vector2(0.5, 0.5); + uvsw[face * 3 + 2] = Vector2(0.5, 0.5); + + smoothw[face] = false; + invertw[face] = invert_val; + materialsw[face] = material; + face++; + + if (!cone) { + //top face 1 + facesw[face * 3 + 0] = face_points[3] * vertex_mul; + facesw[face * 3 + 1] = face_points[2] * vertex_mul; + facesw[face * 3 + 2] = Vector3(0, 1, 0) * vertex_mul; + + uvsw[face * 3 + 0] = Vector2(face_points[1].x, face_points[1].y) * 0.5 + Vector2(0.5, 0.5); + uvsw[face * 3 + 1] = Vector2(face_points[0].x, face_points[0].y) * 0.5 + Vector2(0.5, 0.5); + uvsw[face * 3 + 2] = Vector2(0.5, 0.5); + + smoothw[face] = false; + invertw[face] = invert_val; + materialsw[face] = material; + face++; + } + } + } + + if (face != face_count) { + ERR_PRINT("Face mismatch bug! fix code"); + } + } + + brush->build_from_faces(faces, uvs, smooth, materials, invert); + + if (r_aabb) { + *r_aabb = AABB(Vector3(-radius, -height / 2, -radius), Vector3(radius * 2, height, radius * 2)); + } + return brush; +} + +void CSGCylinder::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CSGCylinder::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &CSGCylinder::get_radius); + + ClassDB::bind_method(D_METHOD("set_height", "height"), &CSGCylinder::set_height); + ClassDB::bind_method(D_METHOD("get_height"), &CSGCylinder::get_height); + + ClassDB::bind_method(D_METHOD("set_sides", "sides"), &CSGCylinder::set_sides); + ClassDB::bind_method(D_METHOD("get_sides"), &CSGCylinder::get_sides); + + ClassDB::bind_method(D_METHOD("set_cone", "cone"), &CSGCylinder::set_cone); + ClassDB::bind_method(D_METHOD("is_cone"), &CSGCylinder::is_cone); + + ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGCylinder::set_material); + ClassDB::bind_method(D_METHOD("get_material"), &CSGCylinder::get_material); + + ClassDB::bind_method(D_METHOD("set_smooth_faces", "smooth_faces"), &CSGCylinder::set_smooth_faces); + ClassDB::bind_method(D_METHOD("get_smooth_faces"), &CSGCylinder::get_smooth_faces); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "radius", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "height", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "sides", PROPERTY_HINT_RANGE, "3,64,1"), "set_sides", "get_sides"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cone"), "set_cone", "is_cone"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_faces"), "set_smooth_faces", "get_smooth_faces"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "SpatialMaterial,ShaderMaterial"), "set_material", "get_material"); +} + +void CSGCylinder::set_radius(const float p_radius) { + radius = p_radius; + _make_dirty(); + update_gizmo(); +} + +float CSGCylinder::get_radius() const { + return radius; +} + +void CSGCylinder::set_height(const float p_height) { + height = p_height; + _make_dirty(); + update_gizmo(); +} + +float CSGCylinder::get_height() const { + return height; +} + +void CSGCylinder::set_sides(const int p_sides) { + ERR_FAIL_COND(p_sides < 3); + sides = p_sides; + _make_dirty(); + update_gizmo(); +} + +int CSGCylinder::get_sides() const { + return sides; +} + +void CSGCylinder::set_cone(const bool p_cone) { + cone = p_cone; + _make_dirty(); + update_gizmo(); +} + +bool CSGCylinder::is_cone() const { + return cone; +} + +void CSGCylinder::set_smooth_faces(const bool p_smooth_faces) { + smooth_faces = p_smooth_faces; + _make_dirty(); +} + +bool CSGCylinder::get_smooth_faces() const { + return smooth_faces; +} + +void CSGCylinder::set_material(const Ref &p_material) { + + material = p_material; + _make_dirty(); +} + +Ref CSGCylinder::get_material() const { + + return material; +} + +CSGCylinder::CSGCylinder() { + // defaults + radius = 1.0; + height = 1.0; + sides = 8; + cone = false; + smooth_faces = true; +} + +/////////////// + +CSGBrush *CSGTorus::_build_brush(AABB *r_aabb) { + + // set our bounding box + + float min_radius = inner_radius; + float max_radius = outer_radius; + + if (min_radius == max_radius) + return NULL; //sorry, can't + + if (min_radius > max_radius) { + SWAP(min_radius, max_radius); + } + + float radius = (max_radius - min_radius) * 0.5; + + CSGBrush *brush = memnew(CSGBrush); + + int face_count = ring_sides * sides * 2; + + bool invert_val = is_inverting_faces(); + Ref material = get_material(); + + PoolVector faces; + PoolVector uvs; + PoolVector smooth; + PoolVector > materials; + PoolVector invert; + + faces.resize(face_count * 3); + uvs.resize(face_count * 3); + + smooth.resize(face_count); + materials.resize(face_count); + invert.resize(face_count); + + { + + PoolVector::Write facesw = faces.write(); + PoolVector::Write uvsw = uvs.write(); + PoolVector::Write smoothw = smooth.write(); + PoolVector >::Write materialsw = materials.write(); + PoolVector::Write invertw = invert.write(); + + int face = 0; + + { + + for (int i = 0; i < sides; i++) { + + float inci = float(i) / sides; + float inci_n = float((i + 1)) / sides; + + float angi = inci * Math_PI * 2.0; + float angi_n = inci_n * Math_PI * 2.0; + + Vector3 normali = Vector3(Math::cos(angi), 0, Math::sin(angi)); + Vector3 normali_n = Vector3(Math::cos(angi_n), 0, Math::sin(angi_n)); + + for (int j = 0; j < ring_sides; j++) { + + float incj = float(j) / ring_sides; + float incj_n = float((j + 1)) / ring_sides; + + float angj = incj * Math_PI * 2.0; + float angj_n = incj_n * Math_PI * 2.0; + + Vector2 normalj = Vector2(Math::cos(angj), Math::sin(angj)) * radius + Vector2(min_radius + radius, 0); + Vector2 normalj_n = Vector2(Math::cos(angj_n), Math::sin(angj_n)) * radius + Vector2(min_radius + radius, 0); + + Vector3 face_points[4] = { + Vector3(normali.x * normalj.x, normalj.y, normali.z * normalj.x), + Vector3(normali.x * normalj_n.x, normalj_n.y, normali.z * normalj_n.x), + Vector3(normali_n.x * normalj_n.x, normalj_n.y, normali_n.z * normalj_n.x), + Vector3(normali_n.x * normalj.x, normalj.y, normali_n.z * normalj.x) + }; + + Vector2 u[4] = { + Vector2(inci, incj), + Vector2(inci, incj_n), + Vector2(inci_n, incj_n), + Vector2(inci_n, incj), + }; + + // face 1 + facesw[face * 3 + 0] = face_points[0]; + facesw[face * 3 + 1] = face_points[2]; + facesw[face * 3 + 2] = face_points[1]; + + uvsw[face * 3 + 0] = u[0]; + uvsw[face * 3 + 1] = u[2]; + uvsw[face * 3 + 2] = u[1]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + + //face 2 + facesw[face * 3 + 0] = face_points[3]; + facesw[face * 3 + 1] = face_points[2]; + facesw[face * 3 + 2] = face_points[0]; + + uvsw[face * 3 + 0] = u[3]; + uvsw[face * 3 + 1] = u[2]; + uvsw[face * 3 + 2] = u[0]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_val; + materialsw[face] = material; + face++; + } + } + } + + if (face != face_count) { + ERR_PRINT("Face mismatch bug! fix code"); + } + } + + brush->build_from_faces(faces, uvs, smooth, materials, invert); + + if (r_aabb) { + *r_aabb = AABB(Vector3(-max_radius, -radius, -max_radius), Vector3(max_radius * 2, radius * 2, max_radius * 2)); + } + + return brush; +} + +void CSGTorus::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_inner_radius", "radius"), &CSGTorus::set_inner_radius); + ClassDB::bind_method(D_METHOD("get_inner_radius"), &CSGTorus::get_inner_radius); + + ClassDB::bind_method(D_METHOD("set_outer_radius", "radius"), &CSGTorus::set_outer_radius); + ClassDB::bind_method(D_METHOD("get_outer_radius"), &CSGTorus::get_outer_radius); + + ClassDB::bind_method(D_METHOD("set_sides", "sides"), &CSGTorus::set_sides); + ClassDB::bind_method(D_METHOD("get_sides"), &CSGTorus::get_sides); + + ClassDB::bind_method(D_METHOD("set_ring_sides", "sides"), &CSGTorus::set_ring_sides); + ClassDB::bind_method(D_METHOD("get_ring_sides"), &CSGTorus::get_ring_sides); + + ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGTorus::set_material); + ClassDB::bind_method(D_METHOD("get_material"), &CSGTorus::get_material); + + ClassDB::bind_method(D_METHOD("set_smooth_faces", "smooth_faces"), &CSGTorus::set_smooth_faces); + ClassDB::bind_method(D_METHOD("get_smooth_faces"), &CSGTorus::get_smooth_faces); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "inner_radius", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001"), "set_inner_radius", "get_inner_radius"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "outer_radius", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001"), "set_outer_radius", "get_outer_radius"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "sides", PROPERTY_HINT_RANGE, "3,64,1"), "set_sides", "get_sides"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "ring_sides", PROPERTY_HINT_RANGE, "3,64,1"), "set_ring_sides", "get_ring_sides"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_faces"), "set_smooth_faces", "get_smooth_faces"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "SpatialMaterial,ShaderMaterial"), "set_material", "get_material"); +} + +void CSGTorus::set_inner_radius(const float p_inner_radius) { + inner_radius = p_inner_radius; + _make_dirty(); + update_gizmo(); +} + +float CSGTorus::get_inner_radius() const { + return inner_radius; +} + +void CSGTorus::set_outer_radius(const float p_outer_radius) { + outer_radius = p_outer_radius; + _make_dirty(); + update_gizmo(); +} + +float CSGTorus::get_outer_radius() const { + return outer_radius; +} + +void CSGTorus::set_sides(const int p_sides) { + ERR_FAIL_COND(p_sides < 3); + sides = p_sides; + _make_dirty(); + update_gizmo(); +} + +int CSGTorus::get_sides() const { + return sides; +} + +void CSGTorus::set_ring_sides(const int p_ring_sides) { + ERR_FAIL_COND(p_ring_sides < 3); + ring_sides = p_ring_sides; + _make_dirty(); + update_gizmo(); +} + +int CSGTorus::get_ring_sides() const { + return ring_sides; +} + +void CSGTorus::set_smooth_faces(const bool p_smooth_faces) { + smooth_faces = p_smooth_faces; + _make_dirty(); +} + +bool CSGTorus::get_smooth_faces() const { + return smooth_faces; +} + +void CSGTorus::set_material(const Ref &p_material) { + + material = p_material; + _make_dirty(); +} + +Ref CSGTorus::get_material() const { + + return material; +} + +CSGTorus::CSGTorus() { + // defaults + inner_radius = 2.0; + outer_radius = 3.0; + sides = 8; + ring_sides = 6; + smooth_faces = true; +} + +/////////////// + +CSGBrush *CSGPolygon::_build_brush(AABB *r_aabb) { + + // set our bounding box + + if (polygon.size() < 3) + return NULL; + + Vector triangles = Geometry::triangulate_polygon(polygon); + + if (triangles.size() < 3) + return NULL; + + Path *path = NULL; + Ref curve; + + if (mode == MODE_PATH) { + if (!has_node(path_node)) + return NULL; + Node *n = get_node(path_node); + if (!n) + return NULL; + path = Object::cast_to(n); + if (!path) + return NULL; + + if (path != path_cache) { + if (path_cache) { + path_cache->disconnect("tree_exited", this, "_path_exited"); + path_cache->disconnect("curve_changed", this, "_path_changed"); + path_cache = NULL; + } + + path_cache = path; + + if (path_cache) { + path_cache->connect("tree_exited", this, "_path_exited"); + path_cache->connect("curve_changed", this, "_path_changed"); + path_cache = NULL; + } + } + curve = path->get_curve(); + if (curve.is_null()) + return NULL; + if (curve->get_baked_length() <= 0) + return NULL; + } + CSGBrush *brush = memnew(CSGBrush); + + int face_count; + + switch (mode) { + case MODE_DEPTH: face_count = triangles.size() * 2 / 3 + (polygon.size()) * 2; break; + case MODE_SPIN: face_count = (spin_degrees < 360 ? triangles.size() * 2 / 3 : 0) + (polygon.size()) * 2 * spin_sides; break; + case MODE_PATH: { + float bl = curve->get_baked_length(); + int splits = MAX(2, Math::ceil(bl / path_interval)); + face_count = triangles.size() * 2 / 3 + splits * polygon.size() * 2; + } break; + } + + bool invert_val = is_inverting_faces(); + Ref material = get_material(); + + PoolVector faces; + PoolVector uvs; + PoolVector smooth; + PoolVector > materials; + PoolVector invert; + + faces.resize(face_count * 3); + uvs.resize(face_count * 3); + + smooth.resize(face_count); + materials.resize(face_count); + invert.resize(face_count); + + AABB aabb; //must be computed + { + + PoolVector::Write facesw = faces.write(); + PoolVector::Write uvsw = uvs.write(); + PoolVector::Write smoothw = smooth.write(); + PoolVector >::Write materialsw = materials.write(); + PoolVector::Write invertw = invert.write(); + + int face = 0; + + switch (mode) { + case MODE_DEPTH: { + + //add triangles, front and back + for (int i = 0; i < 2; i++) { + + for (int j = 0; j < triangles.size(); j += 3) { + for (int k = 0; k < 3; k++) { + int src[3] = { 0, i == 0 ? 1 : 2, i == 0 ? 2 : 1 }; + Vector2 p = polygon[triangles[j + src[k]]]; + Vector3 v = Vector3(p.x, p.y, 0); + if (i == 0) { + v.z -= depth; + } + facesw[face * 3 + k] = v; + } + + smoothw[face] = false; + materialsw[face] = material; + invertw[face] = invert_val; + face++; + } + } + + //add triangles for depth + for (int i = 0; i < polygon.size(); i++) { + + int i_n = (i + 1) % polygon.size(); + + Vector3 v[4] = { + Vector3(polygon[i].x, polygon[i].y, -depth), + Vector3(polygon[i_n].x, polygon[i_n].y, -depth), + Vector3(polygon[i_n].x, polygon[i_n].y, 0), + Vector3(polygon[i].x, polygon[i].y, 0), + }; + + Vector2 u[4] = { + Vector2(0, 0), + Vector2(0, 1), + Vector2(1, 1), + Vector2(1, 0) + }; + + // face 1 + facesw[face * 3 + 0] = v[0]; + facesw[face * 3 + 1] = v[1]; + facesw[face * 3 + 2] = v[2]; + + uvsw[face * 3 + 0] = u[0]; + uvsw[face * 3 + 1] = u[1]; + uvsw[face * 3 + 2] = u[2]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + + // face 2 + facesw[face * 3 + 0] = v[2]; + facesw[face * 3 + 1] = v[3]; + facesw[face * 3 + 2] = v[0]; + + uvsw[face * 3 + 0] = u[2]; + uvsw[face * 3 + 1] = u[3]; + uvsw[face * 3 + 2] = u[0]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + } + + } break; + case MODE_SPIN: { + + for (int i = 0; i < spin_sides; i++) { + + float inci = float(i) / spin_sides; + float inci_n = float((i + 1)) / spin_sides; + + float angi = -(inci * spin_degrees / 360.0) * Math_PI * 2.0; + float angi_n = -(inci_n * spin_degrees / 360.0) * Math_PI * 2.0; + + Vector3 normali = Vector3(Math::cos(angi), 0, Math::sin(angi)); + Vector3 normali_n = Vector3(Math::cos(angi_n), 0, Math::sin(angi_n)); + + //add triangles for depth + for (int j = 0; j < polygon.size(); j++) { + + int j_n = (j + 1) % polygon.size(); + + Vector3 v[4] = { + Vector3(normali.x * polygon[j].x, polygon[j].y, normali.z * polygon[j].x), + Vector3(normali.x * polygon[j_n].x, polygon[j_n].y, normali.z * polygon[j_n].x), + Vector3(normali_n.x * polygon[j_n].x, polygon[j_n].y, normali_n.z * polygon[j_n].x), + Vector3(normali_n.x * polygon[j].x, polygon[j].y, normali_n.z * polygon[j].x), + }; + + Vector2 u[4] = { + Vector2(0, 0), + Vector2(0, 1), + Vector2(1, 1), + Vector2(1, 0) + }; + + // face 1 + facesw[face * 3 + 0] = v[0]; + facesw[face * 3 + 1] = v[2]; + facesw[face * 3 + 2] = v[1]; + + uvsw[face * 3 + 0] = u[0]; + uvsw[face * 3 + 1] = u[2]; + uvsw[face * 3 + 2] = u[1]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + + // face 2 + facesw[face * 3 + 0] = v[2]; + facesw[face * 3 + 1] = v[0]; + facesw[face * 3 + 2] = v[3]; + + uvsw[face * 3 + 0] = u[2]; + uvsw[face * 3 + 1] = u[0]; + uvsw[face * 3 + 2] = u[3]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + } + + if (i == 0 && spin_degrees < 360) { + + for (int j = 0; j < triangles.size(); j += 3) { + for (int k = 0; k < 3; k++) { + int src[3] = { 0, 2, 1 }; + Vector2 p = polygon[triangles[j + src[k]]]; + Vector3 v = Vector3(p.x, p.y, 0); + facesw[face * 3 + k] = v; + } + + smoothw[face] = false; + materialsw[face] = material; + invertw[face] = invert_val; + face++; + } + } + + if (i == spin_sides - 1 && spin_degrees < 360) { + + for (int j = 0; j < triangles.size(); j += 3) { + for (int k = 0; k < 3; k++) { + int src[3] = { 0, 1, 2 }; + Vector2 p = polygon[triangles[j + src[k]]]; + Vector3 v = Vector3(normali_n.x * p.x, p.y, normali_n.z * p.x); + facesw[face * 3 + k] = v; + } + + smoothw[face] = false; + materialsw[face] = material; + invertw[face] = invert_val; + face++; + } + } + } + } break; + case MODE_PATH: { + + float bl = curve->get_baked_length(); + int splits = MAX(2, Math::ceil(bl / path_interval)); + + Transform path_to_this = get_global_transform().affine_inverse() * path->get_global_transform(); + + Transform prev_xf; + + Vector3 lookat_dir; + + if (path_rotation == PATH_ROTATION_POLYGON) { + lookat_dir = (path->get_global_transform().affine_inverse() * get_global_transform()).xform(Vector3(0, 0, -1)); + } else { + Vector3 p1, p2; + p1 = curve->interpolate_baked(0); + p2 = curve->interpolate_baked(0.1); + lookat_dir = (p2 - p1).normalized(); + } + + for (int i = 0; i <= splits; i++) { + + float ofs = i * path_interval; + + Transform xf; + xf.origin = curve->interpolate_baked(ofs); + + Vector3 local_dir; + + if (path_rotation == PATH_ROTATION_PATH_FOLLOW && ofs > 0) { + //before end + Vector3 p1 = curve->interpolate_baked(ofs - 0.1); + Vector3 p2 = curve->interpolate_baked(ofs); + local_dir = (p2 - p1).normalized(); + + } else { + local_dir = lookat_dir; + } + + xf = xf.looking_at(xf.origin + local_dir, Vector3(0, 1, 0)); + Basis rot(Vector3(0, 0, 1), curve->interpolate_baked_tilt(ofs)); + + xf = xf * rot; //post mult + + xf = path_to_this * xf; + + if (i > 0) { + //put triangles where they belong + //add triangles for depth + for (int j = 0; j < polygon.size(); j++) { + + int j_n = (j + 1) % polygon.size(); + + Vector3 v[4] = { + prev_xf.xform(Vector3(polygon[j].x, polygon[j].y, 0)), + prev_xf.xform(Vector3(polygon[j_n].x, polygon[j_n].y, 0)), + xf.xform(Vector3(polygon[j_n].x, polygon[j_n].y, 0)), + xf.xform(Vector3(polygon[j].x, polygon[j].y, 0)), + }; + + Vector2 u[4] = { + Vector2(0, 0), + Vector2(0, 1), + Vector2(1, 1), + Vector2(1, 0) + }; + + // face 1 + facesw[face * 3 + 0] = v[0]; + facesw[face * 3 + 1] = v[1]; + facesw[face * 3 + 2] = v[2]; + + uvsw[face * 3 + 0] = u[0]; + uvsw[face * 3 + 1] = u[1]; + uvsw[face * 3 + 2] = u[2]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + + // face 2 + facesw[face * 3 + 0] = v[2]; + facesw[face * 3 + 1] = v[3]; + facesw[face * 3 + 2] = v[0]; + + uvsw[face * 3 + 0] = u[2]; + uvsw[face * 3 + 1] = u[3]; + uvsw[face * 3 + 2] = u[0]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + } + } + + if (i == 0) { + + for (int j = 0; j < triangles.size(); j += 3) { + for (int k = 0; k < 3; k++) { + int src[3] = { 0, 1, 2 }; + Vector2 p = polygon[triangles[j + src[k]]]; + Vector3 v = Vector3(p.x, p.y, 0); + facesw[face * 3 + k] = xf.xform(v); + } + + smoothw[face] = false; + materialsw[face] = material; + invertw[face] = invert_val; + face++; + } + } + + if (i == splits) { + + for (int j = 0; j < triangles.size(); j += 3) { + for (int k = 0; k < 3; k++) { + int src[3] = { 0, 2, 1 }; + Vector2 p = polygon[triangles[j + src[k]]]; + Vector3 v = Vector3(p.x, p.y, 0); + facesw[face * 3 + k] = xf.xform(v); + } + + smoothw[face] = false; + materialsw[face] = material; + invertw[face] = invert_val; + face++; + } + } + + prev_xf = xf; + } + + } break; + } + + if (face != face_count) { + ERR_PRINT("Face mismatch bug! fix code"); + } + for (int i = 0; i < face_count * 3; i++) { + if (i == 0) { + aabb.position = facesw[i]; + } else { + aabb.expand_to(facesw[i]); + } + } + } + + brush->build_from_faces(faces, uvs, smooth, materials, invert); + + if (r_aabb) { + *r_aabb = aabb; + } + + return brush; +} + +void CSGPolygon::_notification(int p_what) { + if (p_what == NOTIFICATION_EXIT_TREE) { + if (path_cache) { + path_cache->disconnect("tree_exited", this, "_path_exited"); + path_cache->disconnect("curve_changed", this, "_path_changed"); + path_cache = NULL; + } + } +} + +void CSGPolygon::_validate_property(PropertyInfo &property) const { + if (property.name.begins_with("spin") && mode != MODE_SPIN) { + property.usage = 0; + } + if (property.name.begins_with("path") && mode != MODE_PATH) { + property.usage = 0; + } + if (property.name == "depth" && mode != MODE_DEPTH) { + property.usage = 0; + } + + CSGShape::_validate_property(property); +} + +void CSGPolygon::_path_changed() { + _make_dirty(); + update_gizmo(); +} + +void CSGPolygon::_path_exited() { + path_cache = NULL; +} + +void CSGPolygon::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_polygon", "polygon"), &CSGPolygon::set_polygon); + ClassDB::bind_method(D_METHOD("get_polygon"), &CSGPolygon::get_polygon); + + ClassDB::bind_method(D_METHOD("set_mode", "mode"), &CSGPolygon::set_mode); + ClassDB::bind_method(D_METHOD("get_mode"), &CSGPolygon::get_mode); + + ClassDB::bind_method(D_METHOD("set_depth", "depth"), &CSGPolygon::set_depth); + ClassDB::bind_method(D_METHOD("get_depth"), &CSGPolygon::get_depth); + + ClassDB::bind_method(D_METHOD("set_spin_degrees", "degrees"), &CSGPolygon::set_spin_degrees); + ClassDB::bind_method(D_METHOD("get_spin_degrees"), &CSGPolygon::get_spin_degrees); + + ClassDB::bind_method(D_METHOD("set_spin_sides", "spin_sides"), &CSGPolygon::set_spin_sides); + ClassDB::bind_method(D_METHOD("get_spin_sides"), &CSGPolygon::get_spin_sides); + + ClassDB::bind_method(D_METHOD("set_path_node", "path"), &CSGPolygon::set_path_node); + ClassDB::bind_method(D_METHOD("get_path_node"), &CSGPolygon::get_path_node); + + ClassDB::bind_method(D_METHOD("set_path_interval", "distance"), &CSGPolygon::set_path_interval); + ClassDB::bind_method(D_METHOD("get_path_interval"), &CSGPolygon::get_path_interval); + + ClassDB::bind_method(D_METHOD("set_path_rotation", "mode"), &CSGPolygon::set_path_rotation); + ClassDB::bind_method(D_METHOD("get_path_rotation"), &CSGPolygon::get_path_rotation); + + ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGPolygon::set_material); + ClassDB::bind_method(D_METHOD("get_material"), &CSGPolygon::get_material); + + ClassDB::bind_method(D_METHOD("set_smooth_faces", "smooth_faces"), &CSGPolygon::set_smooth_faces); + ClassDB::bind_method(D_METHOD("get_smooth_faces"), &CSGPolygon::get_smooth_faces); + + ClassDB::bind_method(D_METHOD("_is_editable_3d_polygon"), &CSGPolygon::_is_editable_3d_polygon); + ClassDB::bind_method(D_METHOD("_has_editable_3d_polygon_no_depth"), &CSGPolygon::_has_editable_3d_polygon_no_depth); + + ClassDB::bind_method(D_METHOD("_path_exited"), &CSGPolygon::_path_exited); + ClassDB::bind_method(D_METHOD("_path_changed"), &CSGPolygon::_path_changed); + + ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR2_ARRAY, "polygon"), "set_polygon", "get_polygon"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Depth,Spin,Path"), "set_mode", "get_mode"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "depth", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001"), "set_depth", "get_depth"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "spin_degrees", PROPERTY_HINT_RANGE, "1,360,0.1"), "set_spin_degrees", "get_spin_degrees"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "spin_sides", PROPERTY_HINT_RANGE, "3,64,1"), "set_spin_sides", "get_spin_sides"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "path_node"), "set_path_node", "get_path_node"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "path_interval", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001"), "set_path_interval", "get_path_interval"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "path_rotation", PROPERTY_HINT_ENUM, "Polygon,Path,PathFollow"), "set_path_rotation", "get_path_rotation"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_faces"), "set_smooth_faces", "get_smooth_faces"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "SpatialMaterial,ShaderMaterial"), "set_material", "get_material"); + + BIND_ENUM_CONSTANT(MODE_DEPTH); + BIND_ENUM_CONSTANT(MODE_SPIN); + BIND_ENUM_CONSTANT(MODE_PATH); + + BIND_ENUM_CONSTANT(PATH_ROTATION_POLYGON); + BIND_ENUM_CONSTANT(PATH_ROTATION_PATH); + BIND_ENUM_CONSTANT(PATH_ROTATION_PATH_FOLLOW); +} + +void CSGPolygon::set_polygon(const Vector &p_polygon) { + polygon = p_polygon; + _make_dirty(); + update_gizmo(); +} + +Vector CSGPolygon::get_polygon() const { + return polygon; +} + +void CSGPolygon::set_mode(Mode p_mode) { + mode = p_mode; + _make_dirty(); + update_gizmo(); + _change_notify(); +} + +CSGPolygon::Mode CSGPolygon::get_mode() const { + return mode; +} + +void CSGPolygon::set_depth(const float p_depth) { + ERR_FAIL_COND(p_depth < 0.001); + depth = p_depth; + _make_dirty(); + update_gizmo(); +} + +float CSGPolygon::get_depth() const { + return depth; +} + +void CSGPolygon::set_spin_degrees(const float p_spin_degrees) { + ERR_FAIL_COND(p_spin_degrees < 0.01 || p_spin_degrees > 360); + spin_degrees = p_spin_degrees; + _make_dirty(); + update_gizmo(); +} + +float CSGPolygon::get_spin_degrees() const { + return spin_degrees; +} + +void CSGPolygon::set_spin_sides(const int p_spin_sides) { + ERR_FAIL_COND(p_spin_sides < 3); + spin_sides = p_spin_sides; + _make_dirty(); + update_gizmo(); +} + +int CSGPolygon::get_spin_sides() const { + return spin_sides; +} + +void CSGPolygon::set_path_node(const NodePath &p_path) { + path_node = p_path; + _make_dirty(); + update_gizmo(); +} + +NodePath CSGPolygon::get_path_node() const { + return path_node; +} + +void CSGPolygon::set_path_interval(float p_interval) { + ERR_FAIL_COND(p_interval < 0.001); + path_interval = p_interval; + _make_dirty(); + update_gizmo(); +} +float CSGPolygon::get_path_interval() const { + return path_interval; +} + +void CSGPolygon::set_path_rotation(PathRotation p_rotation) { + path_rotation = p_rotation; + _make_dirty(); + update_gizmo(); +} + +CSGPolygon::PathRotation CSGPolygon::get_path_rotation() const { + return path_rotation; +} + +void CSGPolygon::set_smooth_faces(const bool p_smooth_faces) { + smooth_faces = p_smooth_faces; + _make_dirty(); +} + +bool CSGPolygon::get_smooth_faces() const { + return smooth_faces; +} + +void CSGPolygon::set_material(const Ref &p_material) { + + material = p_material; + _make_dirty(); +} + +Ref CSGPolygon::get_material() const { + + return material; +} + +bool CSGPolygon::_is_editable_3d_polygon() const { + return true; +} + +bool CSGPolygon::_has_editable_3d_polygon_no_depth() const { + return true; +} + +CSGPolygon::CSGPolygon() { + // defaults + mode = MODE_DEPTH; + polygon.push_back(Vector2(0, 0)); + polygon.push_back(Vector2(0, 1)); + polygon.push_back(Vector2(1, 1)); + polygon.push_back(Vector2(1, 0)); + depth = 1.0; + spin_degrees = 360; + spin_sides = 8; + smooth_faces = false; + path_interval = 1; + path_rotation = PATH_ROTATION_PATH; + path_cache = NULL; +} diff --git a/modules/csg/csg_shape.h b/modules/csg/csg_shape.h new file mode 100644 index 00000000000..24d5dfb29a9 --- /dev/null +++ b/modules/csg/csg_shape.h @@ -0,0 +1,363 @@ +#ifndef CSG_SHAPE_H +#define CSG_SHAPE_H + +#define CSGJS_HEADER_ONLY + +#include "csg.h" +#include "scene/3d/visual_instance.h" +#include "scene/resources/concave_polygon_shape.h" + +class CSGShape : public VisualInstance { + GDCLASS(CSGShape, VisualInstance); + +public: + enum Operation { + OPERATION_UNION, + OPERATION_INTERSECTION, + OPERATION_SUBTRACTION, + + }; + +private: + Operation operation; + CSGShape *parent; + + CSGBrush *brush; + + AABB node_aabb; + + bool dirty; + + bool use_collision; + Ref root_collision_shape; + RID root_collision_instance; + + Ref root_mesh; + + struct Vector3Hasher { + _ALWAYS_INLINE_ uint32_t hash(const Vector3 &p_vec3) const { + uint32_t h = hash_djb2_one_float(p_vec3.x); + h = hash_djb2_one_float(p_vec3.y, h); + h = hash_djb2_one_float(p_vec3.z, h); + return h; + } + }; + + struct ShapeUpdateSurface { + PoolVector vertices; + PoolVector normals; + PoolVector uvs; + Ref material; + int last_added; + + PoolVector::Write verticesw; + PoolVector::Write normalsw; + PoolVector::Write uvsw; + }; + + void _update_shape(); + +protected: + void _notification(int p_what); + virtual CSGBrush *_build_brush(AABB *r_aabb) = 0; + void _make_dirty(); + + static void _bind_methods(); + + friend class CSGCombiner; + CSGBrush *_get_brush(); + + virtual void _validate_property(PropertyInfo &property) const; + +public: + void set_operation(Operation p_operation); + Operation get_operation() const; + + virtual PoolVector get_brush_faces(); + + virtual AABB get_aabb() const; + virtual PoolVector get_faces(uint32_t p_usage_flags) const; + + void set_use_collision(bool p_enable); + bool is_using_collision() const; + + bool is_root_shape() const; + CSGShape(); + ~CSGShape(); +}; + +VARIANT_ENUM_CAST(CSGShape::Operation) + +class CSGCombiner : public CSGShape { + GDCLASS(CSGCombiner, CSGShape) +private: + float snap; + virtual CSGBrush *_build_brush(AABB *r_aabb); + +protected: + static void _bind_methods(); + +public: + void set_snap(float p_snap); + float get_snap() const; + + CSGCombiner(); +}; + +class CSGPrimitive : public CSGShape { + GDCLASS(CSGPrimitive, CSGShape) + +private: + bool invert_faces; + +protected: + CSGBrush *_create_brush_from_arrays(const PoolVector &p_vertices, const PoolVector &p_uv, const PoolVector &p_smooth, const PoolVector > &p_materials); + static void _bind_methods(); + +public: + void set_invert_faces(bool p_invert); + bool is_inverting_faces(); + + CSGPrimitive(); +}; + +class CSGMesh : public CSGPrimitive { + GDCLASS(CSGMesh, CSGPrimitive) + + virtual CSGBrush *_build_brush(AABB *r_aabb); + + Ref mesh; + + void _mesh_changed(); + +protected: + static void _bind_methods(); + +public: + void set_mesh(const Ref &p_mesh); + Ref get_mesh(); +}; + +class CSGSphere : public CSGPrimitive { + + GDCLASS(CSGSphere, CSGPrimitive) + virtual CSGBrush *_build_brush(AABB *r_aabb); + + Ref material; + bool smooth_faces; + float radius; + int radial_segments; + int rings; + +protected: + static void _bind_methods(); + +public: + void set_radius(const float p_radius); + float get_radius() const; + + void set_radial_segments(const int p_radial_segments); + int get_radial_segments() const; + + void set_rings(const int p_rings); + int get_rings() const; + + void set_material(const Ref &p_material); + Ref get_material() const; + + void set_smooth_faces(bool p_smooth_faces); + bool get_smooth_faces() const; + + CSGSphere(); +}; + +class CSGBox : public CSGPrimitive { + + GDCLASS(CSGBox, CSGPrimitive) + virtual CSGBrush *_build_brush(AABB *r_aabb); + + Ref material; + float width; + float height; + float depth; + +protected: + static void _bind_methods(); + +public: + void set_width(const float p_width); + float get_width() const; + + void set_height(const float p_height); + float get_height() const; + + void set_depth(const float p_depth); + float get_depth() const; + + void set_material(const Ref &p_material); + Ref get_material() const; + + CSGBox(); +}; + +class CSGCylinder : public CSGPrimitive { + + GDCLASS(CSGCylinder, CSGPrimitive) + virtual CSGBrush *_build_brush(AABB *r_aabb); + + Ref material; + float radius; + float height; + int sides; + bool cone; + bool smooth_faces; + +protected: + static void _bind_methods(); + +public: + void set_radius(const float p_radius); + float get_radius() const; + + void set_height(const float p_height); + float get_height() const; + + void set_sides(const int p_sides); + int get_sides() const; + + void set_cone(const bool p_cone); + bool is_cone() const; + + void set_smooth_faces(bool p_smooth_faces); + bool get_smooth_faces() const; + + void set_material(const Ref &p_material); + Ref get_material() const; + + CSGCylinder(); +}; + +class CSGTorus : public CSGPrimitive { + + GDCLASS(CSGTorus, CSGPrimitive) + virtual CSGBrush *_build_brush(AABB *r_aabb); + + Ref material; + float inner_radius; + float outer_radius; + int sides; + int ring_sides; + bool smooth_faces; + +protected: + static void _bind_methods(); + +public: + void set_inner_radius(const float p_inner_radius); + float get_inner_radius() const; + + void set_outer_radius(const float p_outer_radius); + float get_outer_radius() const; + + void set_sides(const int p_sides); + int get_sides() const; + + void set_ring_sides(const int p_ring_sides); + int get_ring_sides() const; + + void set_smooth_faces(bool p_smooth_faces); + bool get_smooth_faces() const; + + void set_material(const Ref &p_material); + Ref get_material() const; + + CSGTorus(); +}; + +class CSGPolygon : public CSGPrimitive { + + GDCLASS(CSGPolygon, CSGPrimitive) + +public: + enum Mode { + MODE_DEPTH, + MODE_SPIN, + MODE_PATH + }; + + enum PathRotation { + PATH_ROTATION_POLYGON, + PATH_ROTATION_PATH, + PATH_ROTATION_PATH_FOLLOW, + }; + +private: + virtual CSGBrush *_build_brush(AABB *r_aabb); + + Vector polygon; + Ref material; + + Mode mode; + + float depth; + + float spin_degrees; + int spin_sides; + + NodePath path_node; + float path_interval; + PathRotation path_rotation; + + Node *path_cache; + + bool smooth_faces; + + bool _is_editable_3d_polygon() const; + bool _has_editable_3d_polygon_no_depth() const; + + void _path_changed(); + void _path_exited(); + +protected: + static void _bind_methods(); + virtual void _validate_property(PropertyInfo &property) const; + void _notification(int p_what); + +public: + void set_polygon(const Vector &p_polygon); + Vector get_polygon() const; + + void set_mode(Mode p_mode); + Mode get_mode() const; + + void set_depth(float p_depth); + float get_depth() const; + + void set_spin_degrees(float p_spin_degrees); + float get_spin_degrees() const; + + void set_spin_sides(int p_sides); + int get_spin_sides() const; + + void set_path_node(const NodePath &p_path); + NodePath get_path_node() const; + + void set_path_interval(float p_interval); + float get_path_interval() const; + + void set_path_rotation(PathRotation p_rotation); + PathRotation get_path_rotation() const; + + void set_smooth_faces(bool p_smooth_faces); + bool get_smooth_faces() const; + + void set_material(const Ref &p_material); + Ref get_material() const; + + CSGPolygon(); +}; + +VARIANT_ENUM_CAST(CSGPolygon::Mode) +VARIANT_ENUM_CAST(CSGPolygon::PathRotation) + +#endif // CSG_SHAPE_H diff --git a/modules/csg/register_types.cpp b/modules/csg/register_types.cpp new file mode 100644 index 00000000000..020724ee59e --- /dev/null +++ b/modules/csg/register_types.cpp @@ -0,0 +1,59 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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 "register_types.h" + +#include "csg_shape.h" +#include "csg_gizmos.h" + +void register_csg_types() { + +#ifndef _3D_DISABLED + + ClassDB::register_virtual_class(); + ClassDB::register_virtual_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + +#ifdef TOOLS_ENABLED + EditorPlugins::add_by_type(); +#endif +#endif + +} + +void unregister_csg_types() { + +} diff --git a/modules/csg/register_types.h b/modules/csg/register_types.h new file mode 100644 index 00000000000..49490d31d33 --- /dev/null +++ b/modules/csg/register_types.h @@ -0,0 +1,32 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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. */ +/*************************************************************************/ + +void register_csg_types(); +void unregister_csg_types(); diff --git a/scene/3d/collision_polygon.cpp b/scene/3d/collision_polygon.cpp index 3a77360bc20..379dd21c393 100644 --- a/scene/3d/collision_polygon.cpp +++ b/scene/3d/collision_polygon.cpp @@ -173,6 +173,9 @@ String CollisionPolygon::get_configuration_warning() const { return String(); } +bool CollisionPolygon::_is_editable_3d_polygon() const { + return true; +} void CollisionPolygon::_bind_methods() { ClassDB::bind_method(D_METHOD("set_depth", "depth"), &CollisionPolygon::set_depth); @@ -184,6 +187,8 @@ void CollisionPolygon::_bind_methods() { ClassDB::bind_method(D_METHOD("set_disabled", "disabled"), &CollisionPolygon::set_disabled); ClassDB::bind_method(D_METHOD("is_disabled"), &CollisionPolygon::is_disabled); + ClassDB::bind_method(D_METHOD("_is_editable_3d_polygon"), &CollisionPolygon::_is_editable_3d_polygon); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "depth"), "set_depth", "get_depth"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled"); ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR2_ARRAY, "polygon"), "set_polygon", "get_polygon"); diff --git a/scene/3d/collision_polygon.h b/scene/3d/collision_polygon.h index 971c67f1ad6..f1f137c9c55 100644 --- a/scene/3d/collision_polygon.h +++ b/scene/3d/collision_polygon.h @@ -53,6 +53,8 @@ protected: void _update_in_shape_owner(bool p_xform_only = false); + bool _is_editable_3d_polygon() const; + protected: void _notification(int p_what); static void _bind_methods(); diff --git a/scene/3d/path.cpp b/scene/3d/path.cpp index 7ac7f74bb05..57d79c960f2 100644 --- a/scene/3d/path.cpp +++ b/scene/3d/path.cpp @@ -40,6 +40,9 @@ void Path::_curve_changed() { if (is_inside_tree() && Engine::get_singleton()->is_editor_hint()) update_gizmo(); + if (is_inside_tree()) { + emit_signal("curve_changed"); + } } void Path::set_curve(const Ref &p_curve) { @@ -68,6 +71,8 @@ void Path::_bind_methods() { ClassDB::bind_method(D_METHOD("_curve_changed"), &Path::_curve_changed); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve3D"), "set_curve", "get_curve"); + + ADD_SIGNAL(MethodInfo("curve_changed")); } Path::Path() { diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index b832ea12397..d87644381c3 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -912,6 +912,7 @@ void ArrayMesh::surface_set_material(int p_idx, const Ref &p_material) VisualServer::get_singleton()->mesh_surface_set_material(mesh, p_idx, p_material.is_null() ? RID() : p_material->get_rid()); _change_notify("material"); + emit_changed(); } void ArrayMesh::surface_set_name(int p_idx, const String &p_name) { @@ -919,6 +920,7 @@ void ArrayMesh::surface_set_name(int p_idx, const String &p_name) { ERR_FAIL_INDEX(p_idx, surfaces.size()); surfaces[p_idx].name = p_name; + emit_changed(); } String ArrayMesh::surface_get_name(int p_idx) const { @@ -931,6 +933,7 @@ void ArrayMesh::surface_update_region(int p_surface, int p_offset, const PoolVec ERR_FAIL_INDEX(p_surface, surfaces.size()); VS::get_singleton()->mesh_surface_update_region(mesh, p_surface, p_offset, p_data); + emit_changed(); } void ArrayMesh::surface_set_custom_aabb(int p_idx, const AABB &p_aabb) { @@ -938,6 +941,7 @@ void ArrayMesh::surface_set_custom_aabb(int p_idx, const AABB &p_aabb) { ERR_FAIL_INDEX(p_idx, surfaces.size()); surfaces[p_idx].aabb = p_aabb; // set custom aabb too? + emit_changed(); } Ref ArrayMesh::surface_get_material(int p_idx) const { @@ -986,6 +990,7 @@ void ArrayMesh::set_custom_aabb(const AABB &p_custom) { custom_aabb = p_custom; VS::get_singleton()->mesh_set_custom_aabb(mesh, custom_aabb); + emit_changed(); } AABB ArrayMesh::get_custom_aabb() const { diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp index 94c54c91d3f..056ac2772bd 100644 --- a/scene/resources/primitive_meshes.cpp +++ b/scene/resources/primitive_meshes.cpp @@ -65,6 +65,8 @@ void PrimitiveMesh::_update() const { pending_request = false; _clear_triangle_mesh(); + + const_cast(this)->emit_changed(); } void PrimitiveMesh::_request_update() {