@tool class_name DestructableWall extends Node3D enum ExtrudeDirection { BACK = 0, # z RIGHT = 1, # x UP = 2 # y } @export_category("TrechBroom Generated Values") @export var verts2d: PackedVector2Array @export var meshInstance3d: MeshInstance3D @export var extrusion_direction: ExtrudeDirection @export var depth: float = 0.1 @export var depth_position_offset: Vector3 = Vector3.ZERO @export_category("User Defined Values") @export var edge_non_fracture: float = 0.1 var parentNode var inner_rtree: RectangleTree var outer_polygon: PackedVector2Array var inner_polygons: Holes var static_body: StaticBody3D func _ready() -> void: if Engine.is_editor_hint(): return parentNode = get_node("..") inner_polygons = Holes.new() inner_rtree = RectangleTree.new() outer_polygon = verts2d _draw() func _exit_tree() -> void: if Engine.is_editor_hint(): return inner_rtree.free() func _func_godot_apply_properties(entity_properties: Dictionary) -> void: meshInstance3d = _find_mesh_body() var verticies = meshInstance3d.mesh.surface_get_arrays(0)[Mesh.ARRAY_VERTEX] extrusion_direction = entity_properties["extrude_direction"] var depth_min: float var depth_max: float var verts_2d: PackedVector2Array = [] if extrusion_direction == ExtrudeDirection.BACK: for vert in verticies: verts_2d.append(Vector2(vert.x, vert.y)) var d = vert.z if d > depth_max: depth_max = d elif d < depth_min: depth_min = d depth = -(abs(depth_min) + abs(depth_max)) depth_position_offset = -(Vector3.BACK * (depth/2)) elif extrusion_direction == ExtrudeDirection.RIGHT: for vert in verticies: verts_2d.append(Vector2(vert.z, vert.y)) var d = vert.x if d > depth_max: depth_max = d elif d < depth_min: depth_min = d depth = -(abs(depth_min) + abs(depth_max)) depth_position_offset = Vector3.RIGHT * (depth/2) elif extrusion_direction == ExtrudeDirection.UP: for vert in verticies: verts_2d.append(Vector2(vert.x, vert.z)) var d = vert.y if d > depth_max: depth_max = d elif d < depth_min: depth_min = d depth = -(abs(depth_min) + abs(depth_max)) depth_position_offset = (Vector3.UP * (depth/2)) var outer_boudary = Geometry2D.convex_hull(verts_2d) verts2d = outer_boudary func _draw(): var vector_indexes = GeoPolyTriangulization.triangulate(outer_polygon, inner_polygons.get_holes()) var vectors = [] vectors.append_array(outer_polygon) vectors.append_array(inner_polygons.get_hole_verticies()) var meshGenerator = GeoPolyMesh.new(vector_indexes, vectors, extrusion_direction, depth) var commited_mesh = meshGenerator.commit_mesh(meshInstance3d.get_active_material(0)) meshGenerator.free() meshInstance3d.mesh = commited_mesh meshInstance3d.position += depth_position_offset meshInstance3d.create_trimesh_collision() static_body = _find_static_body() func _re_draw(): var vector_indexes = GeoPolyTriangulization.triangulate(outer_polygon, inner_polygons.get_holes()) var vectors = [] vectors.append_array(outer_polygon) vectors.append_array(inner_polygons.get_hole_verticies()) WorkerThreadPool.add_task(_generate_mesh.bind(vector_indexes, vectors)) func _deffered_draw(mesh: Mesh, task_id: int): if task_id: WorkerThreadPool.wait_for_task_completion(task_id) meshInstance3d.remove_child(static_body) static_body.queue_free() meshInstance3d.mesh = mesh meshInstance3d.create_trimesh_collision() static_body = _find_static_body() func _generate_mesh(vector_indexes: PackedInt32Array, vectors: PackedVector2Array): var meshGenerator = GeoPolyMesh.new(vector_indexes, vectors, extrusion_direction, depth) var commited_mesh = meshGenerator.commit_mesh(meshInstance3d.get_active_material(0)) meshGenerator.free() self.call_deferred("_deffered_draw", commited_mesh, WorkerThreadPool.get_caller_task_id()) func hit(position: Vector3): var cutter = Cutter.circleCutter() var hole_position_offset = meshInstance3d.to_local(position) var hole_vector2_offset: Vector2 if extrusion_direction == ExtrudeDirection.BACK: hole_vector2_offset = Vector2(hole_position_offset.x, hole_position_offset.y) elif extrusion_direction == ExtrudeDirection.RIGHT: hole_vector2_offset = Vector2(hole_position_offset.z, hole_position_offset.y) elif extrusion_direction == ExtrudeDirection.UP: hole_vector2_offset = Vector2(hole_position_offset.x, hole_position_offset.z) # translate the cutter var hole_vectors = Transform2D(0, hole_vector2_offset) * cutter var draw = _add_hole(hole_vectors) if draw: _re_draw() func _add_hole(new_hole: PackedVector2Array) -> bool: var ids_to_remove = [] var holes_id = [] for vector in new_hole: holes_id.append_array(inner_rtree.query(vector)) # validate that the hole is valid var boundary_polygon = Geometry2D.offset_polygon(outer_polygon, -edge_non_fracture) var boundary_hole = Geometry2D.intersect_polygons(new_hole, boundary_polygon[0]) if !boundary_hole: return false var merged: PackedVector2Array = boundary_hole[0] for id in holes_id: var result = Geometry2D.merge_polygons(merged, inner_polygons.get_hole(id).vectors) if len(result) == 1: merged = result[0] ids_to_remove.append(id) elif len(result) > 1: # need to check that they have no overlapping area if they do clip var overlap = Geometry2D.intersect_polygons(result[0], result[1]) if len(overlap) != 0: merged = Geometry2D.clip_polygons(result[0], result[1])[0] ids_to_remove.append(id) for id in ids_to_remove: inner_rtree.remove(id) inner_polygons.remove_hole(id) var hole = inner_polygons.add_hole(merged) var bound_rect = UTIL.get_bounding_rect(merged) inner_rtree.add(bound_rect.position, bound_rect.end, hole.hole_id) return true func _find_static_body(): for child in meshInstance3d.get_children(): if child is StaticBody3D: return child func _find_mesh_body(): for child in self.get_children(): if child is MeshInstance3D: return child