Browse Source

CSG Support for Godot!
-Missing Icons
-Missing freezing option (for baking light and faster load)
-Missing a way to export from Godot (GLTF2?)
-Probably buggy (may freeze editor, can be worked around easily, but let me know if this happens so it's easier to catch bugs)
Happy testing!

Juan Linietsky 7 years ago
parent
commit
8d199a9b2c

+ 1 - 1
editor/editor_node.cpp

@@ -5858,7 +5858,7 @@ EditorNode::EditorNode() {
 	add_editor_plugin(memnew(ParticlesEditorPlugin(this)));
 	add_editor_plugin(memnew(ParticlesEditorPlugin(this)));
 	add_editor_plugin(memnew(ResourcePreloaderEditorPlugin(this)));
 	add_editor_plugin(memnew(ResourcePreloaderEditorPlugin(this)));
 	add_editor_plugin(memnew(ItemListEditorPlugin(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(CollisionPolygon2DEditorPlugin(this)));
 	add_editor_plugin(memnew(TileSetEditorPlugin(this)));
 	add_editor_plugin(memnew(TileSetEditorPlugin(this)));
 	add_editor_plugin(memnew(TileMapEditorPlugin(this)));
 	add_editor_plugin(memnew(TileMapEditorPlugin(this)));

+ 39 - 30
editor/plugins/collision_polygon_editor_plugin.cpp

@@ -36,7 +36,7 @@
 #include "scene/3d/camera.h"
 #include "scene/3d/camera.h"
 #include "spatial_editor_plugin.h"
 #include "spatial_editor_plugin.h"
 
 
-void CollisionPolygonEditor::_notification(int p_what) {
+void Polygon3DEditor::_notification(int p_what) {
 
 
 	switch (p_what) {
 	switch (p_what) {
 
 
@@ -53,15 +53,15 @@ void CollisionPolygonEditor::_notification(int p_what) {
 				return;
 				return;
 			}
 			}
 
 
-			if (node->get_depth() != prev_depth) {
+			if (_get_depth() != prev_depth) {
 				_polygon_draw();
 				_polygon_draw();
-				prev_depth = node->get_depth();
+				prev_depth = _get_depth();
 			}
 			}
 
 
 		} break;
 		} break;
 	}
 	}
 }
 }
-void CollisionPolygonEditor::_node_removed(Node *p_node) {
+void Polygon3DEditor::_node_removed(Node *p_node) {
 
 
 	if (p_node == node) {
 	if (p_node == node) {
 		node = NULL;
 		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) {
 	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->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(node, "set_polygon", wip);
 	undo_redo->add_do_method(this, "_polygon_draw");
 	undo_redo->add_do_method(this, "_polygon_draw");
 	undo_redo->add_undo_method(this, "_polygon_draw");
 	undo_redo->add_undo_method(this, "_polygon_draw");
@@ -107,14 +107,14 @@ void CollisionPolygonEditor::_wip_close() {
 	undo_redo->commit_action();
 	undo_redo->commit_action();
 }
 }
 
 
-bool CollisionPolygonEditor::forward_spatial_gui_input(Camera *p_camera, const Ref<InputEvent> &p_event) {
+bool Polygon3DEditor::forward_spatial_gui_input(Camera *p_camera, const Ref<InputEvent> &p_event) {
 
 
 	if (!node)
 	if (!node)
 		return false;
 		return false;
 
 
 	Transform gt = node->get_global_transform();
 	Transform gt = node->get_global_transform();
 	Transform gi = gt.affine_inverse();
 	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();
 	Vector3 n = gt.basis.get_axis(2).normalized();
 	Plane p(gt.origin + n * depth, n);
 	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);
 		cpoint = CanvasItemEditor::get_singleton()->snap_point(cpoint);
 
 
-		Vector<Vector2> poly = node->get_polygon();
+		Vector<Vector2> poly = node->call("get_polygon");
 
 
 		//first check if a point is to be added (segment split)
 		//first check if a point is to be added (segment split)
 		real_t grab_threshold = EDITOR_DEF("editors/poly_editor/point_grab_radius", 8);
 		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);
 								poly.insert(closest_idx + 1, cpoint);
 								edited_point = closest_idx + 1;
 								edited_point = closest_idx + 1;
 								edited_point_pos = cpoint;
 								edited_point_pos = cpoint;
-								node->set_polygon(poly);
+								node->call("set_polygon", poly);
 								_polygon_draw();
 								_polygon_draw();
 								return true;
 								return true;
 							}
 							}
@@ -341,7 +341,16 @@ bool CollisionPolygonEditor::forward_spatial_gui_input(Camera *p_camera, const R
 
 
 	return false;
 	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)
 	if (!node)
 		return;
 		return;
@@ -351,9 +360,9 @@ void CollisionPolygonEditor::_polygon_draw() {
 	if (wip_active)
 	if (wip_active)
 		poly = wip;
 		poly = wip;
 	else
 	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->clear();
 	imgeom->set_material_override(line_material);
 	imgeom->set_material_override(line_material);
@@ -464,13 +473,13 @@ void CollisionPolygonEditor::_polygon_draw() {
 	m->surface_set_material(0, handle_material);
 	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) {
 	if (p_collision_polygon) {
 
 
-		node = Object::cast_to<CollisionPolygon>(p_collision_polygon);
+		node = Object::cast_to<Spatial>(p_collision_polygon);
 		//Enable the pencil tool if the polygon is empty
 		//Enable the pencil tool if the polygon is empty
-		if (node->get_polygon().size() == 0) {
+		if (Vector<Vector2>(node->call("get_polygon")).size() == 0) {
 			_menu_option(MODE_CREATE);
 			_menu_option(MODE_CREATE);
 		}
 		}
 		wip.clear();
 		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;
 	node = NULL;
 	editor = p_editor;
 	editor = p_editor;
@@ -545,22 +554,22 @@ CollisionPolygonEditor::CollisionPolygonEditor(EditorNode *p_editor) {
 	pointsm->set_transform(Transform(Basis(), Vector3(0, 0, 0.00001)));
 	pointsm->set_transform(Transform(Basis(), Vector3(0, 0, 0.00001)));
 }
 }
 
 
-CollisionPolygonEditor::~CollisionPolygonEditor() {
+Polygon3DEditor::~Polygon3DEditor() {
 
 
 	memdelete(imgeom);
 	memdelete(imgeom);
 }
 }
 
 
-void CollisionPolygonEditorPlugin::edit(Object *p_object) {
+void Polygon3DEditorPlugin::edit(Object *p_object) {
 
 
 	collision_polygon_editor->edit(Object::cast_to<Node>(p_object));
 	collision_polygon_editor->edit(Object::cast_to<Node>(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<Spatial>(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) {
 	if (p_visible) {
 		collision_polygon_editor->show();
 		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;
 	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);
 	SpatialEditor::get_singleton()->add_control_to_menu_panel(collision_polygon_editor);
 
 
 	collision_polygon_editor->hide();
 	collision_polygon_editor->hide();
 }
 }
 
 
-CollisionPolygonEditorPlugin::~CollisionPolygonEditorPlugin() {
+Polygon3DEditorPlugin::~Polygon3DEditorPlugin() {
 }
 }

+ 13 - 11
editor/plugins/collision_polygon_editor_plugin.h

@@ -44,9 +44,9 @@
 
 
 class CanvasItemEditor;
 class CanvasItemEditor;
 
 
-class CollisionPolygonEditor : public HBoxContainer {
+class Polygon3DEditor : public HBoxContainer {
 
 
-	GDCLASS(CollisionPolygonEditor, HBoxContainer);
+	GDCLASS(Polygon3DEditor, HBoxContainer);
 
 
 	UndoRedo *undo_redo;
 	UndoRedo *undo_redo;
 	enum Mode {
 	enum Mode {
@@ -66,7 +66,7 @@ class CollisionPolygonEditor : public HBoxContainer {
 
 
 	EditorNode *editor;
 	EditorNode *editor;
 	Panel *panel;
 	Panel *panel;
-	CollisionPolygon *node;
+	Spatial *node;
 	ImmediateGeometry *imgeom;
 	ImmediateGeometry *imgeom;
 	MeshInstance *pointsm;
 	MeshInstance *pointsm;
 	Ref<ArrayMesh> m;
 	Ref<ArrayMesh> m;
@@ -85,6 +85,8 @@ class CollisionPolygonEditor : public HBoxContainer {
 	void _polygon_draw();
 	void _polygon_draw();
 	void _menu_option(int p_option);
 	void _menu_option(int p_option);
 
 
+	float _get_depth();
+
 protected:
 protected:
 	void _notification(int p_what);
 	void _notification(int p_what);
 	void _node_removed(Node *p_node);
 	void _node_removed(Node *p_node);
@@ -93,28 +95,28 @@ protected:
 public:
 public:
 	virtual bool forward_spatial_gui_input(Camera *p_camera, const Ref<InputEvent> &p_event);
 	virtual bool forward_spatial_gui_input(Camera *p_camera, const Ref<InputEvent> &p_event);
 	void edit(Node *p_collision_polygon);
 	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;
 	EditorNode *editor;
 
 
 public:
 public:
 	virtual bool forward_spatial_gui_input(Camera *p_camera, const Ref<InputEvent> &p_event) { return collision_polygon_editor->forward_spatial_gui_input(p_camera, p_event); }
 	virtual bool forward_spatial_gui_input(Camera *p_camera, const Ref<InputEvent> &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; }
 	bool has_main_screen() const { return false; }
 	virtual void edit(Object *p_object);
 	virtual void edit(Object *p_object);
 	virtual bool handles(Object *p_object) const;
 	virtual bool handles(Object *p_object) const;
 	virtual void make_visible(bool p_visible);
 	virtual void make_visible(bool p_visible);
 
 
-	CollisionPolygonEditorPlugin(EditorNode *p_node);
-	~CollisionPolygonEditorPlugin();
+	Polygon3DEditorPlugin(EditorNode *p_node);
+	~Polygon3DEditorPlugin();
 };
 };
 
 
 #endif // COLLISION_POLYGON_EDITOR_PLUGIN_H
 #endif // COLLISION_POLYGON_EDITOR_PLUGIN_H

+ 9 - 0
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")

+ 5 - 0
modules/csg/config.py

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

+ 1472 - 0
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<Vector3> &p_vertices, const PoolVector<Vector2> &p_uvs, const PoolVector<bool> &p_smooth, const PoolVector<Ref<Material> > &p_materials, const PoolVector<bool> &p_invert_faces) {
+
+	clear();
+
+	int vc = p_vertices.size();
+
+	ERR_FAIL_COND((vc % 3) != 0)
+
+	PoolVector<Vector3>::Read rv = p_vertices.read();
+	int uvc = p_uvs.size();
+	PoolVector<Vector2>::Read ruv = p_uvs.read();
+	int sc = p_smooth.size();
+	PoolVector<bool>::Read rs = p_smooth.read();
+	int mc = p_materials.size();
+	PoolVector<Ref<Material> >::Read rm = p_materials.read();
+	int ic = p_invert_faces.size();
+	PoolVector<bool>::Read ri = p_invert_faces.read();
+
+	Map<Ref<Material>, int> material_map;
+
+	faces.resize(p_vertices.size() / 3);
+
+	for (int i = 0; i < faces.size(); i++) {
+		Face &f = faces[i];
+		f.vertices[0] = rv[i * 3 + 0];
+		f.vertices[1] = rv[i * 3 + 1];
+		f.vertices[2] = rv[i * 3 + 2];
+		if (uvc == vc) {
+			f.uvs[0] = ruv[i * 3 + 0];
+			f.uvs[1] = ruv[i * 3 + 1];
+			f.uvs[2] = ruv[i * 3 + 2];
+		}
+		if (sc == vc / 3) {
+			f.smooth = rs[i];
+		} else {
+			f.smooth = false;
+		}
+
+		if (ic == vc / 3) {
+			f.invert = ri[i];
+		} else {
+			f.invert = false;
+		}
+
+		if (mc == vc / 3) {
+			Ref<Material> mat = rm[i];
+			if (mat.is_valid()) {
+				const Map<Ref<Material>, int>::Element *E = material_map.find(mat);
+				if (E) {
+					f.material = E->get();
+				} else {
+					f.material = material_map.size();
+					material_map[mat] = f.material;
+				}
+			} else {
+				f.material = -1;
+			}
+		}
+	}
+
+	materials.resize(material_map.size());
+	for (Map<Ref<Material>, int>::Element *E = material_map.front(); E; E = E->next()) {
+		materials[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<int> inserted_points;
+
+	//keep track of point indices for what was inserted, allowing reuse of points.
+	int segment_idx[2] = { -1, -1 };
+
+	//check if edge and poly share a vertex, of so, assign it to segment_idx
+	for (int i = 0; i < points.size(); i++) {
+		for (int j = 0; j < 2; j++) {
+			if (segment[j].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<int, BuildPoly> &build_polys_a, const CSGBrush *B, int p_face_b, Map<int, BuildPoly> &build_polys_b, MeshMerge &mesh_merge) {
+
+	//construct a frame of reference for both transforms, in order to do intersection test
+	Vector3 va[3] = {
+		A->faces[p_face_a].vertices[0],
+		A->faces[p_face_a].vertices[1],
+		A->faces[p_face_a].vertices[2],
+	};
+	Vector3 vb[3] = {
+		B->faces[p_face_b].vertices[0],
+		B->faces[p_face_b].vertices[1],
+		B->faces[p_face_b].vertices[2],
+	};
+
+	{
+		//check if either is a degenerate
+		if (va[0].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<Vector<int> > &vertex_process, Vector<bool> &edge_process, Vector<PolyPoints> &r_poly) {
+
+	//this function follows the polygon points counter clockwise and adds them. It creates lists of unique polygons
+	//every time an unused edge is found, it's pushed to a stack and continues from there.
+
+	List<EdgeSort> edge_stack;
+
+	{
+		EdgeSort es;
+		es.angle = 0; //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<int> points;
+		points.push_back(e.prev_point);
+
+		int prev_point = e.prev_point;
+		int to_point = e.edge_point;
+		int current_edge = e.edge;
+
+		edge_process[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<EdgeSort> next_edges;
+
+			for (int i = 0; i < vertex_process[to_point].size(); i++) {
+
+				int edge = vertex_process[to_point][i];
+				int opposite_point = p_poly.edges[edge].points[0] == to_point ? p_poly.edges[edge].points[1] : p_poly.edges[edge].points[0];
+				if (opposite_point == prev_point)
+					continue; //not going back
+
+				EdgeSort 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<Vector<int> > &vertex_process, Vector<int> &r_outline) {
+
+	//this is the opposite of the function above. It adds polygon outlines instead.
+	//this is used for triangulating holes.
+	//no stack is used here because only the bigger outline is interesting.
+
+	r_outline.push_back(p_from_point);
+
+	int prev_point = p_from_point;
+	int to_point = p_to_point;
+
+	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<Vector<int> > vertex_process;
+	Vector<bool> edge_process;
+
+	vertex_process.resize(p_poly.points.size());
+	edge_process.resize(p_poly.edges.size());
+
+	//none processed by default
+	for (int i = 0; i < edge_process.size(); i++) {
+		edge_process[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<PolyPoints> 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<int> outline;
+			_add_poly_outline(p_poly, p_poly.edges[i].points[0], p_poly.edges[i].points[1], vertex_process, outline);
+
+			if (outline.size() > 1) {
+				polys[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<Vector2> 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<int> indices = Geometry::triangulate_polygon(vertices);
+
+		for (int j = 0; j < indices.size(); j += 3) {
+
+			//obtain the vertex
+
+			Vector3 face[3];
+			Vector2 uv[3];
+			float cp = Geometry::vec2_cross(p_poly.points[polys[i].points[indices[j + 0]]].point, p_poly.points[polys[i].points[indices[j + 1]]].point, p_poly.points[polys[i].points[indices[j + 2]]].point);
+			if (Math::abs(cp) < CMP_EPSILON)
+				continue;
+
+			for (int k = 0; k < 3; k++) {
+
+				Vector2 p = p_poly.points[polys[i].points[indices[j + k]]].point;
+				face[k] = p_poly.to_world.xform(Vector3(p.x, p.y, 0));
+				uv[k] = p_poly.points[polys[i].points[indices[j + k]]].uv;
+			}
+
+			mesh.add_face(face[0], face[1], face[2], uv[0], uv[1], uv[2], p_poly.smooth, p_poly.invert, p_poly.material, p_from_b);
+		}
+	}
+}
+
+//use a limit to speed up bvh and limit the depth
+#define BVH_LIMIT 8
+
+int CSGBrushOperation::MeshMerge::_create_bvh(BVH *p_bvh, BVH **p_bb, int p_from, int p_size, int p_depth, int &max_depth, int &max_alloc) {
+
+	if (p_depth > max_depth) {
+		max_depth = p_depth;
+	}
+
+	if (p_size <= 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<BVH *, BVHCmpX> sort_x;
+			sort_x.nth_element(0, p_size, p_size / 2, &p_bb[p_from]);
+			//sort_x.sort(&p_bb[p_from],p_size);
+		} break;
+		case Vector3::AXIS_Y: {
+			SortArray<BVH *, BVHCmpY> sort_y;
+			sort_y.nth_element(0, p_size, p_size / 2, &p_bb[p_from]);
+			//sort_y.sort(&p_bb[p_from],p_size);
+		} break;
+		case Vector3::AXIS_Z: {
+			SortArray<BVH *, BVHCmpZ> sort_z;
+			sort_z.nth_element(0, p_size, p_size / 2, &p_bb[p_from]);
+			//sort_z.sort(&p_bb[p_from],p_size);
+
+		} break;
+	}
+
+	int left = _create_bvh(p_bvh, p_bb, p_from, p_size / 2, p_depth + 1, max_depth, max_alloc);
+	int right = _create_bvh(p_bvh, p_bb, p_from + p_size / 2, p_size - p_size / 2, p_depth + 1, max_depth, max_alloc);
+
+	int index = max_alloc++;
+	BVH *_new = &p_bvh[index];
+	_new->aabb = aabb;
+	_new->center = aabb.position + aabb.size * 0.5;
+	_new->face = -1;
+	_new->left = left;
+	_new->right = right;
+	_new->next = -1;
+
+	return index;
+}
+
+int CSGBrushOperation::MeshMerge::_bvh_count_intersections(BVH *bvhptr, int p_max_depth, int p_bvh_first, const Vector3 &p_begin, const Vector3 &p_end, int p_exclude) const {
+
+	uint32_t *stack = (uint32_t *)alloca(sizeof(int) * p_max_depth);
+
+	enum {
+		TEST_AABB_BIT = 0,
+		VISIT_LEFT_BIT = 1,
+		VISIT_RIGHT_BIT = 2,
+		VISIT_DONE_BIT = 3,
+		VISITED_BIT_SHIFT = 29,
+		NODE_IDX_MASK = (1 << VISITED_BIT_SHIFT) - 1,
+		VISITED_BIT_MASK = ~NODE_IDX_MASK,
+
+	};
+
+	int intersections = 0;
+
+	int level = 0;
+
+	const Vector3 *vertexptr = points.ptr();
+	const Face *facesptr = faces.ptr();
+	AABB segment_aabb;
+	segment_aabb.position = p_begin;
+	segment_aabb.expand_to(p_end);
+
+	int pos = p_bvh_first;
+
+	stack[0] = pos;
+	while (true) {
+
+		uint32_t node = stack[level] & NODE_IDX_MASK;
+		const BVH &b = bvhptr[node];
+		bool done = false;
+
+		switch (stack[level] >> VISITED_BIT_SHIFT) {
+			case TEST_AABB_BIT: {
+
+				if (b.face >= 0) {
+
+					const BVH *bp = &b;
+
+					while (bp) {
+
+						bool valid = segment_aabb.intersects(bp->aabb) && bp->aabb.intersects_segment(p_begin, p_end);
+
+						if (valid && p_exclude != bp->face) {
+							const Face &s = facesptr[bp->face];
+							Face3 f3(vertexptr[s.points[0]], vertexptr[s.points[1]], vertexptr[s.points[2]]);
+
+							Vector3 res;
+
+							if (f3.intersects_segment(p_begin, p_end, &res)) {
+								intersections++;
+							}
+						}
+						if (bp->next != -1) {
+							bp = &bvhptr[bp->next];
+						} else {
+							bp = NULL;
+						}
+					}
+
+					stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node;
+
+				} else {
+
+					bool valid = segment_aabb.intersects(b.aabb) && b.aabb.intersects_segment(p_begin, p_end);
+
+					if (!valid) {
+
+						stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node;
+
+					} else {
+						stack[level] = (VISIT_LEFT_BIT << VISITED_BIT_SHIFT) | node;
+					}
+				}
+				continue;
+			}
+			case VISIT_LEFT_BIT: {
+
+				stack[level] = (VISIT_RIGHT_BIT << VISITED_BIT_SHIFT) | node;
+				stack[level + 1] = b.left | TEST_AABB_BIT;
+				level++;
+				continue;
+			}
+			case VISIT_RIGHT_BIT: {
+
+				stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node;
+				stack[level + 1] = b.right | TEST_AABB_BIT;
+				level++;
+				continue;
+			}
+			case VISIT_DONE_BIT: {
+
+				if (level == 0) {
+					done = true;
+					break;
+				} else
+					level--;
+				continue;
+			}
+		}
+
+		if (done)
+			break;
+	}
+
+	return intersections;
+}
+
+void CSGBrushOperation::MeshMerge::mark_inside_faces() {
+
+	// mark faces that are inside. This helps later do the boolean ops when merging.
+	// this approach is very brute force (with a bunch of optimizatios, such as BVH and pre AABB intersection test)
+
+	AABB aabb;
+
+	for (int i = 0; i < points.size(); i++) {
+		if (i == 0) {
+			aabb.position = points[i];
+		} else {
+			aabb.expand_to(points[i]);
+		}
+	}
+
+	float max_distance = aabb.size.length() * 1.2;
+
+	Vector<BVH> bvhvec;
+	bvhvec.resize(faces.size() * 3); //will never be larger than this (todo make better)
+	BVH *bvh = bvhvec.ptrw();
+
+	AABB faces_a;
+	AABB faces_b;
+
+	bool first_a = true;
+	bool first_b = true;
+
+	for (int i = 0; i < faces.size(); i++) {
+		bvh[i].left = -1;
+		bvh[i].right = -1;
+		bvh[i].face = i;
+		bvh[i].aabb.position = points[faces[i].points[0]];
+		bvh[i].aabb.expand_to(points[faces[i].points[1]]);
+		bvh[i].aabb.expand_to(points[faces[i].points[2]]);
+		bvh[i].center = bvh[i].aabb.position + bvh[i].aabb.size * 0.5;
+		bvh[i].next = -1;
+		if (faces[i].from_b) {
+			if (first_b) {
+				faces_b = bvh[i].aabb;
+				first_b = false;
+			} else {
+				faces_b.merge_with(bvh[i].aabb);
+			}
+		} else {
+			if (first_a) {
+				faces_a = bvh[i].aabb;
+				first_a = false;
+			} else {
+				faces_a.merge_with(bvh[i].aabb);
+			}
+		}
+	}
+
+	AABB intersection_aabb = faces_a.intersection(faces_b);
+	intersection_aabb.grow_by(intersection_aabb.get_longest_axis_size() * 0.01); //grow a little, avoid numerical error
+
+	if (intersection_aabb.size == Vector3()) //AABB do not intersect, so neither do shapes.
+		return;
+
+	Vector<BVH *> bvhtrvec;
+	bvhtrvec.resize(faces.size());
+	BVH **bvhptr = bvhtrvec.ptrw();
+	for (int i = 0; i < faces.size(); i++) {
+
+		bvhptr[i] = &bvh[i];
+	}
+
+	int max_depth = 0;
+	int max_alloc = faces.size();
+	_create_bvh(bvh, bvhptr, 0, faces.size(), 1, max_depth, max_alloc);
+
+	for (int i = 0; i < faces.size(); i++) {
+
+		if (!intersection_aabb.intersects(bvh[i].aabb))
+			continue; //not in AABB intersection, so not in face intersection
+		Vector3 center = points[faces[i].points[0]];
+		center += points[faces[i].points[1]];
+		center += points[faces[i].points[2]];
+		center /= 3.0;
+
+		Plane plane(points[faces[i].points[0]], points[faces[i].points[1]], points[faces[i].points[2]]);
+		Vector3 target = center + plane.normal * max_distance;
+
+		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<Material> &p_material, bool p_from_b) {
+
+	Vector3 src_points[3] = { p_a, p_b, p_c };
+	Vector2 src_uvs[3] = { p_uv_a, p_uv_b, p_uv_c };
+	int indices[3];
+	for (int i = 0; i < 3; i++) {
+
+		VertexKey vk;
+		vk.x = int((double(src_points[i].x) + double(vertex_snap) * 0.31234) / double(vertex_snap));
+		vk.y = int((double(src_points[i].y) + double(vertex_snap) * 0.31234) / double(vertex_snap));
+		vk.z = int((double(src_points[i].z) + double(vertex_snap) * 0.31234) / double(vertex_snap));
+
+		int res;
+		if (snap_cache.lookup(vk, &res)) {
+			indices[i] = res;
+		} else {
+			indices[i] = points.size();
+			points.push_back(src_points[i]);
+			snap_cache.set(vk, indices[i]);
+		}
+	}
+
+	if (indices[0] == indices[2] || indices[0] == indices[1] || indices[1] == indices[2])
+		return; //not adding degenerate
+
+	MeshMerge::Face face;
+	face.from_b = p_from_b;
+	face.inside = false;
+	face.smooth = p_smooth;
+	face.invert = p_invert;
+	if (p_material.is_valid()) {
+		if (!materials.has(p_material)) {
+			face.material_idx = materials.size();
+			materials[p_material] = face.material_idx;
+		} else {
+			face.material_idx = materials[p_material];
+		}
+	} else {
+		face.material_idx = -1;
+	}
+
+	for (int k = 0; k < 3; k++) {
+
+		face.points[k] = indices[k];
+		face.uvs[k] = src_uvs[k];
+		;
+	}
+
+	faces.push_back(face);
+}
+
+void CSGBrushOperation::merge_brushes(Operation p_operation, const CSGBrush &p_A, const CSGBrush &p_B, CSGBrush &result, float p_snap) {
+
+	CallbackData cd;
+	cd.self = this;
+	cd.A = &p_A;
+	cd.B = &p_B;
+
+	MeshMerge mesh_merge;
+	mesh_merge.vertex_snap = p_snap;
+
+	//check intersections between faces. Use AABB to speed up precheck
+	//this generates list of buildpolys and clips them.
+	//this was originally BVH optimized, but its not really worth it.
+	for (int i = 0; i < p_A.faces.size(); i++) {
+		cd.face_a = i;
+		for (int j = 0; j < p_B.faces.size(); j++) {
+			if (p_A.faces[i].aabb.intersects(p_B.faces[j].aabb)) {
+				_collision_callback(&p_A, i, cd.build_polys_A, &p_B, j, cd.build_polys_B, mesh_merge);
+			}
+		}
+	}
+
+	//merge the already cliped polys back to 3D
+	for (Map<int, BuildPoly>::Element *E = cd.build_polys_A.front(); E; E = E->next()) {
+		_merge_poly(mesh_merge, E->key(), E->get(), false);
+	}
+
+	for (Map<int, BuildPoly>::Element *E = cd.build_polys_B.front(); E; E = E->next()) {
+		_merge_poly(mesh_merge, E->key(), E->get(), true);
+	}
+
+	//merge the non clipped faces back
+
+	for (int i = 0; i < p_A.faces.size(); i++) {
+
+		if (cd.build_polys_A.has(i))
+			continue; //made from buildpoly, skipping
+
+		Vector3 points[3];
+		Vector2 uvs[3];
+		for (int j = 0; j < 3; j++) {
+			points[j] = p_A.faces[i].vertices[j];
+			uvs[j] = p_A.faces[i].uvs[j];
+		}
+		Ref<Material> material;
+		if (p_A.faces[i].material != -1) {
+			material = p_A.materials[p_A.faces[i].material];
+		}
+		mesh_merge.add_face(points[0], points[1], points[2], uvs[0], uvs[1], uvs[2], p_A.faces[i].smooth, p_A.faces[i].invert, material, false);
+	}
+
+	for (int i = 0; i < p_B.faces.size(); i++) {
+
+		if (cd.build_polys_B.has(i))
+			continue; //made from buildpoly, skipping
+
+		Vector3 points[3];
+		Vector2 uvs[3];
+		for (int j = 0; j < 3; j++) {
+			points[j] = p_B.faces[i].vertices[j];
+			uvs[j] = p_B.faces[i].uvs[j];
+		}
+		Ref<Material> material;
+		if (p_B.faces[i].material != -1) {
+			material = p_B.materials[p_B.faces[i].material];
+		}
+		mesh_merge.add_face(points[0], points[1], points[2], uvs[0], uvs[1], uvs[2], p_B.faces[i].smooth, p_B.faces[i].invert, material, true);
+	}
+
+	//mark faces that ended up inside the intersection
+	mesh_merge.mark_inside_faces();
+
+	//regen new brush to start filling it again
+	result.clear();
+
+	switch (p_operation) {
+
+		case OPERATION_UNION: {
+
+			int outside_count = 0;
+
+			for (int i = 0; i < mesh_merge.faces.size(); i++) {
+				if (mesh_merge.faces[i].inside)
+					continue;
+
+				outside_count++;
+			}
+
+			result.faces.resize(outside_count);
+
+			outside_count = 0;
+
+			for (int i = 0; i < mesh_merge.faces.size(); i++) {
+				if (mesh_merge.faces[i].inside)
+					continue;
+				for (int j = 0; j < 3; j++) {
+					result.faces[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<Ref<Material>, int>::Element *E = mesh_merge.materials.front(); E; E = E->next()) {
+		result.materials[E->get()] = E->key();
+	}
+}

+ 206 - 0
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<Face> faces;
+	Vector<Ref<Material> > materials;
+
+	void _regen_face_aabbs();
+	//create a brush from faces
+	void build_from_faces(const PoolVector<Vector3> &p_vertices, const PoolVector<Vector2> &p_uvs, const PoolVector<bool> &p_smooth, const PoolVector<Ref<Material> > &p_materials, const PoolVector<bool> &p_invert_faces);
+	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<VertexKey, int, 64, VertexKeyHash> snap_cache;
+
+		Vector<Vector3> points;
+
+		struct Face {
+			bool from_b;
+			bool inside;
+			int points[3];
+			Vector2 uvs[3];
+			bool smooth;
+			bool invert;
+			int material_idx;
+		};
+
+		Vector<Face> faces;
+
+		Map<Ref<Material>, int> materials;
+
+		Map<Vector3, int> 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<Material> &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<Point> points;
+
+		struct Edge {
+			bool outer;
+			int points[2];
+			Edge() {
+				outer = false;
+			}
+		};
+
+		Vector<Edge> edges;
+		Ref<Material> 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<int> points;
+
+		Vector<Vector<int> > 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<int, BuildPoly> build_polys_A;
+		Map<int, BuildPoly> build_polys_B;
+	};
+
+	void _add_poly_points(const BuildPoly &p_poly, int p_edge, int p_from_point, int p_to_point, const Vector<Vector<int> > &vertex_process, Vector<bool> &edge_process, Vector<PolyPoints> &r_poly);
+	void _add_poly_outline(const BuildPoly &p_poly, int p_from_point, int p_to_point, const Vector<Vector<int> > &vertex_process, Vector<int> &r_outline);
+	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<int, BuildPoly> &build_polys_a, const CSGBrush *B, int p_face_b, Map<int, BuildPoly> &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

+ 315 - 0
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<CSGSphere>(cs)) {
+
+		return "Radius";
+	}
+
+	if (Object::cast_to<CSGBox>(cs)) {
+
+		static const char *hname[3] = { "Width", "Height", "Depth" };
+		return hname[p_idx];
+	}
+
+	if (Object::cast_to<CSGCylinder>(cs)) {
+
+		return p_idx == 0 ? "Radius" : "Height";
+	}
+
+	if (Object::cast_to<CSGTorus>(cs)) {
+
+		return p_idx == 0 ? "InnerRadius" : "OuterRadius";
+	}
+
+	return "";
+}
+Variant CSGShapeSpatialGizmo::get_handle_value(int p_idx) const {
+
+	if (Object::cast_to<CSGSphere>(cs)) {
+
+		CSGSphere *s = Object::cast_to<CSGSphere>(cs);
+		return s->get_radius();
+	}
+
+	if (Object::cast_to<CSGBox>(cs)) {
+
+		CSGBox *s = Object::cast_to<CSGBox>(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<CSGCylinder>(cs)) {
+
+		CSGCylinder *s = Object::cast_to<CSGCylinder>(cs);
+		return p_idx == 0 ? s->get_radius() : s->get_height();
+	}
+
+	if (Object::cast_to<CSGTorus>(cs)) {
+
+		CSGTorus *s = Object::cast_to<CSGTorus>(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<CSGSphere>(cs)) {
+
+		CSGSphere *s = Object::cast_to<CSGSphere>(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<CSGBox>(cs)) {
+
+		CSGBox *s = Object::cast_to<CSGBox>(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<CSGCylinder>(cs)) {
+
+		CSGCylinder *s = Object::cast_to<CSGCylinder>(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<CSGTorus>(cs)) {
+
+		CSGTorus *s = Object::cast_to<CSGTorus>(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<CSGSphere>(cs)) {
+		CSGSphere *s = Object::cast_to<CSGSphere>(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<CSGBox>(cs)) {
+		CSGBox *s = Object::cast_to<CSGBox>(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<CSGCylinder>(cs)) {
+		CSGCylinder *s = Object::cast_to<CSGCylinder>(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<CSGTorus>(cs)) {
+		CSGTorus *s = Object::cast_to<CSGTorus>(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> material = create_material("shape_material", gizmo_color);
+
+	PoolVector<Vector3> faces = cs->get_brush_faces();
+
+	Vector<Vector3> lines;
+	lines.resize(faces.size() * 2);
+	{
+		PoolVector<Vector3>::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<CSGSphere>(cs)) {
+		CSGSphere *s = Object::cast_to<CSGSphere>(cs);
+
+		float r = s->get_radius();
+		Vector<Vector3> handles;
+		handles.push_back(Vector3(r, 0, 0));
+		add_handles(handles);
+	}
+
+	if (Object::cast_to<CSGBox>(cs)) {
+		CSGBox *s = Object::cast_to<CSGBox>(cs);
+
+		Vector<Vector3> 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<CSGCylinder>(cs)) {
+		CSGCylinder *s = Object::cast_to<CSGCylinder>(cs);
+
+		Vector<Vector3> 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<CSGTorus>(cs)) {
+		CSGTorus *s = Object::cast_to<CSGTorus>(cs);
+
+		Vector<Vector3> 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<SpatialEditorGizmo> EditorPluginCSG::create_spatial_gizmo(Spatial *p_spatial) {
+	if (Object::cast_to<CSGSphere>(p_spatial) || Object::cast_to<CSGBox>(p_spatial) || Object::cast_to<CSGCylinder>(p_spatial) || Object::cast_to<CSGTorus>(p_spatial) || Object::cast_to<CSGMesh>(p_spatial) || Object::cast_to<CSGPolygon>(p_spatial)) {
+		Ref<CSGShapeSpatialGizmo> csg = memnew(CSGShapeSpatialGizmo(Object::cast_to<CSGShape>(p_spatial)));
+		return csg;
+	}
+
+	return Ref<SpatialEditorGizmo>();
+}
+
+EditorPluginCSG::EditorPluginCSG(EditorNode *p_editor) {
+
+	EDITOR_DEF("editors/3d_gizmos/gizmo_colors/csg", Color(0.2, 0.5, 1, 0.1));
+}

+ 30 - 0
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<SpatialEditorGizmo> create_spatial_gizmo(Spatial *p_spatial);
+	EditorPluginCSG(EditorNode *p_editor);
+};
+
+#endif // CSG_GIZMOS_H

+ 2152 - 0
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<Vector3, Vector3> vec_map;
+
+	Vector<int> 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<ShapeUpdateSurface> 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<Vector3> 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<Vector3>::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<Vector3>::Write();
+		surfaces[i].normalsw = PoolVector<Vector3>::Write();
+		surfaces[i].uvsw = PoolVector<Vector2>::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<Vector3> CSGShape::get_brush_faces() {
+	ERR_FAIL_COND_V(!is_inside_tree(), PoolVector<Vector3>());
+	CSGBrush *b = _get_brush();
+
+	PoolVector<Vector3> faces;
+	int fc = b->faces.size();
+	faces.resize(fc * 3);
+	{
+		PoolVector<Vector3>::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<Face3> CSGShape::get_faces(uint32_t p_usage_flags) const {
+
+	return PoolVector<Face3>();
+}
+
+void CSGShape::_notification(int p_what) {
+
+	if (p_what == NOTIFICATION_ENTER_TREE) {
+
+		Node *parentn = get_parent();
+		if (parentn) {
+			parent = Object::cast_to<CSGShape>(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<CSGShape>(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<CSGShape>(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<Vector3> &p_vertices, const PoolVector<Vector2> &p_uv, const PoolVector<bool> &p_smooth, const PoolVector<Ref<Material> > &p_materials) {
+
+	CSGBrush *brush = memnew(CSGBrush);
+
+	PoolVector<bool> invert;
+	invert.resize(p_vertices.size() / 3);
+	{
+		int ic = invert.size();
+		PoolVector<bool>::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<Vector3> vertices;
+	PoolVector<bool> smooth;
+	PoolVector<Ref<Material> > materials;
+	PoolVector<Vector2> 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<Vector3> avertices = arrays[Mesh::ARRAY_VERTEX];
+		if (avertices.size() == 0)
+			continue;
+
+		PoolVector<Vector3>::Read vr = avertices.read();
+
+		PoolVector<Vector3> anormals = arrays[Mesh::ARRAY_NORMAL];
+		PoolVector<Vector3>::Read nr;
+		bool nr_used = false;
+		if (anormals.size()) {
+			nr = anormals.read();
+			nr_used = true;
+		}
+
+		PoolVector<Vector2> auvs = arrays[Mesh::ARRAY_TEX_UV];
+		PoolVector<Vector2>::Read uvr;
+		bool uvr_used = false;
+		if (auvs.size()) {
+			uvr = auvs.read();
+			uvr_used = true;
+		}
+
+		Ref<Material> mat = mesh->surface_get_material(i);
+
+		PoolVector<int> 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<Vector3>::Write vw = vertices.write();
+			PoolVector<bool>::Write sw = smooth.write();
+			PoolVector<Vector2>::Write uvw = uvs.write();
+			PoolVector<Ref<Material> >::Write mw = materials.write();
+
+			PoolVector<int>::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<Vector3>::Write vw = vertices.write();
+			PoolVector<bool>::Write sw = smooth.write();
+			PoolVector<Vector2>::Write uvw = uvs.write();
+			PoolVector<Ref<Material> >::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<Mesh> &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<Mesh> 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> material = get_material();
+
+	PoolVector<Vector3> faces;
+	PoolVector<Vector2> uvs;
+	PoolVector<bool> smooth;
+	PoolVector<Ref<Material> > materials;
+	PoolVector<bool> invert;
+
+	faces.resize(face_count * 3);
+	uvs.resize(face_count * 3);
+
+	smooth.resize(face_count);
+	materials.resize(face_count);
+	invert.resize(face_count);
+
+	{
+
+		PoolVector<Vector3>::Write facesw = faces.write();
+		PoolVector<Vector2>::Write uvsw = uvs.write();
+		PoolVector<bool>::Write smoothw = smooth.write();
+		PoolVector<Ref<Material> >::Write materialsw = materials.write();
+		PoolVector<bool>::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<Material> &p_material) {
+
+	material = p_material;
+	_make_dirty();
+}
+
+Ref<Material> 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> material = get_material();
+
+	PoolVector<Vector3> faces;
+	PoolVector<Vector2> uvs;
+	PoolVector<bool> smooth;
+	PoolVector<Ref<Material> > materials;
+	PoolVector<bool> invert;
+
+	faces.resize(face_count * 3);
+	uvs.resize(face_count * 3);
+
+	smooth.resize(face_count);
+	materials.resize(face_count);
+	invert.resize(face_count);
+
+	{
+
+		PoolVector<Vector3>::Write facesw = faces.write();
+		PoolVector<Vector2>::Write uvsw = uvs.write();
+		PoolVector<bool>::Write smoothw = smooth.write();
+		PoolVector<Ref<Material> >::Write materialsw = materials.write();
+		PoolVector<bool>::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<Material> &p_material) {
+
+	material = p_material;
+	_make_dirty();
+	update_gizmo();
+}
+
+Ref<Material> 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> material = get_material();
+
+	PoolVector<Vector3> faces;
+	PoolVector<Vector2> uvs;
+	PoolVector<bool> smooth;
+	PoolVector<Ref<Material> > materials;
+	PoolVector<bool> invert;
+
+	faces.resize(face_count * 3);
+	uvs.resize(face_count * 3);
+
+	smooth.resize(face_count);
+	materials.resize(face_count);
+	invert.resize(face_count);
+
+	{
+
+		PoolVector<Vector3>::Write facesw = faces.write();
+		PoolVector<Vector2>::Write uvsw = uvs.write();
+		PoolVector<bool>::Write smoothw = smooth.write();
+		PoolVector<Ref<Material> >::Write materialsw = materials.write();
+		PoolVector<bool>::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<Material> &p_material) {
+
+	material = p_material;
+	_make_dirty();
+}
+
+Ref<Material> 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> material = get_material();
+
+	PoolVector<Vector3> faces;
+	PoolVector<Vector2> uvs;
+	PoolVector<bool> smooth;
+	PoolVector<Ref<Material> > materials;
+	PoolVector<bool> invert;
+
+	faces.resize(face_count * 3);
+	uvs.resize(face_count * 3);
+
+	smooth.resize(face_count);
+	materials.resize(face_count);
+	invert.resize(face_count);
+
+	{
+
+		PoolVector<Vector3>::Write facesw = faces.write();
+		PoolVector<Vector2>::Write uvsw = uvs.write();
+		PoolVector<bool>::Write smoothw = smooth.write();
+		PoolVector<Ref<Material> >::Write materialsw = materials.write();
+		PoolVector<bool>::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<Material> &p_material) {
+
+	material = p_material;
+	_make_dirty();
+}
+
+Ref<Material> 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<int> triangles = Geometry::triangulate_polygon(polygon);
+
+	if (triangles.size() < 3)
+		return NULL;
+
+	Path *path = NULL;
+	Ref<Curve3D> 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<Path>(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> material = get_material();
+
+	PoolVector<Vector3> faces;
+	PoolVector<Vector2> uvs;
+	PoolVector<bool> smooth;
+	PoolVector<Ref<Material> > materials;
+	PoolVector<bool> 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<Vector3>::Write facesw = faces.write();
+		PoolVector<Vector2>::Write uvsw = uvs.write();
+		PoolVector<bool>::Write smoothw = smooth.write();
+		PoolVector<Ref<Material> >::Write materialsw = materials.write();
+		PoolVector<bool>::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<Vector2> &p_polygon) {
+	polygon = p_polygon;
+	_make_dirty();
+	update_gizmo();
+}
+
+Vector<Vector2> 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<Material> &p_material) {
+
+	material = p_material;
+	_make_dirty();
+}
+
+Ref<Material> 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;
+}

+ 363 - 0
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<ConcavePolygonShape> root_collision_shape;
+	RID root_collision_instance;
+
+	Ref<ArrayMesh> 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<Vector3> vertices;
+		PoolVector<Vector3> normals;
+		PoolVector<Vector2> uvs;
+		Ref<Material> material;
+		int last_added;
+
+		PoolVector<Vector3>::Write verticesw;
+		PoolVector<Vector3>::Write normalsw;
+		PoolVector<Vector2>::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<Vector3> get_brush_faces();
+
+	virtual AABB get_aabb() const;
+	virtual PoolVector<Face3> 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<Vector3> &p_vertices, const PoolVector<Vector2> &p_uv, const PoolVector<bool> &p_smooth, const PoolVector<Ref<Material> > &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> mesh;
+
+	void _mesh_changed();
+
+protected:
+	static void _bind_methods();
+
+public:
+	void set_mesh(const Ref<Mesh> &p_mesh);
+	Ref<Mesh> get_mesh();
+};
+
+class CSGSphere : public CSGPrimitive {
+
+	GDCLASS(CSGSphere, CSGPrimitive)
+	virtual CSGBrush *_build_brush(AABB *r_aabb);
+
+	Ref<Material> 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<Material> &p_material);
+	Ref<Material> 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> 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<Material> &p_material);
+	Ref<Material> get_material() const;
+
+	CSGBox();
+};
+
+class CSGCylinder : public CSGPrimitive {
+
+	GDCLASS(CSGCylinder, CSGPrimitive)
+	virtual CSGBrush *_build_brush(AABB *r_aabb);
+
+	Ref<Material> 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<Material> &p_material);
+	Ref<Material> get_material() const;
+
+	CSGCylinder();
+};
+
+class CSGTorus : public CSGPrimitive {
+
+	GDCLASS(CSGTorus, CSGPrimitive)
+	virtual CSGBrush *_build_brush(AABB *r_aabb);
+
+	Ref<Material> 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<Material> &p_material);
+	Ref<Material> 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<Vector2> polygon;
+	Ref<Material> 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<Vector2> &p_polygon);
+	Vector<Vector2> 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<Material> &p_material);
+	Ref<Material> get_material() const;
+
+	CSGPolygon();
+};
+
+VARIANT_ENUM_CAST(CSGPolygon::Mode)
+VARIANT_ENUM_CAST(CSGPolygon::PathRotation)
+
+#endif // CSG_SHAPE_H

+ 59 - 0
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<CSGShape>();
+	ClassDB::register_virtual_class<CSGPrimitive>();
+	ClassDB::register_class<CSGMesh>();
+	ClassDB::register_class<CSGSphere>();
+	ClassDB::register_class<CSGBox>();
+	ClassDB::register_class<CSGCylinder>();
+	ClassDB::register_class<CSGTorus>();
+	ClassDB::register_class<CSGPolygon>();
+	ClassDB::register_class<CSGCombiner>();
+
+#ifdef TOOLS_ENABLED
+	EditorPlugins::add_by_type<EditorPluginCSG>();
+#endif
+#endif
+
+}
+
+void unregister_csg_types() {
+
+}

+ 32 - 0
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();

+ 5 - 0
scene/3d/collision_polygon.cpp

@@ -173,6 +173,9 @@ String CollisionPolygon::get_configuration_warning() const {
 	return String();
 	return String();
 }
 }
 
 
+bool CollisionPolygon::_is_editable_3d_polygon() const {
+	return true;
+}
 void CollisionPolygon::_bind_methods() {
 void CollisionPolygon::_bind_methods() {
 
 
 	ClassDB::bind_method(D_METHOD("set_depth", "depth"), &CollisionPolygon::set_depth);
 	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("set_disabled", "disabled"), &CollisionPolygon::set_disabled);
 	ClassDB::bind_method(D_METHOD("is_disabled"), &CollisionPolygon::is_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::REAL, "depth"), "set_depth", "get_depth");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled");
 	ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR2_ARRAY, "polygon"), "set_polygon", "get_polygon");
 	ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR2_ARRAY, "polygon"), "set_polygon", "get_polygon");

+ 2 - 0
scene/3d/collision_polygon.h

@@ -53,6 +53,8 @@ protected:
 
 
 	void _update_in_shape_owner(bool p_xform_only = false);
 	void _update_in_shape_owner(bool p_xform_only = false);
 
 
+	bool _is_editable_3d_polygon() const;
+
 protected:
 protected:
 	void _notification(int p_what);
 	void _notification(int p_what);
 	static void _bind_methods();
 	static void _bind_methods();

+ 5 - 0
scene/3d/path.cpp

@@ -40,6 +40,9 @@ void Path::_curve_changed() {
 
 
 	if (is_inside_tree() && Engine::get_singleton()->is_editor_hint())
 	if (is_inside_tree() && Engine::get_singleton()->is_editor_hint())
 		update_gizmo();
 		update_gizmo();
+	if (is_inside_tree()) {
+		emit_signal("curve_changed");
+	}
 }
 }
 
 
 void Path::set_curve(const Ref<Curve3D> &p_curve) {
 void Path::set_curve(const Ref<Curve3D> &p_curve) {
@@ -68,6 +71,8 @@ void Path::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("_curve_changed"), &Path::_curve_changed);
 	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_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve3D"), "set_curve", "get_curve");
+
+	ADD_SIGNAL(MethodInfo("curve_changed"));
 }
 }
 
 
 Path::Path() {
 Path::Path() {

+ 5 - 0
scene/resources/mesh.cpp

@@ -912,6 +912,7 @@ void ArrayMesh::surface_set_material(int p_idx, const Ref<Material> &p_material)
 	VisualServer::get_singleton()->mesh_surface_set_material(mesh, p_idx, p_material.is_null() ? RID() : p_material->get_rid());
 	VisualServer::get_singleton()->mesh_surface_set_material(mesh, p_idx, p_material.is_null() ? RID() : p_material->get_rid());
 
 
 	_change_notify("material");
 	_change_notify("material");
+	emit_changed();
 }
 }
 
 
 void ArrayMesh::surface_set_name(int p_idx, const String &p_name) {
 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());
 	ERR_FAIL_INDEX(p_idx, surfaces.size());
 
 
 	surfaces[p_idx].name = p_name;
 	surfaces[p_idx].name = p_name;
+	emit_changed();
 }
 }
 
 
 String ArrayMesh::surface_get_name(int p_idx) const {
 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());
 	ERR_FAIL_INDEX(p_surface, surfaces.size());
 	VS::get_singleton()->mesh_surface_update_region(mesh, p_surface, p_offset, p_data);
 	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) {
 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());
 	ERR_FAIL_INDEX(p_idx, surfaces.size());
 	surfaces[p_idx].aabb = p_aabb;
 	surfaces[p_idx].aabb = p_aabb;
 	// set custom aabb too?
 	// set custom aabb too?
+	emit_changed();
 }
 }
 
 
 Ref<Material> ArrayMesh::surface_get_material(int p_idx) const {
 Ref<Material> 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;
 	custom_aabb = p_custom;
 	VS::get_singleton()->mesh_set_custom_aabb(mesh, custom_aabb);
 	VS::get_singleton()->mesh_set_custom_aabb(mesh, custom_aabb);
+	emit_changed();
 }
 }
 
 
 AABB ArrayMesh::get_custom_aabb() const {
 AABB ArrayMesh::get_custom_aabb() const {

+ 2 - 0
scene/resources/primitive_meshes.cpp

@@ -65,6 +65,8 @@ void PrimitiveMesh::_update() const {
 	pending_request = false;
 	pending_request = false;
 
 
 	_clear_triangle_mesh();
 	_clear_triangle_mesh();
+
+	const_cast<PrimitiveMesh *>(this)->emit_changed();
 }
 }
 
 
 void PrimitiveMesh::_request_update() {
 void PrimitiveMesh::_request_update() {