From 1665d1e41f197aee96e65417cbfaf08d2e4fe6ce Mon Sep 17 00:00:00 2001 From: "K. S. Ernest (iFire) Lee" Date: Sat, 7 Dec 2024 22:04:00 -0800 Subject: [PATCH] Add csg_manifold as an alternative to modifying csg. --- modules/csg/SCsub | 37 +- modules/csg/csg.cpp | 1428 ++++++++- modules/csg/csg.h | 148 +- modules/csg/csg_shape.cpp | 371 +-- modules/csg/csg_shape.h | 2 - modules/csg/doc_classes/CSGShape3D.xml | 4 +- modules/csg_manifold/SCsub | 47 + modules/csg_manifold/config.py | 24 + modules/csg_manifold/csg_manifold.cpp | 125 + modules/csg_manifold/csg_manifold.h | 72 + modules/csg_manifold/csg_manifold_shape.cpp | 2722 +++++++++++++++++ modules/csg_manifold/csg_manifold_shape.h | 466 +++ .../doc_classes/CSGManifoldBox3D.xml | 21 + .../doc_classes/CSGManifoldCombiner3D.xml | 13 + .../doc_classes/CSGManifoldCylinder3D.xml | 33 + .../doc_classes/CSGManifoldMesh3D.xml | 23 + .../doc_classes/CSGManifoldPolygon3D.xml | 92 + .../doc_classes/CSGManifoldPrimitive3D.xml | 18 + .../doc_classes/CSGManifoldShape3D.xml | 110 + .../doc_classes/CSGManifoldSphere3D.xml | 30 + .../doc_classes/CSGManifoldTorus3D.xml | 33 + .../editor/csg_manifold_gizmos.cpp | 513 ++++ .../csg_manifold/editor/csg_manifold_gizmos.h | 110 + modules/csg_manifold/register_types.cpp | 62 + modules/csg_manifold/register_types.h | 39 + 25 files changed, 6180 insertions(+), 363 deletions(-) create mode 100644 modules/csg_manifold/SCsub create mode 100644 modules/csg_manifold/config.py create mode 100644 modules/csg_manifold/csg_manifold.cpp create mode 100644 modules/csg_manifold/csg_manifold.h create mode 100644 modules/csg_manifold/csg_manifold_shape.cpp create mode 100644 modules/csg_manifold/csg_manifold_shape.h create mode 100644 modules/csg_manifold/doc_classes/CSGManifoldBox3D.xml create mode 100644 modules/csg_manifold/doc_classes/CSGManifoldCombiner3D.xml create mode 100644 modules/csg_manifold/doc_classes/CSGManifoldCylinder3D.xml create mode 100644 modules/csg_manifold/doc_classes/CSGManifoldMesh3D.xml create mode 100644 modules/csg_manifold/doc_classes/CSGManifoldPolygon3D.xml create mode 100644 modules/csg_manifold/doc_classes/CSGManifoldPrimitive3D.xml create mode 100644 modules/csg_manifold/doc_classes/CSGManifoldShape3D.xml create mode 100644 modules/csg_manifold/doc_classes/CSGManifoldSphere3D.xml create mode 100644 modules/csg_manifold/doc_classes/CSGManifoldTorus3D.xml create mode 100644 modules/csg_manifold/editor/csg_manifold_gizmos.cpp create mode 100644 modules/csg_manifold/editor/csg_manifold_gizmos.h create mode 100644 modules/csg_manifold/register_types.cpp create mode 100644 modules/csg_manifold/register_types.h diff --git a/modules/csg/SCsub b/modules/csg/SCsub index a14e999fb898..f71618ab2212 100644 --- a/modules/csg/SCsub +++ b/modules/csg/SCsub @@ -6,42 +6,7 @@ Import("env_modules") env_csg = env_modules.Clone() -env_csg.Append(CPPDEFINES=("MANIFOLD_PAR", "-1")) - -# Thirdparty source files - -thirdparty_obj = [] - -thirdparty_dir = "#thirdparty/manifold/" -thirdparty_sources = [ - "src/boolean_result.cpp", - "src/boolean3.cpp", - "src/constructors.cpp", - "src/csg_tree.cpp", - "src/edge_op.cpp", - "src/face_op.cpp", - "src/impl.cpp", - "src/manifold.cpp", - "src/polygon.cpp", - "src/properties.cpp", - "src/quickhull.cpp", - "src/smoothing.cpp", - "src/sort.cpp", - "src/subdivision.cpp", -] - -thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] -env_csg.Prepend( - CPPPATH=[ - thirdparty_dir + "include", - ] -) -env_thirdparty = env_csg.Clone() -env_thirdparty.disable_warnings() -env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) -env.modules_sources += thirdparty_obj - -# Godot's own source files +# Godot source files env_csg.add_source_files(env.modules_sources, "*.cpp") if env.editor_build: env_csg.add_source_files(env.modules_sources, "editor/*.cpp") diff --git a/modules/csg/csg.cpp b/modules/csg/csg.cpp index f04748a54ea8..a4a3c768e94f 100644 --- a/modules/csg/csg.cpp +++ b/modules/csg/csg.cpp @@ -33,13 +33,167 @@ #include "core/math/geometry_2d.h" #include "core/math/math_funcs.h" #include "core/templates/sort_array.h" -#include "scene/resources/mesh_data_tool.h" -#include "scene/resources/surface_tool.h" -#include "thirdparty/manifold/include/manifold/manifold.h" +// Static helper functions. + +inline static bool is_snapable(const Vector3 &p_point1, const Vector3 &p_point2, real_t p_distance) { + return p_point2.distance_squared_to(p_point1) < p_distance * p_distance; +} + +inline static Vector2 interpolate_segment_uv(const Vector2 p_segment_points[2], const Vector2 p_uvs[2], const Vector2 &p_interpolation_point) { + if (p_segment_points[0].is_equal_approx(p_segment_points[1])) { + return p_uvs[0]; + } + + float segment_length = p_segment_points[0].distance_to(p_segment_points[1]); + float distance = p_segment_points[0].distance_to(p_interpolation_point); + float fraction = distance / segment_length; + + return p_uvs[0].lerp(p_uvs[1], fraction); +} + +inline static Vector2 interpolate_triangle_uv(const Vector2 p_vertices[3], const Vector2 p_uvs[3], const Vector2 &p_interpolation_point) { + if (p_interpolation_point.is_equal_approx(p_vertices[0])) { + return p_uvs[0]; + } + if (p_interpolation_point.is_equal_approx(p_vertices[1])) { + return p_uvs[1]; + } + if (p_interpolation_point.is_equal_approx(p_vertices[2])) { + return p_uvs[2]; + } + + Vector2 edge1 = p_vertices[1] - p_vertices[0]; + Vector2 edge2 = p_vertices[2] - p_vertices[0]; + Vector2 interpolation = p_interpolation_point - p_vertices[0]; + + float edge1_on_edge1 = edge1.dot(edge1); + float edge1_on_edge2 = edge1.dot(edge2); + float edge2_on_edge2 = edge2.dot(edge2); + float inter_on_edge1 = interpolation.dot(edge1); + float inter_on_edge2 = interpolation.dot(edge2); + float scale = (edge1_on_edge1 * edge2_on_edge2 - edge1_on_edge2 * edge1_on_edge2); + if (scale == 0) { + return p_uvs[0]; + } + + float v = (edge2_on_edge2 * inter_on_edge1 - edge1_on_edge2 * inter_on_edge2) / scale; + float w = (edge1_on_edge1 * inter_on_edge2 - edge1_on_edge2 * inter_on_edge1) / scale; + float u = 1.0f - v - w; + + return p_uvs[0] * u + p_uvs[1] * v + p_uvs[2] * w; +} + +static inline bool ray_intersects_triangle(const Vector3 &p_from, const Vector3 &p_dir, const Vector3 p_vertices[3], float p_tolerance, Vector3 &r_intersection_point) { + Vector3 edge1 = p_vertices[1] - p_vertices[0]; + Vector3 edge2 = p_vertices[2] - p_vertices[0]; + Vector3 h = p_dir.cross(edge2); + real_t a = edge1.dot(h); + // Check if ray is parallel to triangle. + if (Math::is_zero_approx(a)) { + return false; + } + real_t f = 1.0 / a; + + Vector3 s = p_from - p_vertices[0]; + real_t u = f * s.dot(h); + if (u < 0.0 - p_tolerance || u > 1.0 + p_tolerance) { + return false; + } + + Vector3 q = s.cross(edge1); + real_t v = f * p_dir.dot(q); + if (v < 0.0 - p_tolerance || u + v > 1.0 + p_tolerance) { + return false; + } + + // Ray intersects triangle. + // Calculate distance. + real_t t = f * edge2.dot(q); + // Confirm triangle is in front of ray. + if (t >= p_tolerance) { + r_intersection_point = p_from + p_dir * t; + return true; + } else { + return false; + } +} + +inline bool is_point_in_triangle(const Vector3 &p_point, const Vector3 p_vertices[3], int p_shifted = 0) { + real_t det = p_vertices[0].dot(p_vertices[1].cross(p_vertices[2])); + + // If determinant is, zero try shift the triangle and the point. + if (Math::is_zero_approx(det)) { + if (p_shifted > 2) { + // Triangle appears degenerate, so ignore it. + return false; + } + Vector3 shift_by; + shift_by[p_shifted] = 1; + Vector3 shifted_point = p_point + shift_by; + Vector3 shifted_vertices[3] = { p_vertices[0] + shift_by, p_vertices[1] + shift_by, p_vertices[2] + shift_by }; + return is_point_in_triangle(shifted_point, shifted_vertices, p_shifted + 1); + } + + // Find the barycentric coordinates of the point with respect to the vertices. + real_t lambda[3]; + lambda[0] = p_vertices[1].cross(p_vertices[2]).dot(p_point) / det; + lambda[1] = p_vertices[2].cross(p_vertices[0]).dot(p_point) / det; + lambda[2] = p_vertices[0].cross(p_vertices[1]).dot(p_point) / det; + + // Point is in the plane if all lambdas sum to 1. + if (!Math::is_equal_approx(lambda[0] + lambda[1] + lambda[2], 1)) { + return false; + } + + // Point is inside the triangle if all lambdas are positive. + if (lambda[0] < 0 || lambda[1] < 0 || lambda[2] < 0) { + return false; + } + + return true; +} + +inline static bool is_triangle_degenerate(const Vector2 p_vertices[3], real_t p_vertex_snap2) { + real_t det = p_vertices[0].x * p_vertices[1].y - p_vertices[0].x * p_vertices[2].y + + p_vertices[0].y * p_vertices[2].x - p_vertices[0].y * p_vertices[1].x + + p_vertices[1].x * p_vertices[2].y - p_vertices[1].y * p_vertices[2].x; + + return det < p_vertex_snap2; +} + +inline static bool are_segments_parallel(const Vector2 p_segment1_points[2], const Vector2 p_segment2_points[2], float p_vertex_snap2) { + Vector2 segment1 = p_segment1_points[1] - p_segment1_points[0]; + Vector2 segment2 = p_segment2_points[1] - p_segment2_points[0]; + real_t segment1_length2 = segment1.dot(segment1); + real_t segment2_length2 = segment2.dot(segment2); + real_t segment_onto_segment = segment2.dot(segment1); + + if (segment1_length2 < p_vertex_snap2 || segment2_length2 < p_vertex_snap2) { + return true; + } + + real_t max_separation2; + if (segment1_length2 > segment2_length2) { + max_separation2 = segment2_length2 - segment_onto_segment * segment_onto_segment / segment1_length2; + } else { + max_separation2 = segment1_length2 - segment_onto_segment * segment_onto_segment / segment2_length2; + } + + return max_separation2 < p_vertex_snap2; +} // CSGBrush +void CSGBrush::_regen_face_aabbs() { + for (int i = 0; i < faces.size(); i++) { + faces.write[i].aabb = AABB(); + faces.write[i].aabb.position = faces[i].vertices[0]; + faces.write[i].aabb.expand_to(faces[i].vertices[1]); + faces.write[i].aabb.expand_to(faces[i].vertices[2]); + } +} + void CSGBrush::build_from_faces(const Vector &p_vertices, const Vector &p_uvs, const Vector &p_smooth, const Vector> &p_materials, const Vector &p_flip_faces) { faces.clear(); @@ -123,3 +277,1271 @@ void CSGBrush::copy_from(const CSGBrush &p_brush, const Transform3D &p_xform) { _regen_face_aabbs(); } + +// CSGBrushOperation + +void CSGBrushOperation::merge_brushes(Operation p_operation, const CSGBrush &p_brush_a, const CSGBrush &p_brush_b, CSGBrush &r_merged_brush, float p_vertex_snap) { + // Check for face collisions and add necessary faces. + Build2DFaceCollection build2DFaceCollection; + for (int i = 0; i < p_brush_a.faces.size(); i++) { + for (int j = 0; j < p_brush_b.faces.size(); j++) { + if (p_brush_a.faces[i].aabb.intersects_inclusive(p_brush_b.faces[j].aabb)) { + update_faces(p_brush_a, i, p_brush_b, j, build2DFaceCollection, p_vertex_snap); + } + } + } + + // Add faces to MeshMerge. + MeshMerge mesh_merge; + mesh_merge.vertex_snap = p_vertex_snap; + + for (int i = 0; i < p_brush_a.faces.size(); i++) { + Ref material; + if (p_brush_a.faces[i].material != -1) { + material = p_brush_a.materials[p_brush_a.faces[i].material]; + } + + if (build2DFaceCollection.build2DFacesA.has(i)) { + build2DFaceCollection.build2DFacesA[i].addFacesToMesh(mesh_merge, p_brush_a.faces[i].smooth, p_brush_a.faces[i].invert, material, false); + } else { + Vector3 points[3]; + Vector2 uvs[3]; + for (int j = 0; j < 3; j++) { + points[j] = p_brush_a.faces[i].vertices[j]; + uvs[j] = p_brush_a.faces[i].uvs[j]; + } + mesh_merge.add_face(points, uvs, p_brush_a.faces[i].smooth, p_brush_a.faces[i].invert, material, false); + } + } + + for (int i = 0; i < p_brush_b.faces.size(); i++) { + Ref material; + if (p_brush_b.faces[i].material != -1) { + material = p_brush_b.materials[p_brush_b.faces[i].material]; + } + + if (build2DFaceCollection.build2DFacesB.has(i)) { + build2DFaceCollection.build2DFacesB[i].addFacesToMesh(mesh_merge, p_brush_b.faces[i].smooth, p_brush_b.faces[i].invert, material, true); + } else { + Vector3 points[3]; + Vector2 uvs[3]; + for (int j = 0; j < 3; j++) { + points[j] = p_brush_b.faces[i].vertices[j]; + uvs[j] = p_brush_b.faces[i].uvs[j]; + } + mesh_merge.add_face(points, uvs, p_brush_b.faces[i].smooth, p_brush_b.faces[i].invert, material, true); + } + } + + // Mark faces that ended up inside the intersection. + mesh_merge.mark_inside_faces(); + + // Create new brush and fill with new faces. + r_merged_brush.faces.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++; + } + + r_merged_brush.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++) { + r_merged_brush.faces.write[outside_count].vertices[j] = mesh_merge.points[mesh_merge.faces[i].points[j]]; + r_merged_brush.faces.write[outside_count].uvs[j] = mesh_merge.faces[i].uvs[j]; + } + + r_merged_brush.faces.write[outside_count].smooth = mesh_merge.faces[i].smooth; + r_merged_brush.faces.write[outside_count].invert = mesh_merge.faces[i].invert; + r_merged_brush.faces.write[outside_count].material = mesh_merge.faces[i].material_idx; + outside_count++; + } + + r_merged_brush._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++; + } + + r_merged_brush.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++) { + r_merged_brush.faces.write[inside_count].vertices[j] = mesh_merge.points[mesh_merge.faces[i].points[j]]; + r_merged_brush.faces.write[inside_count].uvs[j] = mesh_merge.faces[i].uvs[j]; + } + + r_merged_brush.faces.write[inside_count].smooth = mesh_merge.faces[i].smooth; + r_merged_brush.faces.write[inside_count].invert = mesh_merge.faces[i].invert; + r_merged_brush.faces.write[inside_count].material = mesh_merge.faces[i].material_idx; + inside_count++; + } + + r_merged_brush._regen_face_aabbs(); + + } break; + + case OPERATION_SUBTRACTION: { + 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++; + } + + r_merged_brush.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++) { + r_merged_brush.faces.write[face_count].vertices[j] = mesh_merge.points[mesh_merge.faces[i].points[j]]; + r_merged_brush.faces.write[face_count].uvs[j] = mesh_merge.faces[i].uvs[j]; + } + + if (mesh_merge.faces[i].from_b) { + //invert facing of insides of B + SWAP(r_merged_brush.faces.write[face_count].vertices[1], r_merged_brush.faces.write[face_count].vertices[2]); + SWAP(r_merged_brush.faces.write[face_count].uvs[1], r_merged_brush.faces.write[face_count].uvs[2]); + } + + r_merged_brush.faces.write[face_count].smooth = mesh_merge.faces[i].smooth; + r_merged_brush.faces.write[face_count].invert = mesh_merge.faces[i].invert; + r_merged_brush.faces.write[face_count].material = mesh_merge.faces[i].material_idx; + face_count++; + } + + r_merged_brush._regen_face_aabbs(); + + } break; + } + + // Update the list of materials. + r_merged_brush.materials.resize(mesh_merge.materials.size()); + for (const KeyValue, int> &E : mesh_merge.materials) { + r_merged_brush.materials.write[E.value] = E.key; + } +} + +// CSGBrushOperation::MeshMerge + +// Use a limit to speed up bvh and limit the depth. +#define BVH_LIMIT 8 + +int CSGBrushOperation::MeshMerge::_create_bvh(FaceBVH *r_facebvhptr, FaceBVH **r_facebvhptrptr, int p_from, int p_size, int p_depth, int &r_max_depth, int &r_max_alloc) { + if (p_depth > r_max_depth) { + r_max_depth = p_depth; + } + + if (p_size == 0) { + return -1; + } + + if (p_size <= BVH_LIMIT) { + for (int i = 0; i < p_size - 1; i++) { + r_facebvhptrptr[p_from + i]->next = r_facebvhptrptr[p_from + i + 1] - r_facebvhptr; + } + return r_facebvhptrptr[p_from] - r_facebvhptr; + } + + AABB aabb; + aabb = r_facebvhptrptr[p_from]->aabb; + for (int i = 1; i < p_size; i++) { + aabb.merge_with(r_facebvhptrptr[p_from + i]->aabb); + } + + int li = aabb.get_longest_axis_index(); + + switch (li) { + case Vector3::AXIS_X: { + SortArray sort_x; + sort_x.nth_element(0, p_size, p_size / 2, &r_facebvhptrptr[p_from]); + //sort_x.sort(&p_bb[p_from],p_size); + } break; + + case Vector3::AXIS_Y: { + SortArray sort_y; + sort_y.nth_element(0, p_size, p_size / 2, &r_facebvhptrptr[p_from]); + //sort_y.sort(&p_bb[p_from],p_size); + } break; + + case Vector3::AXIS_Z: { + SortArray sort_z; + sort_z.nth_element(0, p_size, p_size / 2, &r_facebvhptrptr[p_from]); + //sort_z.sort(&p_bb[p_from],p_size); + } break; + } + + int left = _create_bvh(r_facebvhptr, r_facebvhptrptr, p_from, p_size / 2, p_depth + 1, r_max_depth, r_max_alloc); + int right = _create_bvh(r_facebvhptr, r_facebvhptrptr, p_from + p_size / 2, p_size - p_size / 2, p_depth + 1, r_max_depth, r_max_alloc); + + int index = r_max_alloc++; + FaceBVH *_new = &r_facebvhptr[index]; + _new->aabb = aabb; + _new->center = aabb.get_center(); + _new->face = -1; + _new->left = left; + _new->right = right; + _new->next = -1; + + return index; +} + +void CSGBrushOperation::MeshMerge::_add_distance(List &r_intersectionsA, List &r_intersectionsB, bool p_from_B, real_t p_distance_squared, bool p_is_conormal) const { + List &intersections = p_from_B ? r_intersectionsB : r_intersectionsA; + + // Check if distance exists. + for (const IntersectionDistance E : intersections) { + if (E.is_conormal == p_is_conormal && Math::is_equal_approx(E.distance_squared, p_distance_squared)) { + return; + } + } + IntersectionDistance distance; + distance.is_conormal = p_is_conormal; + distance.distance_squared = p_distance_squared; + intersections.push_back(distance); +} + +bool CSGBrushOperation::MeshMerge::_bvh_inside(FaceBVH *r_facebvhptr, int p_max_depth, int p_bvh_first, int p_face_idx) const { + Face face = faces[p_face_idx]; + Vector3 face_points[3] = { + points[face.points[0]], + points[face.points[1]], + points[face.points[2]] + }; + Vector3 face_center = (face_points[0] + face_points[1] + face_points[2]) / 3.0; + Vector3 face_normal = Plane(face_points[0], face_points[1], face_points[2]).normal; + + 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 + }; + + List intersectionsA; + List intersectionsB; + + Intersection closest_intersection; + closest_intersection.found = false; + + int level = 0; + int pos = p_bvh_first; + stack[0] = pos; + + while (true) { + uint32_t node = stack[level] & NODE_IDX_MASK; + const FaceBVH *current_facebvhptr = &(r_facebvhptr[node]); + bool done = false; + + switch (stack[level] >> VISITED_BIT_SHIFT) { + case TEST_AABB_BIT: { + if (current_facebvhptr->face >= 0) { + while (current_facebvhptr) { + if (p_face_idx != current_facebvhptr->face && + current_facebvhptr->aabb.intersects_ray(face_center, face_normal)) { + const Face ¤t_face = faces[current_facebvhptr->face]; + Vector3 current_points[3] = { + points[current_face.points[0]], + points[current_face.points[1]], + points[current_face.points[2]] + }; + Vector3 current_normal = Plane(current_points[0], current_points[1], current_points[2]).normal; + Vector3 intersection_point; + // Check if faces are co-planar. + if (current_normal.is_equal_approx(face_normal) && + is_point_in_triangle(face_center, current_points)) { + // Only add an intersection if not a B face. + if (!face.from_b) { + _add_distance(intersectionsA, intersectionsB, current_face.from_b, 0, true); + } + } else if (ray_intersects_triangle(face_center, face_normal, current_points, CMP_EPSILON, intersection_point)) { + real_t distance_squared = face_center.distance_squared_to(intersection_point); + real_t inner = current_normal.dot(face_normal); + // If the faces are perpendicular, ignore this face. + // The triangles on the side should be intersected and result in the correct behavior. + if (!Math::is_zero_approx(inner)) { + _add_distance(intersectionsA, intersectionsB, current_face.from_b, distance_squared, inner > 0.0f); + } + } + + if (face.from_b != current_face.from_b) { + if (current_normal.is_equal_approx(face_normal) && + is_point_in_triangle(face_center, current_points)) { + // Only add an intersection if not a B face. + if (!face.from_b) { + closest_intersection.found = true; + closest_intersection.conormal = 1.0f; + closest_intersection.distance_squared = 0.0f; + closest_intersection.origin_angle = -FLT_MAX; + } + } else if (ray_intersects_triangle(face_center, face_normal, current_points, CMP_EPSILON, intersection_point)) { + Intersection potential_intersection; + potential_intersection.found = true; + potential_intersection.conormal = face_normal.dot(current_normal); + potential_intersection.distance_squared = face_center.distance_squared_to(intersection_point); + potential_intersection.origin_angle = Math::abs(potential_intersection.conormal); + real_t intersection_dist_from_face = face_normal.dot(intersection_point - face_center); + for (int i = 0; i < 3; i++) { + real_t point_dist_from_face = face_normal.dot(current_points[i] - face_center); + if (!Math::is_equal_approx(point_dist_from_face, intersection_dist_from_face) && + point_dist_from_face < intersection_dist_from_face) { + potential_intersection.origin_angle = -potential_intersection.origin_angle; + break; + } + } + if (potential_intersection.conormal != 0.0f) { + if (!closest_intersection.found) { + closest_intersection = potential_intersection; + } else if (!Math::is_equal_approx(potential_intersection.distance_squared, closest_intersection.distance_squared) && + potential_intersection.distance_squared < closest_intersection.distance_squared) { + closest_intersection = potential_intersection; + } else if (Math::is_equal_approx(potential_intersection.distance_squared, closest_intersection.distance_squared)) { + if (potential_intersection.origin_angle < closest_intersection.origin_angle) { + closest_intersection = potential_intersection; + } + } + } + } + } + } + + if (current_facebvhptr->next != -1) { + current_facebvhptr = &r_facebvhptr[current_facebvhptr->next]; + } else { + current_facebvhptr = nullptr; + } + } + + stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node; + + } else { + bool valid = current_facebvhptr->aabb.intersects_ray(face_center, face_normal); + + 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] = current_facebvhptr->left | TEST_AABB_BIT; + level++; + continue; + } + + case VISIT_RIGHT_BIT: { + stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node; + stack[level + 1] = current_facebvhptr->right | TEST_AABB_BIT; + level++; + continue; + } + + case VISIT_DONE_BIT: { + if (level == 0) { + done = true; + break; + } else { + level--; + } + continue; + } + } + + if (done) { + break; + } + } + + if (!closest_intersection.found) { + return false; + } else { + return closest_intersection.conormal > 0.0f; + } +} + +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 optimizations, + // such as BVH and pre AABB intersection test. + + Vector bvhvec; + bvhvec.resize(faces.size() * 3); // Will never be larger than this (TODO: Make better) + FaceBVH *facebvh = bvhvec.ptrw(); + + AABB aabb_a; + AABB aabb_b; + + bool first_a = true; + bool first_b = true; + + for (int i = 0; i < faces.size(); i++) { + facebvh[i].left = -1; + facebvh[i].right = -1; + facebvh[i].face = i; + facebvh[i].aabb.position = points[faces[i].points[0]]; + facebvh[i].aabb.expand_to(points[faces[i].points[1]]); + facebvh[i].aabb.expand_to(points[faces[i].points[2]]); + facebvh[i].center = facebvh[i].aabb.get_center(); + facebvh[i].aabb.grow_by(vertex_snap); + facebvh[i].next = -1; + + if (faces[i].from_b) { + if (first_b) { + aabb_b = facebvh[i].aabb; + first_b = false; + } else { + aabb_b.merge_with(facebvh[i].aabb); + } + } else { + if (first_a) { + aabb_a = facebvh[i].aabb; + first_a = false; + } else { + aabb_a.merge_with(facebvh[i].aabb); + } + } + } + + AABB intersection_aabb = aabb_a.intersection(aabb_b); + + // Check if shape AABBs intersect. + if (intersection_aabb.size == Vector3()) { + return; + } + + Vector bvhtrvec; + bvhtrvec.resize(faces.size()); + FaceBVH **bvhptr = bvhtrvec.ptrw(); + for (int i = 0; i < faces.size(); i++) { + bvhptr[i] = &facebvh[i]; + } + + int max_depth = 0; + int max_alloc = faces.size(); + _create_bvh(facebvh, bvhptr, 0, faces.size(), 1, max_depth, max_alloc); + + for (int i = 0; i < faces.size(); i++) { + // Check if face AABB intersects the intersection AABB. + if (!intersection_aabb.intersects_inclusive(facebvh[i].aabb)) { + continue; + } + + if (_bvh_inside(facebvh, max_depth, max_alloc - 1, i)) { + faces.write[i].inside = true; + } + } +} + +void CSGBrushOperation::MeshMerge::add_face(const Vector3 p_points[3], const Vector2 p_uvs[3], bool p_smooth, bool p_invert, const Ref &p_material, bool p_from_b) { + int indices[3]; + for (int i = 0; i < 3; i++) { + VertexKey vk; + vk.x = int((double(p_points[i].x) + double(vertex_snap) * 0.31234) / double(vertex_snap)); + vk.y = int((double(p_points[i].y) + double(vertex_snap) * 0.31234) / double(vertex_snap)); + vk.z = int((double(p_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(p_points[i]); + snap_cache.set(vk, indices[i]); + } + } + + // Don't add degenerate faces. + if (indices[0] == indices[2] || indices[0] == indices[1] || indices[1] == indices[2]) { + return; + } + + 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] = p_uvs[k]; + } + + faces.push_back(face); +} + +// CSGBrushOperation::Build2DFaces + +int CSGBrushOperation::Build2DFaces::_get_point_idx(const Vector2 &p_point) { + for (int vertex_idx = 0; vertex_idx < vertices.size(); ++vertex_idx) { + if (vertices[vertex_idx].point.distance_squared_to(p_point) < vertex_snap2) { + return vertex_idx; + } + } + return -1; +} + +int CSGBrushOperation::Build2DFaces::_add_vertex(const Vertex2D &p_vertex) { + // Check if vertex exists. + int vertex_id = _get_point_idx(p_vertex.point); + if (vertex_id != -1) { + return vertex_id; + } + + vertices.push_back(p_vertex); + return vertices.size() - 1; +} + +void CSGBrushOperation::Build2DFaces::_add_vertex_idx_sorted(Vector &r_vertex_indices, int p_new_vertex_index) { + if (p_new_vertex_index >= 0 && !r_vertex_indices.has(p_new_vertex_index)) { + ERR_FAIL_COND_MSG(p_new_vertex_index >= vertices.size(), "Invalid vertex index."); + + // The first vertex. + if (r_vertex_indices.size() == 0) { + // Simply add it. + r_vertex_indices.push_back(p_new_vertex_index); + return; + } + + // The second vertex. + if (r_vertex_indices.size() == 1) { + Vector2 first_point = vertices[r_vertex_indices[0]].point; + Vector2 new_point = vertices[p_new_vertex_index].point; + + // Sort along the axis with the greatest difference. + int axis = 0; + if (Math::abs(new_point.x - first_point.x) < Math::abs(new_point.y - first_point.y)) { + axis = 1; + } + + // Add it to the beginning or the end appropriately. + if (new_point[axis] < first_point[axis]) { + r_vertex_indices.insert(0, p_new_vertex_index); + } else { + r_vertex_indices.push_back(p_new_vertex_index); + } + + return; + } + + // Third or later vertices. + Vector2 first_point = vertices[r_vertex_indices[0]].point; + Vector2 last_point = vertices[r_vertex_indices[r_vertex_indices.size() - 1]].point; + Vector2 new_point = vertices[p_new_vertex_index].point; + + // Determine axis being sorted against i.e. the axis with the greatest difference. + int axis = 0; + if (Math::abs(last_point.x - first_point.x) < Math::abs(last_point.y - first_point.y)) { + axis = 1; + } + + // Insert the point at the appropriate index. + for (int insert_idx = 0; insert_idx < r_vertex_indices.size(); ++insert_idx) { + Vector2 insert_point = vertices[r_vertex_indices[insert_idx]].point; + if (new_point[axis] < insert_point[axis]) { + r_vertex_indices.insert(insert_idx, p_new_vertex_index); + return; + } + } + + // New largest, add it to the end. + r_vertex_indices.push_back(p_new_vertex_index); + } +} + +void CSGBrushOperation::Build2DFaces::_merge_faces(const Vector &p_segment_indices) { + int segments = p_segment_indices.size() - 1; + if (segments < 2) { + return; + } + + // Faces around an inner vertex are merged by moving the inner vertex to the first vertex. + for (int sorted_idx = 1; sorted_idx < segments; ++sorted_idx) { + int closest_idx = 0; + int inner_idx = p_segment_indices[sorted_idx]; + + if (sorted_idx > segments / 2) { + // Merge to other segment end. + closest_idx = segments; + // Reverse the merge order. + inner_idx = p_segment_indices[segments + segments / 2 - sorted_idx]; + } + + // Find the mergeable faces. + Vector merge_faces_idx; + Vector merge_faces; + Vector merge_faces_inner_vertex_idx; + for (int face_idx = 0; face_idx < faces.size(); ++face_idx) { + for (int face_vertex_idx = 0; face_vertex_idx < 3; ++face_vertex_idx) { + if (faces[face_idx].vertex_idx[face_vertex_idx] == inner_idx) { + merge_faces_idx.push_back(face_idx); + merge_faces.push_back(faces[face_idx]); + merge_faces_inner_vertex_idx.push_back(face_vertex_idx); + } + } + } + + Vector degenerate_points; + + // Create the new faces. + for (int merge_idx = 0; merge_idx < merge_faces.size(); ++merge_idx) { + int outer_edge_idx[2]; + outer_edge_idx[0] = merge_faces[merge_idx].vertex_idx[(merge_faces_inner_vertex_idx[merge_idx] + 1) % 3]; + outer_edge_idx[1] = merge_faces[merge_idx].vertex_idx[(merge_faces_inner_vertex_idx[merge_idx] + 2) % 3]; + + // Skip flattened faces. + if (outer_edge_idx[0] == p_segment_indices[closest_idx] || + outer_edge_idx[1] == p_segment_indices[closest_idx]) { + continue; + } + + //Don't create degenerate triangles. + Vector2 edge1[2] = { + vertices[outer_edge_idx[0]].point, + vertices[p_segment_indices[closest_idx]].point + }; + Vector2 edge2[2] = { + vertices[outer_edge_idx[1]].point, + vertices[p_segment_indices[closest_idx]].point + }; + if (are_segments_parallel(edge1, edge2, vertex_snap2)) { + if (!degenerate_points.find(outer_edge_idx[0])) { + degenerate_points.push_back(outer_edge_idx[0]); + } + if (!degenerate_points.find(outer_edge_idx[1])) { + degenerate_points.push_back(outer_edge_idx[1]); + } + continue; + } + + // Create new faces. + Face2D new_face; + new_face.vertex_idx[0] = p_segment_indices[closest_idx]; + new_face.vertex_idx[1] = outer_edge_idx[0]; + new_face.vertex_idx[2] = outer_edge_idx[1]; + faces.push_back(new_face); + } + + // Delete the old faces in reverse index order. + merge_faces_idx.sort(); + merge_faces_idx.reverse(); + for (int i = 0; i < merge_faces_idx.size(); ++i) { + faces.remove_at(merge_faces_idx[i]); + } + + if (degenerate_points.size() == 0) { + continue; + } + + // Split faces using degenerate points. + for (int face_idx = 0; face_idx < faces.size(); ++face_idx) { + Face2D face = faces[face_idx]; + Vertex2D face_vertices[3] = { + vertices[face.vertex_idx[0]], + vertices[face.vertex_idx[1]], + vertices[face.vertex_idx[2]] + }; + Vector2 face_points[3] = { + face_vertices[0].point, + face_vertices[1].point, + face_vertices[2].point + }; + + for (int point_idx = 0; point_idx < degenerate_points.size(); ++point_idx) { + int degenerate_idx = degenerate_points[point_idx]; + Vector2 point_2D = vertices[degenerate_idx].point; + + // Check if point is existing face vertex. + bool existing = false; + for (int i = 0; i < 3; ++i) { + if (face_vertices[i].point.distance_squared_to(point_2D) < vertex_snap2) { + existing = true; + break; + } + } + if (existing) { + continue; + } + + // Check if point is on each edge. + for (int face_edge_idx = 0; face_edge_idx < 3; ++face_edge_idx) { + Vector2 edge_points[2] = { + face_points[face_edge_idx], + face_points[(face_edge_idx + 1) % 3] + }; + Vector2 closest_point = Geometry2D::get_closest_point_to_segment(point_2D, edge_points); + + if (point_2D.distance_squared_to(closest_point) < vertex_snap2) { + int opposite_vertex_idx = face.vertex_idx[(face_edge_idx + 2) % 3]; + + // If new vertex snaps to degenerate vertex, just delete this face. + if (degenerate_idx == opposite_vertex_idx) { + faces.remove_at(face_idx); + // Update index. + --face_idx; + break; + } + + // Create two new faces around the new edge and remove this face. + // The new edge is the last edge. + Face2D left_face; + left_face.vertex_idx[0] = degenerate_idx; + left_face.vertex_idx[1] = face.vertex_idx[(face_edge_idx + 1) % 3]; + left_face.vertex_idx[2] = opposite_vertex_idx; + Face2D right_face; + right_face.vertex_idx[0] = opposite_vertex_idx; + right_face.vertex_idx[1] = face.vertex_idx[face_edge_idx]; + right_face.vertex_idx[2] = degenerate_idx; + faces.remove_at(face_idx); + faces.insert(face_idx, right_face); + faces.insert(face_idx, left_face); + + // Don't check against the new faces. + ++face_idx; + + // No need to check other edges. + break; + } + } + } + } + } +} + +void CSGBrushOperation::Build2DFaces::_find_edge_intersections(const Vector2 p_segment_points[2], Vector &r_segment_indices) { + LocalVector> processed_edges; + + // For each face. + for (int face_idx = 0; face_idx < faces.size(); ++face_idx) { + Face2D face = faces[face_idx]; + Vertex2D face_vertices[3] = { + vertices[face.vertex_idx[0]], + vertices[face.vertex_idx[1]], + vertices[face.vertex_idx[2]] + }; + + // Check each edge. + for (int face_edge_idx = 0; face_edge_idx < 3; ++face_edge_idx) { + Vector edge_points_and_uvs = { + face_vertices[face_edge_idx].point, + face_vertices[(face_edge_idx + 1) % 3].point, + face_vertices[face_edge_idx].uv, + face_vertices[(face_edge_idx + 1) % 3].uv + }; + + Vector2 edge_points[2] = { + edge_points_and_uvs[0], + edge_points_and_uvs[1], + }; + Vector2 edge_uvs[2] = { + edge_points_and_uvs[2], + edge_points_and_uvs[3], + }; + + // Check if edge has already been processed. + if (processed_edges.has(edge_points_and_uvs)) { + continue; + } + + processed_edges.push_back(edge_points_and_uvs); + + // First check if the ends of the segment are on the edge. + Vector2 intersection_point; + + bool on_edge = false; + for (int edge_point_idx = 0; edge_point_idx < 2; ++edge_point_idx) { + intersection_point = Geometry2D::get_closest_point_to_segment(p_segment_points[edge_point_idx], edge_points); + if (p_segment_points[edge_point_idx].distance_squared_to(intersection_point) < vertex_snap2) { + on_edge = true; + break; + } + } + + // Else check if the segment intersects the edge. + if (on_edge || Geometry2D::segment_intersects_segment(p_segment_points[0], p_segment_points[1], edge_points[0], edge_points[1], &intersection_point)) { + // Check if intersection point is an edge point. + if ((edge_points[0].distance_squared_to(intersection_point) < vertex_snap2) || + (edge_points[1].distance_squared_to(intersection_point) < vertex_snap2)) { + continue; + } + + // Check if edge exists, by checking if the intersecting segment is parallel to the edge. + if (are_segments_parallel(p_segment_points, edge_points, vertex_snap2)) { + continue; + } + + // Add the intersection point as a new vertex. + Vertex2D new_vertex; + new_vertex.point = intersection_point; + new_vertex.uv = interpolate_segment_uv(edge_points, edge_uvs, intersection_point); + int new_vertex_idx = _add_vertex(new_vertex); + int opposite_vertex_idx = face.vertex_idx[(face_edge_idx + 2) % 3]; + _add_vertex_idx_sorted(r_segment_indices, new_vertex_idx); + + // If new vertex snaps to opposite vertex, just delete this face. + if (new_vertex_idx == opposite_vertex_idx) { + faces.remove_at(face_idx); + // Update index. + --face_idx; + break; + } + + // If opposite point is on the segment, add its index to segment indices too. + Vector2 closest_point = Geometry2D::get_closest_point_to_segment(vertices[opposite_vertex_idx].point, p_segment_points); + if (vertices[opposite_vertex_idx].point.distance_squared_to(closest_point) < vertex_snap2) { + _add_vertex_idx_sorted(r_segment_indices, opposite_vertex_idx); + } + + // Create two new faces around the new edge and remove this face. + // The new edge is the last edge. + Face2D left_face; + left_face.vertex_idx[0] = new_vertex_idx; + left_face.vertex_idx[1] = face.vertex_idx[(face_edge_idx + 1) % 3]; + left_face.vertex_idx[2] = opposite_vertex_idx; + Face2D right_face; + right_face.vertex_idx[0] = opposite_vertex_idx; + right_face.vertex_idx[1] = face.vertex_idx[face_edge_idx]; + right_face.vertex_idx[2] = new_vertex_idx; + faces.remove_at(face_idx); + faces.insert(face_idx, right_face); + faces.insert(face_idx, left_face); + + // Check against the new faces. + --face_idx; + break; + } + } + } +} + +int CSGBrushOperation::Build2DFaces::_insert_point(const Vector2 &p_point) { + int new_vertex_idx = -1; + + for (int face_idx = 0; face_idx < faces.size(); ++face_idx) { + Face2D face = faces[face_idx]; + Vertex2D face_vertices[3] = { + vertices[face.vertex_idx[0]], + vertices[face.vertex_idx[1]], + vertices[face.vertex_idx[2]] + }; + Vector2 points[3] = { + face_vertices[0].point, + face_vertices[1].point, + face_vertices[2].point + }; + Vector2 uvs[3] = { + face_vertices[0].uv, + face_vertices[1].uv, + face_vertices[2].uv + }; + + // Skip degenerate triangles. + if (is_triangle_degenerate(points, vertex_snap2)) { + continue; + } + + // Check if point is existing face vertex. + for (int i = 0; i < 3; ++i) { + if (face_vertices[i].point.distance_squared_to(p_point) < vertex_snap2) { + return face.vertex_idx[i]; + } + } + + // Check if point is on each edge. + bool on_edge = false; + for (int face_edge_idx = 0; face_edge_idx < 3; ++face_edge_idx) { + Vector2 edge_points[2] = { + points[face_edge_idx], + points[(face_edge_idx + 1) % 3] + }; + Vector2 edge_uvs[2] = { + uvs[face_edge_idx], + uvs[(face_edge_idx + 1) % 3] + }; + + Vector2 closest_point = Geometry2D::get_closest_point_to_segment(p_point, edge_points); + if (p_point.distance_squared_to(closest_point) < vertex_snap2) { + on_edge = true; + + // Add the point as a new vertex. + Vertex2D new_vertex; + new_vertex.point = p_point; + new_vertex.uv = interpolate_segment_uv(edge_points, edge_uvs, p_point); + new_vertex_idx = _add_vertex(new_vertex); + int opposite_vertex_idx = face.vertex_idx[(face_edge_idx + 2) % 3]; + + // If new vertex snaps to opposite vertex, just delete this face. + if (new_vertex_idx == opposite_vertex_idx) { + faces.remove_at(face_idx); + // Update index. + --face_idx; + break; + } + + // Don't create degenerate triangles. + Vector2 split_edge1[2] = { vertices[new_vertex_idx].point, edge_points[0] }; + Vector2 split_edge2[2] = { vertices[new_vertex_idx].point, edge_points[1] }; + Vector2 new_edge[2] = { vertices[new_vertex_idx].point, vertices[opposite_vertex_idx].point }; + if (are_segments_parallel(split_edge1, new_edge, vertex_snap2) && + are_segments_parallel(split_edge2, new_edge, vertex_snap2)) { + break; + } + + // Create two new faces around the new edge and remove this face. + // The new edge is the last edge. + Face2D left_face; + left_face.vertex_idx[0] = new_vertex_idx; + left_face.vertex_idx[1] = face.vertex_idx[(face_edge_idx + 1) % 3]; + left_face.vertex_idx[2] = opposite_vertex_idx; + Face2D right_face; + right_face.vertex_idx[0] = opposite_vertex_idx; + right_face.vertex_idx[1] = face.vertex_idx[face_edge_idx]; + right_face.vertex_idx[2] = new_vertex_idx; + faces.remove_at(face_idx); + faces.insert(face_idx, right_face); + faces.insert(face_idx, left_face); + + // Don't check against the new faces. + ++face_idx; + + // No need to check other edges. + break; + } + } + + // If not on an edge, check if the point is inside the face. + if (!on_edge && Geometry2D::is_point_in_triangle(p_point, face_vertices[0].point, face_vertices[1].point, face_vertices[2].point)) { + // Add the point as a new vertex. + Vertex2D new_vertex; + new_vertex.point = p_point; + new_vertex.uv = interpolate_triangle_uv(points, uvs, p_point); + new_vertex_idx = _add_vertex(new_vertex); + + // Create three new faces around this point and remove this face. + // The new vertex is the last vertex. + for (int i = 0; i < 3; ++i) { + // Don't create degenerate triangles. + Vector2 new_points[3] = { points[i], points[(i + 1) % 3], vertices[new_vertex_idx].point }; + if (is_triangle_degenerate(new_points, vertex_snap2)) { + continue; + } + + Face2D new_face; + new_face.vertex_idx[0] = face.vertex_idx[i]; + new_face.vertex_idx[1] = face.vertex_idx[(i + 1) % 3]; + new_face.vertex_idx[2] = new_vertex_idx; + faces.push_back(new_face); + } + faces.remove_at(face_idx); + + // No need to check other faces. + break; + } + } + + return new_vertex_idx; +} + +void CSGBrushOperation::Build2DFaces::insert(const CSGBrush &p_brush, int p_face_idx) { + // Find edge points that cross the plane and face points that are in the plane. + // Map those points to 2D. + // Create new faces from those points. + + Vector2 points_2D[3]; + int points_count = 0; + + for (int i = 0; i < 3; i++) { + Vector3 point_3D = p_brush.faces[p_face_idx].vertices[i]; + + if (plane.has_point(point_3D)) { + // Point is in the plane, add it. + Vector3 point_2D = plane.project(point_3D); + point_2D = to_2D.xform(point_2D); + points_2D[points_count++] = Vector2(point_2D.x, point_2D.y); + + } else { + Vector3 next_point_3D = p_brush.faces[p_face_idx].vertices[(i + 1) % 3]; + + if (plane.has_point(next_point_3D)) { + continue; // Next point is in plane, it will be added separately. + } + if (plane.is_point_over(point_3D) == plane.is_point_over(next_point_3D)) { + continue; // Both points on the same side of the plane, ignore. + } + + // Edge crosses the plane, find and add the intersection point. + Vector3 point_2D; + if (plane.intersects_segment(point_3D, next_point_3D, &point_2D)) { + point_2D = to_2D.xform(point_2D); + points_2D[points_count++] = Vector2(point_2D.x, point_2D.y); + } + } + } + + Vector segment_indices; + Vector2 segment[2]; + int inserted_index[3] = { -1, -1, -1 }; + + // Insert points. + for (int i = 0; i < points_count; ++i) { + inserted_index[i] = _insert_point(points_2D[i]); + } + + if (points_count == 2) { + // Insert a single segment. + segment[0] = points_2D[0]; + segment[1] = points_2D[1]; + _find_edge_intersections(segment, segment_indices); + for (int i = 0; i < 2; ++i) { + _add_vertex_idx_sorted(segment_indices, inserted_index[i]); + } + _merge_faces(segment_indices); + } + + if (points_count == 3) { + // Insert three segments. + for (int edge_idx = 0; edge_idx < 3; ++edge_idx) { + segment[0] = points_2D[edge_idx]; + segment[1] = points_2D[(edge_idx + 1) % 3]; + _find_edge_intersections(segment, segment_indices); + for (int i = 0; i < 2; ++i) { + _add_vertex_idx_sorted(segment_indices, inserted_index[(edge_idx + i) % 3]); + } + _merge_faces(segment_indices); + segment_indices.clear(); + } + } +} + +void CSGBrushOperation::Build2DFaces::addFacesToMesh(MeshMerge &r_mesh_merge, bool p_smooth, bool p_invert, const Ref &p_material, bool p_from_b) { + for (int face_idx = 0; face_idx < faces.size(); ++face_idx) { + Face2D face = faces[face_idx]; + Vertex2D fv[3] = { + vertices[face.vertex_idx[0]], + vertices[face.vertex_idx[1]], + vertices[face.vertex_idx[2]] + }; + + // Convert 2D vertex points to 3D. + Vector3 points_3D[3]; + Vector2 uvs[3]; + for (int i = 0; i < 3; ++i) { + Vector3 point_2D(fv[i].point.x, fv[i].point.y, 0); + points_3D[i] = to_3D.xform(point_2D); + uvs[i] = fv[i].uv; + } + + r_mesh_merge.add_face(points_3D, uvs, p_smooth, p_invert, p_material, p_from_b); + } +} + +CSGBrushOperation::Build2DFaces::Build2DFaces(const CSGBrush &p_brush, int p_face_idx, float p_vertex_snap2) : + vertex_snap2(p_vertex_snap2 * p_vertex_snap2) { + // Convert 3D vertex points to 2D. + Vector3 points_3D[3] = { + p_brush.faces[p_face_idx].vertices[0], + p_brush.faces[p_face_idx].vertices[1], + p_brush.faces[p_face_idx].vertices[2], + }; + + plane = Plane(points_3D[0], points_3D[1], points_3D[2]); + to_3D.origin = points_3D[0]; + to_3D.basis.set_column(2, plane.normal); + to_3D.basis.set_column(0, (points_3D[1] - points_3D[2]).normalized()); + to_3D.basis.set_column(1, to_3D.basis.get_column(0).cross(to_3D.basis.get_column(2)).normalized()); + to_2D = to_3D.affine_inverse(); + + Face2D face; + for (int i = 0; i < 3; i++) { + Vertex2D vertex; + Vector3 point_2D = to_2D.xform(points_3D[i]); + vertex.point.x = point_2D.x; + vertex.point.y = point_2D.y; + vertex.uv = p_brush.faces[p_face_idx].uvs[i]; + vertices.push_back(vertex); + face.vertex_idx[i] = i; + } + faces.push_back(face); +} + +void CSGBrushOperation::update_faces(const CSGBrush &p_brush_a, const int p_face_idx_a, const CSGBrush &p_brush_b, const int p_face_idx_b, Build2DFaceCollection &p_collection, float p_vertex_snap) { + Vector3 vertices_a[3] = { + p_brush_a.faces[p_face_idx_a].vertices[0], + p_brush_a.faces[p_face_idx_a].vertices[1], + p_brush_a.faces[p_face_idx_a].vertices[2], + }; + + Vector3 vertices_b[3] = { + p_brush_b.faces[p_face_idx_b].vertices[0], + p_brush_b.faces[p_face_idx_b].vertices[1], + p_brush_b.faces[p_face_idx_b].vertices[2], + }; + + // Don't use degenerate faces. + bool has_degenerate = false; + if (is_snapable(vertices_a[0], vertices_a[1], p_vertex_snap) || + is_snapable(vertices_a[0], vertices_a[2], p_vertex_snap) || + is_snapable(vertices_a[1], vertices_a[2], p_vertex_snap)) { + p_collection.build2DFacesA[p_face_idx_a] = Build2DFaces(); + has_degenerate = true; + } + + if (is_snapable(vertices_b[0], vertices_b[1], p_vertex_snap) || + is_snapable(vertices_b[0], vertices_b[2], p_vertex_snap) || + is_snapable(vertices_b[1], vertices_b[2], p_vertex_snap)) { + p_collection.build2DFacesB[p_face_idx_b] = Build2DFaces(); + has_degenerate = true; + } + if (has_degenerate) { + return; + } + + // Ensure B has points either side of or in the plane of A. + int over_count = 0, under_count = 0; + Plane plane_a(vertices_a[0], vertices_a[1], vertices_a[2]); + ERR_FAIL_COND_MSG(plane_a.normal == Vector3(), "Couldn't form plane from Brush A face."); + + for (int i = 0; i < 3; i++) { + if (plane_a.has_point(vertices_b[i])) { + // In plane. + } else if (plane_a.is_point_over(vertices_b[i])) { + over_count++; + } else { + under_count++; + } + } + // If all points under or over the plane, there is no intersection. + if (over_count == 3 || under_count == 3) { + return; + } + + // Ensure A has points either side of or in the plane of B. + over_count = 0; + under_count = 0; + Plane plane_b(vertices_b[0], vertices_b[1], vertices_b[2]); + ERR_FAIL_COND_MSG(plane_b.normal == Vector3(), "Couldn't form plane from Brush B face."); + + for (int i = 0; i < 3; i++) { + if (plane_b.has_point(vertices_a[i])) { + // In plane. + } else if (plane_b.is_point_over(vertices_a[i])) { + over_count++; + } else { + under_count++; + } + } + // If all points under or over the plane, there is no intersection. + if (over_count == 3 || under_count == 3) { + return; + } + + // Check for intersection using the SAT theorem. + { + // Edge pair cross product combinations. + for (int i = 0; i < 3; i++) { + Vector3 axis_a = (vertices_a[i] - vertices_a[(i + 1) % 3]).normalized(); + + for (int j = 0; j < 3; j++) { + Vector3 axis_b = (vertices_b[j] - vertices_b[(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(vertices_a[k]); + min_a = MIN(min_a, d); + max_a = MAX(max_a, d); + d = sep_axis.dot(vertices_b[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're still here, the faces probably intersect, so add new faces. + if (!p_collection.build2DFacesA.has(p_face_idx_a)) { + p_collection.build2DFacesA[p_face_idx_a] = Build2DFaces(p_brush_a, p_face_idx_a, p_vertex_snap); + } + p_collection.build2DFacesA[p_face_idx_a].insert(p_brush_b, p_face_idx_b); + + if (!p_collection.build2DFacesB.has(p_face_idx_b)) { + p_collection.build2DFacesB[p_face_idx_b] = Build2DFaces(p_brush_b, p_face_idx_b, p_vertex_snap); + } + p_collection.build2DFacesB[p_face_idx_b].insert(p_brush_a, p_face_idx_a); +} diff --git a/modules/csg/csg.h b/modules/csg/csg.h index b771e60e89e1..2a0831e1ce18 100644 --- a/modules/csg/csg.h +++ b/modules/csg/csg.h @@ -55,18 +55,150 @@ struct CSGBrush { Vector faces; Vector> materials; - inline void _regen_face_aabbs() { - for (int i = 0; i < faces.size(); i++) { - faces.write[i].aabb = AABB(); - faces.write[i].aabb.position = faces[i].vertices[0]; - faces.write[i].aabb.expand_to(faces[i].vertices[1]); - faces.write[i].aabb.expand_to(faces[i].vertices[2]); - } - } + inline void _regen_face_aabbs(); // Create a brush from faces. void build_from_faces(const Vector &p_vertices, const Vector &p_uvs, const Vector &p_smooth, const Vector> &p_materials, const Vector &p_invert_faces); void copy_from(const CSGBrush &p_brush, const Transform3D &p_xform); }; +struct CSGBrushOperation { + enum Operation { + OPERATION_UNION, + OPERATION_INTERSECTION, + OPERATION_SUBTRACTION, + }; + + void merge_brushes(Operation p_operation, const CSGBrush &p_brush_a, const CSGBrush &p_brush_b, CSGBrush &r_merged_brush, float p_vertex_snap); + + struct MeshMerge { + struct Face { + bool from_b = false; + bool inside = false; + int points[3] = {}; + Vector2 uvs[3]; + bool smooth = false; + bool invert = false; + int material_idx = 0; + }; + + struct FaceBVH { + int face = 0; + int left = 0; + int right = 0; + int next = 0; + Vector3 center; + AABB aabb; + }; + + struct FaceBVHCmpX { + _FORCE_INLINE_ bool operator()(const FaceBVH *p_left, const FaceBVH *p_right) const { + return p_left->center.x < p_right->center.x; + } + }; + + struct FaceBVHCmpY { + _FORCE_INLINE_ bool operator()(const FaceBVH *p_left, const FaceBVH *p_right) const { + return p_left->center.y < p_right->center.y; + } + }; + struct FaceBVHCmpZ { + _FORCE_INLINE_ bool operator()(const FaceBVH *p_left, const FaceBVH *p_right) const { + return p_left->center.z < p_right->center.z; + } + }; + + 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_murmur3_one_32(p_vk.x); + h = hash_murmur3_one_32(p_vk.y, h); + h = hash_murmur3_one_32(p_vk.z, h); + return h; + } + }; + struct Intersection { + bool found = false; + real_t conormal = FLT_MAX; + real_t distance_squared = FLT_MAX; + real_t origin_angle = FLT_MAX; + }; + + struct IntersectionDistance { + bool is_conormal; + real_t distance_squared; + }; + + Vector points; + Vector faces; + HashMap, int> materials; + HashMap vertex_map; + OAHashMap snap_cache; + float vertex_snap = 0.0; + + inline void _add_distance(List &r_intersectionsA, List &r_intersectionsB, bool p_from_B, real_t p_distance, bool p_is_conormal) const; + inline bool _bvh_inside(FaceBVH *r_facebvhptr, int p_max_depth, int p_bvh_first, int p_face_idx) const; + inline int _create_bvh(FaceBVH *r_facebvhptr, FaceBVH **r_facebvhptrptr, int p_from, int p_size, int p_depth, int &r_max_depth, int &r_max_alloc); + + void add_face(const Vector3 p_points[3], const Vector2 p_uvs[3], bool p_smooth, bool p_invert, const Ref &p_material, bool p_from_b); + void mark_inside_faces(); + }; + + struct Build2DFaces { + struct Vertex2D { + Vector2 point; + Vector2 uv; + }; + + struct Face2D { + int vertex_idx[3] = {}; + }; + + Vector vertices; + Vector faces; + Plane plane; + Transform3D to_2D; + Transform3D to_3D; + float vertex_snap2 = 0.0; + + inline int _get_point_idx(const Vector2 &p_point); + inline int _add_vertex(const Vertex2D &p_vertex); + inline void _add_vertex_idx_sorted(Vector &r_vertex_indices, int p_new_vertex_index); + inline void _merge_faces(const Vector &p_segment_indices); + inline void _find_edge_intersections(const Vector2 p_segment_points[2], Vector &r_segment_indices); + inline int _insert_point(const Vector2 &p_point); + + void insert(const CSGBrush &p_brush, int p_brush_face); + void addFacesToMesh(MeshMerge &r_mesh_merge, bool p_smooth, bool p_invert, const Ref &p_material, bool p_from_b); + + Build2DFaces() {} + Build2DFaces(const CSGBrush &p_brush, int p_brush_face, float p_vertex_snap2); + }; + + struct Build2DFaceCollection { + HashMap build2DFacesA; + HashMap build2DFacesB; + }; + + void update_faces(const CSGBrush &p_brush_a, const int p_face_idx_a, const CSGBrush &p_brush_b, const int p_face_idx_b, Build2DFaceCollection &p_collection, float p_vertex_snap); +}; + #endif // CSG_H diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index eed5a7673792..8c81c0ce4ec5 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -30,13 +30,8 @@ #include "csg_shape.h" -#ifdef DEV_ENABLED -#include "core/io/json.h" -#endif // DEV_ENABLED #include "core/math/geometry_2d.h" -#include - void CSGShape3D::set_use_collision(bool p_enable) { if (use_collision == p_enable) { return; @@ -145,7 +140,6 @@ bool CSGShape3D::is_root_shape() const { return !parent_shape; } -#ifndef DISABLE_DEPRECATED void CSGShape3D::set_snap(float p_snap) { if (snap == p_snap) { return; @@ -158,7 +152,6 @@ void CSGShape3D::set_snap(float p_snap) { float CSGShape3D::get_snap() const { return snap; } -#endif // DISABLE_DEPRECATED void CSGShape3D::_make_dirty(bool p_parent_removing) { if ((p_parent_removing || is_root_shape()) && !dirty) { @@ -174,320 +167,78 @@ void CSGShape3D::_make_dirty(bool p_parent_removing) { dirty = true; } -enum ManifoldProperty { - MANIFOLD_PROPERTY_POSITION_X = 0, - MANIFOLD_PROPERTY_POSITION_Y, - MANIFOLD_PROPERTY_POSITION_Z, - MANIFOLD_PROPERTY_INVERT, - MANIFOLD_PROPERTY_SMOOTH_GROUP, - MANIFOLD_PROPERTY_UV_X_0, - MANIFOLD_PROPERTY_UV_Y_0, - MANIFOLD_PROPERTY_MAX -}; - -static void _unpack_manifold( - const manifold::Manifold &p_manifold, - const HashMap> &p_mesh_materials, - CSGBrush *r_mesh_merge) { - manifold::MeshGL64 mesh = p_manifold.GetMeshGL64(); - - constexpr int32_t order[3] = { 0, 2, 1 }; - - for (size_t run_i = 0; run_i < mesh.runIndex.size() - 1; run_i++) { - uint32_t original_id = -1; - if (run_i < mesh.runOriginalID.size()) { - original_id = mesh.runOriginalID[run_i]; +CSGBrush *CSGShape3D::_get_brush() { + if (dirty) { + if (brush) { + memdelete(brush); } + brush = nullptr; - Ref material; - if (p_mesh_materials.has(original_id)) { - material = p_mesh_materials[original_id]; - } - // Find or reserve a material ID in the brush. - int32_t material_id = r_mesh_merge->materials.find(material); - if (material_id == -1) { - material_id = r_mesh_merge->materials.size(); - r_mesh_merge->materials.push_back(material); - } + CSGBrush *n = _build_brush(); - size_t begin = mesh.runIndex[run_i]; - size_t end = mesh.runIndex[run_i + 1]; - for (size_t vert_i = begin; vert_i < end; vert_i += 3) { - CSGBrush::Face face; - face.material = material_id; - int32_t first_property_index = mesh.triVerts[vert_i + order[0]]; - face.smooth = mesh.vertProperties[first_property_index * mesh.numProp + MANIFOLD_PROPERTY_SMOOTH_GROUP] > 0.5f; - face.invert = mesh.vertProperties[first_property_index * mesh.numProp + MANIFOLD_PROPERTY_INVERT] > 0.5f; - - for (int32_t tri_order_i = 0; tri_order_i < 3; tri_order_i++) { - int32_t property_i = mesh.triVerts[vert_i + order[tri_order_i]]; - ERR_FAIL_COND_MSG(property_i * mesh.numProp >= mesh.vertProperties.size(), "Invalid index into vertex properties"); - face.vertices[tri_order_i] = Vector3( - mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_POSITION_X], - mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_POSITION_Y], - mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_POSITION_Z]); - face.uvs[tri_order_i] = Vector2( - mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_UV_X_0], - mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_UV_Y_0]); + for (int i = 0; i < get_child_count(); i++) { + CSGShape3D *child = Object::cast_to(get_child(i)); + if (!child) { + continue; + } + if (!child->is_visible()) { + continue; } - r_mesh_merge->faces.push_back(face); - } - } - - r_mesh_merge->_regen_face_aabbs(); -} - -// Errors matching `thirdparty/manifold/include/manifold/manifold.h`. -static String manifold_error_to_string(const manifold::Manifold::Error &p_error) { - switch (p_error) { - case manifold::Manifold::Error::NoError: - return "No Error"; - case manifold::Manifold::Error::NonFiniteVertex: - return "Non Finite Vertex"; - case manifold::Manifold::Error::NotManifold: - return "Not Manifold"; - case manifold::Manifold::Error::VertexOutOfBounds: - return "Vertex Out Of Bounds"; - case manifold::Manifold::Error::PropertiesWrongLength: - return "Properties Wrong Length"; - case manifold::Manifold::Error::MissingPositionProperties: - return "Missing Position Properties"; - case manifold::Manifold::Error::MergeVectorsDifferentLengths: - return "Merge Vectors Different Lengths"; - case manifold::Manifold::Error::MergeIndexOutOfBounds: - return "Merge Index Out Of Bounds"; - case manifold::Manifold::Error::TransformWrongLength: - return "Transform Wrong Length"; - case manifold::Manifold::Error::RunIndexWrongLength: - return "Run Index Wrong Length"; - case manifold::Manifold::Error::FaceIDWrongLength: - return "Face ID Wrong Length"; - case manifold::Manifold::Error::InvalidConstruction: - return "Invalid Construction"; - default: - return "Unknown Error"; - } -} - -#ifdef DEV_ENABLED -static String _export_meshgl_as_json(const manifold::MeshGL64 &p_mesh) { - Dictionary mesh_dict; - mesh_dict["numProp"] = p_mesh.numProp; - - Array vert_properties; - for (const double &val : p_mesh.vertProperties) { - vert_properties.append(val); - } - mesh_dict["vertProperties"] = vert_properties; - - Array tri_verts; - for (const uint64_t &val : p_mesh.triVerts) { - tri_verts.append(val); - } - mesh_dict["triVerts"] = tri_verts; - - Array merge_from_vert; - for (const uint64_t &val : p_mesh.mergeFromVert) { - merge_from_vert.append(val); - } - mesh_dict["mergeFromVert"] = merge_from_vert; - - Array merge_to_vert; - for (const uint64_t &val : p_mesh.mergeToVert) { - merge_to_vert.append(val); - } - mesh_dict["mergeToVert"] = merge_to_vert; - - Array run_index; - for (const uint64_t &val : p_mesh.runIndex) { - run_index.append(val); - } - mesh_dict["runIndex"] = run_index; - - Array run_original_id; - for (const uint32_t &val : p_mesh.runOriginalID) { - run_original_id.append(val); - } - mesh_dict["runOriginalID"] = run_original_id; - - Array run_transform; - for (const double &val : p_mesh.runTransform) { - run_transform.append(val); - } - mesh_dict["runTransform"] = run_transform; - - Array face_id; - for (const uint64_t &val : p_mesh.faceID) { - face_id.append(val); - } - mesh_dict["faceID"] = face_id; - Array halfedge_tangent; - for (const double &val : p_mesh.halfedgeTangent) { - halfedge_tangent.append(val); - } - mesh_dict["halfedgeTangent"] = halfedge_tangent; - - mesh_dict["tolerance"] = p_mesh.tolerance; - - String json_string = JSON::stringify(mesh_dict); - return json_string; -} -#endif // DEV_ENABLED - -static void _pack_manifold( - const CSGBrush *const p_mesh_merge, - manifold::Manifold &r_manifold, - HashMap> &p_mesh_materials, - CSGShape3D *p_csg_shape) { - ERR_FAIL_NULL_MSG(p_mesh_merge, "p_mesh_merge is null"); - ERR_FAIL_NULL_MSG(p_csg_shape, "p_shape is null"); - HashMap> faces_by_material; - for (int face_i = 0; face_i < p_mesh_merge->faces.size(); face_i++) { - const CSGBrush::Face &face = p_mesh_merge->faces[face_i]; - faces_by_material[face.material].push_back(face); - } + CSGBrush *n2 = child->_get_brush(); + if (!n2) { + continue; + } + if (!n) { + n = memnew(CSGBrush); - manifold::MeshGL64 mesh; - mesh.numProp = MANIFOLD_PROPERTY_MAX; - mesh.runOriginalID.reserve(faces_by_material.size()); - mesh.runIndex.reserve(faces_by_material.size() + 1); - mesh.vertProperties.reserve(p_mesh_merge->faces.size() * 3 * MANIFOLD_PROPERTY_MAX); - - // Make a run of triangles for each material. - for (const KeyValue> &E : faces_by_material) { - const uint32_t material_id = E.key; - const Vector &faces = E.value; - mesh.runIndex.push_back(mesh.triVerts.size()); - - // Associate the material with an ID. - uint32_t reserved_id = r_manifold.ReserveIDs(1); - mesh.runOriginalID.push_back(reserved_id); - Ref material; - if (material_id < p_mesh_merge->materials.size()) { - material = p_mesh_merge->materials[material_id]; - } + n->copy_from(*n2, child->get_transform()); - p_mesh_materials.insert(reserved_id, material); - for (const CSGBrush::Face &face : faces) { - for (int32_t tri_order_i = 0; tri_order_i < 3; tri_order_i++) { - constexpr int32_t order[3] = { 0, 2, 1 }; - int i = order[tri_order_i]; - - mesh.triVerts.push_back(mesh.vertProperties.size() / MANIFOLD_PROPERTY_MAX); - - size_t begin = mesh.vertProperties.size(); - mesh.vertProperties.resize(mesh.vertProperties.size() + MANIFOLD_PROPERTY_MAX); - // Add the vertex properties. - // Use CSGBrush constants rather than push_back for clarity. - double *vert = &mesh.vertProperties[begin]; - vert[MANIFOLD_PROPERTY_POSITION_X] = face.vertices[i].x; - vert[MANIFOLD_PROPERTY_POSITION_Y] = face.vertices[i].y; - vert[MANIFOLD_PROPERTY_POSITION_Z] = face.vertices[i].z; - vert[MANIFOLD_PROPERTY_UV_X_0] = face.uvs[i].x; - vert[MANIFOLD_PROPERTY_UV_Y_0] = face.uvs[i].y; - vert[MANIFOLD_PROPERTY_SMOOTH_GROUP] = face.smooth ? 1.0f : 0.0f; - vert[MANIFOLD_PROPERTY_INVERT] = face.invert ? 1.0f : 0.0f; + } else { + CSGBrush *nn = memnew(CSGBrush); + CSGBrush *nn2 = memnew(CSGBrush); + nn2->copy_from(*n2, child->get_transform()); + + CSGBrushOperation bop; + + switch (child->get_operation()) { + case CSGShape3D::OPERATION_UNION: + bop.merge_brushes(CSGBrushOperation::OPERATION_UNION, *n, *nn2, *nn, snap); + break; + case CSGShape3D::OPERATION_INTERSECTION: + bop.merge_brushes(CSGBrushOperation::OPERATION_INTERSECTION, *n, *nn2, *nn, snap); + break; + case CSGShape3D::OPERATION_SUBTRACTION: + bop.merge_brushes(CSGBrushOperation::OPERATION_SUBTRACTION, *n, *nn2, *nn, snap); + break; + } + memdelete(n); + memdelete(nn2); + n = nn; } } - } - // runIndex needs an explicit end value. - mesh.runIndex.push_back(mesh.triVerts.size()); - mesh.tolerance = 2 * FLT_EPSILON; - ERR_FAIL_COND_MSG(mesh.vertProperties.size() % mesh.numProp != 0, "Invalid vertex properties size."); - mesh.Merge(); -#ifdef DEV_ENABLED - print_verbose(_export_meshgl_as_json(mesh)); -#endif // DEV_ENABLED - r_manifold = manifold::Manifold(mesh); - manifold::Manifold::Error error = r_manifold.Status(); - if (error == manifold::Manifold::Error::NoError) { - return; - } - if (p_csg_shape->get_owner()) { - NodePath path = p_csg_shape->get_owner()->get_path_to(p_csg_shape, true); - print_error(vformat("CSGShape3D manifold creation from mesh failed at %s: %s.", path, manifold_error_to_string(error))); - } else { - print_error(vformat("CSGShape3D manifold creation from mesh failed at .: %s.", manifold_error_to_string(error))); - } -} -struct ManifoldOperation { - manifold::Manifold manifold; - manifold::OpType operation; - static manifold::OpType convert_csg_op(CSGShape3D::Operation op) { - switch (op) { - case CSGShape3D::OPERATION_SUBTRACTION: - return manifold::OpType::Subtract; - case CSGShape3D::OPERATION_INTERSECTION: - return manifold::OpType::Intersect; - default: - return manifold::OpType::Add; - } - } - ManifoldOperation() : - operation(manifold::OpType::Add) {} - ManifoldOperation(const manifold::Manifold &m, manifold::OpType op) : - manifold(m), operation(op) {} -}; - -CSGBrush *CSGShape3D::_get_brush() { - if (!dirty) { - return brush; - } - if (brush) { - memdelete(brush); - } - brush = nullptr; - CSGBrush *n = _build_brush(); - HashMap> mesh_materials; - manifold::Manifold root_manifold; - _pack_manifold(n, root_manifold, mesh_materials, this); - manifold::OpType current_op = ManifoldOperation::convert_csg_op(get_operation()); - std::vector manifolds; - manifolds.push_back(root_manifold); - for (int i = 0; i < get_child_count(); i++) { - CSGShape3D *child = Object::cast_to(get_child(i)); - if (!child || !child->is_visible()) { - continue; - } - CSGBrush *child_brush = child->_get_brush(); - if (!child_brush) { - continue; - } - CSGBrush transformed_brush; - transformed_brush.copy_from(*child_brush, child->get_transform()); - manifold::Manifold child_manifold; - _pack_manifold(&transformed_brush, child_manifold, mesh_materials, child); - manifold::OpType child_operation = ManifoldOperation::convert_csg_op(child->get_operation()); - if (child_operation != current_op) { - manifold::Manifold result = manifold::Manifold::BatchBoolean(manifolds, current_op); - manifolds.clear(); - manifolds.push_back(result); - current_op = child_operation; - } - manifolds.push_back(child_manifold); - } - if (!manifolds.empty()) { - manifold::Manifold manifold_result = manifold::Manifold::BatchBoolean(manifolds, current_op); if (n) { - memdelete(n); - } - n = memnew(CSGBrush); - _unpack_manifold(manifold_result, mesh_materials, n); - } - AABB aabb; - if (n && !n->faces.is_empty()) { - aabb.position = n->faces[0].vertices[0]; - for (const CSGBrush::Face &face : n->faces) { - for (int i = 0; i < 3; ++i) { - aabb.expand_to(face.vertices[i]); + 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]); + } + } } + node_aabb = aabb; + } else { + node_aabb = AABB(); } + + brush = n; + + dirty = false; } - node_aabb = aabb; - brush = n; - dirty = false; + return brush; } @@ -946,10 +697,8 @@ void CSGShape3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_operation", "operation"), &CSGShape3D::set_operation); ClassDB::bind_method(D_METHOD("get_operation"), &CSGShape3D::get_operation); -#ifndef DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("set_snap", "snap"), &CSGShape3D::set_snap); ClassDB::bind_method(D_METHOD("get_snap"), &CSGShape3D::get_snap); -#endif // DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("set_use_collision", "operation"), &CSGShape3D::set_use_collision); ClassDB::bind_method(D_METHOD("is_using_collision"), &CSGShape3D::is_using_collision); @@ -978,9 +727,7 @@ void CSGShape3D::_bind_methods() { ClassDB::bind_method(D_METHOD("bake_collision_shape"), &CSGShape3D::bake_collision_shape); ADD_PROPERTY(PropertyInfo(Variant::INT, "operation", PROPERTY_HINT_ENUM, "Union,Intersection,Subtraction"), "set_operation", "get_operation"); -#ifndef DISABLE_DEPRECATED - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_RANGE, "0.000001,1,0.000001,suffix:m", PROPERTY_USAGE_NONE), "set_snap", "get_snap"); -#endif // DISABLE_DEPRECATED + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_RANGE, "0.000001,1,0.000001,suffix:m"), "set_snap", "get_snap"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "calculate_tangents"), "set_calculate_tangents", "is_calculating_tangents"); ADD_GROUP("Collision", "collision_"); diff --git a/modules/csg/csg_shape.h b/modules/csg/csg_shape.h index 9aa612885aa8..8f23ae2f9e2c 100644 --- a/modules/csg/csg_shape.h +++ b/modules/csg/csg_shape.h @@ -155,10 +155,8 @@ class CSGShape3D : public GeometryInstance3D { void set_collision_priority(real_t p_priority); real_t get_collision_priority() const; -#ifndef DISABLE_DEPRECATED void set_snap(float p_snap); float get_snap() const; -#endif // DISABLE_DEPRECATED void set_calculate_tangents(bool p_calculate_tangents); bool is_calculating_tangents() const; diff --git a/modules/csg/doc_classes/CSGShape3D.xml b/modules/csg/doc_classes/CSGShape3D.xml index 8ea471e62d83..ac62d8dd83dc 100644 --- a/modules/csg/doc_classes/CSGShape3D.xml +++ b/modules/csg/doc_classes/CSGShape3D.xml @@ -89,8 +89,8 @@ The operation that is performed on this shape. This is ignored for the first CSG child node as the operation is between this node and the previous child of this nodes parent. - - This property does nothing. + + Snap makes the mesh vertices snap to a given distance so that the faces of two meshes can be perfectly aligned. A lower value results in greater precision but may be harder to adjust. The top-level CSG shape's snap value is used for the entire CSG tree. Adds a collision shape to the physics engine for our CSG shape. This will always act like a static body. Note that the collision shape is still active even if the CSG shape itself is hidden. See also [member collision_mask] and [member collision_priority]. diff --git a/modules/csg_manifold/SCsub b/modules/csg_manifold/SCsub new file mode 100644 index 000000000000..a14e999fb898 --- /dev/null +++ b/modules/csg_manifold/SCsub @@ -0,0 +1,47 @@ +#!/usr/bin/env python +from misc.utility.scons_hints import * + +Import("env") +Import("env_modules") + +env_csg = env_modules.Clone() + +env_csg.Append(CPPDEFINES=("MANIFOLD_PAR", "-1")) + +# Thirdparty source files + +thirdparty_obj = [] + +thirdparty_dir = "#thirdparty/manifold/" +thirdparty_sources = [ + "src/boolean_result.cpp", + "src/boolean3.cpp", + "src/constructors.cpp", + "src/csg_tree.cpp", + "src/edge_op.cpp", + "src/face_op.cpp", + "src/impl.cpp", + "src/manifold.cpp", + "src/polygon.cpp", + "src/properties.cpp", + "src/quickhull.cpp", + "src/smoothing.cpp", + "src/sort.cpp", + "src/subdivision.cpp", +] + +thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] +env_csg.Prepend( + CPPPATH=[ + thirdparty_dir + "include", + ] +) +env_thirdparty = env_csg.Clone() +env_thirdparty.disable_warnings() +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) +env.modules_sources += thirdparty_obj + +# Godot's own source files +env_csg.add_source_files(env.modules_sources, "*.cpp") +if env.editor_build: + env_csg.add_source_files(env.modules_sources, "editor/*.cpp") diff --git a/modules/csg_manifold/config.py b/modules/csg_manifold/config.py new file mode 100644 index 000000000000..2d6cd2e97eca --- /dev/null +++ b/modules/csg_manifold/config.py @@ -0,0 +1,24 @@ +def can_build(env, platform): + return not env["disable_3d"] + + +def configure(env): + pass + + +def get_doc_classes(): + return [ + "CSGManifoldBox3D", + "CSGManifoldCombiner3D", + "CSGManifoldCylinder3D", + "CSGManifoldMesh3D", + "CSGManifoldPolygon3D", + "CSGManifoldPrimitive3D", + "CSGManifoldShape3D", + "CSGManifoldSphere3D", + "CSGManifoldTorus3D", + ] + + +def get_doc_path(): + return "doc_classes" diff --git a/modules/csg_manifold/csg_manifold.cpp b/modules/csg_manifold/csg_manifold.cpp new file mode 100644 index 000000000000..70008c027d10 --- /dev/null +++ b/modules/csg_manifold/csg_manifold.cpp @@ -0,0 +1,125 @@ +/**************************************************************************/ +/* csg_manifold.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "csg_manifold.h" + +#include "core/math/geometry_2d.h" +#include "core/math/math_funcs.h" +#include "core/templates/sort_array.h" +#include "scene/resources/mesh_data_tool.h" +#include "scene/resources/surface_tool.h" + +#include "thirdparty/manifold/include/manifold/manifold.h" + +// CSGManifoldBrush + +void CSGManifoldBrush::build_from_faces(const Vector &p_vertices, const Vector &p_uvs, const Vector &p_smooth, const Vector> &p_materials, const Vector &p_flip_faces) { + faces.clear(); + + int vc = p_vertices.size(); + + ERR_FAIL_COND((vc % 3) != 0); + + const Vector3 *rv = p_vertices.ptr(); + int uvc = p_uvs.size(); + const Vector2 *ruv = p_uvs.ptr(); + int sc = p_smooth.size(); + const bool *rs = p_smooth.ptr(); + int mc = p_materials.size(); + const Ref *rm = p_materials.ptr(); + int ic = p_flip_faces.size(); + const bool *ri = p_flip_faces.ptr(); + + HashMap, int> material_map; + + faces.resize(p_vertices.size() / 3); + + for (int i = 0; i < faces.size(); i++) { + Face &f = faces.write[i]; + f.vertices[0] = rv[i * 3 + 0]; + f.vertices[1] = rv[i * 3 + 1]; + f.vertices[2] = rv[i * 3 + 2]; + + if (uvc == vc) { + f.uvs[0] = ruv[i * 3 + 0]; + f.uvs[1] = ruv[i * 3 + 1]; + f.uvs[2] = ruv[i * 3 + 2]; + } + + if (sc == vc / 3) { + f.smooth = rs[i]; + } else { + f.smooth = false; + } + + if (ic == vc / 3) { + f.invert = ri[i]; + } else { + f.invert = false; + } + + if (mc == vc / 3) { + Ref mat = rm[i]; + if (mat.is_valid()) { + HashMap, int>::ConstIterator E = material_map.find(mat); + + if (E) { + f.material = E->value; + } else { + f.material = material_map.size(); + material_map[mat] = f.material; + } + + } else { + f.material = -1; + } + } + } + + materials.resize(material_map.size()); + for (const KeyValue, int> &E : material_map) { + materials.write[E.value] = E.key; + } + + _regen_face_aabbs(); +} + +void CSGManifoldBrush::copy_from(const CSGManifoldBrush &p_brush, const Transform3D &p_xform) { + faces = p_brush.faces; + materials = p_brush.materials; + + for (int i = 0; i < faces.size(); i++) { + for (int j = 0; j < 3; j++) { + faces.write[i].vertices[j] = p_xform.xform(p_brush.faces[i].vertices[j]); + } + } + + _regen_face_aabbs(); +} diff --git a/modules/csg_manifold/csg_manifold.h b/modules/csg_manifold/csg_manifold.h new file mode 100644 index 000000000000..2e886e4d122e --- /dev/null +++ b/modules/csg_manifold/csg_manifold.h @@ -0,0 +1,72 @@ +/**************************************************************************/ +/* csg_manifold.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#ifndef CSG_MANIFOLD_H +#define CSG_MANIFOLD_H + +#include "core/math/aabb.h" +#include "core/math/plane.h" +#include "core/math/transform_3d.h" +#include "core/math/vector2.h" +#include "core/math/vector3.h" +#include "core/object/ref_counted.h" +#include "core/templates/list.h" +#include "core/templates/oa_hash_map.h" +#include "core/templates/vector.h" +#include "scene/resources/material.h" + +struct CSGManifoldBrush { + struct Face { + Vector3 vertices[3]; + Vector2 uvs[3]; + AABB aabb; + bool smooth = false; + bool invert = false; + int material = 0; + }; + + Vector faces; + Vector> materials; + + inline void _regen_face_aabbs() { + for (int i = 0; i < faces.size(); i++) { + faces.write[i].aabb = AABB(); + faces.write[i].aabb.position = faces[i].vertices[0]; + faces.write[i].aabb.expand_to(faces[i].vertices[1]); + faces.write[i].aabb.expand_to(faces[i].vertices[2]); + } + } + + // Create a brush from faces. + void build_from_faces(const Vector &p_vertices, const Vector &p_uvs, const Vector &p_smooth, const Vector> &p_materials, const Vector &p_invert_faces); + void copy_from(const CSGManifoldBrush &p_brush, const Transform3D &p_xform); +}; + +#endif // CSG_MANIFOLD_H diff --git a/modules/csg_manifold/csg_manifold_shape.cpp b/modules/csg_manifold/csg_manifold_shape.cpp new file mode 100644 index 000000000000..b6883c688587 --- /dev/null +++ b/modules/csg_manifold/csg_manifold_shape.cpp @@ -0,0 +1,2722 @@ +/**************************************************************************/ +/* csg_manifold_shape.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "csg_manifold_shape.h" + +#ifdef DEV_ENABLED +#include "core/io/json.h" +#endif // DEV_ENABLED +#include "core/math/geometry_2d.h" + +#include + +void CSGManifoldShape3D::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.instantiate(); + root_collision_instance = PhysicsServer3D::get_singleton()->body_create(); + PhysicsServer3D::get_singleton()->body_set_mode(root_collision_instance, PhysicsServer3D::BODY_MODE_STATIC); + PhysicsServer3D::get_singleton()->body_set_state(root_collision_instance, PhysicsServer3D::BODY_STATE_TRANSFORM, get_global_transform()); + PhysicsServer3D::get_singleton()->body_add_shape(root_collision_instance, root_collision_shape->get_rid()); + PhysicsServer3D::get_singleton()->body_set_space(root_collision_instance, get_world_3d()->get_space()); + PhysicsServer3D::get_singleton()->body_attach_object_instance_id(root_collision_instance, get_instance_id()); + set_collision_layer(collision_layer); + set_collision_mask(collision_mask); + set_collision_priority(collision_priority); + _make_dirty(); //force update + } else { + PhysicsServer3D::get_singleton()->free(root_collision_instance); + root_collision_instance = RID(); + root_collision_shape.unref(); + } + notify_property_list_changed(); +} + +bool CSGManifoldShape3D::is_using_collision() const { + return use_collision; +} + +void CSGManifoldShape3D::set_collision_layer(uint32_t p_layer) { + collision_layer = p_layer; + if (root_collision_instance.is_valid()) { + PhysicsServer3D::get_singleton()->body_set_collision_layer(root_collision_instance, p_layer); + } +} + +uint32_t CSGManifoldShape3D::get_collision_layer() const { + return collision_layer; +} + +void CSGManifoldShape3D::set_collision_mask(uint32_t p_mask) { + collision_mask = p_mask; + if (root_collision_instance.is_valid()) { + PhysicsServer3D::get_singleton()->body_set_collision_mask(root_collision_instance, p_mask); + } +} + +uint32_t CSGManifoldShape3D::get_collision_mask() const { + return collision_mask; +} + +void CSGManifoldShape3D::set_collision_layer_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive."); + uint32_t layer = get_collision_layer(); + if (p_value) { + layer |= 1 << (p_layer_number - 1); + } else { + layer &= ~(1 << (p_layer_number - 1)); + } + set_collision_layer(layer); +} + +bool CSGManifoldShape3D::get_collision_layer_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Collision layer number must be between 1 and 32 inclusive."); + return get_collision_layer() & (1 << (p_layer_number - 1)); +} + +void CSGManifoldShape3D::set_collision_mask_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive."); + uint32_t mask = get_collision_mask(); + if (p_value) { + mask |= 1 << (p_layer_number - 1); + } else { + mask &= ~(1 << (p_layer_number - 1)); + } + set_collision_mask(mask); +} + +bool CSGManifoldShape3D::get_collision_mask_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Collision layer number must be between 1 and 32 inclusive."); + return get_collision_mask() & (1 << (p_layer_number - 1)); +} + +void CSGManifoldShape3D::set_collision_priority(real_t p_priority) { + collision_priority = p_priority; + if (root_collision_instance.is_valid()) { + PhysicsServer3D::get_singleton()->body_set_collision_priority(root_collision_instance, p_priority); + } +} + +real_t CSGManifoldShape3D::get_collision_priority() const { + return collision_priority; +} + +bool CSGManifoldShape3D::is_root_shape() const { + return !parent_shape; +} + +#ifndef DISABLE_DEPRECATED +void CSGManifoldShape3D::set_snap(float p_snap) { + if (snap == p_snap) { + return; + } + + snap = p_snap; + _make_dirty(); +} + +float CSGManifoldShape3D::get_snap() const { + return snap; +} +#endif // DISABLE_DEPRECATED + +void CSGManifoldShape3D::_make_dirty(bool p_parent_removing) { + if ((p_parent_removing || is_root_shape()) && !dirty) { + callable_mp(this, &CSGManifoldShape3D::_update_shape).call_deferred(); // Must be deferred; otherwise, is_root_shape() will use the previous parent. + } + + if (!is_root_shape()) { + parent_shape->_make_dirty(); + } else if (!dirty) { + callable_mp(this, &CSGManifoldShape3D::_update_shape).call_deferred(); + } + + dirty = true; +} + +enum ManifoldProperty { + MANIFOLD_PROPERTY_POSITION_X = 0, + MANIFOLD_PROPERTY_POSITION_Y, + MANIFOLD_PROPERTY_POSITION_Z, + MANIFOLD_PROPERTY_INVERT, + MANIFOLD_PROPERTY_SMOOTH_GROUP, + MANIFOLD_PROPERTY_UV_X_0, + MANIFOLD_PROPERTY_UV_Y_0, + MANIFOLD_PROPERTY_MAX +}; + +static void _unpack_manifold( + const manifold::Manifold &p_manifold, + const HashMap> &p_mesh_materials, + CSGManifoldBrush *r_mesh_merge) { + manifold::MeshGL64 mesh = p_manifold.GetMeshGL64(); + + constexpr int32_t order[3] = { 0, 2, 1 }; + + for (size_t run_i = 0; run_i < mesh.runIndex.size() - 1; run_i++) { + uint32_t original_id = -1; + if (run_i < mesh.runOriginalID.size()) { + original_id = mesh.runOriginalID[run_i]; + } + + Ref material; + if (p_mesh_materials.has(original_id)) { + material = p_mesh_materials[original_id]; + } + // Find or reserve a material ID in the brush. + int32_t material_id = r_mesh_merge->materials.find(material); + if (material_id == -1) { + material_id = r_mesh_merge->materials.size(); + r_mesh_merge->materials.push_back(material); + } + + size_t begin = mesh.runIndex[run_i]; + size_t end = mesh.runIndex[run_i + 1]; + for (size_t vert_i = begin; vert_i < end; vert_i += 3) { + CSGManifoldBrush::Face face; + face.material = material_id; + int32_t first_property_index = mesh.triVerts[vert_i + order[0]]; + face.smooth = mesh.vertProperties[first_property_index * mesh.numProp + MANIFOLD_PROPERTY_SMOOTH_GROUP] > 0.5f; + face.invert = mesh.vertProperties[first_property_index * mesh.numProp + MANIFOLD_PROPERTY_INVERT] > 0.5f; + + for (int32_t tri_order_i = 0; tri_order_i < 3; tri_order_i++) { + int32_t property_i = mesh.triVerts[vert_i + order[tri_order_i]]; + ERR_FAIL_COND_MSG(property_i * mesh.numProp >= mesh.vertProperties.size(), "Invalid index into vertex properties"); + face.vertices[tri_order_i] = Vector3( + mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_POSITION_X], + mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_POSITION_Y], + mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_POSITION_Z]); + face.uvs[tri_order_i] = Vector2( + mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_UV_X_0], + mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_UV_Y_0]); + } + r_mesh_merge->faces.push_back(face); + } + } + + r_mesh_merge->_regen_face_aabbs(); +} + +// Errors matching `thirdparty/manifold/include/manifold/manifold.h`. +static String manifold_error_to_string(const manifold::Manifold::Error &p_error) { + switch (p_error) { + case manifold::Manifold::Error::NoError: + return "No Error"; + case manifold::Manifold::Error::NonFiniteVertex: + return "Non Finite Vertex"; + case manifold::Manifold::Error::NotManifold: + return "Not Manifold"; + case manifold::Manifold::Error::VertexOutOfBounds: + return "Vertex Out Of Bounds"; + case manifold::Manifold::Error::PropertiesWrongLength: + return "Properties Wrong Length"; + case manifold::Manifold::Error::MissingPositionProperties: + return "Missing Position Properties"; + case manifold::Manifold::Error::MergeVectorsDifferentLengths: + return "Merge Vectors Different Lengths"; + case manifold::Manifold::Error::MergeIndexOutOfBounds: + return "Merge Index Out Of Bounds"; + case manifold::Manifold::Error::TransformWrongLength: + return "Transform Wrong Length"; + case manifold::Manifold::Error::RunIndexWrongLength: + return "Run Index Wrong Length"; + case manifold::Manifold::Error::FaceIDWrongLength: + return "Face ID Wrong Length"; + case manifold::Manifold::Error::InvalidConstruction: + return "Invalid Construction"; + default: + return "Unknown Error"; + } +} + +#ifdef DEV_ENABLED +static String _export_meshgl_as_json(const manifold::MeshGL64 &p_mesh) { + Dictionary mesh_dict; + mesh_dict["numProp"] = p_mesh.numProp; + + Array vert_properties; + for (const double &val : p_mesh.vertProperties) { + vert_properties.append(val); + } + mesh_dict["vertProperties"] = vert_properties; + + Array tri_verts; + for (const uint64_t &val : p_mesh.triVerts) { + tri_verts.append(val); + } + mesh_dict["triVerts"] = tri_verts; + + Array merge_from_vert; + for (const uint64_t &val : p_mesh.mergeFromVert) { + merge_from_vert.append(val); + } + mesh_dict["mergeFromVert"] = merge_from_vert; + + Array merge_to_vert; + for (const uint64_t &val : p_mesh.mergeToVert) { + merge_to_vert.append(val); + } + mesh_dict["mergeToVert"] = merge_to_vert; + + Array run_index; + for (const uint64_t &val : p_mesh.runIndex) { + run_index.append(val); + } + mesh_dict["runIndex"] = run_index; + + Array run_original_id; + for (const uint32_t &val : p_mesh.runOriginalID) { + run_original_id.append(val); + } + mesh_dict["runOriginalID"] = run_original_id; + + Array run_transform; + for (const double &val : p_mesh.runTransform) { + run_transform.append(val); + } + mesh_dict["runTransform"] = run_transform; + + Array face_id; + for (const uint64_t &val : p_mesh.faceID) { + face_id.append(val); + } + mesh_dict["faceID"] = face_id; + + Array halfedge_tangent; + for (const double &val : p_mesh.halfedgeTangent) { + halfedge_tangent.append(val); + } + mesh_dict["halfedgeTangent"] = halfedge_tangent; + + mesh_dict["tolerance"] = p_mesh.tolerance; + + String json_string = JSON::stringify(mesh_dict); + return json_string; +} +#endif // DEV_ENABLED + +static void _pack_manifold( + const CSGManifoldBrush *const p_mesh_merge, + manifold::Manifold &r_manifold, + HashMap> &p_mesh_materials, + CSGManifoldShape3D *p_csg_shape) { + ERR_FAIL_NULL_MSG(p_mesh_merge, "p_mesh_merge is null"); + ERR_FAIL_NULL_MSG(p_csg_shape, "p_shape is null"); + HashMap> faces_by_material; + for (int face_i = 0; face_i < p_mesh_merge->faces.size(); face_i++) { + const CSGManifoldBrush::Face &face = p_mesh_merge->faces[face_i]; + faces_by_material[face.material].push_back(face); + } + + manifold::MeshGL64 mesh; + mesh.numProp = MANIFOLD_PROPERTY_MAX; + mesh.runOriginalID.reserve(faces_by_material.size()); + mesh.runIndex.reserve(faces_by_material.size() + 1); + mesh.vertProperties.reserve(p_mesh_merge->faces.size() * 3 * MANIFOLD_PROPERTY_MAX); + + // Make a run of triangles for each material. + for (const KeyValue> &E : faces_by_material) { + const uint32_t material_id = E.key; + const Vector &faces = E.value; + mesh.runIndex.push_back(mesh.triVerts.size()); + + // Associate the material with an ID. + uint32_t reserved_id = r_manifold.ReserveIDs(1); + mesh.runOriginalID.push_back(reserved_id); + Ref material; + if (material_id < p_mesh_merge->materials.size()) { + material = p_mesh_merge->materials[material_id]; + } + + p_mesh_materials.insert(reserved_id, material); + for (const CSGManifoldBrush::Face &face : faces) { + for (int32_t tri_order_i = 0; tri_order_i < 3; tri_order_i++) { + constexpr int32_t order[3] = { 0, 2, 1 }; + int i = order[tri_order_i]; + + mesh.triVerts.push_back(mesh.vertProperties.size() / MANIFOLD_PROPERTY_MAX); + + size_t begin = mesh.vertProperties.size(); + mesh.vertProperties.resize(mesh.vertProperties.size() + MANIFOLD_PROPERTY_MAX); + // Add the vertex properties. + // Use CSGManifoldBrush constants rather than push_back for clarity. + double *vert = &mesh.vertProperties[begin]; + vert[MANIFOLD_PROPERTY_POSITION_X] = face.vertices[i].x; + vert[MANIFOLD_PROPERTY_POSITION_Y] = face.vertices[i].y; + vert[MANIFOLD_PROPERTY_POSITION_Z] = face.vertices[i].z; + vert[MANIFOLD_PROPERTY_UV_X_0] = face.uvs[i].x; + vert[MANIFOLD_PROPERTY_UV_Y_0] = face.uvs[i].y; + vert[MANIFOLD_PROPERTY_SMOOTH_GROUP] = face.smooth ? 1.0f : 0.0f; + vert[MANIFOLD_PROPERTY_INVERT] = face.invert ? 1.0f : 0.0f; + } + } + } + // runIndex needs an explicit end value. + mesh.runIndex.push_back(mesh.triVerts.size()); + mesh.tolerance = 2 * FLT_EPSILON; + ERR_FAIL_COND_MSG(mesh.vertProperties.size() % mesh.numProp != 0, "Invalid vertex properties size."); + mesh.Merge(); +#ifdef DEV_ENABLED + print_verbose(_export_meshgl_as_json(mesh)); +#endif // DEV_ENABLED + r_manifold = manifold::Manifold(mesh); + manifold::Manifold::Error error = r_manifold.Status(); + if (error == manifold::Manifold::Error::NoError) { + return; + } + if (p_csg_shape->get_owner()) { + NodePath path = p_csg_shape->get_owner()->get_path_to(p_csg_shape, true); + print_error(vformat("CSGManifoldShape3D manifold creation from mesh failed at %s: %s.", path, manifold_error_to_string(error))); + } else { + print_error(vformat("CSGManifoldShape3D manifold creation from mesh failed at .: %s.", manifold_error_to_string(error))); + } +} + +struct ManifoldOperation { + manifold::Manifold manifold; + manifold::OpType operation; + static manifold::OpType convert_csg_op(CSGManifoldShape3D::Operation op) { + switch (op) { + case CSGManifoldShape3D::OPERATION_SUBTRACTION: + return manifold::OpType::Subtract; + case CSGManifoldShape3D::OPERATION_INTERSECTION: + return manifold::OpType::Intersect; + default: + return manifold::OpType::Add; + } + } + ManifoldOperation() : + operation(manifold::OpType::Add) {} + ManifoldOperation(const manifold::Manifold &m, manifold::OpType op) : + manifold(m), operation(op) {} +}; + +CSGManifoldBrush *CSGManifoldShape3D::_get_brush() { + if (!dirty) { + return brush; + } + if (brush) { + memdelete(brush); + } + brush = nullptr; + CSGManifoldBrush *n = _build_brush(); + HashMap> mesh_materials; + manifold::Manifold root_manifold; + _pack_manifold(n, root_manifold, mesh_materials, this); + manifold::OpType current_op = ManifoldOperation::convert_csg_op(get_operation()); + std::vector manifolds; + manifolds.push_back(root_manifold); + for (int i = 0; i < get_child_count(); i++) { + CSGManifoldShape3D *child = Object::cast_to(get_child(i)); + if (!child || !child->is_visible()) { + continue; + } + CSGManifoldBrush *child_brush = child->_get_brush(); + if (!child_brush) { + continue; + } + CSGManifoldBrush transformed_brush; + transformed_brush.copy_from(*child_brush, child->get_transform()); + manifold::Manifold child_manifold; + _pack_manifold(&transformed_brush, child_manifold, mesh_materials, child); + manifold::OpType child_operation = ManifoldOperation::convert_csg_op(child->get_operation()); + if (child_operation != current_op) { + manifold::Manifold result = manifold::Manifold::BatchBoolean(manifolds, current_op); + manifolds.clear(); + manifolds.push_back(result); + current_op = child_operation; + } + manifolds.push_back(child_manifold); + } + if (!manifolds.empty()) { + manifold::Manifold manifold_result = manifold::Manifold::BatchBoolean(manifolds, current_op); + if (n) { + memdelete(n); + } + n = memnew(CSGManifoldBrush); + _unpack_manifold(manifold_result, mesh_materials, n); + } + AABB aabb; + if (n && !n->faces.is_empty()) { + aabb.position = n->faces[0].vertices[0]; + for (const CSGManifoldBrush::Face &face : n->faces) { + for (int i = 0; i < 3; ++i) { + aabb.expand_to(face.vertices[i]); + } + } + } + node_aabb = aabb; + brush = n; + dirty = false; + return brush; +} + +int CSGManifoldShape3D::mikktGetNumFaces(const SMikkTSpaceContext *pContext) { + ShapeUpdateSurface &surface = *((ShapeUpdateSurface *)pContext->m_pUserData); + + return surface.vertices.size() / 3; +} + +int CSGManifoldShape3D::mikktGetNumVerticesOfFace(const SMikkTSpaceContext *pContext, const int iFace) { + // always 3 + return 3; +} + +void CSGManifoldShape3D::mikktGetPosition(const SMikkTSpaceContext *pContext, float fvPosOut[], const int iFace, const int iVert) { + ShapeUpdateSurface &surface = *((ShapeUpdateSurface *)pContext->m_pUserData); + + Vector3 v = surface.verticesw[iFace * 3 + iVert]; + fvPosOut[0] = v.x; + fvPosOut[1] = v.y; + fvPosOut[2] = v.z; +} + +void CSGManifoldShape3D::mikktGetNormal(const SMikkTSpaceContext *pContext, float fvNormOut[], const int iFace, const int iVert) { + ShapeUpdateSurface &surface = *((ShapeUpdateSurface *)pContext->m_pUserData); + + Vector3 n = surface.normalsw[iFace * 3 + iVert]; + fvNormOut[0] = n.x; + fvNormOut[1] = n.y; + fvNormOut[2] = n.z; +} + +void CSGManifoldShape3D::mikktGetTexCoord(const SMikkTSpaceContext *pContext, float fvTexcOut[], const int iFace, const int iVert) { + ShapeUpdateSurface &surface = *((ShapeUpdateSurface *)pContext->m_pUserData); + + Vector2 t = surface.uvsw[iFace * 3 + iVert]; + fvTexcOut[0] = t.x; + fvTexcOut[1] = t.y; +} + +void CSGManifoldShape3D::mikktSetTSpaceDefault(const SMikkTSpaceContext *pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT, + const tbool bIsOrientationPreserving, const int iFace, const int iVert) { + ShapeUpdateSurface &surface = *((ShapeUpdateSurface *)pContext->m_pUserData); + + int i = iFace * 3 + iVert; + Vector3 normal = surface.normalsw[i]; + Vector3 tangent = Vector3(fvTangent[0], fvTangent[1], fvTangent[2]); + Vector3 bitangent = Vector3(-fvBiTangent[0], -fvBiTangent[1], -fvBiTangent[2]); // for some reason these are reversed, something with the coordinate system in Godot + float d = bitangent.dot(normal.cross(tangent)); + + i *= 4; + surface.tansw[i++] = tangent.x; + surface.tansw[i++] = tangent.y; + surface.tansw[i++] = tangent.z; + surface.tansw[i++] = d < 0 ? -1 : 1; +} + +void CSGManifoldShape3D::_update_shape() { + if (!is_root_shape()) { + return; + } + + set_base(RID()); + root_mesh.unref(); //byebye root mesh + + CSGManifoldBrush *n = _get_brush(); + ERR_FAIL_NULL_MSG(n, "Cannot get CSGManifoldBrush."); + + OAHashMap vec_map; + + Vector face_count; + face_count.resize(n->materials.size() + 1); + for (int i = 0; i < face_count.size(); i++) { + face_count.write[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.write[idx]++; + } + + Vector surfaces; + + surfaces.resize(face_count.size()); + + //create arrays + for (int i = 0; i < surfaces.size(); i++) { + surfaces.write[i].vertices.resize(face_count[i] * 3); + surfaces.write[i].normals.resize(face_count[i] * 3); + surfaces.write[i].uvs.resize(face_count[i] * 3); + if (calculate_tangents) { + surfaces.write[i].tans.resize(face_count[i] * 3 * 4); + } + surfaces.write[i].last_added = 0; + + if (i != surfaces.size() - 1) { + surfaces.write[i].material = n->materials[i]; + } + + surfaces.write[i].verticesw = surfaces.write[i].vertices.ptrw(); + surfaces.write[i].normalsw = surfaces.write[i].normals.ptrw(); + surfaces.write[i].uvsw = surfaces.write[i].uvs.ptrw(); + if (calculate_tangents) { + surfaces.write[i].tansw = surfaces.write[i].tans.ptrw(); + } + } + + //fill arrays + { + 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]); + } + + 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; + } + + int k = last + order[j]; + surfaces[idx].verticesw[k] = v; + surfaces[idx].uvsw[k] = n->faces[i].uvs[j]; + surfaces[idx].normalsw[k] = normal; + + if (calculate_tangents) { + // zero out our tangents for now + k *= 4; + surfaces[idx].tansw[k++] = 0.0; + surfaces[idx].tansw[k++] = 0.0; + surfaces[idx].tansw[k++] = 0.0; + surfaces[idx].tansw[k++] = 0.0; + } + } + + surfaces.write[idx].last_added += 3; + } + } + + root_mesh.instantiate(); + //create surfaces + + for (int i = 0; i < surfaces.size(); i++) { + // calculate tangents for this surface + bool have_tangents = calculate_tangents; + if (have_tangents) { + SMikkTSpaceInterface mkif; + mkif.m_getNormal = mikktGetNormal; + mkif.m_getNumFaces = mikktGetNumFaces; + mkif.m_getNumVerticesOfFace = mikktGetNumVerticesOfFace; + mkif.m_getPosition = mikktGetPosition; + mkif.m_getTexCoord = mikktGetTexCoord; + mkif.m_setTSpace = mikktSetTSpaceDefault; + mkif.m_setTSpaceBasic = nullptr; + + SMikkTSpaceContext msc; + msc.m_pInterface = &mkif; + msc.m_pUserData = &surfaces.write[i]; + have_tangents = genTangSpaceDefault(&msc); + } + + if (surfaces[i].last_added == 0) { + continue; + } + + // and convert to surface array + 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; + if (have_tangents) { + array[Mesh::ARRAY_TANGENT] = surfaces[i].tans; + } + + 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); + } + + set_base(root_mesh->get_rid()); + + _update_collision_faces(); +} + +Vector CSGManifoldShape3D::_get_brush_collision_faces() { + Vector collision_faces; + CSGManifoldBrush *n = _get_brush(); + ERR_FAIL_NULL_V_MSG(n, collision_faces, "Cannot get CSGManifoldBrush."); + collision_faces.resize(n->faces.size() * 3); + Vector3 *collision_faces_ptrw = collision_faces.ptrw(); + + 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]); + } + + collision_faces_ptrw[i * 3 + 0] = n->faces[i].vertices[order[0]]; + collision_faces_ptrw[i * 3 + 1] = n->faces[i].vertices[order[1]]; + collision_faces_ptrw[i * 3 + 2] = n->faces[i].vertices[order[2]]; + } + + return collision_faces; +} + +void CSGManifoldShape3D::_update_collision_faces() { + if (use_collision && is_root_shape() && root_collision_shape.is_valid()) { + root_collision_shape->set_faces(_get_brush_collision_faces()); + + if (_is_debug_collision_shape_visible()) { + _update_debug_collision_shape(); + } + } +} + +Ref CSGManifoldShape3D::bake_static_mesh() { + Ref baked_mesh; + if (is_root_shape() && root_mesh.is_valid()) { + baked_mesh = root_mesh; + } + return baked_mesh; +} + +Ref CSGManifoldShape3D::bake_collision_shape() { + Ref baked_collision_shape; + if (is_root_shape() && root_collision_shape.is_valid()) { + baked_collision_shape.instantiate(); + baked_collision_shape->set_faces(root_collision_shape->get_faces()); + } else if (is_root_shape()) { + baked_collision_shape.instantiate(); + baked_collision_shape->set_faces(_get_brush_collision_faces()); + } + return baked_collision_shape; +} + +bool CSGManifoldShape3D::_is_debug_collision_shape_visible() { + return !Engine::get_singleton()->is_editor_hint() && is_inside_tree() && get_tree()->is_debugging_collisions_hint(); +} + +void CSGManifoldShape3D::_update_debug_collision_shape() { + if (!use_collision || !is_root_shape() || !root_collision_shape.is_valid() || !_is_debug_collision_shape_visible()) { + return; + } + + ERR_FAIL_NULL(RenderingServer::get_singleton()); + + if (root_collision_debug_instance.is_null()) { + root_collision_debug_instance = RS::get_singleton()->instance_create(); + } + + Ref debug_mesh = root_collision_shape->get_debug_mesh(); + RS::get_singleton()->instance_set_scenario(root_collision_debug_instance, get_world_3d()->get_scenario()); + RS::get_singleton()->instance_set_base(root_collision_debug_instance, debug_mesh->get_rid()); + RS::get_singleton()->instance_set_transform(root_collision_debug_instance, get_global_transform()); +} + +void CSGManifoldShape3D::_clear_debug_collision_shape() { + if (root_collision_debug_instance.is_valid()) { + RS::get_singleton()->free(root_collision_debug_instance); + root_collision_debug_instance = RID(); + } +} + +void CSGManifoldShape3D::_on_transform_changed() { + if (root_collision_debug_instance.is_valid() && !debug_shape_old_transform.is_equal_approx(get_global_transform())) { + debug_shape_old_transform = get_global_transform(); + RS::get_singleton()->instance_set_transform(root_collision_debug_instance, debug_shape_old_transform); + } +} + +AABB CSGManifoldShape3D::get_aabb() const { + return node_aabb; +} + +Vector CSGManifoldShape3D::get_brush_faces() { + ERR_FAIL_COND_V(!is_inside_tree(), Vector()); + CSGManifoldBrush *b = _get_brush(); + if (!b) { + return Vector(); + } + + Vector faces; + int fc = b->faces.size(); + faces.resize(fc * 3); + { + Vector3 *w = faces.ptrw(); + 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; +} + +void CSGManifoldShape3D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_PARENTED: { + Node *parentn = get_parent(); + if (parentn) { + parent_shape = Object::cast_to(parentn); + if (parent_shape) { + set_base(RID()); + root_mesh.unref(); + } + } + if (!brush || parent_shape) { + // Update this node if uninitialized, or both this node and its new parent if it gets added to another CSG shape + _make_dirty(); + } + last_visible = is_visible(); + } break; + + case NOTIFICATION_UNPARENTED: { + if (!is_root_shape()) { + // Update this node and its previous parent only if it's currently being removed from another CSG shape + _make_dirty(true); // Must be forced since is_root_shape() uses the previous parent + } + parent_shape = nullptr; + } break; + + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_root_shape() && last_visible != is_visible()) { + // Update this node's parent only if its own visibility has changed, not the visibility of parent nodes + parent_shape->_make_dirty(); + } + last_visible = is_visible(); + } break; + + case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { + if (!is_root_shape()) { + // Update this node's parent only if its own transformation has changed, not the transformation of parent nodes + parent_shape->_make_dirty(); + } + } break; + + case NOTIFICATION_ENTER_TREE: { + if (use_collision && is_root_shape()) { + root_collision_shape.instantiate(); + root_collision_instance = PhysicsServer3D::get_singleton()->body_create(); + PhysicsServer3D::get_singleton()->body_set_mode(root_collision_instance, PhysicsServer3D::BODY_MODE_STATIC); + PhysicsServer3D::get_singleton()->body_set_state(root_collision_instance, PhysicsServer3D::BODY_STATE_TRANSFORM, get_global_transform()); + PhysicsServer3D::get_singleton()->body_add_shape(root_collision_instance, root_collision_shape->get_rid()); + PhysicsServer3D::get_singleton()->body_set_space(root_collision_instance, get_world_3d()->get_space()); + PhysicsServer3D::get_singleton()->body_attach_object_instance_id(root_collision_instance, get_instance_id()); + set_collision_layer(collision_layer); + set_collision_mask(collision_mask); + set_collision_priority(collision_priority); + debug_shape_old_transform = get_global_transform(); + _make_dirty(); + } + } break; + + case NOTIFICATION_EXIT_TREE: { + if (use_collision && is_root_shape() && root_collision_instance.is_valid()) { + PhysicsServer3D::get_singleton()->free(root_collision_instance); + root_collision_instance = RID(); + root_collision_shape.unref(); + _clear_debug_collision_shape(); + } + } break; + + case NOTIFICATION_TRANSFORM_CHANGED: { + if (use_collision && is_root_shape() && root_collision_instance.is_valid()) { + PhysicsServer3D::get_singleton()->body_set_state(root_collision_instance, PhysicsServer3D::BODY_STATE_TRANSFORM, get_global_transform()); + } + _on_transform_changed(); + } break; + } +} + +void CSGManifoldShape3D::set_operation(Operation p_operation) { + operation = p_operation; + _make_dirty(); + update_gizmos(); +} + +CSGManifoldShape3D::Operation CSGManifoldShape3D::get_operation() const { + return operation; +} + +void CSGManifoldShape3D::set_calculate_tangents(bool p_calculate_tangents) { + calculate_tangents = p_calculate_tangents; + _make_dirty(); +} + +bool CSGManifoldShape3D::is_calculating_tangents() const { + return calculate_tangents; +} + +void CSGManifoldShape3D::_validate_property(PropertyInfo &p_property) const { + bool is_collision_prefixed = p_property.name.begins_with("collision_"); + if ((is_collision_prefixed || p_property.name.begins_with("use_collision")) && is_inside_tree() && !is_root_shape()) { + //hide collision if not root + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } else if (is_collision_prefixed && !bool(get("use_collision"))) { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } +} + +Array CSGManifoldShape3D::get_meshes() const { + if (root_mesh.is_valid()) { + Array arr; + arr.resize(2); + arr[0] = Transform3D(); + arr[1] = root_mesh; + return arr; + } + + return Array(); +} + +void CSGManifoldShape3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("_update_shape"), &CSGManifoldShape3D::_update_shape); + ClassDB::bind_method(D_METHOD("is_root_shape"), &CSGManifoldShape3D::is_root_shape); + + ClassDB::bind_method(D_METHOD("set_operation", "operation"), &CSGManifoldShape3D::set_operation); + ClassDB::bind_method(D_METHOD("get_operation"), &CSGManifoldShape3D::get_operation); + +#ifndef DISABLE_DEPRECATED + ClassDB::bind_method(D_METHOD("set_snap", "snap"), &CSGManifoldShape3D::set_snap); + ClassDB::bind_method(D_METHOD("get_snap"), &CSGManifoldShape3D::get_snap); +#endif // DISABLE_DEPRECATED + + ClassDB::bind_method(D_METHOD("set_use_collision", "operation"), &CSGManifoldShape3D::set_use_collision); + ClassDB::bind_method(D_METHOD("is_using_collision"), &CSGManifoldShape3D::is_using_collision); + + ClassDB::bind_method(D_METHOD("set_collision_layer", "layer"), &CSGManifoldShape3D::set_collision_layer); + ClassDB::bind_method(D_METHOD("get_collision_layer"), &CSGManifoldShape3D::get_collision_layer); + + ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &CSGManifoldShape3D::set_collision_mask); + ClassDB::bind_method(D_METHOD("get_collision_mask"), &CSGManifoldShape3D::get_collision_mask); + + ClassDB::bind_method(D_METHOD("set_collision_mask_value", "layer_number", "value"), &CSGManifoldShape3D::set_collision_mask_value); + ClassDB::bind_method(D_METHOD("get_collision_mask_value", "layer_number"), &CSGManifoldShape3D::get_collision_mask_value); + + ClassDB::bind_method(D_METHOD("set_collision_layer_value", "layer_number", "value"), &CSGManifoldShape3D::set_collision_layer_value); + ClassDB::bind_method(D_METHOD("get_collision_layer_value", "layer_number"), &CSGManifoldShape3D::get_collision_layer_value); + + ClassDB::bind_method(D_METHOD("set_collision_priority", "priority"), &CSGManifoldShape3D::set_collision_priority); + ClassDB::bind_method(D_METHOD("get_collision_priority"), &CSGManifoldShape3D::get_collision_priority); + + ClassDB::bind_method(D_METHOD("set_calculate_tangents", "enabled"), &CSGManifoldShape3D::set_calculate_tangents); + ClassDB::bind_method(D_METHOD("is_calculating_tangents"), &CSGManifoldShape3D::is_calculating_tangents); + + ClassDB::bind_method(D_METHOD("get_meshes"), &CSGManifoldShape3D::get_meshes); + + ClassDB::bind_method(D_METHOD("bake_static_mesh"), &CSGManifoldShape3D::bake_static_mesh); + ClassDB::bind_method(D_METHOD("bake_collision_shape"), &CSGManifoldShape3D::bake_collision_shape); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "operation", PROPERTY_HINT_ENUM, "Union,Intersection,Subtraction"), "set_operation", "get_operation"); +#ifndef DISABLE_DEPRECATED + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_RANGE, "0.000001,1,0.000001,suffix:m", PROPERTY_USAGE_NONE), "set_snap", "get_snap"); +#endif // DISABLE_DEPRECATED + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "calculate_tangents"), "set_calculate_tangents", "is_calculating_tangents"); + + ADD_GROUP("Collision", "collision_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_collision"), "set_use_collision", "is_using_collision"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_layer", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_layer", "get_collision_layer"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_mask", "get_collision_mask"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_priority"), "set_collision_priority", "get_collision_priority"); + + BIND_ENUM_CONSTANT(OPERATION_UNION); + BIND_ENUM_CONSTANT(OPERATION_INTERSECTION); + BIND_ENUM_CONSTANT(OPERATION_SUBTRACTION); +} + +CSGManifoldShape3D::CSGManifoldShape3D() { + set_notify_local_transform(true); +} + +CSGManifoldShape3D::~CSGManifoldShape3D() { + if (brush) { + memdelete(brush); + brush = nullptr; + } +} + +////////////////////////////////// + +CSGManifoldBrush *CSGManifoldCombiner3D::_build_brush() { + return memnew(CSGManifoldBrush); //does not build anything +} + +CSGManifoldCombiner3D::CSGManifoldCombiner3D() { +} + +///////////////////// + +CSGManifoldBrush *CSGManifoldPrimitive3D::_create_brush_from_arrays(const Vector &p_vertices, const Vector &p_uv, const Vector &p_smooth, const Vector> &p_materials) { + CSGManifoldBrush *new_brush = memnew(CSGManifoldBrush); + + Vector invert; + invert.resize(p_vertices.size() / 3); + { + int ic = invert.size(); + bool *w = invert.ptrw(); + for (int i = 0; i < ic; i++) { + w[i] = flip_faces; + } + } + new_brush->build_from_faces(p_vertices, p_uv, p_smooth, p_materials, invert); + + return new_brush; +} + +void CSGManifoldPrimitive3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_flip_faces", "flip_faces"), &CSGManifoldPrimitive3D::set_flip_faces); + ClassDB::bind_method(D_METHOD("get_flip_faces"), &CSGManifoldPrimitive3D::get_flip_faces); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_faces"), "set_flip_faces", "get_flip_faces"); +} + +void CSGManifoldPrimitive3D::set_flip_faces(bool p_invert) { + if (flip_faces == p_invert) { + return; + } + + flip_faces = p_invert; + + _make_dirty(); +} + +bool CSGManifoldPrimitive3D::get_flip_faces() { + return flip_faces; +} + +CSGManifoldPrimitive3D::CSGManifoldPrimitive3D() { + flip_faces = false; +} + +///////////////////// + +CSGManifoldBrush *CSGManifoldMesh3D::_build_brush() { + if (!mesh.is_valid()) { + return memnew(CSGManifoldBrush); + } + + Vector vertices; + Vector smooth; + Vector> materials; + Vector uvs; + Ref base_material = get_material(); + + 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); + + if (arrays.size() == 0) { + _make_dirty(); + ERR_FAIL_COND_V(arrays.is_empty(), memnew(CSGManifoldBrush)); + } + + Vector avertices = arrays[Mesh::ARRAY_VERTEX]; + if (avertices.size() == 0) { + continue; + } + + const Vector3 *vr = avertices.ptr(); + + Vector anormals = arrays[Mesh::ARRAY_NORMAL]; + const Vector3 *nr = nullptr; + if (anormals.size()) { + nr = anormals.ptr(); + } + + Vector auvs = arrays[Mesh::ARRAY_TEX_UV]; + const Vector2 *uvr = nullptr; + if (auvs.size()) { + uvr = auvs.ptr(); + } + + Ref mat; + if (base_material.is_valid()) { + mat = base_material; + } else { + mat = mesh->surface_get_material(i); + } + + Vector 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); + + Vector3 *vw = vertices.ptrw(); + bool *sw = smooth.ptrw(); + Vector2 *uvw = uvs.ptrw(); + Ref *mw = materials.ptrw(); + + const int *ir = aindices.ptr(); + + 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) { + normal[k] = nr[idx]; + } + if (uvr) { + uv[k] = uvr[idx]; + } + } + + bool flat = normal[0].is_equal_approx(normal[1]) && normal[0].is_equal_approx(normal[2]); + + 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[(as + j) / 3] = !flat; + mw[(as + j) / 3] = mat; + } + } else { + int as = vertices.size(); + int is = avertices.size(); + + vertices.resize(as + is); + smooth.resize((as + is) / 3); + uvs.resize(as + is); + materials.resize((as + is) / 3); + + Vector3 *vw = vertices.ptrw(); + bool *sw = smooth.ptrw(); + Vector2 *uvw = uvs.ptrw(); + Ref *mw = materials.ptrw(); + + 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) { + normal[k] = nr[j + k]; + } + if (uvr) { + uv[k] = uvr[j + k]; + } + } + + bool flat = normal[0].is_equal_approx(normal[1]) && normal[0].is_equal_approx(normal[2]); + + 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[(as + j) / 3] = !flat; + mw[(as + j) / 3] = mat; + } + } + } + + if (vertices.size() == 0) { + return memnew(CSGManifoldBrush); + } + + return _create_brush_from_arrays(vertices, uvs, smooth, materials); +} + +void CSGManifoldMesh3D::_mesh_changed() { + _make_dirty(); + + callable_mp((Node3D *)this, &Node3D::update_gizmos).call_deferred(); +} + +void CSGManifoldMesh3D::set_material(const Ref &p_material) { + if (material == p_material) { + return; + } + material = p_material; + _make_dirty(); +} + +Ref CSGManifoldMesh3D::get_material() const { + return material; +} + +void CSGManifoldMesh3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &CSGManifoldMesh3D::set_mesh); + ClassDB::bind_method(D_METHOD("get_mesh"), &CSGManifoldMesh3D::get_mesh); + + ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGManifoldMesh3D::set_material); + ClassDB::bind_method(D_METHOD("get_material"), &CSGManifoldMesh3D::get_material); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial"), "set_material", "get_material"); +} + +void CSGManifoldMesh3D::set_mesh(const Ref &p_mesh) { + if (mesh == p_mesh) { + return; + } + if (mesh.is_valid()) { + mesh->disconnect_changed(callable_mp(this, &CSGManifoldMesh3D::_mesh_changed)); + } + mesh = p_mesh; + + if (mesh.is_valid()) { + mesh->connect_changed(callable_mp(this, &CSGManifoldMesh3D::_mesh_changed)); + } + + _mesh_changed(); +} + +Ref CSGManifoldMesh3D::get_mesh() { + return mesh; +} + +//////////////////////////////// + +CSGManifoldBrush *CSGManifoldSphere3D::_build_brush() { + // set our bounding box + + CSGManifoldBrush *new_brush = memnew(CSGManifoldBrush); + + int face_count = rings * radial_segments * 2 - radial_segments * 2; + + bool invert_val = get_flip_faces(); + Ref base_material = get_material(); + + Vector faces; + Vector uvs; + Vector smooth; + Vector> materials; + Vector invert; + + faces.resize(face_count * 3); + uvs.resize(face_count * 3); + + smooth.resize(face_count); + materials.resize(face_count); + invert.resize(face_count); + + { + Vector3 *facesw = faces.ptrw(); + Vector2 *uvsw = uvs.ptrw(); + bool *smoothw = smooth.ptrw(); + Ref *materialsw = materials.ptrw(); + bool *invertw = invert.ptrw(); + + // We want to follow an order that's convenient for UVs. + // For latitude step we start at the top and move down like in an image. + const double latitude_step = -Math_PI / rings; + const double longitude_step = Math_TAU / radial_segments; + int face = 0; + for (int i = 0; i < rings; i++) { + double latitude0 = latitude_step * i + Math_TAU / 4; + double cos0 = Math::cos(latitude0); + double sin0 = Math::sin(latitude0); + double v0 = double(i) / rings; + + double latitude1 = latitude_step * (i + 1) + Math_TAU / 4; + double cos1 = Math::cos(latitude1); + double sin1 = Math::sin(latitude1); + double v1 = double(i + 1) / rings; + + for (int j = 0; j < radial_segments; j++) { + double longitude0 = longitude_step * j; + // We give sin to X and cos to Z on purpose. + // This allows UVs to be CCW on +X so it maps to images well. + double x0 = Math::sin(longitude0); + double z0 = Math::cos(longitude0); + double u0 = double(j) / radial_segments; + + double longitude1 = longitude_step * (j + 1); + if (j == radial_segments - 1) { + longitude1 = 0; + } + + double x1 = Math::sin(longitude1); + double z1 = Math::cos(longitude1); + double u1 = double(j + 1) / radial_segments; + + Vector3 v[4] = { + Vector3(x0 * cos0, sin0, z0 * cos0) * radius, + Vector3(x1 * cos0, sin0, z1 * cos0) * radius, + Vector3(x1 * cos1, sin1, z1 * cos1) * radius, + Vector3(x0 * cos1, sin1, z0 * cos1) * radius, + }; + + Vector2 u[4] = { + Vector2(u0, v0), + Vector2(u1, v0), + Vector2(u1, v1), + Vector2(u0, v1), + }; + + // Draw the first face, but skip this at the north pole (i == 0). + if (i > 0) { + 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] = base_material; + + face++; + } + + // Draw the second face, but skip this at the south pole (i == rings - 1). + if (i < rings - 1) { + 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] = base_material; + + face++; + } + } + } + + if (face != face_count) { + ERR_PRINT("Face mismatch bug! fix code"); + } + } + + new_brush->build_from_faces(faces, uvs, smooth, materials, invert); + + return new_brush; +} + +void CSGManifoldSphere3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CSGManifoldSphere3D::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &CSGManifoldSphere3D::get_radius); + + ClassDB::bind_method(D_METHOD("set_radial_segments", "radial_segments"), &CSGManifoldSphere3D::set_radial_segments); + ClassDB::bind_method(D_METHOD("get_radial_segments"), &CSGManifoldSphere3D::get_radial_segments); + ClassDB::bind_method(D_METHOD("set_rings", "rings"), &CSGManifoldSphere3D::set_rings); + ClassDB::bind_method(D_METHOD("get_rings"), &CSGManifoldSphere3D::get_rings); + + ClassDB::bind_method(D_METHOD("set_smooth_faces", "smooth_faces"), &CSGManifoldSphere3D::set_smooth_faces); + ClassDB::bind_method(D_METHOD("get_smooth_faces"), &CSGManifoldSphere3D::get_smooth_faces); + + ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGManifoldSphere3D::set_material); + ClassDB::bind_method(D_METHOD("get_material"), &CSGManifoldSphere3D::get_material); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,suffix:m"), "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, "BaseMaterial3D,ShaderMaterial"), "set_material", "get_material"); +} + +void CSGManifoldSphere3D::set_radius(const float p_radius) { + ERR_FAIL_COND(p_radius <= 0); + radius = p_radius; + _make_dirty(); + update_gizmos(); +} + +float CSGManifoldSphere3D::get_radius() const { + return radius; +} + +void CSGManifoldSphere3D::set_radial_segments(const int p_radial_segments) { + radial_segments = p_radial_segments > 4 ? p_radial_segments : 4; + _make_dirty(); + update_gizmos(); +} + +int CSGManifoldSphere3D::get_radial_segments() const { + return radial_segments; +} + +void CSGManifoldSphere3D::set_rings(const int p_rings) { + rings = p_rings > 1 ? p_rings : 1; + _make_dirty(); + update_gizmos(); +} + +int CSGManifoldSphere3D::get_rings() const { + return rings; +} + +void CSGManifoldSphere3D::set_smooth_faces(const bool p_smooth_faces) { + smooth_faces = p_smooth_faces; + _make_dirty(); +} + +bool CSGManifoldSphere3D::get_smooth_faces() const { + return smooth_faces; +} + +void CSGManifoldSphere3D::set_material(const Ref &p_material) { + material = p_material; + _make_dirty(); +} + +Ref CSGManifoldSphere3D::get_material() const { + return material; +} + +CSGManifoldSphere3D::CSGManifoldSphere3D() { + // defaults + radius = 0.5; + radial_segments = 12; + rings = 6; + smooth_faces = true; +} + +/////////////// + +CSGManifoldBrush *CSGManifoldBox3D::_build_brush() { + // set our bounding box + + CSGManifoldBrush *new_brush = memnew(CSGManifoldBrush); + + int face_count = 12; //it's a cube.. + + bool invert_val = get_flip_faces(); + Ref base_material = get_material(); + + Vector faces; + Vector uvs; + Vector smooth; + Vector> materials; + Vector invert; + + faces.resize(face_count * 3); + uvs.resize(face_count * 3); + + smooth.resize(face_count); + materials.resize(face_count); + invert.resize(face_count); + + { + Vector3 *facesw = faces.ptrw(); + Vector2 *uvsw = uvs.ptrw(); + bool *smoothw = smooth.ptrw(); + Ref *materialsw = materials.ptrw(); + bool *invertw = invert.ptrw(); + + int face = 0; + + Vector3 vertex_mul = size / 2; + + { + 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]; + } else { + face_points[3 - j][(i + k) % 3] = -v[k]; + } + } + } + + 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] = base_material; + + face++; + //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] = false; + invertw[face] = invert_val; + materialsw[face] = base_material; + + face++; + } + } + + if (face != face_count) { + ERR_PRINT("Face mismatch bug! fix code"); + } + } + + new_brush->build_from_faces(faces, uvs, smooth, materials, invert); + + return new_brush; +} + +void CSGManifoldBox3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_size", "size"), &CSGManifoldBox3D::set_size); + ClassDB::bind_method(D_METHOD("get_size"), &CSGManifoldBox3D::get_size); + + ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGManifoldBox3D::set_material); + ClassDB::bind_method(D_METHOD("get_material"), &CSGManifoldBox3D::get_material); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size", PROPERTY_HINT_NONE, "suffix:m"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial"), "set_material", "get_material"); +} + +void CSGManifoldBox3D::set_size(const Vector3 &p_size) { + size = p_size; + _make_dirty(); + update_gizmos(); +} + +Vector3 CSGManifoldBox3D::get_size() const { + return size; +} + +#ifndef DISABLE_DEPRECATED +// Kept for compatibility from 3.x to 4.0. +bool CSGManifoldBox3D::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "width") { + size.x = p_value; + _make_dirty(); + update_gizmos(); + return true; + } else if (p_name == "height") { + size.y = p_value; + _make_dirty(); + update_gizmos(); + return true; + } else if (p_name == "depth") { + size.z = p_value; + _make_dirty(); + update_gizmos(); + return true; + } else { + return false; + } +} +#endif + +void CSGManifoldBox3D::set_material(const Ref &p_material) { + material = p_material; + _make_dirty(); + update_gizmos(); +} + +Ref CSGManifoldBox3D::get_material() const { + return material; +} + +/////////////// + +CSGManifoldBrush *CSGManifoldCylinder3D::_build_brush() { + // set our bounding box + + CSGManifoldBrush *new_brush = memnew(CSGManifoldBrush); + + int face_count = sides * (cone ? 1 : 2) + sides + (cone ? 0 : sides); + + bool invert_val = get_flip_faces(); + Ref base_material = get_material(); + + Vector faces; + Vector uvs; + Vector smooth; + Vector> materials; + Vector invert; + + faces.resize(face_count * 3); + uvs.resize(face_count * 3); + + smooth.resize(face_count); + materials.resize(face_count); + invert.resize(face_count); + + { + Vector3 *facesw = faces.ptrw(); + Vector2 *uvsw = uvs.ptrw(); + bool *smoothw = smooth.ptrw(); + Ref *materialsw = materials.ptrw(); + bool *invertw = invert.ptrw(); + + 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; + if (i == sides - 1) { + inc_n = 0; + } + + float ang = inc * Math_TAU; + float ang_n = inc_n * Math_TAU; + + Vector3 face_base(Math::cos(ang), 0, Math::sin(ang)); + Vector3 face_base_n(Math::cos(ang_n), 0, Math::sin(ang_n)); + + Vector3 face_points[4] = { + face_base + Vector3(0, -1, 0), + face_base_n + Vector3(0, -1, 0), + face_base_n * (cone ? 0.0 : 1.0) + Vector3(0, 1, 0), + face_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] = base_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] = base_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] = base_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] = base_material; + face++; + } + } + } + + if (face != face_count) { + ERR_PRINT("Face mismatch bug! fix code"); + } + } + + new_brush->build_from_faces(faces, uvs, smooth, materials, invert); + + return new_brush; +} + +void CSGManifoldCylinder3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CSGManifoldCylinder3D::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &CSGManifoldCylinder3D::get_radius); + + ClassDB::bind_method(D_METHOD("set_height", "height"), &CSGManifoldCylinder3D::set_height); + ClassDB::bind_method(D_METHOD("get_height"), &CSGManifoldCylinder3D::get_height); + + ClassDB::bind_method(D_METHOD("set_sides", "sides"), &CSGManifoldCylinder3D::set_sides); + ClassDB::bind_method(D_METHOD("get_sides"), &CSGManifoldCylinder3D::get_sides); + + ClassDB::bind_method(D_METHOD("set_cone", "cone"), &CSGManifoldCylinder3D::set_cone); + ClassDB::bind_method(D_METHOD("is_cone"), &CSGManifoldCylinder3D::is_cone); + + ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGManifoldCylinder3D::set_material); + ClassDB::bind_method(D_METHOD("get_material"), &CSGManifoldCylinder3D::get_material); + + ClassDB::bind_method(D_METHOD("set_smooth_faces", "smooth_faces"), &CSGManifoldCylinder3D::set_smooth_faces); + ClassDB::bind_method(D_METHOD("get_smooth_faces"), &CSGManifoldCylinder3D::get_smooth_faces); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001,or_greater,exp,suffix:m"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001,or_greater,exp,suffix:m"), "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, "BaseMaterial3D,ShaderMaterial"), "set_material", "get_material"); +} + +void CSGManifoldCylinder3D::set_radius(const float p_radius) { + radius = p_radius; + _make_dirty(); + update_gizmos(); +} + +float CSGManifoldCylinder3D::get_radius() const { + return radius; +} + +void CSGManifoldCylinder3D::set_height(const float p_height) { + height = p_height; + _make_dirty(); + update_gizmos(); +} + +float CSGManifoldCylinder3D::get_height() const { + return height; +} + +void CSGManifoldCylinder3D::set_sides(const int p_sides) { + ERR_FAIL_COND(p_sides < 3); + sides = p_sides; + _make_dirty(); + update_gizmos(); +} + +int CSGManifoldCylinder3D::get_sides() const { + return sides; +} + +void CSGManifoldCylinder3D::set_cone(const bool p_cone) { + cone = p_cone; + _make_dirty(); + update_gizmos(); +} + +bool CSGManifoldCylinder3D::is_cone() const { + return cone; +} + +void CSGManifoldCylinder3D::set_smooth_faces(const bool p_smooth_faces) { + smooth_faces = p_smooth_faces; + _make_dirty(); +} + +bool CSGManifoldCylinder3D::get_smooth_faces() const { + return smooth_faces; +} + +void CSGManifoldCylinder3D::set_material(const Ref &p_material) { + material = p_material; + _make_dirty(); +} + +Ref CSGManifoldCylinder3D::get_material() const { + return material; +} + +CSGManifoldCylinder3D::CSGManifoldCylinder3D() { + // defaults + radius = 0.5; + height = 2.0; + sides = 8; + cone = false; + smooth_faces = true; +} + +/////////////// + +CSGManifoldBrush *CSGManifoldTorus3D::_build_brush() { + // set our bounding box + + float min_radius = inner_radius; + float max_radius = outer_radius; + + if (min_radius == max_radius) { + return memnew(CSGManifoldBrush); //sorry, can't + } + + if (min_radius > max_radius) { + SWAP(min_radius, max_radius); + } + + float radius = (max_radius - min_radius) * 0.5; + + CSGManifoldBrush *new_brush = memnew(CSGManifoldBrush); + + int face_count = ring_sides * sides * 2; + + bool invert_val = get_flip_faces(); + Ref base_material = get_material(); + + Vector faces; + Vector uvs; + Vector smooth; + Vector> materials; + Vector invert; + + faces.resize(face_count * 3); + uvs.resize(face_count * 3); + + smooth.resize(face_count); + materials.resize(face_count); + invert.resize(face_count); + + { + Vector3 *facesw = faces.ptrw(); + Vector2 *uvsw = uvs.ptrw(); + bool *smoothw = smooth.ptrw(); + Ref *materialsw = materials.ptrw(); + bool *invertw = invert.ptrw(); + + int face = 0; + + { + for (int i = 0; i < sides; i++) { + float inci = float(i) / sides; + float inci_n = float((i + 1)) / sides; + if (i == sides - 1) { + inci_n = 0; + } + + float angi = inci * Math_TAU; + float angi_n = inci_n * Math_TAU; + + 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; + if (j == ring_sides - 1) { + incj_n = 0; + } + + float angj = incj * Math_TAU; + float angj_n = incj_n * Math_TAU; + + 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] = base_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] = base_material; + face++; + } + } + } + + if (face != face_count) { + ERR_PRINT("Face mismatch bug! fix code"); + } + } + + new_brush->build_from_faces(faces, uvs, smooth, materials, invert); + + return new_brush; +} + +void CSGManifoldTorus3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_inner_radius", "radius"), &CSGManifoldTorus3D::set_inner_radius); + ClassDB::bind_method(D_METHOD("get_inner_radius"), &CSGManifoldTorus3D::get_inner_radius); + + ClassDB::bind_method(D_METHOD("set_outer_radius", "radius"), &CSGManifoldTorus3D::set_outer_radius); + ClassDB::bind_method(D_METHOD("get_outer_radius"), &CSGManifoldTorus3D::get_outer_radius); + + ClassDB::bind_method(D_METHOD("set_sides", "sides"), &CSGManifoldTorus3D::set_sides); + ClassDB::bind_method(D_METHOD("get_sides"), &CSGManifoldTorus3D::get_sides); + + ClassDB::bind_method(D_METHOD("set_ring_sides", "sides"), &CSGManifoldTorus3D::set_ring_sides); + ClassDB::bind_method(D_METHOD("get_ring_sides"), &CSGManifoldTorus3D::get_ring_sides); + + ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGManifoldTorus3D::set_material); + ClassDB::bind_method(D_METHOD("get_material"), &CSGManifoldTorus3D::get_material); + + ClassDB::bind_method(D_METHOD("set_smooth_faces", "smooth_faces"), &CSGManifoldTorus3D::set_smooth_faces); + ClassDB::bind_method(D_METHOD("get_smooth_faces"), &CSGManifoldTorus3D::get_smooth_faces); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "inner_radius", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001,or_greater,exp,suffix:m"), "set_inner_radius", "get_inner_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "outer_radius", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001,or_greater,exp,suffix:m"), "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, "BaseMaterial3D,ShaderMaterial"), "set_material", "get_material"); +} + +void CSGManifoldTorus3D::set_inner_radius(const float p_inner_radius) { + inner_radius = p_inner_radius; + _make_dirty(); + update_gizmos(); +} + +float CSGManifoldTorus3D::get_inner_radius() const { + return inner_radius; +} + +void CSGManifoldTorus3D::set_outer_radius(const float p_outer_radius) { + outer_radius = p_outer_radius; + _make_dirty(); + update_gizmos(); +} + +float CSGManifoldTorus3D::get_outer_radius() const { + return outer_radius; +} + +void CSGManifoldTorus3D::set_sides(const int p_sides) { + ERR_FAIL_COND(p_sides < 3); + sides = p_sides; + _make_dirty(); + update_gizmos(); +} + +int CSGManifoldTorus3D::get_sides() const { + return sides; +} + +void CSGManifoldTorus3D::set_ring_sides(const int p_ring_sides) { + ERR_FAIL_COND(p_ring_sides < 3); + ring_sides = p_ring_sides; + _make_dirty(); + update_gizmos(); +} + +int CSGManifoldTorus3D::get_ring_sides() const { + return ring_sides; +} + +void CSGManifoldTorus3D::set_smooth_faces(const bool p_smooth_faces) { + smooth_faces = p_smooth_faces; + _make_dirty(); +} + +bool CSGManifoldTorus3D::get_smooth_faces() const { + return smooth_faces; +} + +void CSGManifoldTorus3D::set_material(const Ref &p_material) { + material = p_material; + _make_dirty(); +} + +Ref CSGManifoldTorus3D::get_material() const { + return material; +} + +CSGManifoldTorus3D::CSGManifoldTorus3D() { + // defaults + inner_radius = 0.5; + outer_radius = 1.0; + sides = 8; + ring_sides = 6; + smooth_faces = true; +} + +/////////////// + +CSGManifoldBrush *CSGManifoldPolygon3D::_build_brush() { + CSGManifoldBrush *new_brush = memnew(CSGManifoldBrush); + + if (polygon.size() < 3) { + return new_brush; + } + + // Triangulate polygon shape. + Vector shape_polygon = polygon; + if (Triangulate::get_area(shape_polygon) > 0) { + shape_polygon.reverse(); + } + int shape_sides = shape_polygon.size(); + Vector shape_faces = Geometry2D::triangulate_polygon(shape_polygon); + ERR_FAIL_COND_V_MSG(shape_faces.size() < 3, new_brush, "Failed to triangulate CSGPolygon. Make sure the polygon doesn't have any intersecting edges."); + + // 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]); + } + + // If MODE_PATH, check if curve has changed. + Ref curve; + if (mode == MODE_PATH) { + Path3D *current_path = Object::cast_to(get_node_or_null(path_node)); + if (path != current_path) { + if (path) { + path->disconnect(SceneStringName(tree_exited), callable_mp(this, &CSGManifoldPolygon3D::_path_exited)); + path->disconnect("curve_changed", callable_mp(this, &CSGManifoldPolygon3D::_path_changed)); + path->set_update_callback(Callable()); + } + path = current_path; + if (path) { + path->connect(SceneStringName(tree_exited), callable_mp(this, &CSGManifoldPolygon3D::_path_exited)); + path->connect("curve_changed", callable_mp(this, &CSGManifoldPolygon3D::_path_changed)); + path->set_update_callback(callable_mp(this, &CSGManifoldPolygon3D::_path_changed)); + } + } + + if (!path) { + return new_brush; + } + + curve = path->get_curve(); + if (curve.is_null() || curve->get_point_count() < 2) { + return new_brush; + } + } + + // 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; + real_t curve_length = 1.0; + switch (mode) { + case MODE_DEPTH: + extrusions = 1; + end_count = 2; + break; + case MODE_SPIN: + extrusions = spin_sides; + if (spin_degrees < 360) { + end_count = 2; + } + break; + case MODE_PATH: { + curve_length = curve->get_baked_length(); + if (path_interval_type == PATH_INTERVAL_DISTANCE) { + extrusions = MAX(1, Math::ceil(curve_length / path_interval)) + 1; + } else { + 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; + + // Initialize variables used to create the mesh. + Ref base_material = get_material(); + + Vector faces; + Vector uvs; + Vector smooth; + Vector> materials; + Vector invert; + + faces.resize(face_count * 3); + uvs.resize(face_count * 3); + smooth.resize(face_count); + materials.resize(face_count); + invert.resize(face_count); + int faces_removed = 0; + + { + Vector3 *facesw = faces.ptrw(); + Vector2 *uvsw = uvs.ptrw(); + bool *smoothw = smooth.ptrw(); + Ref *materialsw = materials.ptrw(); + bool *invertw = invert.ptrw(); + + int face = 0; + Transform3D base_xform; + Transform3D current_xform; + Transform3D previous_xform; + Transform3D previous_previous_xform; + double u_step = 1.0 / extrusions; + if (path_u_distance > 0.0) { + u_step *= curve_length / path_u_distance; + } + double v_step = 1.0 / shape_sides; + double spin_step = Math::deg_to_rad(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_length; + } + + if (mode == MODE_PATH) { + if (!path_local) { + base_xform = path->get_global_transform(); + } + + Vector3 current_point = curve->sample_baked(0); + Vector3 next_point = curve->sample_baked(extrusion_step); + Vector3 current_up = Vector3(0, 1, 0); + Vector3 direction = next_point - current_point; + + if (path_joined) { + Vector3 last_point = curve->sample_baked(curve->get_baked_length()); + direction = next_point - last_point; + } + + 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->sample_baked_up_vector(0, true); + break; + } + + Transform3D facing = Transform3D().looking_at(direction, current_up); + current_xform = base_xform.translated_local(current_point) * facing; + } + + // 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] = false; + materialsw[face] = base_material; + invertw[face] = flip_faces; + face++; + } + } + + real_t angle_simplify_dot = Math::cos(Math::deg_to_rad(path_simplify_angle)); + Vector3 previous_simplify_dir = Vector3(0, 0, 0); + int faces_combined = 0; + + // Add extrusion faces. + for (int x0 = 0; x0 < extrusions; x0++) { + previous_previous_xform = previous_xform; + previous_xform = current_xform; + + switch (mode) { + case MODE_DEPTH: { + current_xform.translate_local(Vector3(0, 0, -depth)); + } break; + case MODE_SPIN: { + current_xform.rotate(Vector3(0, 1, 0), spin_step); + } break; + case MODE_PATH: { + 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 { + next_offset = current_offset; + } + } + + Vector3 previous_point = curve->sample_baked(previous_offset); + Vector3 current_point = curve->sample_baked(current_offset); + Vector3 next_point = curve->sample_baked(next_offset); + Vector3 current_up = Vector3(0, 1, 0); + Vector3 direction = next_point - previous_point; + Vector3 current_dir = (current_point - previous_point).normalized(); + + // If the angles are similar, remove the previous face and replace it with this one. + if (path_simplify_angle > 0.0 && x0 > 0 && previous_simplify_dir.dot(current_dir) > angle_simplify_dot) { + faces_combined += 1; + previous_xform = previous_previous_xform; + face -= extrusion_face_count; + faces_removed += extrusion_face_count; + } else { + faces_combined = 0; + previous_simplify_dir = current_dir; + } + + 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->sample_baked_up_vector(current_offset, true); + break; + } + + Transform3D facing = Transform3D().looking_at(direction, current_up); + current_xform = base_xform.translated_local(current_point) * facing; + } break; + } + + double u0 = (x0 - faces_combined) * u_step; + double u1 = ((x0 + 1) * u_step); + if (mode == MODE_PATH && !path_continuous_u) { + u0 = 0.0; + u1 = 1.0; + } + + 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] = flip_faces; + materialsw[face] = base_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] = flip_faces; + materialsw[face] = base_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; + } + + smoothw[face] = false; + materialsw[face] = base_material; + invertw[face] = flip_faces; + face++; + } + } + + face_count -= faces_removed; + ERR_FAIL_COND_V_MSG(face != face_count, new_brush, "Bug: Failed to create the CSGPolygon mesh correctly."); + } + + if (faces_removed > 0) { + faces.resize(face_count * 3); + uvs.resize(face_count * 3); + smooth.resize(face_count); + materials.resize(face_count); + invert.resize(face_count); + } + + new_brush->build_from_faces(faces, uvs, smooth, materials, invert); + + return new_brush; +} + +void CSGManifoldPolygon3D::_notification(int p_what) { + if (p_what == NOTIFICATION_EXIT_TREE) { + if (path) { + path->disconnect(SceneStringName(tree_exited), callable_mp(this, &CSGManifoldPolygon3D::_path_exited)); + path->disconnect("curve_changed", callable_mp(this, &CSGManifoldPolygon3D::_path_changed)); + path = nullptr; + } + } +} + +void CSGManifoldPolygon3D::_validate_property(PropertyInfo &p_property) const { + if (p_property.name.begins_with("spin") && mode != MODE_SPIN) { + p_property.usage = PROPERTY_USAGE_NONE; + } + if (p_property.name.begins_with("path") && mode != MODE_PATH) { + p_property.usage = PROPERTY_USAGE_NONE; + } + if (p_property.name == "depth" && mode != MODE_DEPTH) { + p_property.usage = PROPERTY_USAGE_NONE; + } +} + +void CSGManifoldPolygon3D::_path_changed() { + _make_dirty(); + update_gizmos(); +} + +void CSGManifoldPolygon3D::_path_exited() { + path = nullptr; +} + +void CSGManifoldPolygon3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_polygon", "polygon"), &CSGManifoldPolygon3D::set_polygon); + ClassDB::bind_method(D_METHOD("get_polygon"), &CSGManifoldPolygon3D::get_polygon); + + ClassDB::bind_method(D_METHOD("set_mode", "mode"), &CSGManifoldPolygon3D::set_mode); + ClassDB::bind_method(D_METHOD("get_mode"), &CSGManifoldPolygon3D::get_mode); + + ClassDB::bind_method(D_METHOD("set_depth", "depth"), &CSGManifoldPolygon3D::set_depth); + ClassDB::bind_method(D_METHOD("get_depth"), &CSGManifoldPolygon3D::get_depth); + + ClassDB::bind_method(D_METHOD("set_spin_degrees", "degrees"), &CSGManifoldPolygon3D::set_spin_degrees); + ClassDB::bind_method(D_METHOD("get_spin_degrees"), &CSGManifoldPolygon3D::get_spin_degrees); + + ClassDB::bind_method(D_METHOD("set_spin_sides", "spin_sides"), &CSGManifoldPolygon3D::set_spin_sides); + ClassDB::bind_method(D_METHOD("get_spin_sides"), &CSGManifoldPolygon3D::get_spin_sides); + + ClassDB::bind_method(D_METHOD("set_path_node", "path"), &CSGManifoldPolygon3D::set_path_node); + ClassDB::bind_method(D_METHOD("get_path_node"), &CSGManifoldPolygon3D::get_path_node); + + ClassDB::bind_method(D_METHOD("set_path_interval_type", "interval_type"), &CSGManifoldPolygon3D::set_path_interval_type); + ClassDB::bind_method(D_METHOD("get_path_interval_type"), &CSGManifoldPolygon3D::get_path_interval_type); + + ClassDB::bind_method(D_METHOD("set_path_interval", "interval"), &CSGManifoldPolygon3D::set_path_interval); + ClassDB::bind_method(D_METHOD("get_path_interval"), &CSGManifoldPolygon3D::get_path_interval); + + ClassDB::bind_method(D_METHOD("set_path_simplify_angle", "degrees"), &CSGManifoldPolygon3D::set_path_simplify_angle); + ClassDB::bind_method(D_METHOD("get_path_simplify_angle"), &CSGManifoldPolygon3D::get_path_simplify_angle); + + ClassDB::bind_method(D_METHOD("set_path_rotation", "path_rotation"), &CSGManifoldPolygon3D::set_path_rotation); + ClassDB::bind_method(D_METHOD("get_path_rotation"), &CSGManifoldPolygon3D::get_path_rotation); + + ClassDB::bind_method(D_METHOD("set_path_local", "enable"), &CSGManifoldPolygon3D::set_path_local); + ClassDB::bind_method(D_METHOD("is_path_local"), &CSGManifoldPolygon3D::is_path_local); + + ClassDB::bind_method(D_METHOD("set_path_continuous_u", "enable"), &CSGManifoldPolygon3D::set_path_continuous_u); + ClassDB::bind_method(D_METHOD("is_path_continuous_u"), &CSGManifoldPolygon3D::is_path_continuous_u); + + ClassDB::bind_method(D_METHOD("set_path_u_distance", "distance"), &CSGManifoldPolygon3D::set_path_u_distance); + ClassDB::bind_method(D_METHOD("get_path_u_distance"), &CSGManifoldPolygon3D::get_path_u_distance); + + ClassDB::bind_method(D_METHOD("set_path_joined", "enable"), &CSGManifoldPolygon3D::set_path_joined); + ClassDB::bind_method(D_METHOD("is_path_joined"), &CSGManifoldPolygon3D::is_path_joined); + + ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGManifoldPolygon3D::set_material); + ClassDB::bind_method(D_METHOD("get_material"), &CSGManifoldPolygon3D::get_material); + + ClassDB::bind_method(D_METHOD("set_smooth_faces", "smooth_faces"), &CSGManifoldPolygon3D::set_smooth_faces); + ClassDB::bind_method(D_METHOD("get_smooth_faces"), &CSGManifoldPolygon3D::get_smooth_faces); + + ClassDB::bind_method(D_METHOD("_is_editable_3d_polygon"), &CSGManifoldPolygon3D::_is_editable_3d_polygon); + ClassDB::bind_method(D_METHOD("_has_editable_3d_polygon_no_depth"), &CSGManifoldPolygon3D::_has_editable_3d_polygon_no_depth); + + 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.01,100.0,0.01,or_greater,exp,suffix:m"), "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::INT, "path_interval_type", PROPERTY_HINT_ENUM, "Distance,Subdivide"), "set_path_interval_type", "get_path_interval_type"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_interval", PROPERTY_HINT_RANGE, "0.01,1.0,0.01,exp,or_greater"), "set_path_interval", "get_path_interval"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_simplify_angle", PROPERTY_HINT_RANGE, "0.0,180.0,0.1"), "set_path_simplify_angle", "get_path_simplify_angle"); + 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"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_u_distance", PROPERTY_HINT_RANGE, "0.0,10.0,0.01,or_greater,suffix:m"), "set_path_u_distance", "get_path_u_distance"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "path_joined"), "set_path_joined", "is_path_joined"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_faces"), "set_smooth_faces", "get_smooth_faces"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,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); + + BIND_ENUM_CONSTANT(PATH_INTERVAL_DISTANCE); + BIND_ENUM_CONSTANT(PATH_INTERVAL_SUBDIVIDE); +} + +void CSGManifoldPolygon3D::set_polygon(const Vector &p_polygon) { + polygon = p_polygon; + _make_dirty(); + update_gizmos(); +} + +Vector CSGManifoldPolygon3D::get_polygon() const { + return polygon; +} + +void CSGManifoldPolygon3D::set_mode(Mode p_mode) { + mode = p_mode; + _make_dirty(); + update_gizmos(); + notify_property_list_changed(); +} + +CSGManifoldPolygon3D::Mode CSGManifoldPolygon3D::get_mode() const { + return mode; +} + +void CSGManifoldPolygon3D::set_depth(const float p_depth) { + ERR_FAIL_COND(p_depth < 0.001); + depth = p_depth; + _make_dirty(); + update_gizmos(); +} + +float CSGManifoldPolygon3D::get_depth() const { + return depth; +} + +void CSGManifoldPolygon3D::set_path_continuous_u(bool p_enable) { + path_continuous_u = p_enable; + _make_dirty(); +} + +bool CSGManifoldPolygon3D::is_path_continuous_u() const { + return path_continuous_u; +} + +void CSGManifoldPolygon3D::set_path_u_distance(real_t p_path_u_distance) { + path_u_distance = p_path_u_distance; + _make_dirty(); + update_gizmos(); +} + +real_t CSGManifoldPolygon3D::get_path_u_distance() const { + return path_u_distance; +} + +void CSGManifoldPolygon3D::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_gizmos(); +} + +float CSGManifoldPolygon3D::get_spin_degrees() const { + return spin_degrees; +} + +void CSGManifoldPolygon3D::set_spin_sides(int p_spin_sides) { + ERR_FAIL_COND(p_spin_sides < 3); + spin_sides = p_spin_sides; + _make_dirty(); + update_gizmos(); +} + +int CSGManifoldPolygon3D::get_spin_sides() const { + return spin_sides; +} + +void CSGManifoldPolygon3D::set_path_node(const NodePath &p_path) { + path_node = p_path; + _make_dirty(); + update_gizmos(); +} + +NodePath CSGManifoldPolygon3D::get_path_node() const { + return path_node; +} + +void CSGManifoldPolygon3D::set_path_interval_type(PathIntervalType p_interval_type) { + path_interval_type = p_interval_type; + _make_dirty(); + update_gizmos(); +} + +CSGManifoldPolygon3D::PathIntervalType CSGManifoldPolygon3D::get_path_interval_type() const { + return path_interval_type; +} + +void CSGManifoldPolygon3D::set_path_interval(float p_interval) { + path_interval = p_interval; + _make_dirty(); + update_gizmos(); +} + +float CSGManifoldPolygon3D::get_path_interval() const { + return path_interval; +} + +void CSGManifoldPolygon3D::set_path_simplify_angle(float p_angle) { + path_simplify_angle = p_angle; + _make_dirty(); + update_gizmos(); +} + +float CSGManifoldPolygon3D::get_path_simplify_angle() const { + return path_simplify_angle; +} + +void CSGManifoldPolygon3D::set_path_rotation(PathRotation p_rotation) { + path_rotation = p_rotation; + _make_dirty(); + update_gizmos(); +} + +CSGManifoldPolygon3D::PathRotation CSGManifoldPolygon3D::get_path_rotation() const { + return path_rotation; +} + +void CSGManifoldPolygon3D::set_path_local(bool p_enable) { + path_local = p_enable; + _make_dirty(); + update_gizmos(); +} + +bool CSGManifoldPolygon3D::is_path_local() const { + return path_local; +} + +void CSGManifoldPolygon3D::set_path_joined(bool p_enable) { + path_joined = p_enable; + _make_dirty(); + update_gizmos(); +} + +bool CSGManifoldPolygon3D::is_path_joined() const { + return path_joined; +} + +void CSGManifoldPolygon3D::set_smooth_faces(const bool p_smooth_faces) { + smooth_faces = p_smooth_faces; + _make_dirty(); +} + +bool CSGManifoldPolygon3D::get_smooth_faces() const { + return smooth_faces; +} + +void CSGManifoldPolygon3D::set_material(const Ref &p_material) { + material = p_material; + _make_dirty(); +} + +Ref CSGManifoldPolygon3D::get_material() const { + return material; +} + +bool CSGManifoldPolygon3D::_is_editable_3d_polygon() const { + return true; +} + +bool CSGManifoldPolygon3D::_has_editable_3d_polygon_no_depth() const { + return true; +} + +CSGManifoldPolygon3D::CSGManifoldPolygon3D() { + // 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_type = PATH_INTERVAL_DISTANCE; + path_interval = 1.0; + path_simplify_angle = 0.0; + path_rotation = PATH_ROTATION_PATH_FOLLOW; + path_local = false; + path_continuous_u = true; + path_u_distance = 1.0; + path_joined = false; + path = nullptr; +} diff --git a/modules/csg_manifold/csg_manifold_shape.h b/modules/csg_manifold/csg_manifold_shape.h new file mode 100644 index 000000000000..d58f102a2a91 --- /dev/null +++ b/modules/csg_manifold/csg_manifold_shape.h @@ -0,0 +1,466 @@ +/**************************************************************************/ +/* csg_manifold_shape.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#ifndef CSG_MANIFOLD_SHAPE_H +#define CSG_MANIFOLD_SHAPE_H + +#include "csg_manifold.h" + +#include "scene/3d/path_3d.h" +#include "scene/3d/visual_instance_3d.h" +#include "scene/resources/3d/concave_polygon_shape_3d.h" + +#include "thirdparty/misc/mikktspace.h" + +class CSGManifoldShape3D : public GeometryInstance3D { + GDCLASS(CSGManifoldShape3D, GeometryInstance3D); + +public: + enum Operation { + OPERATION_UNION, + OPERATION_INTERSECTION, + OPERATION_SUBTRACTION, + + }; + +private: + Operation operation = OPERATION_UNION; + CSGManifoldShape3D *parent_shape = nullptr; + + CSGManifoldBrush *brush = nullptr; + + AABB node_aabb; + + bool dirty = false; + bool last_visible = false; + float snap = 0.001; + + bool use_collision = false; + uint32_t collision_layer = 1; + uint32_t collision_mask = 1; + real_t collision_priority = 1.0; + Ref root_collision_shape; + RID root_collision_instance; + RID root_collision_debug_instance; + Transform3D debug_shape_old_transform; + + bool calculate_tangents = true; + + Ref root_mesh; + + struct Vector3Hasher { + _ALWAYS_INLINE_ uint32_t hash(const Vector3 &p_vec3) const { + uint32_t h = hash_murmur3_one_float(p_vec3.x); + h = hash_murmur3_one_float(p_vec3.y, h); + h = hash_murmur3_one_float(p_vec3.z, h); + return h; + } + }; + + struct ShapeUpdateSurface { + Vector vertices; + Vector normals; + Vector uvs; + Vector tans; + Ref material; + int last_added = 0; + + Vector3 *verticesw = nullptr; + Vector3 *normalsw = nullptr; + Vector2 *uvsw = nullptr; + real_t *tansw = nullptr; + }; + + //mikktspace callbacks + static int mikktGetNumFaces(const SMikkTSpaceContext *pContext); + static int mikktGetNumVerticesOfFace(const SMikkTSpaceContext *pContext, const int iFace); + static void mikktGetPosition(const SMikkTSpaceContext *pContext, float fvPosOut[], const int iFace, const int iVert); + static void mikktGetNormal(const SMikkTSpaceContext *pContext, float fvNormOut[], const int iFace, const int iVert); + static void mikktGetTexCoord(const SMikkTSpaceContext *pContext, float fvTexcOut[], const int iFace, const int iVert); + static void mikktSetTSpaceDefault(const SMikkTSpaceContext *pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT, + const tbool bIsOrientationPreserving, const int iFace, const int iVert); + + void _update_shape(); + void _update_collision_faces(); + bool _is_debug_collision_shape_visible(); + void _update_debug_collision_shape(); + void _clear_debug_collision_shape(); + void _on_transform_changed(); + Vector _get_brush_collision_faces(); + +protected: + void _notification(int p_what); + virtual CSGManifoldBrush *_build_brush() = 0; + void _make_dirty(bool p_parent_removing = false); + + static void _bind_methods(); + + friend class CSGManifoldCombiner3D; + CSGManifoldBrush *_get_brush(); + + void _validate_property(PropertyInfo &p_property) const; + +public: + Array get_meshes() const; + + void set_operation(Operation p_operation); + Operation get_operation() const; + + virtual Vector get_brush_faces(); + + virtual AABB get_aabb() const override; + + void set_use_collision(bool p_enable); + bool is_using_collision() const; + + void set_collision_layer(uint32_t p_layer); + uint32_t get_collision_layer() const; + + void set_collision_mask(uint32_t p_mask); + uint32_t get_collision_mask() const; + + void set_collision_layer_value(int p_layer_number, bool p_value); + bool get_collision_layer_value(int p_layer_number) const; + + void set_collision_mask_value(int p_layer_number, bool p_value); + bool get_collision_mask_value(int p_layer_number) const; + + void set_collision_priority(real_t p_priority); + real_t get_collision_priority() const; + +#ifndef DISABLE_DEPRECATED + void set_snap(float p_snap); + float get_snap() const; +#endif // DISABLE_DEPRECATED + + void set_calculate_tangents(bool p_calculate_tangents); + bool is_calculating_tangents() const; + + bool is_root_shape() const; + + Ref bake_static_mesh(); + Ref bake_collision_shape(); + + CSGManifoldShape3D(); + ~CSGManifoldShape3D(); +}; + +VARIANT_ENUM_CAST(CSGManifoldShape3D::Operation) + +class CSGManifoldCombiner3D : public CSGManifoldShape3D { + GDCLASS(CSGManifoldCombiner3D, CSGManifoldShape3D); + +private: + virtual CSGManifoldBrush *_build_brush() override; + +public: + CSGManifoldCombiner3D(); +}; + +class CSGManifoldPrimitive3D : public CSGManifoldShape3D { + GDCLASS(CSGManifoldPrimitive3D, CSGManifoldShape3D); + +protected: + bool flip_faces; + CSGManifoldBrush *_create_brush_from_arrays(const Vector &p_vertices, const Vector &p_uv, const Vector &p_smooth, const Vector> &p_materials); + static void _bind_methods(); + +public: + void set_flip_faces(bool p_invert); + bool get_flip_faces(); + + CSGManifoldPrimitive3D(); +}; + +class CSGManifoldMesh3D : public CSGManifoldPrimitive3D { + GDCLASS(CSGManifoldMesh3D, CSGManifoldPrimitive3D); + + virtual CSGManifoldBrush *_build_brush() override; + + Ref mesh; + Ref material; + + void _mesh_changed(); + +protected: + static void _bind_methods(); + +public: + void set_mesh(const Ref &p_mesh); + Ref get_mesh(); + + void set_material(const Ref &p_material); + Ref get_material() const; +}; + +class CSGManifoldSphere3D : public CSGManifoldPrimitive3D { + GDCLASS(CSGManifoldSphere3D, CSGManifoldPrimitive3D); + virtual CSGManifoldBrush *_build_brush() override; + + Ref material; + bool smooth_faces; + float radius; + int radial_segments; + int rings; + +protected: + static void _bind_methods(); + +public: + void set_radius(const float p_radius); + float get_radius() const; + + void set_radial_segments(const int p_radial_segments); + int get_radial_segments() const; + + void set_rings(const int p_rings); + int get_rings() const; + + void set_material(const Ref &p_material); + Ref get_material() const; + + void set_smooth_faces(bool p_smooth_faces); + bool get_smooth_faces() const; + + CSGManifoldSphere3D(); +}; + +class CSGManifoldBox3D : public CSGManifoldPrimitive3D { + GDCLASS(CSGManifoldBox3D, CSGManifoldPrimitive3D); + virtual CSGManifoldBrush *_build_brush() override; + + Ref material; + Vector3 size = Vector3(1, 1, 1); + +protected: + static void _bind_methods(); +#ifndef DISABLE_DEPRECATED + // Kept for compatibility from 3.x to 4.0. + bool _set(const StringName &p_name, const Variant &p_value); +#endif + +public: + void set_size(const Vector3 &p_size); + Vector3 get_size() const; + + void set_material(const Ref &p_material); + Ref get_material() const; + + CSGManifoldBox3D() {} +}; + +class CSGManifoldCylinder3D : public CSGManifoldPrimitive3D { + GDCLASS(CSGManifoldCylinder3D, CSGManifoldPrimitive3D); + virtual CSGManifoldBrush *_build_brush() override; + + Ref material; + float radius; + float height; + int sides; + bool cone; + bool smooth_faces; + +protected: + static void _bind_methods(); + +public: + void set_radius(const float p_radius); + float get_radius() const; + + void set_height(const float p_height); + float get_height() const; + + void set_sides(const int p_sides); + int get_sides() const; + + void set_cone(const bool p_cone); + bool is_cone() const; + + void set_smooth_faces(bool p_smooth_faces); + bool get_smooth_faces() const; + + void set_material(const Ref &p_material); + Ref get_material() const; + + CSGManifoldCylinder3D(); +}; + +class CSGManifoldTorus3D : public CSGManifoldPrimitive3D { + GDCLASS(CSGManifoldTorus3D, CSGManifoldPrimitive3D); + virtual CSGManifoldBrush *_build_brush() override; + + Ref material; + float inner_radius; + float outer_radius; + int sides; + int ring_sides; + bool smooth_faces; + +protected: + static void _bind_methods(); + +public: + void set_inner_radius(const float p_inner_radius); + float get_inner_radius() const; + + void set_outer_radius(const float p_outer_radius); + float get_outer_radius() const; + + void set_sides(const int p_sides); + int get_sides() const; + + void set_ring_sides(const int p_ring_sides); + int get_ring_sides() const; + + void set_smooth_faces(bool p_smooth_faces); + bool get_smooth_faces() const; + + void set_material(const Ref &p_material); + Ref get_material() const; + + CSGManifoldTorus3D(); +}; + +class CSGManifoldPolygon3D : public CSGManifoldPrimitive3D { + GDCLASS(CSGManifoldPolygon3D, CSGManifoldPrimitive3D); + +public: + enum Mode { + MODE_DEPTH, + MODE_SPIN, + MODE_PATH + }; + + enum PathIntervalType { + PATH_INTERVAL_DISTANCE, + PATH_INTERVAL_SUBDIVIDE + }; + + enum PathRotation { + PATH_ROTATION_POLYGON, + PATH_ROTATION_PATH, + PATH_ROTATION_PATH_FOLLOW, + }; + +private: + virtual CSGManifoldBrush *_build_brush() override; + + Vector polygon; + Ref material; + + Mode mode; + + float depth; + + float spin_degrees; + int spin_sides; + + NodePath path_node; + PathIntervalType path_interval_type; + float path_interval; + float path_simplify_angle; + PathRotation path_rotation; + bool path_local; + + Path3D *path = nullptr; + + bool smooth_faces; + bool path_continuous_u; + real_t path_u_distance; + bool path_joined; + + 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(); + void _validate_property(PropertyInfo &p_property) const; + void _notification(int p_what); + +public: + void set_polygon(const Vector &p_polygon); + Vector get_polygon() const; + + void set_mode(Mode p_mode); + Mode get_mode() const; + + void set_depth(float p_depth); + float get_depth() const; + + void set_spin_degrees(float p_spin_degrees); + float get_spin_degrees() const; + + void set_spin_sides(int p_spin_sides); + int get_spin_sides() const; + + void set_path_node(const NodePath &p_path); + NodePath get_path_node() const; + + void set_path_interval_type(PathIntervalType p_interval_type); + PathIntervalType get_path_interval_type() const; + + void set_path_interval(float p_interval); + float get_path_interval() const; + + void set_path_simplify_angle(float p_angle); + float get_path_simplify_angle() const; + + void set_path_rotation(PathRotation p_rotation); + PathRotation get_path_rotation() const; + + void set_path_local(bool p_enable); + bool is_path_local() const; + + void set_path_continuous_u(bool p_enable); + bool is_path_continuous_u() const; + + void set_path_u_distance(real_t p_path_u_distance); + real_t get_path_u_distance() const; + + void set_path_joined(bool p_enable); + bool is_path_joined() const; + + void set_smooth_faces(bool p_smooth_faces); + bool get_smooth_faces() const; + + void set_material(const Ref &p_material); + Ref get_material() const; + + CSGManifoldPolygon3D(); +}; + +VARIANT_ENUM_CAST(CSGManifoldPolygon3D::Mode) +VARIANT_ENUM_CAST(CSGManifoldPolygon3D::PathRotation) +VARIANT_ENUM_CAST(CSGManifoldPolygon3D::PathIntervalType) + +#endif // CSG_MANIFOLD_SHAPE_H diff --git a/modules/csg_manifold/doc_classes/CSGManifoldBox3D.xml b/modules/csg_manifold/doc_classes/CSGManifoldBox3D.xml new file mode 100644 index 000000000000..156e2da562b4 --- /dev/null +++ b/modules/csg_manifold/doc_classes/CSGManifoldBox3D.xml @@ -0,0 +1,21 @@ + + + + A CSG Box shape. + + + This node allows you to create a box for use with the CSG system. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. + + + $DOCS_URL/tutorials/3d/csg_tools.html + + + + The material used to render the box. + + + The box's width, height and depth. + + + diff --git a/modules/csg_manifold/doc_classes/CSGManifoldCombiner3D.xml b/modules/csg_manifold/doc_classes/CSGManifoldCombiner3D.xml new file mode 100644 index 000000000000..e162b9f57eaf --- /dev/null +++ b/modules/csg_manifold/doc_classes/CSGManifoldCombiner3D.xml @@ -0,0 +1,13 @@ + + + + A CSG node that allows you to combine other CSG modifiers. + + + For complex arrangements of shapes, it is sometimes needed to add structure to your CSG nodes. The CSGManifoldCombiner3D node allows you to create this structure. The node encapsulates the result of the CSG operations of its children. In this way, it is possible to do operations on one set of shapes that are children of one CSGManifoldCombiner3D node, and a set of separate operations on a second set of shapes that are children of a second CSGManifoldCombiner3D node, and then do an operation that takes the two end results as its input to create the final shape. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. + + + $DOCS_URL/tutorials/3d/csg_tools.html + + diff --git a/modules/csg_manifold/doc_classes/CSGManifoldCylinder3D.xml b/modules/csg_manifold/doc_classes/CSGManifoldCylinder3D.xml new file mode 100644 index 000000000000..fe3c00db55c6 --- /dev/null +++ b/modules/csg_manifold/doc_classes/CSGManifoldCylinder3D.xml @@ -0,0 +1,33 @@ + + + + A CSG Cylinder shape. + + + This node allows you to create a cylinder (or cone) for use with the CSG system. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. + + + $DOCS_URL/tutorials/3d/csg_tools.html + + + + If [code]true[/code] a cone is created, the [member radius] will only apply to one side. + + + The height of the cylinder. + + + The material used to render the cylinder. + + + The radius of the cylinder. + + + The number of sides of the cylinder, the higher this number the more detail there will be in the cylinder. + + + If [code]true[/code] the normals of the cylinder are set to give a smooth effect making the cylinder seem rounded. If [code]false[/code] the cylinder will have a flat shaded look. + + + diff --git a/modules/csg_manifold/doc_classes/CSGManifoldMesh3D.xml b/modules/csg_manifold/doc_classes/CSGManifoldMesh3D.xml new file mode 100644 index 000000000000..a90942872f25 --- /dev/null +++ b/modules/csg_manifold/doc_classes/CSGManifoldMesh3D.xml @@ -0,0 +1,23 @@ + + + + A CSG Mesh shape that uses a mesh resource. + + + This CSG node allows you to use any mesh resource as a CSG shape, provided it is closed, does not self-intersect, does not contain internal faces and has no edges that connect to more than two faces. See also [CSGManifoldPolygon3D] for drawing 2D extruded polygons to be used as CSG nodes. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. + + + $DOCS_URL/tutorials/3d/csg_tools.html + + + + The [Material] used in drawing the CSG shape. + + + The [Mesh] resource to use as a CSG shape. + [b]Note:[/b] When using an [ArrayMesh], all vertex attributes except [constant Mesh.ARRAY_VERTEX], [constant Mesh.ARRAY_NORMAL] and [constant Mesh.ARRAY_TEX_UV] are left unused. Only [constant Mesh.ARRAY_VERTEX] and [constant Mesh.ARRAY_TEX_UV] will be passed to the GPU. + [constant Mesh.ARRAY_NORMAL] is only used to determine which faces require the use of flat shading. By default, CSGMesh will ignore the mesh's vertex normals, recalculate them for each vertex and use a smooth shader. If a flat shader is required for a face, ensure that all vertex normals of the face are approximately equal. + + + diff --git a/modules/csg_manifold/doc_classes/CSGManifoldPolygon3D.xml b/modules/csg_manifold/doc_classes/CSGManifoldPolygon3D.xml new file mode 100644 index 000000000000..58cbc4bc4033 --- /dev/null +++ b/modules/csg_manifold/doc_classes/CSGManifoldPolygon3D.xml @@ -0,0 +1,92 @@ + + + + Extrudes a 2D polygon shape to create a 3D mesh. + + + An array of 2D points is extruded to quickly and easily create a variety of 3D meshes. See also [CSGManifoldMesh3D] for using 3D meshes as CSG nodes. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. + + + $DOCS_URL/tutorials/3d/csg_tools.html + + + + When [member mode] is [constant MODE_DEPTH], the depth of the extrusion. + + + 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. + + + The [member mode] used to extrude the [member polygon]. + + + 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. + + + When [member mode] is [constant MODE_PATH], the path interval or ratio of path points to extrusions. + + + When [member mode] is [constant MODE_PATH], this will determine if the interval should be by distance ([constant PATH_INTERVAL_DISTANCE]) or subdivision fractions ([constant PATH_INTERVAL_SUBDIVIDE]). + + + 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. + + + When [member mode] is [constant MODE_PATH], if [code]true[/code] the [Transform3D] of the [CSGManifoldPolygon3D] is used as the starting point for the extrusions, not the [Transform3D] of the [member path_node]. + + + When [member mode] is [constant MODE_PATH], the location of the [Path3D] object used to extrude the [member polygon]. + + + When [member mode] is [constant MODE_PATH], the path rotation method used to rotate the [member polygon] as it is extruded. + + + When [member mode] is [constant MODE_PATH], extrusions that are less than this angle, will be merged together to reduce polygon count. + + + When [member mode] is [constant MODE_PATH], this is the distance along the path, in meters, the texture coordinates will tile. When set to 0, texture coordinates will match geometry exactly with no tiling. + + + The point array that defines the 2D polygon that is extruded. This can be a convex or concave polygon with 3 or more points. The polygon must [i]not[/i] have any intersecting edges. Otherwise, triangulation will fail and no mesh will be generated. + [b]Note:[/b] If only 1 or 2 points are defined in [member polygon], no mesh will be generated. + + + If [code]true[/code], applies smooth shading to the extrusions. + + + When [member mode] is [constant MODE_SPIN], the total number of degrees the [member polygon] is rotated when extruding. + + + When [member mode] is [constant MODE_SPIN], the number of extrusions made. + + + + + The [member polygon] shape is extruded along the negative Z axis. + + + The [member polygon] shape is extruded by rotating it around the Y axis. + + + The [member polygon] shape is extruded along the [Path3D] specified in [member path_node]. + + + The [member polygon] shape is not rotated. + [b]Note:[/b] Requires the path Z coordinates to continually decrease to ensure viable shapes. + + + 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. + + + The [member polygon] shape follows the path and its rotations around the path axis. + + + When [member mode] is set to [constant MODE_PATH], [member path_interval] will determine the distance, in meters, each interval of the path will extrude. + + + When [member mode] is set to [constant MODE_PATH], [member path_interval] will subdivide the polygons along the path. + + + diff --git a/modules/csg_manifold/doc_classes/CSGManifoldPrimitive3D.xml b/modules/csg_manifold/doc_classes/CSGManifoldPrimitive3D.xml new file mode 100644 index 000000000000..795217678b76 --- /dev/null +++ b/modules/csg_manifold/doc_classes/CSGManifoldPrimitive3D.xml @@ -0,0 +1,18 @@ + + + + Base class for CSG primitives. + + + Parent class for various CSG primitives. It contains code and functionality that is common between them. It cannot be used directly. Instead use one of the various classes that inherit from it. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. + + + $DOCS_URL/tutorials/3d/csg_tools.html + + + + If set, the order of the vertices in each triangle are reversed resulting in the backside of the mesh being drawn. + + + diff --git a/modules/csg_manifold/doc_classes/CSGManifoldShape3D.xml b/modules/csg_manifold/doc_classes/CSGManifoldShape3D.xml new file mode 100644 index 000000000000..4346542737bb --- /dev/null +++ b/modules/csg_manifold/doc_classes/CSGManifoldShape3D.xml @@ -0,0 +1,110 @@ + + + + The CSG base class. + + + This is the CSG base class that provides CSG operation support to the various CSG nodes in Godot. + [b]Performance:[/b] CSG nodes are only intended for prototyping as they have a significant CPU performance cost. + Consider baking final CSG operation results into static geometry that replaces the CSG nodes. + Individual CSG root node results can be baked to nodes with static resources with the editor menu that appears when a CSG root node is selected. + Individual CSG root nodes can also be baked to static resources with scripts by calling [method bake_static_mesh] for the visual mesh or [method bake_collision_shape] for the physics collision. + Entire scenes of CSG nodes can be baked to static geometry and exported with the editor gltf scene exporter. + + + $DOCS_URL/tutorials/3d/csg_tools.html + + + + + + Returns a baked physics [ConcavePolygonShape3D] of this node's CSG operation result. Returns an empty shape if the node is not a CSG root node or has no valid geometry. + [b]Performance:[/b] If the CSG operation results in a very detailed geometry with many faces physics performance will be very slow. Concave shapes should in general only be used for static level geometry and not with dynamic objects that are moving. + + + + + + Returns a baked static [ArrayMesh] of this node's CSG operation result. Materials from involved CSG nodes are added as extra mesh surfaces. Returns an empty mesh if the node is not a CSG root node or has no valid geometry. + + + + + + + Returns whether or not the specified layer of the [member collision_layer] is enabled, given a [param layer_number] between 1 and 32. + + + + + + + Returns whether or not the specified layer of the [member collision_mask] is enabled, given a [param layer_number] between 1 and 32. + + + + + + Returns an [Array] with two elements, the first is the [Transform3D] of this node and the second is the root [Mesh] of this node. Only works when this node is the root shape. + + + + + + Returns [code]true[/code] if this is a root shape and is thus the object that is rendered. + + + + + + + + Based on [param value], enables or disables the specified layer in the [member collision_layer], given a [param layer_number] between 1 and 32. + + + + + + + + Based on [param value], enables or disables the specified layer in the [member collision_mask], given a [param layer_number] between 1 and 32. + + + + + + Calculate tangents for the CSG shape which allows the use of normal maps. This is only applied on the root shape, this setting is ignored on any child. + + + The physics layers this area is in. + Collidable objects can exist in any of 32 different layers. These layers work like a tagging system, and are not visual. A collidable can use these layers to select with which objects it can collide, using the collision_mask property. + A contact is detected if object A is in any of the layers that object B scans, or object B is in any layer scanned by object A. See [url=$DOCS_URL/tutorials/physics/physics_introduction.html#collision-layers-and-masks]Collision layers and masks[/url] in the documentation for more information. + + + The physics layers this CSG shape scans for collisions. Only effective if [member use_collision] is [code]true[/code]. See [url=$DOCS_URL/tutorials/physics/physics_introduction.html#collision-layers-and-masks]Collision layers and masks[/url] in the documentation for more information. + + + The priority used to solve colliding when occurring penetration. Only effective if [member use_collision] is [code]true[/code]. The higher the priority is, the lower the penetration into the object will be. This can for example be used to prevent the player from breaking through the boundaries of a level. + + + The operation that is performed on this shape. This is ignored for the first CSG child node as the operation is between this node and the previous child of this nodes parent. + + + This property does nothing. + + + Adds a collision shape to the physics engine for our CSG shape. This will always act like a static body. Note that the collision shape is still active even if the CSG shape itself is hidden. See also [member collision_mask] and [member collision_priority]. + + + + + Geometry of both primitives is merged, intersecting geometry is removed. + + + Only intersecting geometry remains, the rest is removed. + + + The second shape is subtracted from the first, leaving a dent with its shape. + + + diff --git a/modules/csg_manifold/doc_classes/CSGManifoldSphere3D.xml b/modules/csg_manifold/doc_classes/CSGManifoldSphere3D.xml new file mode 100644 index 000000000000..596e1e87291e --- /dev/null +++ b/modules/csg_manifold/doc_classes/CSGManifoldSphere3D.xml @@ -0,0 +1,30 @@ + + + + A CSG Sphere shape. + + + This node allows you to create a sphere for use with the CSG system. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. + + + $DOCS_URL/tutorials/3d/csg_tools.html + + + + The material used to render the sphere. + + + Number of vertical slices for the sphere. + + + Radius of the sphere. + + + Number of horizontal slices for the sphere. + + + If [code]true[/code] the normals of the sphere are set to give a smooth effect making the sphere seem rounded. If [code]false[/code] the sphere will have a flat shaded look. + + + diff --git a/modules/csg_manifold/doc_classes/CSGManifoldTorus3D.xml b/modules/csg_manifold/doc_classes/CSGManifoldTorus3D.xml new file mode 100644 index 000000000000..7ee3b8e66e81 --- /dev/null +++ b/modules/csg_manifold/doc_classes/CSGManifoldTorus3D.xml @@ -0,0 +1,33 @@ + + + + A CSG Torus shape. + + + This node allows you to create a torus for use with the CSG system. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. + + + $DOCS_URL/tutorials/3d/csg_tools.html + + + + The inner radius of the torus. + + + The material used to render the torus. + + + The outer radius of the torus. + + + The number of edges each ring of the torus is constructed of. + + + The number of slices the torus is constructed of. + + + If [code]true[/code] the normals of the torus are set to give a smooth effect making the torus seem rounded. If [code]false[/code] the torus will have a flat shaded look. + + + diff --git a/modules/csg_manifold/editor/csg_manifold_gizmos.cpp b/modules/csg_manifold/editor/csg_manifold_gizmos.cpp new file mode 100644 index 000000000000..8aac048b141a --- /dev/null +++ b/modules/csg_manifold/editor/csg_manifold_gizmos.cpp @@ -0,0 +1,513 @@ +/**************************************************************************/ +/* csg_manifold_gizmos.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "csg_manifold_gizmos.h" + +#ifdef TOOLS_ENABLED + +#include "editor/editor_node.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" +#include "editor/plugins/gizmos/gizmo_3d_helper.h" +#include "editor/plugins/node_3d_editor_plugin.h" +#include "scene/3d/camera_3d.h" +#include "scene/3d/mesh_instance_3d.h" +#include "scene/3d/physics/collision_shape_3d.h" +#include "scene/3d/physics/static_body_3d.h" +#include "scene/gui/dialogs.h" +#include "scene/gui/menu_button.h" + +void CSGManifoldShapeEditor::_node_removed(Node *p_node) { + if (p_node == node) { + node = nullptr; + options->hide(); + } +} + +void CSGManifoldShapeEditor::edit(CSGManifoldShape3D *p_csg_shape) { + node = p_csg_shape; + if (node) { + options->show(); + } else { + options->hide(); + } +} + +void CSGManifoldShapeEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + options->set_button_icon(get_editor_theme_icon(SNAME("CSGManifoldCombiner3D"))); + } break; + } +} + +void CSGManifoldShapeEditor::_menu_option(int p_option) { + Array meshes = node->get_meshes(); + if (meshes.is_empty()) { + err_dialog->set_text(TTR("CSG operation returned an empty array.")); + err_dialog->popup_centered(); + return; + } + + switch (p_option) { + case MENU_OPTION_BAKE_MESH_INSTANCE: { + _create_baked_mesh_instance(); + } break; + case MENU_OPTION_BAKE_COLLISION_SHAPE: { + _create_baked_collision_shape(); + } break; + } +} + +void CSGManifoldShapeEditor::_create_baked_mesh_instance() { + if (node == get_tree()->get_edited_scene_root()) { + err_dialog->set_text(TTR("Can not add a baked mesh as sibling for the scene root.\nMove the CSG root node below a parent node.")); + err_dialog->popup_centered(); + return; + } + + Ref mesh = node->bake_static_mesh(); + if (mesh.is_null()) { + err_dialog->set_text(TTR("CSG operation returned an empty mesh.")); + err_dialog->popup_centered(); + return; + } + + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Create baked CSGManifoldShape3D Mesh Instance")); + + Node *owner = get_tree()->get_edited_scene_root(); + + MeshInstance3D *mi = memnew(MeshInstance3D); + mi->set_mesh(mesh); + mi->set_name("CSGBakedMeshInstance3D"); + mi->set_transform(node->get_transform()); + ur->add_do_method(node, "add_sibling", mi, true); + ur->add_do_method(mi, "set_owner", owner); + ur->add_do_method(Node3DEditor::get_singleton(), SceneStringName(_request_gizmo), mi); + + ur->add_do_reference(mi); + ur->add_undo_method(node->get_parent(), "remove_child", mi); + + ur->commit_action(); +} + +void CSGManifoldShapeEditor::_create_baked_collision_shape() { + if (node == get_tree()->get_edited_scene_root()) { + err_dialog->set_text(TTR("Can not add a baked collision shape as sibling for the scene root.\nMove the CSG root node below a parent node.")); + err_dialog->popup_centered(); + return; + } + + Ref shape = node->bake_collision_shape(); + if (shape.is_null()) { + err_dialog->set_text(TTR("CSG operation returned an empty shape.")); + err_dialog->popup_centered(); + return; + } + + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Create baked CSGManifoldShape3D Collision Shape")); + + Node *owner = get_tree()->get_edited_scene_root(); + + CollisionShape3D *cshape = memnew(CollisionShape3D); + cshape->set_shape(shape); + cshape->set_name("CSGBakedCollisionShape3D"); + cshape->set_transform(node->get_transform()); + ur->add_do_method(node, "add_sibling", cshape, true); + ur->add_do_method(cshape, "set_owner", owner); + ur->add_do_method(Node3DEditor::get_singleton(), SceneStringName(_request_gizmo), cshape); + + ur->add_do_reference(cshape); + ur->add_undo_method(node->get_parent(), "remove_child", cshape); + + ur->commit_action(); +} + +CSGManifoldShapeEditor::CSGManifoldShapeEditor() { + options = memnew(MenuButton); + options->hide(); + options->set_text(TTR("CSG")); + options->set_switch_on_hover(true); + Node3DEditor::get_singleton()->add_control_to_menu_panel(options); + + options->get_popup()->add_item(TTR("Bake Mesh Instance"), MENU_OPTION_BAKE_MESH_INSTANCE); + options->get_popup()->add_item(TTR("Bake Collision Shape"), MENU_OPTION_BAKE_COLLISION_SHAPE); + + options->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &CSGManifoldShapeEditor::_menu_option)); + + err_dialog = memnew(AcceptDialog); + add_child(err_dialog); +} + +/////////// + +CSGManifoldShape3DGizmoPlugin::CSGManifoldShape3DGizmoPlugin() { + helper.instantiate(); + + Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/csg"); + create_material("shape_union_material", gizmo_color); + create_material("shape_union_solid_material", gizmo_color); + gizmo_color.invert(); + create_material("shape_subtraction_material", gizmo_color); + create_material("shape_subtraction_solid_material", gizmo_color); + gizmo_color.r = 0.95; + gizmo_color.g = 0.95; + gizmo_color.b = 0.95; + create_material("shape_intersection_material", gizmo_color); + create_material("shape_intersection_solid_material", gizmo_color); + + create_handle_material("handles"); +} + +CSGManifoldShape3DGizmoPlugin::~CSGManifoldShape3DGizmoPlugin() { +} + +String CSGManifoldShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + CSGManifoldShape3D *cs = Object::cast_to(p_gizmo->get_node_3d()); + + if (Object::cast_to(cs)) { + return "Radius"; + } + + if (Object::cast_to(cs)) { + return helper->box_get_handle_name(p_id); + } + + if (Object::cast_to(cs)) { + return p_id == 0 ? "Radius" : "Height"; + } + + if (Object::cast_to(cs)) { + return p_id == 0 ? "InnerRadius" : "OuterRadius"; + } + + return ""; +} + +Variant CSGManifoldShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + CSGManifoldShape3D *cs = Object::cast_to(p_gizmo->get_node_3d()); + + if (Object::cast_to(cs)) { + CSGManifoldSphere3D *s = Object::cast_to(cs); + return s->get_radius(); + } + + if (Object::cast_to(cs)) { + CSGManifoldBox3D *s = Object::cast_to(cs); + return s->get_size(); + } + + if (Object::cast_to(cs)) { + CSGManifoldCylinder3D *s = Object::cast_to(cs); + return p_id == 0 ? s->get_radius() : s->get_height(); + } + + if (Object::cast_to(cs)) { + CSGManifoldTorus3D *s = Object::cast_to(cs); + return p_id == 0 ? s->get_inner_radius() : s->get_outer_radius(); + } + + return Variant(); +} + +void CSGManifoldShape3DGizmoPlugin::begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) { + helper->initialize_handle_action(get_handle_value(p_gizmo, p_id, p_secondary), p_gizmo->get_node_3d()->get_global_transform()); +} + +void CSGManifoldShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + CSGManifoldShape3D *cs = Object::cast_to(p_gizmo->get_node_3d()); + + Vector3 sg[2]; + helper->get_segment(p_camera, p_point, sg); + + if (Object::cast_to(cs)) { + CSGManifoldSphere3D *s = Object::cast_to(cs); + + Vector3 ra, rb; + Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), sg[0], sg[1], ra, rb); + float d = ra.x; + if (Node3DEditor::get_singleton()->is_snap_enabled()) { + d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); + } + + if (d < 0.001) { + d = 0.001; + } + + s->set_radius(d); + } + + if (Object::cast_to(cs)) { + CSGManifoldBox3D *s = Object::cast_to(cs); + Vector3 size = s->get_size(); + Vector3 position; + helper->box_set_handle(sg, p_id, size, position); + s->set_size(size); + s->set_global_position(position); + } + + if (Object::cast_to(cs)) { + CSGManifoldCylinder3D *s = Object::cast_to(cs); + + real_t height = s->get_height(); + real_t radius = s->get_radius(); + Vector3 position; + helper->cylinder_set_handle(sg, p_id, height, radius, position); + s->set_height(height); + s->set_radius(radius); + s->set_global_position(position); + } + + if (Object::cast_to(cs)) { + CSGManifoldTorus3D *s = Object::cast_to(cs); + + Vector3 axis; + axis[0] = 1.0; + Vector3 ra, rb; + Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); + float d = axis.dot(ra); + if (Node3DEditor::get_singleton()->is_snap_enabled()) { + d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); + } + + if (d < 0.001) { + d = 0.001; + } + + if (p_id == 0) { + s->set_inner_radius(d); + } else if (p_id == 1) { + s->set_outer_radius(d); + } + } +} + +void CSGManifoldShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + CSGManifoldShape3D *cs = Object::cast_to(p_gizmo->get_node_3d()); + + if (Object::cast_to(cs)) { + CSGManifoldSphere3D *s = Object::cast_to(cs); + if (p_cancel) { + s->set_radius(p_restore); + return; + } + + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Change Sphere Shape Radius")); + ur->add_do_method(s, "set_radius", s->get_radius()); + ur->add_undo_method(s, "set_radius", p_restore); + ur->commit_action(); + } + + if (Object::cast_to(cs)) { + helper->box_commit_handle(TTR("Change CSG Box Size"), p_cancel, cs); + } + + if (Object::cast_to(cs)) { + helper->cylinder_commit_handle(p_id, TTR("Change CSG Cylinder Radius"), TTR("Change CSG Cylinder Height"), p_cancel, cs); + } + + if (Object::cast_to(cs)) { + CSGManifoldTorus3D *s = Object::cast_to(cs); + if (p_cancel) { + if (p_id == 0) { + s->set_inner_radius(p_restore); + } else { + s->set_outer_radius(p_restore); + } + return; + } + + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + if (p_id == 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(); + } +} + +bool CSGManifoldShape3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return Object::cast_to(p_spatial) || Object::cast_to(p_spatial) || Object::cast_to(p_spatial) || Object::cast_to(p_spatial) || Object::cast_to(p_spatial) || Object::cast_to(p_spatial); +} + +String CSGManifoldShape3DGizmoPlugin::get_gizmo_name() const { + return "CSGManifoldShape3D"; +} + +int CSGManifoldShape3DGizmoPlugin::get_priority() const { + return -1; +} + +bool CSGManifoldShape3DGizmoPlugin::is_selectable_when_hidden() const { + return true; +} + +void CSGManifoldShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + p_gizmo->clear(); + + CSGManifoldShape3D *cs = Object::cast_to(p_gizmo->get_node_3d()); + + Vector faces = cs->get_brush_faces(); + + if (faces.size() == 0) { + return; + } + + Vector lines; + lines.resize(faces.size() * 2); + { + const Vector3 *r = faces.ptr(); + + 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.write[i + j * 2 + 0] = r[f * 3 + j]; + lines.write[i + j * 2 + 1] = r[f * 3 + j_n]; + } + } + } + + Ref material; + switch (cs->get_operation()) { + case CSGManifoldShape3D::OPERATION_UNION: + material = get_material("shape_union_material", p_gizmo); + break; + case CSGManifoldShape3D::OPERATION_INTERSECTION: + material = get_material("shape_intersection_material", p_gizmo); + break; + case CSGManifoldShape3D::OPERATION_SUBTRACTION: + material = get_material("shape_subtraction_material", p_gizmo); + break; + } + + Ref handles_material = get_material("handles"); + + p_gizmo->add_lines(lines, material); + p_gizmo->add_collision_segments(lines); + + if (cs->is_root_shape()) { + Array csg_meshes = cs->get_meshes(); + if (csg_meshes.size() == 2) { + Ref csg_mesh = csg_meshes[1]; + if (csg_mesh.is_valid()) { + p_gizmo->add_collision_triangles(csg_mesh->generate_triangle_mesh()); + } + } + } + + if (p_gizmo->is_selected()) { + // Draw a translucent representation of the CSG node + Ref mesh = memnew(ArrayMesh); + Array array; + array.resize(Mesh::ARRAY_MAX); + array[Mesh::ARRAY_VERTEX] = faces; + mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, array); + + Ref solid_material; + switch (cs->get_operation()) { + case CSGManifoldShape3D::OPERATION_UNION: + solid_material = get_material("shape_union_solid_material", p_gizmo); + break; + case CSGManifoldShape3D::OPERATION_INTERSECTION: + solid_material = get_material("shape_intersection_solid_material", p_gizmo); + break; + case CSGManifoldShape3D::OPERATION_SUBTRACTION: + solid_material = get_material("shape_subtraction_solid_material", p_gizmo); + break; + } + + p_gizmo->add_mesh(mesh, solid_material); + } + + if (Object::cast_to(cs)) { + CSGManifoldSphere3D *s = Object::cast_to(cs); + + float r = s->get_radius(); + Vector handles; + handles.push_back(Vector3(r, 0, 0)); + p_gizmo->add_handles(handles, handles_material); + } + + if (Object::cast_to(cs)) { + CSGManifoldBox3D *s = Object::cast_to(cs); + Vector handles = helper->box_get_handles(s->get_size()); + p_gizmo->add_handles(handles, handles_material); + } + + if (Object::cast_to(cs)) { + CSGManifoldCylinder3D *s = Object::cast_to(cs); + + Vector handles = helper->cylinder_get_handles(s->get_height(), s->get_radius()); + p_gizmo->add_handles(handles, handles_material); + } + + if (Object::cast_to(cs)) { + CSGManifoldTorus3D *s = Object::cast_to(cs); + + Vector handles; + handles.push_back(Vector3(s->get_inner_radius(), 0, 0)); + handles.push_back(Vector3(s->get_outer_radius(), 0, 0)); + p_gizmo->add_handles(handles, handles_material); + } +} + +void EditorPluginCSGManifold::edit(Object *p_object) { + CSGManifoldShape3D *csg_shape = Object::cast_to(p_object); + if (csg_shape && csg_shape->is_root_shape()) { + csg_shape_editor->edit(csg_shape); + } else { + csg_shape_editor->edit(nullptr); + } +} + +bool EditorPluginCSGManifold::handles(Object *p_object) const { + CSGManifoldShape3D *csg_shape = Object::cast_to(p_object); + return csg_shape && csg_shape->is_root_shape(); +} + +EditorPluginCSGManifold::EditorPluginCSGManifold() { + Ref gizmo_plugin = Ref(memnew(CSGManifoldShape3DGizmoPlugin)); + Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin); + + csg_shape_editor = memnew(CSGManifoldShapeEditor); + EditorNode::get_singleton()->get_gui_base()->add_child(csg_shape_editor); +} + +#endif // TOOLS_ENABLED diff --git a/modules/csg_manifold/editor/csg_manifold_gizmos.h b/modules/csg_manifold/editor/csg_manifold_gizmos.h new file mode 100644 index 000000000000..751e6597b919 --- /dev/null +++ b/modules/csg_manifold/editor/csg_manifold_gizmos.h @@ -0,0 +1,110 @@ +/**************************************************************************/ +/* csg_manifold_gizmos.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#ifndef CSG_MANIFOLD_GIZMOS_H +#define CSG_MANIFOLD_GIZMOS_H + +#ifdef TOOLS_ENABLED + +#include "../csg_manifold_shape.h" + +#include "editor/plugins/editor_plugin.h" +#include "editor/plugins/node_3d_editor_gizmos.h" +#include "scene/gui/control.h" + +class AcceptDialog; +class Gizmo3DHelper; +class MenuButton; + +class CSGManifoldShape3DGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(CSGManifoldShape3DGizmoPlugin, EditorNode3DGizmoPlugin); + + Ref helper; + +public: + virtual bool has_gizmo(Node3D *p_spatial) override; + virtual String get_gizmo_name() const override; + virtual int get_priority() const override; + virtual bool is_selectable_when_hidden() const override; + virtual void redraw(EditorNode3DGizmo *p_gizmo) override; + + virtual String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + virtual Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) override; + virtual void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + virtual void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) override; + + CSGManifoldShape3DGizmoPlugin(); + ~CSGManifoldShape3DGizmoPlugin(); +}; + +class CSGManifoldShapeEditor : public Control { + GDCLASS(CSGManifoldShapeEditor, Control); + + enum Menu { + MENU_OPTION_BAKE_MESH_INSTANCE, + MENU_OPTION_BAKE_COLLISION_SHAPE, + }; + + CSGManifoldShape3D *node = nullptr; + MenuButton *options = nullptr; + AcceptDialog *err_dialog = nullptr; + + void _menu_option(int p_option); + + void _create_baked_mesh_instance(); + void _create_baked_collision_shape(); + +protected: + void _node_removed(Node *p_node); + + void _notification(int p_what); + +public: + void edit(CSGManifoldShape3D *p_csg_shape); + CSGManifoldShapeEditor(); +}; + +class EditorPluginCSGManifold : public EditorPlugin { + GDCLASS(EditorPluginCSGManifold, EditorPlugin); + + CSGManifoldShapeEditor *csg_shape_editor = nullptr; + +public: + virtual String get_name() const override { return "CSGManifoldShape3D"; } + virtual void edit(Object *p_object) override; + virtual bool handles(Object *p_object) const override; + + EditorPluginCSGManifold(); +}; + +#endif // TOOLS_ENABLED + +#endif // CSG_MANIFOLD_GIZMOS_H diff --git a/modules/csg_manifold/register_types.cpp b/modules/csg_manifold/register_types.cpp new file mode 100644 index 000000000000..f814e12cdce8 --- /dev/null +++ b/modules/csg_manifold/register_types.cpp @@ -0,0 +1,62 @@ +/**************************************************************************/ +/* register_types.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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_manifold_shape.h" + +#ifdef TOOLS_ENABLED +#include "editor/csg_manifold_gizmos.h" +#endif + +void initialize_csg_manifold_module(ModuleInitializationLevel p_level) { + if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) { + GDREGISTER_ABSTRACT_CLASS(CSGManifoldShape3D); + GDREGISTER_ABSTRACT_CLASS(CSGManifoldPrimitive3D); + GDREGISTER_CLASS(CSGManifoldMesh3D); + GDREGISTER_CLASS(CSGManifoldSphere3D); + GDREGISTER_CLASS(CSGManifoldBox3D); + GDREGISTER_CLASS(CSGManifoldCylinder3D); + GDREGISTER_CLASS(CSGManifoldTorus3D); + GDREGISTER_CLASS(CSGManifoldPolygon3D); + GDREGISTER_CLASS(CSGManifoldCombiner3D); + } +#ifdef TOOLS_ENABLED + if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) { + EditorPlugins::add_by_type(); + } +#endif +} + +void uninitialize_csg_manifold_module(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + return; + } +} diff --git a/modules/csg_manifold/register_types.h b/modules/csg_manifold/register_types.h new file mode 100644 index 000000000000..43cab4c1f51b --- /dev/null +++ b/modules/csg_manifold/register_types.h @@ -0,0 +1,39 @@ +/**************************************************************************/ +/* register_types.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#ifndef CSG_MANIFOLD_REGISTER_TYPES_H +#define CSG_MANIFOLD_REGISTER_TYPES_H + +#include "modules/register_module_types.h" + +void initialize_csg_manifold_module(ModuleInitializationLevel p_level); +void uninitialize_csg_manifold_module(ModuleInitializationLevel p_level); + +#endif // CSG_MANIFOLD_REGISTER_TYPES_H