Browse Source

Merge pull request #49313 from madmiraal/fix-48408

Fix multiple issues with CSGPolygon
Rémi Verschelde 4 years ago
parent
commit
2eb3c9580f
3 changed files with 264 additions and 438 deletions
  1. 239 414
      modules/csg/csg_shape.cpp
  2. 3 4
      modules/csg/csg_shape.h
  3. 22 20
      modules/csg/doc_classes/CSGPolygon3D.xml

+ 239 - 414
modules/csg/csg_shape.cpp

@@ -29,8 +29,8 @@
 /*************************************************************************/
 
 #include "csg_shape.h"
+
 #include "core/math/geometry_2d.h"
-#include "scene/3d/path_3d.h"
 
 void CSGShape3D::set_use_collision(bool p_enable) {
 	if (use_collision == p_enable) {
@@ -1676,109 +1676,80 @@ CSGTorus3D::CSGTorus3D() {
 ///////////////
 
 CSGBrush *CSGPolygon3D::_build_brush() {
-	// set our bounding box
+	CSGBrush *brush = memnew(CSGBrush);
 
 	if (polygon.size() < 3) {
-		return memnew(CSGBrush);
+		return brush;
 	}
 
-	Vector<Point2> final_polygon = polygon;
-
-	if (Triangulate::get_area(final_polygon) > 0) {
-		final_polygon.reverse();
+	// Triangulate polygon shape.
+	Vector<Point2> shape_polygon = polygon;
+	if (Triangulate::get_area(shape_polygon) > 0) {
+		shape_polygon.reverse();
 	}
-
-	Vector<int> triangles = Geometry2D::triangulate_polygon(final_polygon);
-
-	if (triangles.size() < 3) {
-		return memnew(CSGBrush);
+	int shape_sides = shape_polygon.size();
+	Vector<int> shape_faces = Geometry2D::triangulate_polygon(shape_polygon);
+	ERR_FAIL_COND_V_MSG(shape_faces.size() < 3, brush, "Failed to triangulate CSGPolygon");
+
+	// Get polygon enclosing Rect2.
+	Rect2 shape_rect(shape_polygon[0], Vector2());
+	for (int i = 1; i < shape_sides; i++) {
+		shape_rect.expand_to(shape_polygon[i]);
 	}
 
-	Path3D *path = nullptr;
+	// If MODE_PATH, check if curve has changed.
 	Ref<Curve3D> curve;
-
-	// get bounds for our polygon
-	Vector2 final_polygon_min;
-	Vector2 final_polygon_max;
-	for (int i = 0; i < final_polygon.size(); i++) {
-		Vector2 p = final_polygon[i];
-		if (i == 0) {
-			final_polygon_min = p;
-			final_polygon_max = final_polygon_min;
-		} else {
-			if (p.x < final_polygon_min.x) {
-				final_polygon_min.x = p.x;
-			}
-			if (p.y < final_polygon_min.y) {
-				final_polygon_min.y = p.y;
-			}
-
-			if (p.x > final_polygon_max.x) {
-				final_polygon_max.x = p.x;
+	if (mode == MODE_PATH) {
+		Path3D *current_path = Object::cast_to<Path3D>(get_node_or_null(path_node));
+		if (path != current_path) {
+			if (path) {
+				path->disconnect("tree_exited", callable_mp(this, &CSGPolygon3D::_path_exited));
+				path->disconnect("curve_changed", callable_mp(this, &CSGPolygon3D::_path_changed));
 			}
-			if (p.y > final_polygon_max.y) {
-				final_polygon_max.y = p.y;
+			path = current_path;
+			if (path) {
+				path->connect("tree_exited", callable_mp(this, &CSGPolygon3D::_path_exited));
+				path->connect("curve_changed", callable_mp(this, &CSGPolygon3D::_path_changed));
 			}
 		}
-	}
-	Vector2 final_polygon_size = final_polygon_max - final_polygon_min;
 
-	if (mode == MODE_PATH) {
-		if (!has_node(path_node)) {
-			return memnew(CSGBrush);
-		}
-		Node *n = get_node(path_node);
-		if (!n) {
-			return memnew(CSGBrush);
-		}
-		path = Object::cast_to<Path3D>(n);
 		if (!path) {
-			return memnew(CSGBrush);
+			return brush;
 		}
 
-		if (path != path_cache) {
-			if (path_cache) {
-				path_cache->disconnect("tree_exited", callable_mp(this, &CSGPolygon3D::_path_exited));
-				path_cache->disconnect("curve_changed", callable_mp(this, &CSGPolygon3D::_path_changed));
-				path_cache = nullptr;
-			}
-
-			path_cache = path;
-
-			path_cache->connect("tree_exited", callable_mp(this, &CSGPolygon3D::_path_exited));
-			path_cache->connect("curve_changed", callable_mp(this, &CSGPolygon3D::_path_changed));
-		}
 		curve = path->get_curve();
-		if (curve.is_null()) {
-			return memnew(CSGBrush);
-		}
-		if (curve->get_baked_length() <= 0) {
-			return memnew(CSGBrush);
+		if (curve.is_null() || curve->get_point_count() < 2) {
+			return brush;
 		}
 	}
-	CSGBrush *brush = memnew(CSGBrush);
-
-	int face_count = 0;
 
+	// Calculate the number extrusions, ends and faces.
+	int extrusions = 0;
+	int extrusion_face_count = shape_sides * 2;
+	int end_count = 0;
+	int shape_face_count = shape_faces.size() / 3;
 	switch (mode) {
 		case MODE_DEPTH:
-			face_count = triangles.size() * 2 / 3 + (final_polygon.size()) * 2;
+			extrusions = 1;
+			end_count = 2;
 			break;
 		case MODE_SPIN:
-			face_count = (spin_degrees < 360 ? triangles.size() * 2 / 3 : 0) + (final_polygon.size()) * 2 * spin_sides;
+			extrusions = spin_sides;
+			if (spin_degrees < 360) {
+				end_count = 2;
+			}
 			break;
 		case MODE_PATH: {
-			float bl = curve->get_baked_length();
-			int splits = MAX(2, Math::ceil(bl / path_interval));
-			if (path_joined) {
-				face_count = splits * final_polygon.size() * 2;
-			} else {
-				face_count = triangles.size() * 2 / 3 + splits * final_polygon.size() * 2;
+			extrusions = Math::ceil(1.0 * curve->get_point_count() / path_interval);
+			if (!path_joined) {
+				end_count = 2;
+				extrusions -= 1;
 			}
 		} break;
 	}
+	int face_count = extrusions * extrusion_face_count + end_count * shape_face_count;
 
-	bool invert_val = is_inverting_faces();
+	// Intialize variables used to create the mesh.
 	Ref<Material> material = get_material();
 
 	Vector<Vector3> faces;
@@ -1789,362 +1760,216 @@ CSGBrush *CSGPolygon3D::_build_brush() {
 
 	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
-	{
-		Vector3 *facesw = faces.ptrw();
-		Vector2 *uvsw = uvs.ptrw();
-		bool *smoothw = smooth.ptrw();
-		Ref<Material> *materialsw = materials.ptrw();
-		bool *invertw = invert.ptrw();
-
-		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 = final_polygon[triangles[j + src[k]]];
-							Vector3 v = Vector3(p.x, p.y, 0);
-							if (i == 0) {
-								v.z -= depth;
-							}
-							facesw[face * 3 + k] = v;
-							uvsw[face * 3 + k] = (p - final_polygon_min) / final_polygon_size;
-							if (i == 0) {
-								uvsw[face * 3 + k].x = 1.0 - uvsw[face * 3 + k].x; /* flip x */
-							}
-						}
-
-						smoothw[face] = false;
-						materialsw[face] = material;
-						invertw[face] = invert_val;
-						face++;
-					}
-				}
-
-				//add triangles for depth
-				for (int i = 0; i < final_polygon.size(); i++) {
-					int i_n = (i + 1) % final_polygon.size();
-
-					Vector3 v[4] = {
-						Vector3(final_polygon[i].x, final_polygon[i].y, -depth),
-						Vector3(final_polygon[i_n].x, final_polygon[i_n].y, -depth),
-						Vector3(final_polygon[i_n].x, final_polygon[i_n].y, 0),
-						Vector3(final_polygon[i].x, final_polygon[i].y, 0),
-					};
-
-					Vector2 u[4] = {
-						Vector2(0, 0),
-						Vector2(0, 1),
-						Vector2(1, 1),
-						Vector2(1, 0)
-					};
+	Vector3 *facesw = faces.ptrw();
+	Vector2 *uvsw = uvs.ptrw();
+	bool *smoothw = smooth.ptrw();
+	Ref<Material> *materialsw = materials.ptrw();
+	bool *invertw = invert.ptrw();
+
+	int face = 0;
+	Transform3D base_xform;
+	Transform3D current_xform;
+	Transform3D previous_xform;
+	double u_step = 1.0 / extrusions;
+	double v_step = 1.0 / shape_sides;
+	double spin_step = Math::deg2rad(spin_degrees / spin_sides);
+	double extrusion_step = 1.0 / extrusions;
+	if (mode == MODE_PATH) {
+		if (path_joined) {
+			extrusion_step = 1.0 / (extrusions - 1);
+		}
+		extrusion_step *= curve->get_baked_length();
+	}
 
-					// face 1
-					facesw[face * 3 + 0] = v[0];
-					facesw[face * 3 + 1] = v[1];
-					facesw[face * 3 + 2] = v[2];
+	if (mode == MODE_PATH) {
+		if (!path_local) {
+			base_xform = path->get_global_transform();
+		}
 
-					uvsw[face * 3 + 0] = u[0];
-					uvsw[face * 3 + 1] = u[1];
-					uvsw[face * 3 + 2] = u[2];
+		Vector3 current_point = curve->interpolate_baked(0);
+		Vector3 next_point = curve->interpolate_baked(extrusion_step);
+		Vector3 current_up = Vector3(0, 1, 0);
+		Vector3 direction = next_point - current_point;
 
-					smoothw[face] = smooth_faces;
-					invertw[face] = invert_val;
-					materialsw[face] = material;
+		if (path_joined) {
+			Vector3 last_point = curve->interpolate_baked(curve->get_baked_length());
+			direction = next_point - last_point;
+		}
 
-					face++;
+		switch (path_rotation) {
+			case PATH_ROTATION_POLYGON:
+				direction = Vector3(0, 0, -1);
+				break;
+			case PATH_ROTATION_PATH:
+				break;
+			case PATH_ROTATION_PATH_FOLLOW:
+				current_up = curve->interpolate_baked_up_vector(0);
+				break;
+		}
 
-					// face 2
-					facesw[face * 3 + 0] = v[2];
-					facesw[face * 3 + 1] = v[3];
-					facesw[face * 3 + 2] = v[0];
+		Transform3D facing = Transform3D().looking_at(direction, current_up);
+		current_xform = base_xform.translated(current_point) * facing;
+	}
 
-					uvsw[face * 3 + 0] = u[2];
-					uvsw[face * 3 + 1] = u[3];
-					uvsw[face * 3 + 2] = u[0];
+	// Create the mesh.
+	if (end_count > 0) {
+		// Add front end face.
+		for (int face_idx = 0; face_idx < shape_face_count; face_idx++) {
+			for (int face_vertex_idx = 0; face_vertex_idx < 3; face_vertex_idx++) {
+				// We need to reverse the rotation of the shape face vertices.
+				int index = shape_faces[face_idx * 3 + 2 - face_vertex_idx];
+				Point2 p = shape_polygon[index];
+				Point2 uv = (p - shape_rect.position) / shape_rect.size;
+
+				// Use the left side of the bottom half of the y-inverted texture.
+				uv.x = uv.x / 2;
+				uv.y = 1 - (uv.y / 2);
+
+				facesw[face * 3 + face_vertex_idx] = current_xform.xform(Vector3(p.x, p.y, 0));
+				uvsw[face * 3 + face_vertex_idx] = uv;
+			}
 
-					smoothw[face] = smooth_faces;
-					invertw[face] = invert_val;
-					materialsw[face] = material;
+			smoothw[face] = false;
+			materialsw[face] = material;
+			invertw[face] = invert_faces;
+			face++;
+		}
+	}
 
-					face++;
-				}
+	// Add extrusion faces.
+	for (int x0 = 0; x0 < extrusions; x0++) {
+		previous_xform = current_xform;
 
+		switch (mode) {
+			case MODE_DEPTH: {
+				current_xform.translate(Vector3(0, 0, -depth));
 			} 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 = -Math::deg2rad(inci * spin_degrees);
-					float angi_n = -Math::deg2rad(inci_n * spin_degrees);
-
-					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 < final_polygon.size(); j++) {
-						int j_n = (j + 1) % final_polygon.size();
-
-						Vector3 v[4] = {
-							Vector3(normali.x * final_polygon[j].x, final_polygon[j].y, normali.z * final_polygon[j].x),
-							Vector3(normali.x * final_polygon[j_n].x, final_polygon[j_n].y, normali.z * final_polygon[j_n].x),
-							Vector3(normali_n.x * final_polygon[j_n].x, final_polygon[j_n].y, normali_n.z * final_polygon[j_n].x),
-							Vector3(normali_n.x * final_polygon[j].x, final_polygon[j].y, normali_n.z * final_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 = final_polygon[triangles[j + src[k]]];
-								Vector3 v = Vector3(p.x, p.y, 0);
-								facesw[face * 3 + k] = v;
-								uvsw[face * 3 + k] = (p - final_polygon_min) / final_polygon_size;
-							}
-
-							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 = final_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;
-								uvsw[face * 3 + k] = (p - final_polygon_min) / final_polygon_size;
-								uvsw[face * 3 + k].x = 1.0 - uvsw[face * 3 + k].x; /* flip x */
-							}
-
-							smoothw[face] = false;
-							materialsw[face] = material;
-							invertw[face] = invert_val;
-							face++;
-						}
-					}
-				}
+				current_xform.rotate(Vector3(0, 1, 0), spin_step);
 			} break;
 			case MODE_PATH: {
-				float bl = curve->get_baked_length();
-				int splits = MAX(2, Math::ceil(bl / path_interval));
-				float u1 = 0.0;
-				float u2 = path_continuous_u ? 0.0 : 1.0;
-
-				Transform3D path_to_this;
-				if (!path_local) {
-					// center on paths origin
-					path_to_this = get_global_transform().affine_inverse() * path->get_global_transform();
-				}
-
-				Transform3D 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;
-					if (ofs > bl) {
-						ofs = bl;
-					}
-					if (i == splits && path_joined) {
-						ofs = 0.0;
-					}
-
-					Transform3D 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();
-
+				double previous_offset = x0 * extrusion_step;
+				double current_offset = (x0 + 1) * extrusion_step;
+				double next_offset = (x0 + 2) * extrusion_step;
+				if (x0 == extrusions - 1) {
+					if (path_joined) {
+						current_offset = 0;
+						next_offset = extrusion_step;
 					} 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) {
-						if (path_continuous_u) {
-							u1 = u2;
-							u2 += (prev_xf.origin - xf.origin).length();
-						};
-
-						//put triangles where they belong
-						//add triangles for depth
-						for (int j = 0; j < final_polygon.size(); j++) {
-							int j_n = (j + 1) % final_polygon.size();
-
-							Vector3 v[4] = {
-								prev_xf.xform(Vector3(final_polygon[j].x, final_polygon[j].y, 0)),
-								prev_xf.xform(Vector3(final_polygon[j_n].x, final_polygon[j_n].y, 0)),
-								xf.xform(Vector3(final_polygon[j_n].x, final_polygon[j_n].y, 0)),
-								xf.xform(Vector3(final_polygon[j].x, final_polygon[j].y, 0)),
-							};
-
-							Vector2 u[4] = {
-								Vector2(u1, 1),
-								Vector2(u1, 0),
-								Vector2(u2, 0),
-								Vector2(u2, 1)
-							};
-
-							// 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 && !path_joined) {
-						for (int j = 0; j < triangles.size(); j += 3) {
-							for (int k = 0; k < 3; k++) {
-								int src[3] = { 0, 1, 2 };
-								Vector2 p = final_polygon[triangles[j + src[k]]];
-								Vector3 v = Vector3(p.x, p.y, 0);
-								facesw[face * 3 + k] = xf.xform(v);
-								uvsw[face * 3 + k] = (p - final_polygon_min) / final_polygon_size;
-							}
-
-							smoothw[face] = false;
-							materialsw[face] = material;
-							invertw[face] = invert_val;
-							face++;
-						}
+						next_offset = current_offset;
 					}
+				}
 
-					if (i == splits && !path_joined) {
-						for (int j = 0; j < triangles.size(); j += 3) {
-							for (int k = 0; k < 3; k++) {
-								int src[3] = { 0, 2, 1 };
-								Vector2 p = final_polygon[triangles[j + src[k]]];
-								Vector3 v = Vector3(p.x, p.y, 0);
-								facesw[face * 3 + k] = xf.xform(v);
-								uvsw[face * 3 + k] = (p - final_polygon_min) / final_polygon_size;
-								uvsw[face * 3 + k].x = 1.0 - uvsw[face * 3 + k].x; /* flip x */
-							}
-
-							smoothw[face] = false;
-							materialsw[face] = material;
-							invertw[face] = invert_val;
-							face++;
-						}
-					}
+				Vector3 previous_point = curve->interpolate_baked(previous_offset);
+				Vector3 current_point = curve->interpolate_baked(current_offset);
+				Vector3 next_point = curve->interpolate_baked(next_offset);
+				Vector3 current_up = Vector3(0, 1, 0);
+				Vector3 direction = next_point - previous_point;
 
-					prev_xf = xf;
+				switch (path_rotation) {
+					case PATH_ROTATION_POLYGON:
+						direction = Vector3(0, 0, -1);
+						break;
+					case PATH_ROTATION_PATH:
+						break;
+					case PATH_ROTATION_PATH_FOLLOW:
+						current_up = curve->interpolate_baked_up_vector(current_offset);
+						break;
 				}
 
+				Transform3D facing = Transform3D().looking_at(direction, current_up);
+				current_xform = base_xform.translated(current_point) * facing;
 			} break;
 		}
 
-		if (face != face_count) {
-			ERR_PRINT("Face mismatch bug! fix code");
+		double u0 = x0 * u_step;
+		double u1 = ((x0 + 1) * u_step);
+		if (mode == MODE_PATH && !path_continuous_u) {
+			u0 = 0.0;
+			u1 = 1.0;
 		}
-		for (int i = 0; i < face_count * 3; i++) {
-			if (i == 0) {
-				aabb.position = facesw[i];
-			} else {
-				aabb.expand_to(facesw[i]);
+
+		for (int y0 = 0; y0 < shape_sides; y0++) {
+			int y1 = (y0 + 1) % shape_sides;
+			// Use the top half of the texture.
+			double v0 = (y0 * v_step) / 2;
+			double v1 = ((y0 + 1) * v_step) / 2;
+
+			Vector3 v[4] = {
+				previous_xform.xform(Vector3(shape_polygon[y0].x, shape_polygon[y0].y, 0)),
+				current_xform.xform(Vector3(shape_polygon[y0].x, shape_polygon[y0].y, 0)),
+				current_xform.xform(Vector3(shape_polygon[y1].x, shape_polygon[y1].y, 0)),
+				previous_xform.xform(Vector3(shape_polygon[y1].x, shape_polygon[y1].y, 0)),
+			};
+
+			Vector2 u[4] = {
+				Vector2(u0, v0),
+				Vector2(u1, v0),
+				Vector2(u1, v1),
+				Vector2(u0, v1),
+			};
+
+			// 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_faces;
+			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_faces;
+			materialsw[face] = material;
+
+			face++;
+		}
+	}
+
+	if (end_count > 1) {
+		// Add back end face.
+		for (int face_idx = 0; face_idx < shape_face_count; face_idx++) {
+			for (int face_vertex_idx = 0; face_vertex_idx < 3; face_vertex_idx++) {
+				int index = shape_faces[face_idx * 3 + face_vertex_idx];
+				Point2 p = shape_polygon[index];
+				Point2 uv = (p - shape_rect.position) / shape_rect.size;
+
+				// Use the x-inverted ride side of the bottom half of the y-inverted texture.
+				uv.x = 1 - uv.x / 2;
+				uv.y = 1 - (uv.y / 2);
+
+				facesw[face * 3 + face_vertex_idx] = current_xform.xform(Vector3(p.x, p.y, 0));
+				uvsw[face * 3 + face_vertex_idx] = uv;
 			}
 
-			// invert UVs on the Y-axis OpenGL = upside down
-			uvsw[i].y = 1.0 - uvsw[i].y;
+			smoothw[face] = false;
+			materialsw[face] = material;
+			invertw[face] = invert_faces;
+			face++;
 		}
 	}
 
+	ERR_FAIL_COND_V_MSG(face != face_count, brush, "Bug: Failed to create the CSGPolygon mesh correctly.");
+
 	brush->build_from_faces(faces, uvs, smooth, materials, invert);
 
 	return brush;
@@ -2152,10 +1977,10 @@ CSGBrush *CSGPolygon3D::_build_brush() {
 
 void CSGPolygon3D::_notification(int p_what) {
 	if (p_what == NOTIFICATION_EXIT_TREE) {
-		if (path_cache) {
-			path_cache->disconnect("tree_exited", callable_mp(this, &CSGPolygon3D::_path_exited));
-			path_cache->disconnect("curve_changed", callable_mp(this, &CSGPolygon3D::_path_changed));
-			path_cache = nullptr;
+		if (path) {
+			path->disconnect("tree_exited", callable_mp(this, &CSGPolygon3D::_path_exited));
+			path->disconnect("curve_changed", callable_mp(this, &CSGPolygon3D::_path_changed));
+			path = nullptr;
 		}
 	}
 }
@@ -2180,7 +2005,7 @@ void CSGPolygon3D::_path_changed() {
 }
 
 void CSGPolygon3D::_path_exited() {
-	path_cache = nullptr;
+	path = nullptr;
 }
 
 void CSGPolygon3D::_bind_methods() {
@@ -2202,10 +2027,10 @@ void CSGPolygon3D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_path_node", "path"), &CSGPolygon3D::set_path_node);
 	ClassDB::bind_method(D_METHOD("get_path_node"), &CSGPolygon3D::get_path_node);
 
-	ClassDB::bind_method(D_METHOD("set_path_interval", "distance"), &CSGPolygon3D::set_path_interval);
+	ClassDB::bind_method(D_METHOD("set_path_interval", "interval"), &CSGPolygon3D::set_path_interval);
 	ClassDB::bind_method(D_METHOD("get_path_interval"), &CSGPolygon3D::get_path_interval);
 
-	ClassDB::bind_method(D_METHOD("set_path_rotation", "mode"), &CSGPolygon3D::set_path_rotation);
+	ClassDB::bind_method(D_METHOD("set_path_rotation", "path_rotation"), &CSGPolygon3D::set_path_rotation);
 	ClassDB::bind_method(D_METHOD("get_path_rotation"), &CSGPolygon3D::get_path_rotation);
 
 	ClassDB::bind_method(D_METHOD("set_path_local", "enable"), &CSGPolygon3D::set_path_local);
@@ -2228,11 +2053,11 @@ void CSGPolygon3D::_bind_methods() {
 
 	ADD_PROPERTY(PropertyInfo(Variant::PACKED_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::FLOAT, "depth", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001,or_greater,exp"), "set_depth", "get_depth");
+	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "depth", PROPERTY_HINT_RANGE, "0.01,100.0,0.01,or_greater,exp"), "set_depth", "get_depth");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "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", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Path3D"), "set_path_node", "get_path_node");
-	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_interval", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001,or_greater,exp"), "set_path_interval", "get_path_interval");
+	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_interval", PROPERTY_HINT_RANGE, "0.1,1.0,0.05,exp"), "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, "path_local"), "set_path_local", "is_path_local");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "path_continuous_u"), "set_path_continuous_u", "is_path_continuous_u");
@@ -2301,7 +2126,7 @@ float CSGPolygon3D::get_spin_degrees() const {
 	return spin_degrees;
 }
 
-void CSGPolygon3D::set_spin_sides(const int p_spin_sides) {
+void CSGPolygon3D::set_spin_sides(int p_spin_sides) {
 	ERR_FAIL_COND(p_spin_sides < 3);
 	spin_sides = p_spin_sides;
 	_make_dirty();
@@ -2323,7 +2148,7 @@ NodePath CSGPolygon3D::get_path_node() const {
 }
 
 void CSGPolygon3D::set_path_interval(float p_interval) {
-	ERR_FAIL_COND_MSG(p_interval < 0.001, "Path interval cannot be smaller than 0.001.");
+	ERR_FAIL_COND_MSG(p_interval <= 0 || p_interval > 1, "Path interval must be greater than 0 and less than or equal to 1.0.");
 	path_interval = p_interval;
 	_make_dirty();
 	update_gizmos();
@@ -2400,10 +2225,10 @@ CSGPolygon3D::CSGPolygon3D() {
 	spin_degrees = 360;
 	spin_sides = 8;
 	smooth_faces = false;
-	path_interval = 1;
-	path_rotation = PATH_ROTATION_PATH;
+	path_interval = 1.0;
+	path_rotation = PATH_ROTATION_PATH_FOLLOW;
 	path_local = false;
-	path_continuous_u = false;
+	path_continuous_u = true;
 	path_joined = false;
-	path_cache = nullptr;
+	path = nullptr;
 }

+ 3 - 4
modules/csg/csg_shape.h

@@ -34,6 +34,7 @@
 #define CSGJS_HEADER_ONLY
 
 #include "csg.h"
+#include "scene/3d/path_3d.h"
 #include "scene/3d/visual_instance_3d.h"
 #include "scene/resources/concave_polygon_shape_3d.h"
 #include "thirdparty/misc/mikktspace.h"
@@ -168,10 +169,8 @@ public:
 class CSGPrimitive3D : public CSGShape3D {
 	GDCLASS(CSGPrimitive3D, CSGShape3D);
 
-private:
-	bool invert_faces;
-
 protected:
+	bool invert_faces;
 	CSGBrush *_create_brush_from_arrays(const Vector<Vector3> &p_vertices, const Vector<Vector2> &p_uv, const Vector<bool> &p_smooth, const Vector<Ref<Material>> &p_materials);
 	static void _bind_methods();
 
@@ -361,7 +360,7 @@ private:
 	PathRotation path_rotation;
 	bool path_local;
 
-	Node *path_cache;
+	Path3D *path;
 
 	bool smooth_faces;
 	bool path_continuous_u;

+ 22 - 20
modules/csg/doc_classes/CSGPolygon3D.xml

@@ -4,7 +4,7 @@
 		Extrudes a 2D polygon shape to create a 3D mesh.
 	</brief_description>
 	<description>
-		This node takes a 2D polygon shape and extrudes it to create a 3D mesh.
+		An array of 2D points is extruded to quickly and easily create a variety of 3D meshes.
 	</description>
 	<tutorials>
 	</tutorials>
@@ -12,63 +12,65 @@
 	</methods>
 	<members>
 		<member name="depth" type="float" setter="set_depth" getter="get_depth" default="1.0">
-			Extrusion depth when [member mode] is [constant MODE_DEPTH].
+			When [member mode] is [constant MODE_DEPTH], the depth of the extrusion.
 		</member>
 		<member name="material" type="Material" setter="set_material" getter="get_material">
-			Material to use for the resulting mesh.
+			Material to use for the resulting mesh. The UV maps the top half of the material to the extruded shape (U along the length of the extrusions and V around the outline of the [member polygon]), the bottom-left quarter to the front end face, and the bottom-right quarter to the back end face.
 		</member>
 		<member name="mode" type="int" setter="set_mode" getter="get_mode" enum="CSGPolygon3D.Mode" default="0">
-			Extrusion mode.
+			The [member mode] used to extrude the [member polygon].
 		</member>
 		<member name="path_continuous_u" type="bool" setter="set_path_continuous_u" getter="is_path_continuous_u">
-			If [code]true[/code] the u component of our uv will continuously increase in unison with the distance traveled along our path when [member mode] is [constant MODE_PATH].
+			When [member mode] is [constant MODE_PATH], by default, the top half of the [member material] is stretched along the entire length of the extruded shape. If [code]false[/code] the top half of the material is repeated every step of the extrusion.
 		</member>
 		<member name="path_interval" type="float" setter="set_path_interval" getter="get_path_interval">
-			Interval at which a new extrusion slice is added along the path when [member mode] is [constant MODE_PATH].
+			When [member mode] is [constant MODE_PATH], the path interval or ratio of path points to extrusions.
 		</member>
 		<member name="path_joined" type="bool" setter="set_path_joined" getter="is_path_joined">
-			If [code]true[/code] the start and end of our path are joined together ensuring there is no seam when [member mode] is [constant MODE_PATH].
+			When [member mode] is [constant MODE_PATH], if [code]true[/code] the ends of the path are joined, by adding an extrusion between the last and first points of the path.
 		</member>
 		<member name="path_local" type="bool" setter="set_path_local" getter="is_path_local">
-			If [code]false[/code] we extrude centered on our path, if [code]true[/code] we extrude in relation to the position of our CSGPolygon3D when [member mode] is [constant MODE_PATH].
+			When [member mode] is [constant MODE_PATH], if [code]true[/code] the [Transform3D] of the [CSGPolygon3D] is used as the starting point for the extrusions, not the [Transform3D] of the [member path_node].
 		</member>
 		<member name="path_node" type="NodePath" setter="set_path_node" getter="get_path_node">
-			The [Shape3D] object containing the path along which we extrude when [member mode] is [constant MODE_PATH].
+			When [member mode] is [constant MODE_PATH], the location of the [Path3D] object used to extrude the [member polygon].
 		</member>
 		<member name="path_rotation" type="int" setter="set_path_rotation" getter="get_path_rotation" enum="CSGPolygon3D.PathRotation">
-			The method by which each slice is rotated along the path when [member mode] is [constant MODE_PATH].
+			When [member mode] is [constant MODE_PATH], the [enum PathRotation] method used to rotate the [member polygon] as it is extruded.
 		</member>
 		<member name="polygon" type="PackedVector2Array" setter="set_polygon" getter="get_polygon" default="PackedVector2Array(0, 0, 0, 1, 1, 1, 1, 0)">
-			Point array that defines the shape that we'll extrude.
+			The point array that defines the 2D polygon that is extruded.
 		</member>
 		<member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces" default="false">
-			Generates smooth normals so smooth shading is applied to our mesh.
+			If [code]true[/code], applies smooth shading to the extrusions.
 		</member>
 		<member name="spin_degrees" type="float" setter="set_spin_degrees" getter="get_spin_degrees">
-			Degrees to rotate our extrusion for each slice when [member mode] is [constant MODE_SPIN].
+			When [member mode] is [constant MODE_SPIN], the total number of degrees the [member polygon] is rotated when extruding.
 		</member>
 		<member name="spin_sides" type="int" setter="set_spin_sides" getter="get_spin_sides">
-			Number of extrusion when [member mode] is [constant MODE_SPIN].
+			When [member mode] is [constant MODE_SPIN], the number of extrusions made.
 		</member>
 	</members>
 	<constants>
 		<constant name="MODE_DEPTH" value="0" enum="Mode">
-			Shape3D is extruded to [member depth].
+			The [member polygon] shape is extruded along the negative Z axis.
 		</constant>
 		<constant name="MODE_SPIN" value="1" enum="Mode">
-			Shape3D is extruded by rotating it around an axis.
+			The [member polygon] shape is extruded by rotating it around the Y axis.
 		</constant>
 		<constant name="MODE_PATH" value="2" enum="Mode">
-			Shape3D is extruded along a path set by a [Shape3D] set in [member path_node].
+			The [member polygon] shape is extruded along the [Path3D] specified in [member path_node].
 		</constant>
 		<constant name="PATH_ROTATION_POLYGON" value="0" enum="PathRotation">
-			Slice is not rotated.
+			The [member polygon] shape is not rotated.
+			[b]Note:[/b] Requires the path Z coordinates to continually decrease to ensure viable shapes.
 		</constant>
 		<constant name="PATH_ROTATION_PATH" value="1" enum="PathRotation">
-			Slice is rotated around the up vector of the path.
+			The [member polygon] shape is rotated along the path, but it is not rotated around the path axis.
+			[b]Note:[/b] Requires the path Z coordinates to continually decrease to ensure viable shapes.
 		</constant>
 		<constant name="PATH_ROTATION_PATH_FOLLOW" value="2" enum="PathRotation">
-			Slice is rotate to match the path exactly.
+			The [member polygon] shape follows the path and its rotations around the path axis.
 		</constant>
 	</constants>
 </class>