diff --git a/demo/main.tscn b/demo/main.tscn index 0f200cf..33dd2b7 100644 --- a/demo/main.tscn +++ b/demo/main.tscn @@ -1,19 +1,24 @@ -[gd_scene load_steps=3 format=3 uid="uid://dvgdgi3jrhrvt"] +[gd_scene load_steps=4 format=3 uid="uid://dvgdgi3jrhrvt"] -[ext_resource type="Script" uid="uid://dysaws7hlg4td" path="res://scripts/test.gd" id="1_ig7tw"] +[ext_resource type="Script" uid="uid://cs057402w7w17" path="res://scripts/camera.gd" id="2_0xm2m"] +[ext_resource type="Script" uid="uid://42cwsrh6jyns" path="res://scripts/destructable_wall.gd" id="3_h2yge"] [sub_resource type="Environment" id="Environment_ig7tw"] [node name="Node3D" type="Node3D"] -script = ExtResource("1_ig7tw") [node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] -transform = Transform3D(0.8192554, 0.57171863, 0.04425077, 0.028913066, 0.03588575, -0.9989376, -0.5726993, 0.81966454, 0.012869433, 0, 0.54785013, 6.354968) +transform = Transform3D(0.8192555, 0.5717188, 0.044250764, -0.5075839, 0.7589243, -0.40791288, -0.2667944, 0.31172383, 0.91194814, 0, 0.54785013, 6.354968) shadow_enabled = true [node name="WorldEnvironment" type="WorldEnvironment" parent="."] environment = SubResource("Environment_ig7tw") [node name="Camera3D" type="Camera3D" parent="."] -transform = Transform3D(1, 0, 0, 0, 0.99687606, -0.078981146, 0, 0.078981146, 0.99687606, 1.3097496, 0.96856904, 8.280736) +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.546, 1, 1.6508019) current = true +script = ExtResource("2_0xm2m") + +[node name="Wall" type="Node3D" parent="."] +script = ExtResource("3_h2yge") +outer_polygon = PackedVector2Array(0, 2, 2, 2, 2, 0, 0, 0) diff --git a/demo/project.godot b/demo/project.godot index 7c7eb94..7042149 100644 --- a/demo/project.godot +++ b/demo/project.godot @@ -18,3 +18,11 @@ config/icon="res://icon.svg" [debug_draw_3d] settings/addon_root_folder="res://addons/debug_draw_3d" + +[input] + +left_click={ +"deadzone": 0.2, +"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null) +] +} diff --git a/demo/scripts/camera.gd b/demo/scripts/camera.gd new file mode 100644 index 0000000..8d5202a --- /dev/null +++ b/demo/scripts/camera.gd @@ -0,0 +1,23 @@ +extends Camera3D + +signal wall_hit(wall_id, position, cutter) + +func shoot_ray() -> Dictionary: + var mouse_position = get_viewport().get_mouse_position() + var ray_length = 1000 + var from = project_ray_origin(mouse_position) + var to = from + project_ray_normal(mouse_position) * ray_length + var space = get_world_3d().direct_space_state + var ray_query = PhysicsRayQueryParameters3D.new() + ray_query.from = from + ray_query.to = to + var raycast_result = space.intersect_ray(ray_query) + + return raycast_result + +func _input(event: InputEvent) -> void: + if event.is_action_pressed("left_click"): + var query_result = shoot_ray() + if query_result: + var cutter: PackedVector2Array = Cutter.circleCutter() + wall_hit.emit(query_result["collider_id"], query_result["position"], cutter) diff --git a/demo/scripts/camera.gd.uid b/demo/scripts/camera.gd.uid new file mode 100644 index 0000000..d4e99e7 --- /dev/null +++ b/demo/scripts/camera.gd.uid @@ -0,0 +1 @@ +uid://cs057402w7w17 diff --git a/demo/scripts/cutters.gd b/demo/scripts/cutters.gd new file mode 100644 index 0000000..08a2158 --- /dev/null +++ b/demo/scripts/cutters.gd @@ -0,0 +1,24 @@ +extends Node + +class_name Cutter + +static func circleCutter(num_sides = 10, perimerter_length = 0.5) -> PackedVector2Array: + var line_length: float = perimerter_length/num_sides + var line_angle: float = 360.0/num_sides + var current_angle: float = 0.0 + + var vectors: PackedVector2Array + + for i in num_sides: + var vector: Vector2 = Vector2.ZERO + + if i == 0: + current_angle += line_angle + vector = Vector2(line_length * cos(deg_to_rad(current_angle)), line_length * sin(deg_to_rad(current_angle))) + vectors.append(vector) + else: + current_angle += line_angle + vector = Vector2(vectors[i-1].x + line_length * cos(deg_to_rad(current_angle)), vectors[i-1].y + line_length * sin(deg_to_rad(current_angle))) + vectors.append(vector) + + return vectors diff --git a/demo/scripts/cutters.gd.uid b/demo/scripts/cutters.gd.uid new file mode 100644 index 0000000..f297965 --- /dev/null +++ b/demo/scripts/cutters.gd.uid @@ -0,0 +1 @@ +uid://cd5la8wwjk34w diff --git a/demo/scripts/destructable_wall.gd b/demo/scripts/destructable_wall.gd new file mode 100644 index 0000000..d7f436f --- /dev/null +++ b/demo/scripts/destructable_wall.gd @@ -0,0 +1,103 @@ +extends Node + +@export var outer_polygon: PackedVector2Array +@export var inner_polygons: Array[PackedVector2Array] +@export var edge_non_fracture: float = 0.1 + +@onready var camera: Camera3D = get_node("/root/Node3D/Camera3D") +@onready var meshInstance = MeshInstance3D.new() +@onready var parentNode = get_node("..") + +var depth: float: + set(value): + if value > 0: + depth = -value + depth = value + +var static_body: StaticBody3D + +func _ready() -> void: + assert(camera, "camera was not found") + assert(len(outer_polygon) > 2, "outer polygon does not meet the required length") + + camera.connect("wall_hit", _handle_hit_signal) + + self.add_child(meshInstance) + _draw() + +func _draw(): + var vector_indexes = GeoPolyTriangulization.triangulate(outer_polygon, inner_polygons) + var vectors = [] + + vectors.append_array(outer_polygon) + for ip in inner_polygons: + vectors.append_array(ip) + + var meshGenerator = GeoPolyMesh.new(vector_indexes, vectors) + var mesh = meshGenerator.commit_mesh() + + meshInstance.mesh = mesh + meshInstance.create_trimesh_collision() + static_body = _find_static_body() + +func _re_draw(): + meshInstance.remove_child(static_body) + static_body.queue_free() + + var vector_indexes = GeoPolyTriangulization.triangulate(outer_polygon, inner_polygons) + var vectors = [] + + vectors.append_array(outer_polygon) + for ip in inner_polygons: + vectors.append_array(ip) + + var meshGenerator = GeoPolyMesh.new(vector_indexes, vectors) + var mesh = meshGenerator.commit_mesh() + + meshInstance.mesh = mesh + meshInstance.create_trimesh_collision() + static_body = _find_static_body() + + +func _handle_hit_signal(wall_id: int, position: Vector3, cutter: PackedVector2Array): + # validate that the hit was to this wall, if not return + if wall_id != static_body.get_instance_id(): + return + + var hole_position_offset = self.position + position + var hole_vector2_offset = Vector2(hole_position_offset.x, hole_position_offset.y) + + # translate the cutter + var hole_vectors = Transform2D(0, hole_vector2_offset) * cutter + + _add_hole(hole_vectors) + + _re_draw() + +func _add_hole(new_hole: PackedVector2Array): + var values_to_remove = [] + + var merged: PackedVector2Array = new_hole + for index in range(len(self.inner_polygons)): + var result = Geometry2D.merge_polygons(merged, self.inner_polygons[index]) + + if len(result) == 1: + merged = result[0] + values_to_remove.append(self.inner_polygons[index]) + 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] + values_to_remove.append(self.inner_polygons[index]) + + for i in values_to_remove: + self.inner_polygons.erase(i) + + inner_polygons.append(merged) + +func _find_static_body(): + for child in meshInstance.get_children(): + if child is StaticBody3D: + return child diff --git a/demo/scripts/destructable_wall.gd.uid b/demo/scripts/destructable_wall.gd.uid new file mode 100644 index 0000000..4662e61 --- /dev/null +++ b/demo/scripts/destructable_wall.gd.uid @@ -0,0 +1 @@ +uid://42cwsrh6jyns diff --git a/demo/scripts/geometry/geopolymesh.gd b/demo/scripts/geometry/geopolymesh.gd index ce9358d..fc88414 100644 --- a/demo/scripts/geometry/geopolymesh.gd +++ b/demo/scripts/geometry/geopolymesh.gd @@ -14,8 +14,8 @@ var _v_points = [] var edges = {} -func _init(vector_indexes: PackedInt32Array, vector_points: PackedVector2Array, depth: float = -2.0): - assert(len(vector_indexes) % 3 == 0, "Number of vertex points is not divisible by 3, invalid triangle verticies") +func _init(vector_indexes: PackedInt32Array, vector_points: PackedVector2Array, depth: float = -0.1): + assert(len(vector_indexes) % 3 == 0 && len(vector_indexes) != 0, "Number of vertex points is not divisible by 3, invalid triangle verticies") surface_array.resize(Mesh.ARRAY_MAX) self._v_indexes = vector_indexes @@ -39,7 +39,6 @@ func calculate_area(mesh_vertices: PackedVector2Array) -> float: return result * 0.5 - # edges are any verticies that share only on triangle func _pre_process_edges(): var faces = UTIL.chunk_array(self._v_indexes, 3) @@ -144,9 +143,6 @@ func _draw_sides(depth: float): var v3 = self._v_points[outline_edges_ordered[outline_vector_ind + 1]]+ Vector3(0, 0, depth) var n1 = self._calc_triangle_normal(v2, v1, v0) - # - #print(v0, ", ", v1, ", ", v2, ", ", v3) - #print(n1) self.verts.append_array([v0, v1, v2, v1, v3, v2]) self.normals.append_array([n1, n1, n1, n1, n1, n1]) diff --git a/demo/scripts/geometry/geopolytriangulization.gd b/demo/scripts/geometry/geopolytriangulization.gd index 7665701..b3c4536 100644 --- a/demo/scripts/geometry/geopolytriangulization.gd +++ b/demo/scripts/geometry/geopolytriangulization.gd @@ -4,37 +4,8 @@ extends Node # a triangulized Polygon with holes class_name GeoPolyTriangulization -# triangles are made by 3 vector -const CHUNK_RESULT: int = 3 - -# the boundary polygon -var _outer_polygon: PackedVector2Array - -# inner polygons (holes) -var _inner_polygons: Array[PackedVector2Array] - -# all vector points defined by outer and inner polygons -var vectors: PackedVector2Array = [] - -var _edge_safety: float = 0.0 - -func _init(outer_polygon: PackedVector2Array, inner_polygons: Array[PackedVector2Array], edge_safety: float): - self._outer_polygon = outer_polygon - self._edge_safety = edge_safety - - # calulate boundary - var hole_boundary = Geometry2D.offset_polygon(outer_polygon, self._edge_safety)[0] - - for ip in inner_polygons: - var new_hole = Geometry2D.intersect_polygons(hole_boundary, ip)[0] - self._inner_polygons.append(new_hole) - - self.vectors.append_array(self._outer_polygon) - - for inner_polygon in self._inner_polygons: - self.vectors.append_array(inner_polygon) - # returns all triangles generated by ear clipping the outer and inner polygons -func triangulate() -> PackedInt32Array: +static func triangulate(outer_polygon: PackedVector2Array, inner_polygons: Array[PackedVector2Array]) -> PackedInt32Array: var triag = Triangulization.new() - return triag.triangulate_with_holes(self._outer_polygon, self._inner_polygons) + var result = triag.triangulate_with_holes(outer_polygon, inner_polygons) + return result diff --git a/demo/scripts/test.gd b/demo/scripts/test.gd deleted file mode 100644 index ee2d940..0000000 --- a/demo/scripts/test.gd +++ /dev/null @@ -1,29 +0,0 @@ -extends Node - -@onready var meshInstance = MeshInstance3D.new() - -var lines = [] - -func _process(delta: float) -> void: - for l in lines: - DebugDraw3D.draw_lines(l, Color.DARK_RED) - - meshInstance.rotate(Vector3.UP, (0.9 * delta)) - meshInstance.rotate(Vector3.RIGHT, (0.9 * delta)) - -func _ready() -> void: - var p1 = [Vector2(4, 0), Vector2(4, 4), Vector2(0, 4), Vector2(0, 0)] - var p2 = [Vector2(0, 2), Vector2(0, 1), Vector2(2, 1), Vector2(2, 2)] - var p3 = [Vector2(2, 2), Vector2(2, 1), Vector2(4, 1), Vector2(4, 2)] - - - var trianglizationInstance = GeoPolyTriangulization.new(PackedVector2Array(p1), [PackedVector2Array(p2), PackedVector2Array(p3)], -0.001) - var vector_indexes = trianglizationInstance.triangulate() - var vectors_points = trianglizationInstance.vectors - - var meshGenerator = GeoPolyMesh.new(vector_indexes, vectors_points) - var mesh = meshGenerator.commit_mesh() - - meshInstance.mesh = mesh - - add_child(meshInstance) diff --git a/demo/scripts/test.gd.uid b/demo/scripts/test.gd.uid deleted file mode 100644 index 43a4476..0000000 --- a/demo/scripts/test.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://dysaws7hlg4td