637 lines
28 KiB
GDScript
637 lines
28 KiB
GDScript
@tool
|
|
extends "res://addons/modular_builder/modular_builder_editor_vertices.gd"
|
|
|
|
const ROOF_SUPPORT = preload("res://addons/modular_builder/modular_roof_support.gd")
|
|
|
|
func _forward_3d_gui_input(camera: Camera3D, event: InputEvent) -> int:
|
|
if active_tool != TOOL_NONE and event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE:
|
|
pending_points.clear()
|
|
_editor_sync_elapsed = 1.0
|
|
status_label.text = "Current points cleared."
|
|
update_overlays()
|
|
return EditorPlugin.AFTER_GUI_INPUT_STOP
|
|
if _is_detail_tool(active_tool) and event is InputEventMouseMotion:
|
|
_preview_camera = camera
|
|
_preview_screen_position = event.position
|
|
_update_detail_preview()
|
|
return EditorPlugin.AFTER_GUI_INPUT_PASS
|
|
if not (event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT):
|
|
return EditorPlugin.AFTER_GUI_INPUT_PASS
|
|
_editor_sync_elapsed = 1.0
|
|
var root := _get_selected_piece()
|
|
if root == null:
|
|
return EditorPlugin.AFTER_GUI_INPUT_PASS
|
|
if active_tool == TOOL_FOUNDATION:
|
|
var side := _nearest_side_button(camera, event.position)
|
|
if side >= 0:
|
|
_add_foundation_at_side(root, side)
|
|
return EditorPlugin.AFTER_GUI_INPUT_STOP
|
|
return EditorPlugin.AFTER_GUI_INPUT_PASS
|
|
if active_tool == TOOL_NONE:
|
|
return EditorPlugin.AFTER_GUI_INPUT_PASS
|
|
if active_tool == TOOL_CREATE_VERTEX:
|
|
var create_position := _raycast_piece_mesh(root, camera, event.position)
|
|
if create_position == null:
|
|
status_label.text = "Click a structure surface to create a connection vertex."
|
|
return EditorPlugin.AFTER_GUI_INPUT_STOP
|
|
_create_manual_vertex(root, create_position)
|
|
return EditorPlugin.AFTER_GUI_INPUT_STOP
|
|
if active_tool == TOOL_MOVE_VERTEX:
|
|
if pending_points.is_empty():
|
|
var selected_vertex := _nearest_vertex(camera, event.position, _get_manual_vertices(root))
|
|
if selected_vertex == null:
|
|
status_label.text = "Click a blue manual vertex to select it."
|
|
return EditorPlugin.AFTER_GUI_INPUT_STOP
|
|
pending_points.append(selected_vertex)
|
|
status_label.text = "Vertex selected. Click the surface where you want to move it."
|
|
else:
|
|
var move_position := _raycast_piece_mesh(root, camera, event.position)
|
|
if move_position == null:
|
|
status_label.text = "Click a structure surface to move the vertex."
|
|
return EditorPlugin.AFTER_GUI_INPUT_STOP
|
|
_move_manual_vertex(root, pending_points[0], move_position)
|
|
return EditorPlugin.AFTER_GUI_INPUT_STOP
|
|
if active_tool == TOOL_REMOVE_VERTEX:
|
|
var remove_position := _nearest_vertex(camera, event.position, _get_manual_vertices(root))
|
|
if remove_position == null:
|
|
status_label.text = "Click a blue manual vertex to remove it."
|
|
else:
|
|
_remove_manual_vertex(root, remove_position)
|
|
return EditorPlugin.AFTER_GUI_INPUT_STOP
|
|
if active_tool == TOOL_PILLAR:
|
|
var pillar_vertex := _nearest_vertex(camera, event.position, _get_tool_vertices(root))
|
|
if pillar_vertex == null:
|
|
return EditorPlugin.AFTER_GUI_INPUT_PASS
|
|
if event.alt_pressed:
|
|
_create_vertical_pillar_from_vertex(root, pillar_vertex)
|
|
return EditorPlugin.AFTER_GUI_INPUT_STOP
|
|
for previous_point in pending_points:
|
|
if previous_point.distance_to(pillar_vertex) < 0.02:
|
|
status_label.text = "Choose a different second vertex."
|
|
return EditorPlugin.AFTER_GUI_INPUT_STOP
|
|
pending_points.append(pillar_vertex)
|
|
if pending_points.size() >= 2:
|
|
_create_pillar_from_vertices(root, pending_points[0], pending_points[1])
|
|
else:
|
|
status_label.text = "First vertex marked. Click another vertex to place the pillar between them."
|
|
update_overlays()
|
|
return EditorPlugin.AFTER_GUI_INPUT_STOP
|
|
if active_tool == TOOL_FENCE or active_tool == TOOL_PARAPET:
|
|
var fence_vertex := _nearest_vertex(camera, event.position, _get_tool_vertices(root))
|
|
if fence_vertex == null:
|
|
return EditorPlugin.AFTER_GUI_INPUT_PASS
|
|
for previous_point in pending_points:
|
|
if previous_point.distance_to(fence_vertex) < 0.02:
|
|
status_label.text = "Choose a different second vertex for the path."
|
|
return EditorPlugin.AFTER_GUI_INPUT_STOP
|
|
pending_points.append(fence_vertex)
|
|
if pending_points.size() >= 2:
|
|
_create_fence_decoration(root, pending_points[0], pending_points[1])
|
|
else:
|
|
status_label.text = "Start marked. Click the final fence vertex."
|
|
update_overlays()
|
|
return EditorPlugin.AFTER_GUI_INPUT_STOP
|
|
if _is_detail_tool(active_tool):
|
|
if _create_detail_from_click(camera, event.position):
|
|
return EditorPlugin.AFTER_GUI_INPUT_STOP
|
|
status_label.text = "Click a surface compatible with the selected tool."
|
|
return EditorPlugin.AFTER_GUI_INPUT_PASS
|
|
var point := _nearest_vertex(camera, event.position, _get_tool_vertices(root))
|
|
if point == null:
|
|
return EditorPlugin.AFTER_GUI_INPUT_PASS
|
|
for previous_point in pending_points:
|
|
if previous_point.distance_to(point) < 0.02:
|
|
status_label.text = "Choose a different vertex."
|
|
return EditorPlugin.AFTER_GUI_INPUT_STOP
|
|
if active_tool == TOOL_FLOOR_SQUARE and pending_points.size() == 1:
|
|
var first_local := root.to_local(pending_points[0])
|
|
var second_local := root.to_local(point)
|
|
if absf(first_local.x - second_local.x) < 0.02 or absf(first_local.z - second_local.z) < 0.02:
|
|
status_label.text = "To complete a square floor, choose the opposite diagonal vertex."
|
|
return EditorPlugin.AFTER_GUI_INPUT_STOP
|
|
pending_points.append(point)
|
|
var needed := _required_clicks()
|
|
if pending_points.size() >= needed:
|
|
_finish_shape(root)
|
|
elif active_tool == TOOL_ROOF_SHED:
|
|
status_label.text = "Low point marked. Click the high point on the other side of the roof."
|
|
elif active_tool == TOOL_MERGE_VERTICES:
|
|
status_label.text = "First vertex marked. Click the second to merge the markers."
|
|
else:
|
|
status_label.text = "Point %d of %d marked. Click the next vertex." % [pending_points.size(), needed]
|
|
update_overlays()
|
|
return EditorPlugin.AFTER_GUI_INPUT_STOP
|
|
|
|
func _forward_3d_draw_over_viewport(overlay: Control) -> void:
|
|
pass
|
|
|
|
func _required_clicks() -> int:
|
|
if active_tool == TOOL_WALL or active_tool == TOOL_GABLE_WALL or active_tool == TOOL_GABLE_WALL_RIGHT or active_tool == TOOL_MERGE_VERTICES or active_tool == TOOL_FLOOR_SQUARE 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:
|
|
return 2
|
|
return 3
|
|
|
|
func _finish_shape(root: ModularBuilderPiece) -> void:
|
|
if active_tool == TOOL_MERGE_VERTICES:
|
|
_finish_merge_vertices(root)
|
|
return
|
|
if active_tool == TOOL_WALL:
|
|
_finish_wall_segments(root)
|
|
return
|
|
if active_tool == TOOL_GABLE_WALL or active_tool == TOOL_GABLE_WALL_RIGHT:
|
|
_finish_gable_wall(root)
|
|
return
|
|
if active_tool == TOOL_ROOF_GABLE or active_tool == TOOL_ROOF_SHED or active_tool == TOOL_ROOF_PYRAMID:
|
|
_finish_roof(root)
|
|
return
|
|
if active_tool == TOOL_STAIRS:
|
|
_finish_stairs(root)
|
|
return
|
|
if active_tool == TOOL_FENCE:
|
|
_finish_fence(root)
|
|
return
|
|
var local_points := PackedVector3Array()
|
|
for world_point in pending_points:
|
|
local_points.append(root.to_local(world_point))
|
|
var piece := Node3D.new()
|
|
piece.set_script(PIECE_SCRIPT)
|
|
piece.set("centered_origin", true)
|
|
piece.name = "FloorTriangle" if active_tool == TOOL_FLOOR_TRIANGLE else "FloorSquare"
|
|
piece.set("piece_type", ModularBuilderPiece.PieceType.FLOOR)
|
|
piece.set("thickness", root.thickness)
|
|
if active_tool == TOOL_FLOOR_SQUARE:
|
|
var a := local_points[0]
|
|
var b := local_points[1]
|
|
var left := minf(a.x, b.x)
|
|
var right := maxf(a.x, b.x)
|
|
var near_z := minf(a.z, b.z)
|
|
var far_z := maxf(a.z, b.z)
|
|
var y := (a.y + b.y) * 0.5
|
|
local_points = PackedVector3Array([
|
|
Vector3(left, y, near_z), Vector3(right, y, near_z),
|
|
Vector3(right, y, far_z), Vector3(left, y, far_z)
|
|
])
|
|
var floor_pivot := _packed_points_bounds_center(local_points)
|
|
floor_pivot.y -= piece.thickness * 0.5
|
|
piece.position = floor_pivot
|
|
for i in range(local_points.size()):
|
|
local_points[i] -= floor_pivot
|
|
piece.set("custom_points", local_points)
|
|
var undo := get_undo_redo()
|
|
undo.create_action("Create %s from vertices" % piece.name)
|
|
undo.add_do_method(root, "add_child", piece, true)
|
|
undo.add_do_property(piece, "owner", get_editor_interface().get_edited_scene_root())
|
|
undo.add_undo_method(root, "remove_child", piece)
|
|
undo.commit_action()
|
|
_last_created_piece = piece
|
|
pending_points.clear()
|
|
status_label.text = "%s created inside ModularBuilderPiece. The tool remains active." % piece.name
|
|
update_overlays()
|
|
|
|
func _finish_wall_segments(root: ModularBuilderPiece) -> void:
|
|
var start_world := pending_points[0]
|
|
var finish_world := pending_points[1]
|
|
var path := finish_world - start_world
|
|
var path_length_squared := path.length_squared()
|
|
if path_length_squared < 0.0001:
|
|
pending_points.clear()
|
|
status_label.text = "Wall points are too close together."
|
|
update_overlays()
|
|
return
|
|
var parameters: Array[float] = [0.0, 1.0]
|
|
for vertex in _get_tool_vertices(root):
|
|
var t := (vertex - start_world).dot(path) / path_length_squared
|
|
if t <= 0.001 or t >= 0.999:
|
|
continue
|
|
var projected := start_world + path * t
|
|
if vertex.distance_to(projected) > 0.02:
|
|
continue
|
|
var already_added := false
|
|
for existing_t in parameters:
|
|
if absf(existing_t - t) < 0.005:
|
|
already_added = true
|
|
break
|
|
if not already_added:
|
|
parameters.append(t)
|
|
parameters.sort()
|
|
var pieces: Array[ModularBuilderPiece] = []
|
|
var centralize_wall := _gable_centralize_check.button_pressed
|
|
for i in range(parameters.size() - 1):
|
|
var a_world := start_world.lerp(finish_world, parameters[i])
|
|
var b_world := start_world.lerp(finish_world, parameters[i + 1])
|
|
pieces.append(_make_wall_segment(root, a_world, b_world, i + 1, centralize_wall))
|
|
var scene_root := get_editor_interface().get_edited_scene_root()
|
|
var undo := get_undo_redo()
|
|
undo.create_action("Create wall in %d segments" % pieces.size())
|
|
for piece in pieces:
|
|
undo.add_do_method(root, "add_child", piece, true)
|
|
undo.add_do_property(piece, "owner", scene_root)
|
|
undo.add_undo_method(root, "remove_child", piece)
|
|
undo.commit_action()
|
|
_last_created_piece = pieces[-1]
|
|
pending_points.clear()
|
|
status_label.text = "Wall split into %d segments at foundation vertices." % pieces.size()
|
|
update_overlays()
|
|
|
|
func _finish_gable_wall(root: ModularBuilderPiece) -> void:
|
|
var start := root.to_local(pending_points[0])
|
|
var finish := root.to_local(pending_points[1])
|
|
if Vector2(start.x - finish.x, start.z - finish.z).length() < 0.02 or absf(start.y - finish.y) > 0.05:
|
|
pending_points.clear()
|
|
status_label.text = "Choose two separate vertices at the same height for the gable base."
|
|
update_overlays()
|
|
return
|
|
var piece_node := Node3D.new()
|
|
piece_node.set_script(PIECE_SCRIPT)
|
|
var piece := piece_node as ModularBuilderPiece
|
|
var is_right_triangle := active_tool == TOOL_GABLE_WALL_RIGHT
|
|
piece.name = "GableWallRight" if is_right_triangle else "GableWall"
|
|
piece.piece_type = ModularBuilderPiece.PieceType.GABLE_WALL_RIGHT if is_right_triangle else ModularBuilderPiece.PieceType.GABLE_WALL
|
|
piece.unit_size = root.unit_size
|
|
piece.thickness = root.thickness
|
|
piece.centered_origin = true
|
|
piece.center_align = _gable_centralize_check.button_pressed
|
|
var pivot := (start + finish) * 0.5 + Vector3.UP * rise_spin.value * 0.5
|
|
piece.position = pivot
|
|
piece.rise = rise_spin.value
|
|
piece.custom_points = PackedVector3Array([start - pivot, finish - pivot])
|
|
var undo := get_undo_redo()
|
|
undo.create_action("Create gable wall")
|
|
undo.add_do_method(root, "add_child", piece, true)
|
|
undo.add_do_property(piece, "owner", get_editor_interface().get_edited_scene_root())
|
|
undo.add_undo_method(root, "remove_child", piece)
|
|
undo.commit_action()
|
|
_last_created_piece = piece
|
|
pending_points.clear()
|
|
status_label.text = "Gable wall created. The tool remains active."
|
|
update_overlays()
|
|
|
|
func _finish_roof(root: ModularBuilderPiece) -> void:
|
|
var first := root.to_local(pending_points[0])
|
|
var second := root.to_local(pending_points[1])
|
|
var is_gable := active_tool == TOOL_ROOF_GABLE
|
|
var is_pyramid := active_tool == TOOL_ROOF_PYRAMID
|
|
var is_shed := active_tool == TOOL_ROOF_SHED
|
|
if (is_gable or is_pyramid) and absf(first.y - second.y) > 0.05:
|
|
pending_points.clear()
|
|
status_label.text = "For this roof, choose two diagonal points at the same height."
|
|
update_overlays()
|
|
return
|
|
if is_shed and second.y <= first.y + 0.05:
|
|
pending_points.clear()
|
|
status_label.text = "Choose a low point first, then a higher point on the opposite side."
|
|
update_overlays()
|
|
return
|
|
if is_shed and not _shed_points_are_on_opposite_sides(root, pending_points[0], pending_points[1]):
|
|
pending_points.clear()
|
|
status_label.text = "Roof points must be on opposite, parallel walls."
|
|
update_overlays()
|
|
return
|
|
var roof_rise := rise_spin.value
|
|
var roof_width := 0.0
|
|
var corners := PackedVector3Array()
|
|
if is_gable:
|
|
var gable_result := _find_roof_between_gables(root, pending_points[0], pending_points[1])
|
|
if not gable_result.is_empty():
|
|
corners = gable_result.corners
|
|
roof_rise = gable_result.rise
|
|
elif is_pyramid:
|
|
corners = _find_roof_corners(root, first, second)
|
|
else:
|
|
corners = _find_shed_roof_corners(root, first, second)
|
|
if corners.size() == 4:
|
|
roof_width = corners[0].distance_to(corners[1])
|
|
if is_gable and corners.is_empty():
|
|
corners = ROOF_SUPPORT.find_corners(root, pending_points[0], pending_points[1], _simplify_vertices_check.button_pressed)
|
|
if corners.is_empty():
|
|
corners = _find_roof_corners(root, first, second)
|
|
if corners.size() != 4:
|
|
pending_points.clear()
|
|
status_label.text = "Could not find the other roof corners. Choose two diagonal vertices on the top edges of opposite walls."
|
|
update_overlays()
|
|
return
|
|
var piece_node := Node3D.new()
|
|
piece_node.set_script(PIECE_SCRIPT)
|
|
var piece := piece_node as ModularBuilderPiece
|
|
piece.name = "RoofGable" if is_gable else ("RoofPyramid" if is_pyramid else "RoofShed")
|
|
piece.piece_type = ModularBuilderPiece.PieceType.ROOF_GABLE if is_gable else (ModularBuilderPiece.PieceType.ROOF_PYRAMID if is_pyramid else ModularBuilderPiece.PieceType.ROOF_SHED)
|
|
piece.unit_size = root.unit_size
|
|
piece.thickness = 0.05
|
|
piece.centered_origin = true
|
|
piece.rise = roof_rise
|
|
if is_shed:
|
|
piece.roof_width = roof_width
|
|
var pivot := Vector3.ZERO
|
|
for corner in corners:
|
|
pivot += corner
|
|
pivot /= corners.size()
|
|
if is_gable or is_pyramid:
|
|
pivot += Vector3.UP * roof_rise * 0.5
|
|
piece.position = pivot
|
|
if is_gable or is_pyramid:
|
|
var local_corners := PackedVector3Array()
|
|
for corner in corners:
|
|
var local_corner: Vector3 = corner - pivot
|
|
local_corner.y = -roof_rise * 0.5
|
|
local_corners.append(local_corner)
|
|
piece.custom_points = local_corners
|
|
else:
|
|
var local_corners := PackedVector3Array()
|
|
for corner in corners:
|
|
local_corners.append(corner - pivot)
|
|
piece.custom_points = local_corners
|
|
var undo := get_undo_redo()
|
|
var roof_title := "gable roof" if is_gable else ("pyramid roof" if is_pyramid else "shed roof")
|
|
undo.create_action("Create %s" % roof_title)
|
|
undo.add_do_method(root, "add_child", piece, true)
|
|
undo.add_do_property(piece, "owner", get_editor_interface().get_edited_scene_root())
|
|
undo.add_undo_method(root, "remove_child", piece)
|
|
undo.commit_action()
|
|
_last_created_piece = piece
|
|
pending_points.clear()
|
|
status_label.text = "%s created. The tool remains active." % piece.name
|
|
update_overlays()
|
|
|
|
func _finish_stairs(root: ModularBuilderPiece) -> void:
|
|
if pending_points.size() < 2:
|
|
return
|
|
var low := root.to_local(pending_points[0])
|
|
var high := root.to_local(pending_points[1])
|
|
if high.y <= low.y + 0.05:
|
|
pending_points.clear()
|
|
status_label.text = "Choose the low point first, then a point at least 5 cm higher."
|
|
update_overlays()
|
|
return
|
|
if Vector2(high.x - low.x, high.z - low.z).length() < 0.1:
|
|
pending_points.clear()
|
|
status_label.text = "Stair points need horizontal separation."
|
|
update_overlays()
|
|
return
|
|
var piece_node := Node3D.new()
|
|
piece_node.set_script(PIECE_SCRIPT)
|
|
var piece := piece_node as ModularBuilderPiece
|
|
var pivot := (low + high) * 0.5
|
|
piece.name = "Stairs"
|
|
piece.piece_type = ModularBuilderPiece.PieceType.STAIRS
|
|
piece.position = pivot
|
|
piece.unit_size = root.unit_size
|
|
piece.thickness = 0.1
|
|
piece.centered_origin = true
|
|
piece.stair_count = int(stair_count_spin.value)
|
|
piece.stair_width = stair_width_spin.value
|
|
piece.custom_points = PackedVector3Array([low - pivot, high - pivot])
|
|
var undo := get_undo_redo()
|
|
undo.create_action("Create stairs")
|
|
undo.add_do_method(root, "add_child", piece, true)
|
|
undo.add_do_property(piece, "owner", get_editor_interface().get_edited_scene_root())
|
|
undo.add_undo_method(root, "remove_child", piece)
|
|
undo.commit_action()
|
|
_last_created_piece = piece
|
|
pending_points.clear()
|
|
status_label.text = "Stairs created with %d steps. The tool remains active." % piece.stair_count
|
|
update_overlays()
|
|
|
|
func _finish_fence(root: ModularBuilderPiece) -> void:
|
|
if pending_points.size() < 2:
|
|
return
|
|
var start := root.to_local(pending_points[0])
|
|
var finish := root.to_local(pending_points[1])
|
|
if start.distance_to(finish) < 0.1:
|
|
pending_points.clear()
|
|
status_label.text = "Choose two points far enough apart for the fence."
|
|
update_overlays()
|
|
return
|
|
var piece_node := Node3D.new()
|
|
piece_node.set_script(PIECE_SCRIPT)
|
|
var piece := piece_node as ModularBuilderPiece
|
|
var pivot := (start + finish) * 0.5
|
|
piece.name = "Fence"
|
|
piece.piece_type = ModularBuilderPiece.PieceType.FENCE
|
|
piece.position = pivot
|
|
piece.unit_size = root.unit_size
|
|
piece.thickness = 0.08
|
|
piece.centered_origin = true
|
|
piece.fence_divisions = int(fence_divisions_spin.value)
|
|
piece.fence_height = 1.0
|
|
piece.custom_points = PackedVector3Array([start - pivot, finish - pivot])
|
|
var undo := get_undo_redo()
|
|
undo.create_action("Create fence")
|
|
undo.add_do_method(root, "add_child", piece, true)
|
|
undo.add_do_property(piece, "owner", get_editor_interface().get_edited_scene_root())
|
|
undo.add_undo_method(root, "remove_child", piece)
|
|
undo.commit_action()
|
|
_last_created_piece = piece
|
|
pending_points.clear()
|
|
status_label.text = "Fence created with %d intermediate post(s). The tool remains active." % piece.fence_divisions
|
|
update_overlays()
|
|
|
|
func _find_roof_corners(root: ModularBuilderPiece, first: Vector3, second: Vector3) -> PackedVector3Array:
|
|
var diagonal_center := (first + second) * 0.5
|
|
var diagonal_length := first.distance_to(second)
|
|
var candidates: Array[Vector3] = []
|
|
for world_point in _get_tool_vertices(root):
|
|
var local_point := root.to_local(world_point)
|
|
if absf(local_point.y - first.y) > 0.04:
|
|
continue
|
|
_add_unique_vertex(candidates, local_point)
|
|
for adjacent in candidates:
|
|
if adjacent.distance_to(first) < 0.02 or adjacent.distance_to(second) < 0.02:
|
|
continue
|
|
var opposite := diagonal_center * 2.0 - adjacent
|
|
for other_adjacent in candidates:
|
|
if other_adjacent.distance_to(first) < 0.02 or other_adjacent.distance_to(second) < 0.02 or other_adjacent.distance_to(adjacent) < 0.02:
|
|
continue
|
|
if other_adjacent.distance_to(opposite) > 0.04:
|
|
continue
|
|
if absf(adjacent.distance_to(other_adjacent) - diagonal_length) > 0.06:
|
|
continue
|
|
return PackedVector3Array([first, adjacent, second, other_adjacent])
|
|
return PackedVector3Array()
|
|
|
|
func _find_shed_roof_corners(root: ModularBuilderPiece, low: Vector3, high: Vector3) -> PackedVector3Array:
|
|
var candidates: Array[Vector3] = []
|
|
for world_point in _get_tool_vertices(root):
|
|
_add_unique_vertex(candidates, root.to_local(world_point))
|
|
var match_tolerance := maxf(0.08, root.thickness * 1.5)
|
|
var best := PackedVector3Array()
|
|
var best_score := INF
|
|
for low_adjacent in candidates:
|
|
if absf(low_adjacent.y - low.y) > 0.04 or low_adjacent.distance_to(low) < 0.4:
|
|
continue
|
|
var target_high_adjacent := low + high - low_adjacent
|
|
for high_adjacent in candidates:
|
|
if absf(high_adjacent.y - high.y) > 0.04 or high_adjacent.distance_to(high) < 0.4:
|
|
continue
|
|
var corner_error := high_adjacent.distance_to(target_high_adjacent)
|
|
if corner_error > match_tolerance:
|
|
continue
|
|
var low_edge := Vector2(low_adjacent.x - low.x, low_adjacent.z - low.z)
|
|
var high_edge := Vector2(high.x - high_adjacent.x, high.z - high_adjacent.z)
|
|
if low_edge.length() < 0.4 or high_edge.length() < 0.4:
|
|
continue
|
|
if absf(low_edge.normalized().dot(high_edge.normalized())) < 0.94:
|
|
continue
|
|
var edge_error := absf(low_edge.length() - high_edge.length())
|
|
if edge_error > match_tolerance * 2.0:
|
|
continue
|
|
var score := corner_error + edge_error
|
|
if score < best_score:
|
|
best_score = score
|
|
best = PackedVector3Array([low, low_adjacent, high_adjacent, high])
|
|
return best
|
|
|
|
func _shed_points_are_on_opposite_sides(root: ModularBuilderPiece, low_world: Vector3, high_world: Vector3) -> bool:
|
|
var low_supports := _get_roof_supports_at_point(root, low_world)
|
|
var high_supports := _get_roof_supports_at_point(root, high_world)
|
|
var across := root.to_local(high_world) - root.to_local(low_world)
|
|
var across_horizontal := Vector2(across.x, across.z)
|
|
if across_horizontal.length() < 0.1:
|
|
return false
|
|
across_horizontal = across_horizontal.normalized()
|
|
for low_support in low_supports:
|
|
for high_support in high_supports:
|
|
if low_support.piece == high_support.piece:
|
|
continue
|
|
var low_direction: Vector2 = low_support.direction
|
|
var high_direction: Vector2 = high_support.direction
|
|
if absf(low_direction.dot(high_direction)) < 0.85:
|
|
continue
|
|
var side_axis := Vector2(-low_direction.y, low_direction.x)
|
|
var center_delta: Vector3 = high_support.center - low_support.center
|
|
var side_separation := absf(Vector2(center_delta.x, center_delta.z).dot(side_axis))
|
|
if side_separation >= 0.4:
|
|
return true
|
|
return false
|
|
|
|
func _get_roof_supports_at_point(root: ModularBuilderPiece, world_point: Vector3) -> Array[Dictionary]:
|
|
var result: Array[Dictionary] = []
|
|
var walls: Array[Node] = []
|
|
_collect_walls(root, walls)
|
|
for node in walls:
|
|
var 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 world_a := wall.to_global(endpoints[0])
|
|
var world_b := wall.to_global(endpoints[1])
|
|
var local_direction := root.to_local(world_b) - root.to_local(world_a)
|
|
var direction := Vector2(local_direction.x, local_direction.z).normalized()
|
|
var wall_local_direction := (endpoints[1] - endpoints[0]).normalized()
|
|
var side := Vector3(-wall_local_direction.z, 0.0, wall_local_direction.x).normalized() * wall.thickness * 0.5
|
|
for endpoint in endpoints:
|
|
for height in [0.0, wall.unit_size]:
|
|
var center := wall.to_global(endpoint + Vector3.UP * height)
|
|
var support_points := [center + side, center - side]
|
|
if _simplify_vertices_check.button_pressed:
|
|
support_points.append(center)
|
|
for point in support_points:
|
|
if point.distance_to(world_point) <= 0.04:
|
|
result.append({"piece": wall, "direction": direction, "center": root.to_local(center)})
|
|
var gables: Array[Node] = []
|
|
_collect_gable_walls(root, gables)
|
|
for node in gables:
|
|
var gable := node as ModularBuilderPiece
|
|
if gable.custom_points.size() < 2:
|
|
continue
|
|
var base: PackedVector3Array = gable.call("_get_centered_gable_base") if gable.center_align else gable.custom_points
|
|
var a: Vector3 = base[0]
|
|
var b: Vector3 = base[1]
|
|
var local_direction := b - a
|
|
var direction := Vector2(local_direction.x, local_direction.z).normalized()
|
|
var side := Vector3(-local_direction.z, 0.0, local_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
|
|
var support_points := [a + side, a - side, b + side, b - side, peak + side, peak - side]
|
|
if _simplify_vertices_check.button_pressed:
|
|
support_points.append_array([a, b, peak])
|
|
for point_local in support_points:
|
|
var point := gable.to_global(point_local)
|
|
if point.distance_to(world_point) <= 0.04:
|
|
result.append({"piece": gable, "direction": direction, "center": root.to_local(gable.to_global((a + b) * 0.5))})
|
|
if result.is_empty() and root.has_meta(MERGED_VERTEX_GROUPS_META):
|
|
for group_value in root.get_meta(MERGED_VERTEX_GROUPS_META, []):
|
|
if not group_value is Dictionary:
|
|
continue
|
|
var group: Dictionary = group_value
|
|
var anchor: Vector3 = group.get("position", Vector3.ZERO)
|
|
if root.to_global(anchor).distance_to(world_point) > 0.04:
|
|
continue
|
|
var members: PackedVector3Array = group.get("members", PackedVector3Array())
|
|
for member in members:
|
|
result.append_array(_get_roof_supports_at_point(root, root.to_global(member)))
|
|
return result
|
|
|
|
func _find_roof_between_gables(root: ModularBuilderPiece, first_world: Vector3, second_world: Vector3) -> Dictionary:
|
|
var gables: Array[Node] = []
|
|
_collect_gable_walls(root, gables)
|
|
var first_gable: ModularBuilderPiece
|
|
var second_gable: ModularBuilderPiece
|
|
var first_base := PackedVector3Array()
|
|
var second_base := PackedVector3Array()
|
|
for node in gables:
|
|
var gable := node as ModularBuilderPiece
|
|
if gable.custom_points.size() < 2:
|
|
continue
|
|
var base: PackedVector3Array = gable.call("_get_centered_gable_base") if gable.center_align else gable.custom_points
|
|
var a: Vector3 = base[0]
|
|
var b: Vector3 = base[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
|
|
var peak_a := gable.to_global(peak + side)
|
|
var peak_b := gable.to_global(peak - side)
|
|
var peak_center := gable.to_global(peak)
|
|
if first_gable == null and (peak_a.distance_to(first_world) < 0.12 or peak_b.distance_to(first_world) < 0.12 or (_simplify_vertices_check.button_pressed and peak_center.distance_to(first_world) < 0.12)):
|
|
first_gable = gable
|
|
first_base = base
|
|
elif second_gable == null and gable != first_gable and (peak_a.distance_to(second_world) < 0.12 or peak_b.distance_to(second_world) < 0.12 or (_simplify_vertices_check.button_pressed and peak_center.distance_to(second_world) < 0.12)):
|
|
second_gable = gable
|
|
second_base = base
|
|
if first_gable == null or second_gable == null:
|
|
return {}
|
|
var a0 := root.to_local(first_gable.to_global(first_base[0]))
|
|
var a1 := root.to_local(first_gable.to_global(first_base[1]))
|
|
var b0 := root.to_local(second_gable.to_global(second_base[0]))
|
|
var b1 := root.to_local(second_gable.to_global(second_base[1]))
|
|
var direct_score := a0.distance_to(b0) + a1.distance_to(b1)
|
|
var crossed_score := a0.distance_to(b1) + a1.distance_to(b0)
|
|
var corners := PackedVector3Array([a0, a1, b1, b0]) if direct_score <= crossed_score else PackedVector3Array([a0, a1, b0, b1])
|
|
return {"corners": corners, "rise": (first_gable.rise + second_gable.rise) * 0.5}
|
|
|
|
func _make_wall_segment(root: ModularBuilderPiece, start_world: Vector3, finish_world: Vector3, index: int, centralize_wall: bool) -> ModularBuilderPiece:
|
|
var centered_start := _center_wall_anchor(root, start_world) if centralize_wall else start_world
|
|
var centered_finish := _center_wall_anchor(root, finish_world) if centralize_wall else finish_world
|
|
var start := root.to_local(centered_start)
|
|
var finish := root.to_local(centered_finish)
|
|
var piece_node := Node3D.new()
|
|
piece_node.set_script(PIECE_SCRIPT)
|
|
var piece := piece_node as ModularBuilderPiece
|
|
piece.name = "Wall_%02d" % index
|
|
piece.set("piece_type", ModularBuilderPiece.PieceType.WALL)
|
|
piece.set("unit_size", root.unit_size)
|
|
piece.set("thickness", root.thickness)
|
|
piece.set("centered_origin", true)
|
|
piece.set("center_align", centralize_wall)
|
|
var pivot := (start + finish) * 0.5 + Vector3.UP * root.unit_size * 0.5
|
|
piece.position = pivot
|
|
piece.set("custom_points", PackedVector3Array([start - pivot, finish - pivot]))
|
|
return piece
|
|
|
|
func _center_wall_anchor(root: ModularBuilderPiece, world_point: Vector3) -> Vector3:
|
|
var walls: Array[Node] = []
|
|
_collect_walls(root, walls)
|
|
var best_point := world_point
|
|
var best_distance := INF
|
|
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:
|
|
var center := wall.to_global(endpoint + Vector3.UP * wall.unit_size)
|
|
for surface_corner in [center + side, center - side]:
|
|
var distance := world_point.distance_to(surface_corner)
|
|
if distance < best_distance and distance <= wall.thickness + 0.04:
|
|
best_distance = distance
|
|
best_point = center
|
|
return best_point
|