Level Setup

Integrated with Trenchbroom
This commit is contained in:
2025-11-13 19:04:33 -05:00
parent 14000f0096
commit 9f5183bed2
572 changed files with 15202 additions and 766 deletions

View File

@@ -0,0 +1,24 @@
extends Node
class_name Cutter
static func circleCutter(num_sides = 8, perimerter_length = 0.1) -> 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

View File

@@ -0,0 +1 @@
uid://cd5la8wwjk34w

View File

@@ -0,0 +1,180 @@
@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 var edge_non_fracture: float = 0.1
@onready var parentNode = get_node("..")
var outer_polygon: PackedVector2Array
var inner_polygons: Array[PackedVector2Array]
var static_body: StaticBody3D
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 _ready() -> void:
if Engine.is_editor_hint():
return
outer_polygon = verts2d
_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, extrusion_direction, depth)
var commited_mesh = meshGenerator.commit_mesh(meshInstance3d.get_active_material(0))
meshInstance3d.mesh = commited_mesh
meshInstance3d.position += depth_position_offset
meshInstance3d.create_trimesh_collision()
static_body = _find_static_body()
func _re_draw():
meshInstance3d.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, extrusion_direction, depth)
var commited_mesh = meshGenerator.commit_mesh(meshInstance3d.get_active_material(0))
meshInstance3d.mesh = commited_mesh
meshInstance3d.create_trimesh_collision()
static_body = _find_static_body()
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 values_to_remove = []
# 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 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:
inner_polygons.erase(i)
inner_polygons.append(merged)
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

View File

@@ -0,0 +1 @@
uid://42cwsrh6jyns

View File

@@ -0,0 +1,259 @@
extends Node
class_name GeoPolyMesh
var surface_array = []
var verts = PackedVector3Array()
var uvs = PackedVector2Array()
var normals = PackedVector3Array()
var array_mesh = ArrayMesh.new()
var _v_indexes = []
var _v_points = []
var edges = {}
var depth_vector: Vector3
var extrude_direction: DestructableWall.ExtrudeDirection
func _init(vector_indexes: PackedInt32Array, vector_points: PackedVector2Array, extrude_direction: DestructableWall.ExtrudeDirection, 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
self.extrude_direction = extrude_direction
if extrude_direction == DestructableWall.ExtrudeDirection.BACK:
for point in vector_points:
self._v_points.append(Vector3(point.x, point.y, 0))
depth_vector = Vector3.BACK * depth
elif extrude_direction == DestructableWall.ExtrudeDirection.RIGHT:
for point in vector_points:
self._v_points.append(Vector3(0, point.y, point.x))
depth_vector = -Vector3.RIGHT * depth
elif extrude_direction == DestructableWall.ExtrudeDirection.UP:
for point in vector_points:
self._v_points.append(Vector3(point.x, 0, point.y))
depth_vector = Vector3.DOWN * depth
self._pre_process_edges()
self._draw_mesh()
self._extrude_mesh()
self._draw_sides()
func calculate_area(mesh_vertices: PackedVector2Array) -> float:
var result := 0.0
var num_vertices := mesh_vertices.size()
for q in range(num_vertices):
var p = (q - 1 + num_vertices) % num_vertices
result += mesh_vertices[q].cross(mesh_vertices[p])
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)
for vertices in faces:
for v_inx in range(len(vertices)):
var vertexA = vertices[v_inx]
var vertexB = vertices[(v_inx+1) % 3]
var min_index = min(vertexA, vertexB)
var max_index = max(vertexA, vertexB)
var edge = self.edges.get([min_index, max_index])
if !edge:
self.edges[[min_index, max_index]] = 1
else:
self.edges[[min_index, max_index]] += 1
func get_loops():
var unvisted_edges = self.get_outline_edge()
var loops = []
while len(unvisted_edges) != 0:
var loop = []
loop.append_array(unvisted_edges.pop_back())
var v_next = loop.back()
while loop[0] != v_next:
for ue_ind in range(len(unvisted_edges)):
var v1 = unvisted_edges[ue_ind][0]
var v2 = unvisted_edges[ue_ind][1]
if v_next == v1:
loop.append(v2)
unvisted_edges.pop_at(ue_ind)
v_next = loop.back()
break
elif v_next == v2:
loop.append(v1)
unvisted_edges.pop_at(ue_ind)
v_next = loop.back()
break
loops.append(loop)
var vector2_loops = []
for loop in loops:
var vector2_loop = []
for ind in loop:
var point = self._v_points[ind]
if extrude_direction == DestructableWall.ExtrudeDirection.BACK:
vector2_loop.append(Vector2(point.x, point.y))
elif extrude_direction == DestructableWall.ExtrudeDirection.RIGHT:
vector2_loop.append(Vector2(point.z, point.y))
elif extrude_direction == DestructableWall.ExtrudeDirection.UP:
vector2_loop.append(Vector2(point.x, point.z))
vector2_loops.append(vector2_loop)
var area = []
for vector2_loop in vector2_loops:
var result = self.calculate_area(vector2_loop)
area.append(result)
# reorder the loops where outer boundary is the first one
var index = 0
var index_value = area[0]
for ind in range(len(area)):
var val = abs(area[ind])
if val > index_value:
index = ind
index_value = val
var bloop = loops.pop_at(index)
var barea = area.pop_at(index)
loops.push_front(bloop)
area.push_front(barea)
# check for CW or CCW
if area[0] > 0: # Outer boundary should be CW, if area is <0 then it is CCW
loops[0].reverse()
for area_index in range(1, len(area)):
var a = area[area_index]
if a < 0: # all inside loops need to rendered in CCW, if + then it is CW
loops[area_index].reverse()
return loops
func get_outline_edge():
var outline_edges = []
for e in self.edges:
var value = self.edges[e]
if value == 1:
outline_edges.append(e)
return outline_edges
func _calc_triangle_normal(a: Vector3, b: Vector3, c: Vector3):
return ((b-a).cross(c-a)).normalized()
func _draw_sides():
var loops = get_loops()
for l in loops:
var outline_edges_ordered = l
for outline_vector_ind in range(len(outline_edges_ordered) - 1):
var v0 = self._v_points[outline_edges_ordered[outline_vector_ind]]
var v1 = self._v_points[outline_edges_ordered[outline_vector_ind + 1]]
var v2 = self._v_points[outline_edges_ordered[outline_vector_ind]] + depth_vector
var v3 = self._v_points[outline_edges_ordered[outline_vector_ind + 1]] + depth_vector
#print("v0: ", v0, " v1: ", v1, " v2: ", v2, " v3: ", v3)
var n1 = self._calc_triangle_normal(v2, v1, v0)
self.verts.append_array([v0, v1, v2, v1, v3, v2])
_append_uvs4(v0, v1, v2, v3, n1)
self.normals.append_array([n1, n1, n1, n1, n1, n1])
func _append_uvs4(v0: Vector3, v1: Vector3, v2: Vector3, v3: Vector3, normal: Vector3):
if normal == Vector3.BACK or normal == Vector3.FORWARD:
self.uvs.append_array([Vector2(-v0.x, -v0.y), Vector2(-v1.x, -v1.y), Vector2(-v2.x, -v2.y), Vector2(-v1.x, -v1.y), Vector2(-v3.x, -v3.y), Vector2(-v2.x, -v2.y)])
return
elif normal == Vector3.RIGHT or normal == Vector3.LEFT:
self.uvs.append_array([Vector2(-v0.z, -v0.y), Vector2(-v1.z, -v1.y), Vector2(-v2.z, -v2.y), Vector2(-v1.z, -v1.y), Vector2(-v3.z, -v3.y), Vector2(-v2.z, -v2.y)])
return
elif normal == Vector3.UP or normal == Vector3.DOWN:
self.uvs.append_array([Vector2(-v0.x, -v0.z), Vector2(-v1.x, -v1.z), Vector2(-v2.x, -v2.z), Vector2(-v1.x, -v1.z), Vector2(-v3.x, -v3.z), Vector2(-v2.x, -v2.z)])
return
self.uvs.append_array([Vector2(-v0.x, -v0.y), Vector2(-v1.x, -v1.y), Vector2(-v2.x, -v2.y), Vector2(-v1.x, -v1.y), Vector2(-v3.x, -v3.y), Vector2(-v2.x, -v2.y)])
func _append_uvs3(v0: Vector3, v1: Vector3, v2: Vector3, normal: Vector3):
if normal == Vector3.BACK or normal == Vector3.FORWARD:
self.uvs.append_array([Vector2(-v0.x, -v0.y), Vector2(-v1.x, -v1.y), Vector2(-v2.x, -v2.y)])
return
elif normal == Vector3.RIGHT or normal == Vector3.LEFT:
self.uvs.append_array([Vector2(-v0.z, -v0.y), Vector2(-v1.z, -v1.y), Vector2(-v2.z, -v2.y)])
return
elif normal == Vector3.UP or normal == Vector3.DOWN:
self.uvs.append_array([Vector2(-v0.x, -v0.z), Vector2(-v1.x, -v1.z), Vector2(-v2.x, -v2.z)])
return
self.uvs.append_array([Vector2(-v0.x, -v0.y), Vector2(-v1.x, -v1.y), Vector2(-v2.x, -v2.y)])
# front face
func _draw_mesh():
var vector3A = self._v_points[self._v_indexes[0]]
var vector3B = self._v_points[self._v_indexes[1]]
var vector3C = self._v_points[self._v_indexes[2]]
var normal = self._calc_triangle_normal(vector3C, vector3B, vector3A)
self.verts.append_array([vector3A, vector3B, vector3C])
_append_uvs3(vector3A, vector3B, vector3C, normal)
self.normals.append_array([normal, normal, normal])
# insert each front face into the mesh "clockwise"
for index in range(3, len(self._v_indexes) - 1, 3):
vector3A = self._v_points[self._v_indexes[index]]
vector3B = self._v_points[self._v_indexes[index+1]]
vector3C = self._v_points[self._v_indexes[index+2]]
self.verts.append_array([vector3A, vector3B, vector3C])
_append_uvs3(vector3A, vector3B, vector3C, normal)
self.normals.append_array([normal, normal, normal])
#
## back face
func _extrude_mesh():
var vector3A = self._v_points[self._v_indexes[len(self._v_indexes) - 1]]
var vector3B = self._v_points[self._v_indexes[len(self._v_indexes) - 2]]
var vector3C = self._v_points[self._v_indexes[len(self._v_indexes) - 3]]
var normal = self._calc_triangle_normal(vector3C, vector3B, vector3A)
vector3A = self._v_points[self._v_indexes[len(self._v_indexes) - 1]] + depth_vector
vector3B = self._v_points[self._v_indexes[len(self._v_indexes) - 2]] + depth_vector
vector3C = self._v_points[self._v_indexes[len(self._v_indexes) - 3]] + depth_vector
self.verts.append_array([vector3A, vector3B, vector3C])
_append_uvs3(vector3A, vector3B, vector3C, normal)
self.normals.append_array([normal, normal, normal])
# insert each back face into the mesh "counter-clockwise" extruded
for index in range(len(self._v_indexes) - 4, -1, -3):
vector3A = self._v_points[self._v_indexes[index]] + depth_vector
vector3B = self._v_points[self._v_indexes[index-1]] + depth_vector
vector3C = self._v_points[self._v_indexes[index-2]] + depth_vector
self.verts.append_array([vector3A, vector3B, vector3C])
_append_uvs3(vector3A, vector3B, vector3C, normal)
self.normals.append_array([normal, normal, normal])
func commit_mesh(mat) -> Mesh:
surface_array[Mesh.ARRAY_VERTEX] = verts
surface_array[Mesh.ARRAY_NORMAL] = normals
surface_array[Mesh.ARRAY_TEX_UV] = uvs
#print("VERTS: ", self.verts)
array_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, surface_array)
array_mesh.surface_set_material(0, mat)
return array_mesh

View File

@@ -0,0 +1 @@
uid://c61g8fppm366c

View File

@@ -0,0 +1,11 @@
extends Node
# provides all the utility functions for generating
# a triangulized Polygon with holes
class_name GeoPolyTriangulization
# returns all triangles generated by ear clipping the outer and inner polygons
static func triangulate(outer_polygon: PackedVector2Array, inner_polygons: Array[PackedVector2Array]) -> PackedInt32Array:
var triag = Triangulization.new()
var result = triag.triangulate_with_holes(outer_polygon, inner_polygons)
return result

View File

@@ -0,0 +1 @@
uid://comj14x4uckt2

View File

@@ -0,0 +1,16 @@
extends Object
class_name UTIL
# chunks array into 'chunk_size' pieces
static func chunk_array(arr: Array, chunk_size: int) -> Array:
var result_chunks = []
var i = 0
while i < arr.size():
var chunk = arr.slice(i, i + chunk_size)
result_chunks.append(chunk)
i += chunk_size
return result_chunks
static func vector2_to_vector3(v: Vector2) -> Vector3:
return Vector3(v.x, v.y, 0)

View File

@@ -0,0 +1 @@
uid://bjv35mc6440ea