727 lines
35 KiB
GDScript
727 lines
35 KiB
GDScript
@tool
|
|
extends "res://addons/modular_builder/modular_builder_editor_details.gd"
|
|
|
|
const MESH_PICKER = preload("res://addons/modular_builder/modular_mesh_picker.gd")
|
|
|
|
func _sync_vertex_markers() -> void:
|
|
var root := _get_selected_piece()
|
|
var vertex_points: Array[Vector3] = []
|
|
var side_points: Array[Dictionary] = []
|
|
if root != null:
|
|
if active_tool == TOOL_WALL or active_tool == TOOL_GABLE_WALL or active_tool == TOOL_GABLE_WALL_RIGHT or active_tool == TOOL_FLOOR_SQUARE or active_tool == TOOL_FLOOR_TRIANGLE or active_tool == TOOL_ROOF_GABLE or active_tool == TOOL_ROOF_SHED or active_tool == TOOL_ROOF_PYRAMID or active_tool == TOOL_STAIRS or active_tool == TOOL_FENCE or active_tool == TOOL_PILLAR or active_tool == TOOL_MERGE_VERTICES or active_tool == TOOL_CREATE_VERTEX or active_tool == TOOL_MOVE_VERTEX or active_tool == TOOL_REMOVE_VERTEX:
|
|
vertex_points = _get_tool_vertices(root)
|
|
elif active_tool == TOOL_FOUNDATION:
|
|
side_points = _get_side_button_points(root)
|
|
if root != _marker_root or (root == null and _marker_container != null) or vertex_points.size() != _marker_point_nodes.size() or side_points.size() != _side_button_nodes.size():
|
|
_clear_marker_container()
|
|
_marker_root = root
|
|
if root == null:
|
|
return
|
|
_marker_container = Node3D.new()
|
|
_marker_container.name = "ModularBuilderMarkers"
|
|
root.add_child(_marker_container)
|
|
for _point in vertex_points:
|
|
var marker := Label3D.new()
|
|
marker.text = "●"
|
|
marker.font_size = 56
|
|
marker.pixel_size = 0.004
|
|
marker.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
marker.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
marker.billboard = BaseMaterial3D.BILLBOARD_ENABLED
|
|
marker.no_depth_test = true
|
|
marker.modulate = Color(0.05, 0.58, 1.0)
|
|
marker.outline_size = 8
|
|
marker.outline_modulate = Color.BLACK
|
|
_marker_container.add_child(marker)
|
|
_marker_point_nodes.append(marker)
|
|
for _button in side_points:
|
|
var label := Label3D.new()
|
|
label.text = "+"
|
|
label.font_size = 56
|
|
label.outline_size = 8
|
|
label.outline_modulate = Color.BLACK
|
|
label.pixel_size = 0.01
|
|
label.billboard = BaseMaterial3D.BILLBOARD_ENABLED
|
|
label.no_depth_test = true
|
|
label.modulate = Color(0.2, 1.0, 0.55)
|
|
_marker_container.add_child(label)
|
|
_side_button_nodes.append(label)
|
|
for i in range(vertex_points.size()):
|
|
_marker_point_nodes[i].position = root.to_local(vertex_points[i])
|
|
var is_pending := false
|
|
for pending_point in pending_points:
|
|
if pending_point.distance_to(vertex_points[i]) < 0.025:
|
|
is_pending = true
|
|
break
|
|
_marker_point_nodes[i].modulate = Color(1.0, 0.88, 0.08) if is_pending else Color(0.05, 0.58, 1.0)
|
|
for i in range(side_points.size()):
|
|
_side_button_nodes[i].position = root.to_local(side_points[i].position)
|
|
if _marker_container:
|
|
_marker_container.visible = active_tool != TOOL_NONE or not side_points.is_empty()
|
|
|
|
func _clear_marker_container() -> void:
|
|
if is_instance_valid(_marker_container):
|
|
_marker_container.queue_free()
|
|
_marker_container = null
|
|
_marker_root = null
|
|
_marker_point_nodes.clear()
|
|
_side_button_nodes.clear()
|
|
|
|
func _get_side_button_points(root: ModularBuilderPiece) -> Array[Dictionary]:
|
|
var result: Array[Dictionary] = []
|
|
for foundation in _get_foundations(root):
|
|
var top_y := foundation.thickness * 0.5 if foundation.centered_origin else foundation.thickness
|
|
var y := top_y + 0.22
|
|
var points := _foundation_polygon(foundation)
|
|
var centroid := Vector2.ZERO
|
|
for point in points:
|
|
centroid += point
|
|
centroid /= points.size()
|
|
for i in range(points.size()):
|
|
var a: Vector2 = points[i]
|
|
var b: Vector2 = points[(i + 1) % points.size()]
|
|
if _foundation_edge_is_occupied(root, foundation, a, b):
|
|
continue
|
|
var midpoint := (a + b) * 0.5
|
|
var outward := (midpoint - centroid).normalized()
|
|
var edge_length := a.distance_to(b)
|
|
var neighbor_polygon: Array[Vector2] = []
|
|
if _foundation_type_selected == ModularBuilderPiece.PieceType.FOUNDATION_SQUARE:
|
|
var square_side := minf(foundation.unit_size, edge_length)
|
|
var edge_direction := (b - a).normalized()
|
|
var square_a := midpoint - edge_direction * square_side * 0.5
|
|
var square_b := midpoint + edge_direction * square_side * 0.5
|
|
neighbor_polygon = [square_a, square_b, square_b + outward * square_side, square_a + outward * square_side]
|
|
elif points.size() == 3:
|
|
for source_point in points:
|
|
neighbor_polygon.append(_reflect_point_across_edge(source_point, a, b))
|
|
else:
|
|
neighbor_polygon = [a, b, a + outward * edge_length]
|
|
var custom_points := PackedVector3Array()
|
|
for neighbor_point in neighbor_polygon:
|
|
var base_y := -foundation.thickness * 0.5 if foundation.centered_origin else 0.0
|
|
var foundation_local := Vector3(neighbor_point.x, base_y, neighbor_point.y)
|
|
custom_points.append(root.to_local(foundation.to_global(foundation_local)))
|
|
result.append({
|
|
"position": foundation.to_global(Vector3(midpoint.x + outward.x * 0.55, y, midpoint.y + outward.y * 0.55)),
|
|
"custom_points": custom_points,
|
|
"foundation": foundation
|
|
})
|
|
return result
|
|
|
|
func _foundation_edge_is_occupied(root: ModularBuilderPiece, foundation: ModularBuilderPiece, edge_a: Vector2, edge_b: Vector2) -> bool:
|
|
var foundation_base_y := -foundation.thickness * 0.5 if foundation.centered_origin else 0.0
|
|
var edge_a_3d := root.to_local(foundation.to_global(Vector3(edge_a.x, foundation_base_y, edge_a.y)))
|
|
var edge_b_3d := root.to_local(foundation.to_global(Vector3(edge_b.x, foundation_base_y, edge_b.y)))
|
|
var target_a := Vector2(edge_a_3d.x, edge_a_3d.z)
|
|
var target_b := Vector2(edge_b_3d.x, edge_b_3d.z)
|
|
for other in _get_foundations(root):
|
|
if other == foundation:
|
|
continue
|
|
var other_polygon := _foundation_polygon(other)
|
|
for i in range(other_polygon.size()):
|
|
var a := other_polygon[i]
|
|
var b := other_polygon[(i + 1) % other_polygon.size()]
|
|
var other_base_y := -other.thickness * 0.5 if other.centered_origin else 0.0
|
|
var a_in_root := root.to_local(other.to_global(Vector3(a.x, other_base_y, a.y)))
|
|
var b_in_root := root.to_local(other.to_global(Vector3(b.x, other_base_y, b.y)))
|
|
var other_a := Vector2(a_in_root.x, a_in_root.z)
|
|
var other_b := Vector2(b_in_root.x, b_in_root.z)
|
|
if absf(a_in_root.y - edge_a_3d.y) < 0.02 and absf(b_in_root.y - edge_b_3d.y) < 0.02 and _segments_overlap(target_a, target_b, other_a, other_b):
|
|
return true
|
|
return false
|
|
|
|
func _get_foundations(root: ModularBuilderPiece) -> Array[ModularBuilderPiece]:
|
|
var result: Array[ModularBuilderPiece] = []
|
|
if root.piece_type == ModularBuilderPiece.PieceType.FOUNDATION_SQUARE or root.piece_type == ModularBuilderPiece.PieceType.FOUNDATION_TRIANGLE:
|
|
result.append(root)
|
|
for child in root.get_children():
|
|
_collect_foundations(child, result)
|
|
return result
|
|
|
|
func _collect_foundations(node: Node, result: Array[ModularBuilderPiece]) -> void:
|
|
if node is ModularBuilderPiece and (node.piece_type == ModularBuilderPiece.PieceType.FOUNDATION_SQUARE or node.piece_type == ModularBuilderPiece.PieceType.FOUNDATION_TRIANGLE):
|
|
result.append(node)
|
|
for child in node.get_children():
|
|
_collect_foundations(child, result)
|
|
|
|
func _reflect_point_across_edge(point: Vector2, a: Vector2, b: Vector2) -> Vector2:
|
|
var edge := b - a
|
|
var projection := a + edge * ((point - a).dot(edge) / edge.length_squared())
|
|
return projection * 2.0 - point
|
|
|
|
func _nearest_side_button(camera: Camera3D, screen_position: Vector2) -> int:
|
|
var root := _get_selected_piece()
|
|
if root == null:
|
|
return -1
|
|
var buttons := _get_side_button_points(root)
|
|
var closest := MARKER_HIT_RADIUS + 5.0
|
|
var result := -1
|
|
for i in range(buttons.size()):
|
|
var point: Vector3 = buttons[i].position
|
|
if camera.is_position_behind(point):
|
|
continue
|
|
var distance := camera.unproject_position(point).distance_to(screen_position)
|
|
if distance < closest:
|
|
closest = distance
|
|
result = i
|
|
return result
|
|
|
|
func _add_foundation_at_side(root: ModularBuilderPiece, side: int) -> void:
|
|
var buttons := _get_side_button_points(root)
|
|
if side < 0 or side >= buttons.size():
|
|
return
|
|
var scene_root := get_editor_interface().get_edited_scene_root() as Node3D
|
|
var piece := Node3D.new()
|
|
piece.name = "Foundation"
|
|
piece.set_script(PIECE_SCRIPT)
|
|
var source_foundation: ModularBuilderPiece = buttons[side].foundation
|
|
piece.set("unit_size", source_foundation.unit_size)
|
|
piece.set("thickness", source_foundation.thickness)
|
|
piece.set("centered_origin", true)
|
|
piece.set("piece_type", _foundation_type_selected)
|
|
var points_in_root: PackedVector3Array = buttons[side].custom_points
|
|
var pivot := _packed_points_bounds_center(points_in_root)
|
|
pivot.y += source_foundation.thickness * 0.5
|
|
var custom_points := PackedVector3Array()
|
|
for point in points_in_root:
|
|
custom_points.append(point - pivot)
|
|
piece.position = pivot
|
|
var undo := get_undo_redo()
|
|
undo.create_action("Add foundation to selected side")
|
|
undo.add_do_method(root, "add_child", piece, true)
|
|
undo.add_do_property(piece, "custom_points", custom_points)
|
|
undo.add_do_property(piece, "owner", scene_root)
|
|
undo.add_undo_method(root, "remove_child", piece)
|
|
undo.commit_action()
|
|
_last_created_piece = piece
|
|
status_label.text = "Foundation added to structure."
|
|
|
|
func _packed_points_bounds_center(points: PackedVector3Array) -> Vector3:
|
|
if points.is_empty():
|
|
return Vector3.ZERO
|
|
var minimum := points[0]
|
|
var maximum := points[0]
|
|
for point in points:
|
|
minimum = minimum.min(point)
|
|
maximum = maximum.max(point)
|
|
return (minimum + maximum) * 0.5
|
|
|
|
func _get_tool_vertices(root: ModularBuilderPiece) -> Array[Vector3]:
|
|
var result: Array[Vector3] = []
|
|
if active_tool == TOOL_MERGE_VERTICES:
|
|
_collect_all_connection_vertices(root, result)
|
|
elif active_tool == TOOL_CREATE_VERTEX or active_tool == TOOL_MOVE_VERTEX or active_tool == TOOL_REMOVE_VERTEX:
|
|
_collect_all_connection_vertices(root, result)
|
|
elif active_tool == TOOL_STAIRS:
|
|
_collect_stair_vertices(root, result)
|
|
elif active_tool == TOOL_FENCE or active_tool == TOOL_PARAPET:
|
|
_collect_fence_vertices(root, result)
|
|
elif active_tool == TOOL_PILLAR:
|
|
var surfaces: Array[ModularBuilderPiece] = []
|
|
_collect_surface_pieces(root, surfaces)
|
|
for piece in surfaces:
|
|
if piece.piece_type == ModularBuilderPiece.PieceType.BUILDING:
|
|
continue
|
|
var polygon := _piece_polygon_xz(piece) if piece.piece_type == ModularBuilderPiece.PieceType.FLOOR else _foundation_polygon(piece)
|
|
var top_y := piece.custom_points[0].y if piece.piece_type == ModularBuilderPiece.PieceType.FLOOR and not piece.custom_points.is_empty() else (piece.thickness * 0.5 if piece.centered_origin else piece.thickness)
|
|
var bottom_y := top_y - piece.thickness
|
|
for point in polygon:
|
|
_add_unique_vertex(result, piece.to_global(Vector3(point.x, top_y, point.y)))
|
|
_add_unique_vertex(result, piece.to_global(Vector3(point.x, bottom_y, point.y)))
|
|
var walls: Array[Node] = []
|
|
_collect_walls(root, walls)
|
|
for wall_node in walls:
|
|
var wall := wall_node as ModularBuilderPiece
|
|
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]).normalized()
|
|
var side := Vector3(-direction.z, 0.0, direction.x).normalized() * wall.thickness * 0.5
|
|
for endpoint in endpoints:
|
|
for offset in [Vector3.ZERO, Vector3.UP * wall.unit_size]:
|
|
_add_unique_vertex(result, wall.to_global(endpoint + offset + side))
|
|
_add_unique_vertex(result, wall.to_global(endpoint + offset - side))
|
|
_add_gable_wall_vertices(root, result)
|
|
elif active_tool == TOOL_WALL:
|
|
for foundation in _get_foundations(root):
|
|
var top_y := foundation.thickness * 0.5 if foundation.centered_origin else foundation.thickness
|
|
for point in _foundation_polygon(foundation):
|
|
_add_unique_vertex(result, foundation.to_global(Vector3(point.x, top_y, point.y)))
|
|
var surfaces: Array[ModularBuilderPiece] = []
|
|
_collect_surface_pieces(root, surfaces)
|
|
for surface in surfaces:
|
|
if surface.piece_type != ModularBuilderPiece.PieceType.FLOOR:
|
|
continue
|
|
for point in surface.custom_points:
|
|
_add_unique_vertex(result, surface.to_global(point))
|
|
var existing_walls: Array[Node] = []
|
|
_collect_walls(root, existing_walls)
|
|
for wall_node in existing_walls:
|
|
var wall := wall_node as ModularBuilderPiece
|
|
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]).normalized()
|
|
var side := Vector3(-direction.z, 0.0, direction.x) * wall.thickness * 0.5
|
|
for endpoint in endpoints:
|
|
var top := endpoint + Vector3.UP * wall.unit_size
|
|
_add_unique_vertex(result, wall.to_global(top + side))
|
|
_add_unique_vertex(result, wall.to_global(top - side))
|
|
_add_gable_wall_vertices(root, result)
|
|
else:
|
|
var walls: Array[Node] = []
|
|
_collect_walls(root, walls)
|
|
for wall_node in walls:
|
|
var wall := wall_node as ModularBuilderPiece
|
|
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]).normalized()
|
|
var side := Vector3(-direction.z, 0.0, direction.x).normalized() * float(wall.get("thickness")) * 0.5
|
|
for endpoint in endpoints:
|
|
if active_tool == TOOL_ROOF_GABLE or active_tool == TOOL_ROOF_SHED:
|
|
_add_unique_vertex(result, wall.to_global(endpoint + side))
|
|
_add_unique_vertex(result, wall.to_global(endpoint - side))
|
|
var top := endpoint + Vector3.UP * float(wall.get("unit_size"))
|
|
_add_unique_vertex(result, wall.to_global(top + side))
|
|
_add_unique_vertex(result, wall.to_global(top - side))
|
|
_add_gable_wall_vertices(root, result)
|
|
_collect_detail_connection_vertices(root, result)
|
|
for manual_vertex in _get_manual_vertices(root):
|
|
_add_unique_vertex(result, manual_vertex)
|
|
_collapse_merged_vertex_groups(root, result)
|
|
if _marker_scope_check != null and _marker_scope_check.button_pressed:
|
|
var scope_pieces := _get_marker_scope_pieces(root)
|
|
var scope_vertices: Array[Vector3] = []
|
|
for piece in scope_pieces:
|
|
_collect_piece_geometry_vertices(piece, scope_vertices)
|
|
for manual_vertex in _get_manual_vertices(piece):
|
|
_add_unique_vertex(scope_vertices, manual_vertex)
|
|
for selected_node in get_editor_interface().get_selection().get_selected_nodes():
|
|
if selected_node is ModularBuilderDetail:
|
|
_collect_detail_connection_vertices(selected_node, scope_vertices)
|
|
_collapse_merged_vertex_groups(root, scope_vertices)
|
|
var filtered: Array[Vector3] = []
|
|
for vertex in result:
|
|
for scope_vertex in scope_vertices:
|
|
if vertex.distance_to(scope_vertex) < 0.025:
|
|
_add_unique_vertex(filtered, vertex)
|
|
break
|
|
result = filtered
|
|
if _simplify_vertices_check != null and _simplify_vertices_check.button_pressed and active_tool != TOOL_MERGE_VERTICES and active_tool != TOOL_CREATE_VERTEX and active_tool != TOOL_MOVE_VERTEX and active_tool != TOOL_REMOVE_VERTEX:
|
|
result = _simplify_tool_vertices(root, result)
|
|
return result
|
|
|
|
func _simplify_tool_vertices(root: ModularBuilderPiece, vertices: Array[Vector3]) -> Array[Vector3]:
|
|
var replacements := {}
|
|
var pieces: Array[ModularBuilderPiece] = []
|
|
_collect_all_modular_pieces(root, pieces)
|
|
for piece in pieces:
|
|
if piece.piece_type != ModularBuilderPiece.PieceType.FOUNDATION_SQUARE and piece.piece_type != ModularBuilderPiece.PieceType.FOUNDATION_TRIANGLE and piece.piece_type != ModularBuilderPiece.PieceType.FLOOR and piece.piece_type != ModularBuilderPiece.PieceType.WALL and piece.piece_type != ModularBuilderPiece.PieceType.GABLE_WALL and piece.piece_type != ModularBuilderPiece.PieceType.GABLE_WALL_RIGHT:
|
|
continue
|
|
var piece_vertices: Array[Vector3] = []
|
|
_collect_piece_geometry_vertices(piece, piece_vertices)
|
|
for original in piece_vertices:
|
|
var key := _vertex_position_key(original)
|
|
if not replacements.has(key):
|
|
replacements[key] = VERTEX_SIMPLIFIER.piece_vertex(piece, original)
|
|
_add_pillar_simplification_candidates(root, replacements)
|
|
var result: Array[Vector3] = []
|
|
for original in vertices:
|
|
_add_unique_vertex(result, replacements.get(_vertex_position_key(original), original))
|
|
return result
|
|
|
|
func _add_pillar_simplification_candidates(node: Node, replacements: Dictionary) -> void:
|
|
if node is ModularBuilderDetail:
|
|
var detail := node as ModularBuilderDetail
|
|
if detail.detail_type == ModularBuilderDetail.DetailType.PILLAR:
|
|
var half := detail.width * 0.5
|
|
for x in [-half, half]:
|
|
for y in [-detail.height * 0.5, detail.height * 0.5]:
|
|
for z in [-half, half]:
|
|
var original := detail.to_global(Vector3(x, y, z))
|
|
var key := _vertex_position_key(original)
|
|
if not replacements.has(key):
|
|
replacements[key] = VERTEX_SIMPLIFIER.pillar_vertex(detail, original)
|
|
for child in node.get_children():
|
|
_add_pillar_simplification_candidates(child, replacements)
|
|
|
|
func _vertex_position_key(point: Vector3) -> Vector3i:
|
|
return Vector3i(roundi(point.x * 1000.0), roundi(point.y * 1000.0), roundi(point.z * 1000.0))
|
|
|
|
func _collect_stair_vertices(root: ModularBuilderPiece, vertices: Array[Vector3]) -> void:
|
|
var surfaces: Array[ModularBuilderPiece] = []
|
|
_collect_surface_pieces(root, surfaces)
|
|
for surface in surfaces:
|
|
var polygon := _piece_polygon_xz(surface) if surface.piece_type == ModularBuilderPiece.PieceType.FLOOR else _foundation_polygon(surface)
|
|
var y := surface.thickness * 0.5 if surface.centered_origin else surface.thickness
|
|
for point in polygon:
|
|
_add_unique_vertex(vertices, surface.to_global(Vector3(point.x, y, point.y)))
|
|
var walls: Array[Node] = []
|
|
_collect_walls(root, walls)
|
|
for wall_node in walls:
|
|
var wall := wall_node as ModularBuilderPiece
|
|
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]).normalized()
|
|
var side := Vector3(-direction.z, 0.0, direction.x) * wall.thickness * 0.5
|
|
for endpoint in endpoints:
|
|
var top := endpoint + Vector3.UP * wall.unit_size
|
|
_add_unique_vertex(vertices, wall.to_global(top + side))
|
|
_add_unique_vertex(vertices, wall.to_global(top - side))
|
|
_add_gable_wall_vertices(root, vertices)
|
|
|
|
func _collect_fence_vertices(root: ModularBuilderPiece, vertices: Array[Vector3]) -> void:
|
|
var surfaces: Array[ModularBuilderPiece] = []
|
|
_collect_surface_pieces(root, surfaces)
|
|
for surface in surfaces:
|
|
var polygon := _piece_polygon_xz(surface) if surface.piece_type == ModularBuilderPiece.PieceType.FLOOR else _foundation_polygon(surface)
|
|
var top_y := surface.thickness * 0.5 if surface.centered_origin else surface.thickness
|
|
for point in polygon:
|
|
_add_unique_vertex(vertices, surface.to_global(Vector3(point.x, top_y, point.y)))
|
|
var walls: Array[Node] = []
|
|
_collect_walls(root, walls)
|
|
for wall_node in walls:
|
|
var wall := wall_node as ModularBuilderPiece
|
|
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]).normalized()
|
|
var side := Vector3(-direction.z, 0.0, direction.x) * wall.thickness * 0.5
|
|
for endpoint in endpoints:
|
|
for height in [0.0, wall.unit_size]:
|
|
_add_unique_vertex(vertices, wall.to_global(endpoint + Vector3.UP * height + side))
|
|
_add_unique_vertex(vertices, wall.to_global(endpoint + Vector3.UP * height - side))
|
|
_add_gable_wall_vertices(root, vertices)
|
|
|
|
func _get_marker_scope_pieces(root: ModularBuilderPiece) -> Array[ModularBuilderPiece]:
|
|
var result: Array[ModularBuilderPiece] = []
|
|
var selected := get_editor_interface().get_selection().get_selected_nodes()
|
|
for selected_node in selected:
|
|
var piece := selected_node as ModularBuilderPiece
|
|
if piece == null and selected_node is ModularBuilderDetail:
|
|
var parent := selected_node.get_parent()
|
|
while parent != null and not parent is ModularBuilderPiece:
|
|
parent = parent.get_parent()
|
|
piece = parent as ModularBuilderPiece
|
|
if piece == root:
|
|
_collect_all_modular_pieces(root, result)
|
|
return result
|
|
if piece != null and _is_descendant_of_piece(piece, root):
|
|
if not result.has(piece):
|
|
result.append(piece)
|
|
return result
|
|
|
|
func _collect_all_modular_pieces(node: Node, result: Array[ModularBuilderPiece]) -> void:
|
|
if node is ModularBuilderPiece and not result.has(node):
|
|
result.append(node)
|
|
for child in node.get_children():
|
|
_collect_all_modular_pieces(child, result)
|
|
|
|
func _is_descendant_of_piece(node: Node, ancestor: Node) -> bool:
|
|
var current := node.get_parent()
|
|
while current != null:
|
|
if current == ancestor:
|
|
return true
|
|
current = current.get_parent()
|
|
return false
|
|
|
|
func _collect_piece_geometry_vertices(piece: ModularBuilderPiece, vertices: Array[Vector3]) -> void:
|
|
if piece.piece_type == ModularBuilderPiece.PieceType.FOUNDATION_SQUARE or piece.piece_type == ModularBuilderPiece.PieceType.FOUNDATION_TRIANGLE:
|
|
var top_y := piece.thickness * 0.5 if piece.centered_origin else piece.thickness
|
|
var bottom_y := -piece.thickness * 0.5 if piece.centered_origin else 0.0
|
|
for point in _foundation_polygon(piece):
|
|
_add_unique_vertex(vertices, piece.to_global(Vector3(point.x, top_y, point.y)))
|
|
_add_unique_vertex(vertices, piece.to_global(Vector3(point.x, bottom_y, point.y)))
|
|
elif piece.piece_type == ModularBuilderPiece.PieceType.FLOOR:
|
|
for point in piece.custom_points:
|
|
_add_unique_vertex(vertices, piece.to_global(point))
|
|
_add_unique_vertex(vertices, piece.to_global(point - Vector3.UP * piece.thickness))
|
|
elif piece.piece_type == ModularBuilderPiece.PieceType.WALL:
|
|
var endpoints: PackedVector3Array = piece.call("_get_centered_wall_points") if piece.center_align else piece.custom_points
|
|
if endpoints.size() < 2:
|
|
return
|
|
var direction := (endpoints[1] - endpoints[0]).normalized()
|
|
var side := Vector3(-direction.z, 0.0, direction.x) * piece.thickness * 0.5
|
|
for endpoint in endpoints:
|
|
for height in [0.0, piece.unit_size]:
|
|
_add_unique_vertex(vertices, piece.to_global(endpoint + Vector3.UP * height + side))
|
|
_add_unique_vertex(vertices, piece.to_global(endpoint + Vector3.UP * height - side))
|
|
elif piece.piece_type == ModularBuilderPiece.PieceType.GABLE_WALL or piece.piece_type == ModularBuilderPiece.PieceType.GABLE_WALL_RIGHT:
|
|
_add_single_gable_wall_vertices(piece, vertices)
|
|
elif piece.piece_type == ModularBuilderPiece.PieceType.ROOF_GABLE or piece.piece_type == ModularBuilderPiece.PieceType.ROOF_SHED or piece.piece_type == ModularBuilderPiece.PieceType.ROOF_PYRAMID:
|
|
for point in piece.custom_points:
|
|
_add_unique_vertex(vertices, piece.to_global(point))
|
|
if piece.piece_type == ModularBuilderPiece.PieceType.ROOF_PYRAMID and piece.custom_points.size() >= 4:
|
|
var base_center := (piece.custom_points[0] + piece.custom_points[1] + piece.custom_points[2] + piece.custom_points[3]) * 0.25
|
|
_add_unique_vertex(vertices, piece.to_global(base_center + Vector3.UP * piece.rise))
|
|
|
|
func _collect_all_connection_vertices(root: ModularBuilderPiece, vertices: Array[Vector3]) -> void:
|
|
var surfaces: Array[ModularBuilderPiece] = []
|
|
_collect_surface_pieces(root, surfaces)
|
|
for piece in surfaces:
|
|
var polygon := _piece_polygon_xz(piece) if piece.piece_type == ModularBuilderPiece.PieceType.FLOOR else _foundation_polygon(piece)
|
|
var top_y := piece.thickness * 0.5 if piece.centered_origin else piece.thickness
|
|
var bottom_y := -piece.thickness * 0.5 if piece.centered_origin else 0.0
|
|
for point in polygon:
|
|
_add_unique_vertex(vertices, piece.to_global(Vector3(point.x, top_y, point.y)))
|
|
_add_unique_vertex(vertices, piece.to_global(Vector3(point.x, bottom_y, point.y)))
|
|
var walls: Array[Node] = []
|
|
_collect_walls(root, walls)
|
|
for wall_node in walls:
|
|
var wall := wall_node as ModularBuilderPiece
|
|
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]).normalized()
|
|
var side := Vector3(-direction.z, 0.0, direction.x) * wall.thickness * 0.5
|
|
for endpoint in endpoints:
|
|
for height in [0.0, wall.unit_size]:
|
|
_add_unique_vertex(vertices, wall.to_global(endpoint + Vector3.UP * height + side))
|
|
_add_unique_vertex(vertices, wall.to_global(endpoint + Vector3.UP * height - side))
|
|
_add_gable_wall_vertices(root, vertices)
|
|
_collect_detail_connection_vertices(root, vertices)
|
|
var roofs: Array[ModularBuilderPiece] = []
|
|
_collect_roof_pieces(root, roofs)
|
|
for roof in roofs:
|
|
for point in roof.custom_points:
|
|
_add_unique_vertex(vertices, roof.to_global(point))
|
|
if roof.piece_type == ModularBuilderPiece.PieceType.ROOF_PYRAMID and roof.custom_points.size() >= 4:
|
|
var base_center := (roof.custom_points[0] + roof.custom_points[1] + roof.custom_points[2] + roof.custom_points[3]) * 0.25
|
|
_add_unique_vertex(vertices, roof.to_global(base_center + Vector3.UP * roof.rise))
|
|
|
|
func _collect_roof_pieces(node: Node, roofs: Array[ModularBuilderPiece]) -> void:
|
|
if node is ModularBuilderPiece and (node.piece_type == ModularBuilderPiece.PieceType.ROOF_GABLE or node.piece_type == ModularBuilderPiece.PieceType.ROOF_SHED or node.piece_type == ModularBuilderPiece.PieceType.ROOF_PYRAMID):
|
|
roofs.append(node)
|
|
for child in node.get_children():
|
|
_collect_roof_pieces(child, roofs)
|
|
|
|
func _collect_detail_connection_vertices(node: Node, vertices: Array[Vector3]) -> void:
|
|
if node is ModularBuilderDetail:
|
|
var detail := node as ModularBuilderDetail
|
|
if detail.detail_type == ModularBuilderDetail.DetailType.PILLAR:
|
|
var half := detail.width * 0.5
|
|
for x in [-half, half]:
|
|
for y in [-detail.height * 0.5, detail.height * 0.5]:
|
|
for z in [-half, half]:
|
|
_add_unique_vertex(vertices, detail.to_global(Vector3(x, y, z)))
|
|
elif (detail.detail_type == ModularBuilderDetail.DetailType.FENCE or detail.detail_type == ModularBuilderDetail.DetailType.PARAPET) and detail.path_points.size() >= 2:
|
|
for point in detail.path_points:
|
|
_add_unique_vertex(vertices, detail.to_global(point))
|
|
for local_point in detail.get_meta(CUSTOM_VERTICES_META, []):
|
|
_add_unique_vertex(vertices, detail.to_global(local_point))
|
|
for child in node.get_children():
|
|
_collect_detail_connection_vertices(child, vertices)
|
|
|
|
func _collapse_merged_vertex_groups(root: ModularBuilderPiece, vertices: Array[Vector3]) -> void:
|
|
if not root.has_meta(MERGED_VERTEX_GROUPS_META):
|
|
return
|
|
var groups: Array = root.get_meta(MERGED_VERTEX_GROUPS_META, [])
|
|
for group_value in groups:
|
|
if not group_value is Dictionary:
|
|
continue
|
|
var group: Dictionary = group_value
|
|
var members: PackedVector3Array = group.get("members", PackedVector3Array())
|
|
var anchor: Vector3 = group.get("position", Vector3.ZERO)
|
|
var had_member := false
|
|
for i in range(vertices.size() - 1, -1, -1):
|
|
var local_vertex := root.to_local(vertices[i])
|
|
for member in members:
|
|
if local_vertex.distance_to(member) <= 0.025:
|
|
vertices.remove_at(i)
|
|
had_member = true
|
|
break
|
|
if had_member:
|
|
_add_unique_vertex(vertices, root.to_global(anchor))
|
|
|
|
func _merge_members_for_point(root: ModularBuilderPiece, world_point: Vector3) -> PackedVector3Array:
|
|
var local_point := root.to_local(world_point)
|
|
var groups: Array = root.get_meta(MERGED_VERTEX_GROUPS_META, [])
|
|
for group_value in groups:
|
|
if not group_value is Dictionary:
|
|
continue
|
|
var group: Dictionary = group_value
|
|
var anchor: Vector3 = group.get("position", Vector3.ZERO)
|
|
if local_point.distance_to(anchor) <= 0.04:
|
|
return group.get("members", PackedVector3Array())
|
|
return PackedVector3Array([local_point])
|
|
|
|
func _finish_merge_vertices(root: ModularBuilderPiece) -> void:
|
|
if pending_points.size() < 2:
|
|
return
|
|
var selected_members := _merge_members_for_point(root, pending_points[0])
|
|
for member in _merge_members_for_point(root, pending_points[1]):
|
|
var found := false
|
|
for existing in selected_members:
|
|
if existing.distance_to(member) <= 0.025:
|
|
found = true
|
|
break
|
|
if not found:
|
|
selected_members.append(member)
|
|
if selected_members.size() < 2:
|
|
pending_points.clear()
|
|
status_label.text = "Choose two different vertices to merge."
|
|
update_overlays()
|
|
return
|
|
var old_groups: Array = root.get_meta(MERGED_VERTEX_GROUPS_META, []).duplicate(true)
|
|
var new_groups: Array = []
|
|
for group_value in old_groups:
|
|
if not group_value is Dictionary:
|
|
continue
|
|
var group: Dictionary = group_value
|
|
var members: PackedVector3Array = group.get("members", PackedVector3Array())
|
|
var overlaps := false
|
|
for member in members:
|
|
for selected in selected_members:
|
|
if member.distance_to(selected) <= 0.025:
|
|
overlaps = true
|
|
break
|
|
if overlaps:
|
|
break
|
|
if not overlaps:
|
|
new_groups.append(group)
|
|
var center := Vector3.ZERO
|
|
for member in selected_members:
|
|
center += member
|
|
center /= selected_members.size()
|
|
new_groups.append({"members": selected_members, "position": center})
|
|
var undo := get_undo_redo()
|
|
undo.create_action("Merge vertex markers")
|
|
undo.add_do_method(root, "set_meta", MERGED_VERTEX_GROUPS_META, new_groups)
|
|
undo.add_undo_method(root, "set_meta", MERGED_VERTEX_GROUPS_META, old_groups)
|
|
undo.commit_action()
|
|
pending_points.clear()
|
|
status_label.text = "Markers merged. Ctrl+Z undoes the merge."
|
|
update_overlays()
|
|
|
|
func _add_gable_wall_vertices(root: ModularBuilderPiece, vertices: Array[Vector3]) -> void:
|
|
var gable_walls: Array[Node] = []
|
|
_collect_gable_walls(root, gable_walls)
|
|
for node in gable_walls:
|
|
_add_single_gable_wall_vertices(node as ModularBuilderPiece, vertices)
|
|
|
|
func _add_single_gable_wall_vertices(gable: ModularBuilderPiece, vertices: Array[Vector3]) -> void:
|
|
if gable.custom_points.size() < 2:
|
|
return
|
|
var base_points: PackedVector3Array = gable.call("_get_centered_gable_base") if gable.center_align else gable.custom_points
|
|
var a: Vector3 = base_points[0]
|
|
var b: Vector3 = base_points[1]
|
|
var direction := (b - a).normalized()
|
|
var side := Vector3(-direction.z, 0.0, direction.x).normalized() * gable.thickness * 0.5
|
|
var peak := (a + b) * 0.5 + Vector3.UP * gable.rise if gable.piece_type == ModularBuilderPiece.PieceType.GABLE_WALL else b + Vector3.UP * gable.rise
|
|
for endpoint in [a, b]:
|
|
_add_unique_vertex(vertices, gable.to_global(endpoint + side))
|
|
_add_unique_vertex(vertices, gable.to_global(endpoint - side))
|
|
_add_unique_vertex(vertices, gable.to_global(peak + side))
|
|
_add_unique_vertex(vertices, gable.to_global(peak - side))
|
|
|
|
func _collect_gable_walls(node: Node, gable_walls: Array[Node]) -> void:
|
|
if node is ModularBuilderPiece and (node.piece_type == ModularBuilderPiece.PieceType.GABLE_WALL or node.piece_type == ModularBuilderPiece.PieceType.GABLE_WALL_RIGHT) and not gable_walls.has(node):
|
|
gable_walls.append(node)
|
|
for child in node.get_children():
|
|
_collect_gable_walls(child, gable_walls)
|
|
|
|
func _collect_walls(node: Node, walls: Array[Node]) -> void:
|
|
if node is ModularBuilderPiece and node.piece_type == ModularBuilderPiece.PieceType.WALL and not walls.has(node):
|
|
walls.append(node)
|
|
for child in node.get_children():
|
|
if child is ModularBuilderPiece and child.piece_type == ModularBuilderPiece.PieceType.WALL:
|
|
if not walls.has(child):
|
|
walls.append(child)
|
|
_collect_walls(child, walls)
|
|
|
|
func _add_unique_vertex(vertices: Array[Vector3], point: Vector3) -> void:
|
|
for existing in vertices:
|
|
if existing.distance_to(point) < 0.02:
|
|
return
|
|
vertices.append(point)
|
|
|
|
func _get_manual_vertices(piece: ModularBuilderPiece) -> Array[Vector3]:
|
|
var result: Array[Vector3] = []
|
|
var owners: Array[Node3D] = []
|
|
_collect_manual_vertex_owners(piece, owners)
|
|
for owner_node in owners:
|
|
for local_point_value in owner_node.get_meta(CUSTOM_VERTICES_META, []):
|
|
var local_point: Vector3 = local_point_value
|
|
result.append(owner_node.to_global(local_point))
|
|
return result
|
|
|
|
func _collect_manual_vertex_owners(node: Node, owners: Array[Node3D]) -> void:
|
|
if node is ModularBuilderPiece or node is ModularBuilderDetail:
|
|
owners.append(node as Node3D)
|
|
for child in node.get_children():
|
|
_collect_manual_vertex_owners(child, owners)
|
|
|
|
func _raycast_piece_mesh(root: ModularBuilderPiece, camera: Camera3D, screen_position: Vector2) -> Variant:
|
|
return MESH_PICKER.raycast(root, camera, screen_position)
|
|
|
|
func _commit_manual_vertices(owner_node: Node3D, new_vertices: Array, action_name: String) -> void:
|
|
var old_vertices: Array = owner_node.get_meta(CUSTOM_VERTICES_META, []).duplicate()
|
|
var undo := get_undo_redo()
|
|
undo.create_action(action_name)
|
|
undo.add_do_method(owner_node, "set_meta", CUSTOM_VERTICES_META, new_vertices)
|
|
undo.add_undo_method(owner_node, "set_meta", CUSTOM_VERTICES_META, old_vertices)
|
|
undo.commit_action()
|
|
|
|
func _create_manual_vertex(root: ModularBuilderPiece, world_position: Vector3) -> void:
|
|
var owner_node := _get_vertex_attachment_node(root)
|
|
var local_position := owner_node.to_local(world_position)
|
|
var vertices: Array = owner_node.get_meta(CUSTOM_VERTICES_META, []).duplicate()
|
|
for existing in vertices:
|
|
var existing_position: Vector3 = existing
|
|
if existing_position.distance_to(local_position) < 0.025:
|
|
status_label.text = "A manual vertex already exists there."
|
|
return
|
|
vertices.append(local_position)
|
|
_commit_manual_vertices(owner_node, vertices, "Create connection vertex")
|
|
status_label.text = "Connection vertex created and linked to the selected object."
|
|
|
|
func _move_manual_vertex(root: ModularBuilderPiece, old_world_position: Vector3, new_world_position: Vector3) -> void:
|
|
var owner_record := _find_manual_vertex_record(root, old_world_position)
|
|
if owner_record.is_empty():
|
|
pending_points.clear()
|
|
status_label.text = "The selected vertex is no longer available."
|
|
return
|
|
var owner_node: Node3D = owner_record.node
|
|
var vertices: Array = owner_node.get_meta(CUSTOM_VERTICES_META, []).duplicate()
|
|
vertices[owner_record.index] = owner_node.to_local(new_world_position)
|
|
pending_points.clear()
|
|
_commit_manual_vertices(owner_node, vertices, "Move connection vertex")
|
|
status_label.text = "Vertex moved."
|
|
|
|
func _remove_manual_vertex(root: ModularBuilderPiece, world_position: Vector3) -> void:
|
|
var owner_record := _find_manual_vertex_record(root, world_position)
|
|
if owner_record.is_empty():
|
|
return
|
|
var owner_node: Node3D = owner_record.node
|
|
var vertices: Array = owner_node.get_meta(CUSTOM_VERTICES_META, []).duplicate()
|
|
vertices.remove_at(owner_record.index)
|
|
_commit_manual_vertices(owner_node, vertices, "Remove connection vertex")
|
|
status_label.text = "Vertex removed."
|
|
|
|
func _get_vertex_attachment_node(root: ModularBuilderPiece) -> Node3D:
|
|
var selected := get_editor_interface().get_selection().get_selected_nodes()
|
|
if selected.size() == 1:
|
|
var selected_piece := selected[0] as ModularBuilderPiece
|
|
if selected_piece != null and (selected_piece == root or _is_descendant_of_piece(selected_piece, root)):
|
|
return selected_piece
|
|
if selected[0] is ModularBuilderDetail and _is_descendant_of_piece(selected[0], root):
|
|
return selected[0] as ModularBuilderDetail
|
|
return root
|
|
|
|
func _find_manual_vertex_record(root: ModularBuilderPiece, world_position: Vector3) -> Dictionary:
|
|
var owners: Array[Node3D] = []
|
|
_collect_manual_vertex_owners(root, owners)
|
|
var nearest_record := {}
|
|
var nearest_distance := 0.05
|
|
for owner_node in owners:
|
|
var vertices: Array = owner_node.get_meta(CUSTOM_VERTICES_META, [])
|
|
for i in range(vertices.size()):
|
|
var local_point: Vector3 = vertices[i]
|
|
var distance := owner_node.to_global(local_point).distance_to(world_position)
|
|
if distance < nearest_distance:
|
|
nearest_distance = distance
|
|
nearest_record = {"node": owner_node, "index": i}
|
|
return nearest_record
|