scopedvr-map-editor/addons/modular_builder/modular_roof_support.gd

72 lines
3.6 KiB
GDScript

@tool
extends RefCounted
# Find the roof outline from two diagonal corners at the top
# of parallel walls, even when the structure is rotated around Y.
static func find_corners(root: ModularBuilderPiece, first_world: Vector3, second_world: Vector3, allow_center: bool = false) -> PackedVector3Array:
var first_edges := _matching_top_edges(root, first_world, allow_center)
var second_edges := _matching_top_edges(root, second_world, allow_center)
var best_corners := PackedVector3Array()
var best_score := INF
var best_length := 0.0
for first_edge in first_edges:
for second_edge in second_edges:
if first_edge["wall"] == second_edge["wall"]:
continue
var a: Vector3 = first_edge["selected"]
var adjacent_a: Vector3 = first_edge["other"]
var c: Vector3 = second_edge["selected"]
var adjacent_c: Vector3 = second_edge["other"]
var edge_a := Vector2(adjacent_a.x - a.x, adjacent_a.z - a.z)
var edge_c := Vector2(adjacent_c.x - c.x, adjacent_c.z - c.z)
var length_a := edge_a.length()
var length_c := edge_c.length()
if length_a < 0.2 or length_c < 0.2:
continue
if edge_a.normalized().dot(edge_c.normalized()) > -0.9:
continue
var center_a := (a + adjacent_a) * 0.5
var center_c := (c + adjacent_c) * 0.5
if Vector2(center_c.x - center_a.x, center_c.z - center_a.z).length() < 0.2:
continue
var diagonal_error := (a + c - adjacent_a - adjacent_c).length()
var length_error := absf(length_a - length_c)
var tolerance := maxf(0.12, maxf(first_edge["thickness"], second_edge["thickness"]) * 0.5)
if diagonal_error > tolerance or length_error > tolerance:
continue
var score := diagonal_error + length_error
if score < best_score - 0.01 or (absf(score - best_score) <= 0.01 and length_a > best_length):
best_score = score
best_length = length_a
best_corners = PackedVector3Array([a, adjacent_a, c, adjacent_c])
return best_corners
static func _matching_top_edges(root: ModularBuilderPiece, world_point: Vector3, allow_center: bool) -> Array[Dictionary]:
var result: Array[Dictionary] = []
var walls: Array[ModularBuilderPiece] = []
_collect_walls(root, walls)
for wall in walls:
var endpoints: PackedVector3Array = wall.call("_get_centered_wall_points") if wall.center_align else wall.custom_points
if endpoints.size() < 2:
continue
var direction := endpoints[1] - endpoints[0]
var horizontal := Vector2(direction.x, direction.z)
if horizontal.length() < 0.2:
continue
var side := Vector3(-horizontal.y, 0.0, horizontal.x).normalized() * wall.thickness * 0.5
for offset in [side, -side]:
var top_a := wall.to_global(endpoints[0] + Vector3.UP * wall.unit_size + offset)
var top_b := wall.to_global(endpoints[1] + Vector3.UP * wall.unit_size + offset)
var center_a := wall.to_global(endpoints[0] + Vector3.UP * wall.unit_size)
var center_b := wall.to_global(endpoints[1] + Vector3.UP * wall.unit_size)
if top_a.distance_to(world_point) <= 0.08 or (allow_center and center_a.distance_to(world_point) <= 0.08):
result.append({"wall": wall, "selected": root.to_local(top_a), "other": root.to_local(top_b), "thickness": wall.thickness})
if top_b.distance_to(world_point) <= 0.08 or (allow_center and center_b.distance_to(world_point) <= 0.08):
result.append({"wall": wall, "selected": root.to_local(top_b), "other": root.to_local(top_a), "thickness": wall.thickness})
return result
static func _collect_walls(node: Node, result: Array[ModularBuilderPiece]) -> void:
if node is ModularBuilderPiece and node.piece_type == ModularBuilderPiece.PieceType.WALL:
result.append(node)
for child in node.get_children():
_collect_walls(child, result)