diff --git a/modules/csg/csg.h b/modules/csg/csg.h index 3c1d4d8fbeee..bcdada72afd7 100644 --- a/modules/csg/csg.h +++ b/modules/csg/csg.h @@ -51,6 +51,7 @@ struct CSGBrush { Vector faces; Vector> materials; + bool lock_faces = false; inline void _regen_face_aabbs() { for (int i = 0; i < faces.size(); i++) { diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index b9f33dc7571a..f24bf6afa33f 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -218,6 +218,7 @@ enum ManifoldProperty { MANIFOLD_PROPERTY_SMOOTH_GROUP, MANIFOLD_PROPERTY_UV_X_0, MANIFOLD_PROPERTY_UV_Y_0, + MANIFOLD_PROPERTY_FACE_LOCK, MANIFOLD_PROPERTY_MAX }; @@ -339,6 +340,38 @@ static String _export_meshgl_as_json(const manifold::MeshGL64 &p_mesh) { } #endif // DEV_ENABLED +// Yes, Manifold removes extra triangles from coplanar faces. There are several ways to make them stay - probably the easiest is adding any kind of extra vertex property. +// +// TODO: Adding any kind of extra vertex property to lock the faces. + +// struct CSGBrush { +// struct Face { +// Vector3 vertices[3]; +// Vector2 uvs[3]; +// AABB aabb; +// bool smooth = false; +// bool invert = false; +// int material = 0; +// }; + +// Vector faces; +// Vector> materials; +// bool lock_faces = false; + +// 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 CSGBrush &p_brush, const Transform3D &p_xform); +// }; + static void _pack_manifold( const CSGBrush *const p_mesh_merge, manifold::Manifold &r_manifold, @@ -347,6 +380,7 @@ static void _pack_manifold( 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; + bool lock_faces = p_mesh_merge->lock_faces; 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); @@ -373,6 +407,7 @@ static void _pack_manifold( } p_mesh_materials.insert(reserved_id, material); + int face_i = 0; 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 }; @@ -392,7 +427,13 @@ static void _pack_manifold( 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; + if (lock_faces) { + vert[MANIFOLD_PROPERTY_FACE_LOCK] = material_id * face_i + face_i; + } else { + vert[MANIFOLD_PROPERTY_FACE_LOCK] = 0.0f; + } } + face_i++; } } // runIndex needs an explicit end value. @@ -434,6 +475,9 @@ CSGBrush *CSGShape3D::_get_brush() { } brush = nullptr; CSGBrush *n = _build_brush(); + if (!n) { + n->lock_faces = lock_faces; + } HashMap> mesh_materials; manifold::Manifold root_manifold; _pack_manifold(n, root_manifold, mesh_materials, this); @@ -449,6 +493,9 @@ CSGBrush *CSGShape3D::_get_brush() { if (!child_brush) { continue; } + if (!child_brush) { + child_brush->lock_faces = child->lock_faces; + } CSGBrush transformed_brush; transformed_brush.copy_from(*child_brush, child->get_transform()); manifold::Manifold child_manifold; @@ -996,6 +1043,10 @@ void CSGShape3D::_bind_methods() { ClassDB::bind_method(D_METHOD("bake_static_mesh"), &CSGShape3D::bake_static_mesh); ClassDB::bind_method(D_METHOD("bake_collision_shape"), &CSGShape3D::bake_collision_shape); + ClassDB::bind_method(D_METHOD("set_lock_faces", "lock"), &CSGShape3D::set_lock_faces); + ClassDB::bind_method(D_METHOD("get_lock_faces"), &CSGShape3D::get_lock_faces); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "lock_faces"), "set_lock_faces", "get_lock_faces"); 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"); @@ -2784,3 +2835,11 @@ CSGPolygon3D::CSGPolygon3D() { path_joined = false; path = nullptr; } + +void CSGShape3D::set_lock_faces(bool p_lock) { + lock_faces = p_lock; +} + +bool CSGShape3D::get_lock_faces() const { + return lock_faces; +} diff --git a/modules/csg/csg_shape.h b/modules/csg/csg_shape.h index 39b42afc085b..8ec0bdefa8e5 100644 --- a/modules/csg/csg_shape.h +++ b/modules/csg/csg_shape.h @@ -76,6 +76,8 @@ class CSGShape3D : public GeometryInstance3D { bool calculate_tangents = true; + bool lock_faces = false; + Ref root_mesh; struct Vector3Hasher { @@ -134,6 +136,9 @@ class CSGShape3D : public GeometryInstance3D { public: Array get_meshes() const; + void set_lock_faces(bool p_lock); + bool get_lock_faces() const; + void set_operation(Operation p_operation); Operation get_operation() const; diff --git a/modules/csg/doc_classes/CSGShape3D.xml b/modules/csg/doc_classes/CSGShape3D.xml index 8ea471e62d83..d8b198ac1b47 100644 --- a/modules/csg/doc_classes/CSGShape3D.xml +++ b/modules/csg/doc_classes/CSGShape3D.xml @@ -86,6 +86,9 @@ 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. + + Locks the faces of the CSG shape so they are lightly optimized. + 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.