748 lines
33 KiB
GDScript
748 lines
33 KiB
GDScript
@tool
|
|
class_name ModularBuilderPieceGeometry
|
|
extends Node3D
|
|
|
|
const DETAIL_SCRIPT_PATH := "res://addons/modular_builder/modular_building_detail.gd"
|
|
const DETAIL_TYPE_HATCH := 2
|
|
const DETAIL_TYPE_PILLAR := 3
|
|
|
|
enum PieceType { FOUNDATION_SQUARE, FOUNDATION_TRIANGLE, FLOOR, WALL, BUILDING, GABLE_WALL, ROOF_GABLE, ROOF_SHED, GABLE_WALL_RIGHT, ROOF_PYRAMID, STAIRS, FENCE }
|
|
|
|
@export var piece_type: PieceType = PieceType.FOUNDATION_SQUARE:
|
|
set(value):
|
|
piece_type = value
|
|
notify_property_list_changed()
|
|
_rebuild()
|
|
@export_range(0.5, 8.0, 0.001, "suffix:m") var unit_size: float = 2.0:
|
|
set(value):
|
|
unit_size = value
|
|
_rebuild()
|
|
@export_range(0.01, 1.0, 0.001, "suffix:m") var thickness: float = 0.2:
|
|
set(value):
|
|
thickness = value
|
|
_rebuild()
|
|
for child in get_children():
|
|
if child.has_method("_on_host_thickness_changed"):
|
|
child.call("_on_host_thickness_changed")
|
|
@export_range(0.1, 8.0, 0.001, "suffix:m") var rise: float = 1.0:
|
|
set(value):
|
|
rise = value
|
|
_rebuild()
|
|
@export_range(0.01, 32.0, 0.001, "suffix:m") var roof_width: float = 2.0:
|
|
set(value):
|
|
roof_width = value
|
|
_rebuild()
|
|
@export_range(1, 100, 1, "suffix: steps") var stair_count: int = 10:
|
|
set(value):
|
|
stair_count = value
|
|
_rebuild()
|
|
@export_range(0.01, 8.0, 0.001, "suffix:m") var stair_width: float = 1.0:
|
|
set(value):
|
|
stair_width = value
|
|
_rebuild()
|
|
@export_range(0, 100, 1, "suffix: posts") var fence_divisions: int = 1:
|
|
set(value):
|
|
fence_divisions = value
|
|
_rebuild()
|
|
@export_range(0.1, 8.0, 0.001, "suffix:m") var fence_height: float = 1.0:
|
|
set(value):
|
|
fence_height = value
|
|
_rebuild()
|
|
@export var symmetric_dimensions: bool = true:
|
|
set(value):
|
|
if symmetric_dimensions == value:
|
|
return
|
|
if not value:
|
|
_capture_mesh_dimensions()
|
|
symmetric_dimensions = value
|
|
notify_property_list_changed()
|
|
if value:
|
|
scale = Vector3.ONE
|
|
else:
|
|
_apply_dimension_scale()
|
|
@export_range(0.01, 100.0, 0.001, "suffix:m") var dimension_width: float = 2.0:
|
|
set(value):
|
|
dimension_width = value
|
|
_apply_dimension_scale()
|
|
@export_range(0.01, 100.0, 0.001, "suffix:m") var dimension_height: float = 2.0:
|
|
set(value):
|
|
dimension_height = value
|
|
_apply_dimension_scale()
|
|
@export_range(0.01, 100.0, 0.001, "suffix:m") var dimension_depth: float = 2.0:
|
|
set(value):
|
|
dimension_depth = value
|
|
_apply_dimension_scale()
|
|
@export_storage var _dimension_reference: Vector3 = Vector3.ZERO
|
|
@export var center_align: bool = false:
|
|
set(value):
|
|
center_align = value
|
|
_rebuild()
|
|
@export var surface_material: Material:
|
|
set(value):
|
|
surface_material = value
|
|
_rebuild()
|
|
@export var custom_points: PackedVector3Array = PackedVector3Array():
|
|
set(value):
|
|
custom_points = value
|
|
_rebuild()
|
|
@export var centered_origin: bool = false:
|
|
set(value):
|
|
centered_origin = value
|
|
_rebuild()
|
|
|
|
func _rebuild() -> void:
|
|
pass
|
|
|
|
func _capture_mesh_dimensions() -> void:
|
|
var geometry := get_node_or_null("Geometry") as MeshInstance3D
|
|
if geometry == null or geometry.mesh == null:
|
|
return
|
|
var dimensions := geometry.mesh.get_aabb().size
|
|
if dimensions.x > 0.001:
|
|
dimension_width = dimensions.x
|
|
if dimensions.y > 0.001:
|
|
dimension_height = dimensions.y
|
|
if dimensions.z > 0.001:
|
|
dimension_depth = dimensions.z
|
|
_dimension_reference = dimensions
|
|
|
|
func _apply_dimension_scale() -> void:
|
|
if symmetric_dimensions or _dimension_reference.x <= 0.001 or _dimension_reference.y <= 0.001 or _dimension_reference.z <= 0.001:
|
|
return
|
|
var target := Vector3(dimension_width, dimension_height, dimension_depth)
|
|
if piece_type == PieceType.FOUNDATION_SQUARE or piece_type == PieceType.FOUNDATION_TRIANGLE or piece_type == PieceType.FLOOR:
|
|
target.y = _dimension_reference.y
|
|
if piece_type == PieceType.WALL:
|
|
target.z = _dimension_reference.z
|
|
scale = Vector3(target.x / _dimension_reference.x, target.y / _dimension_reference.y, target.z / _dimension_reference.z)
|
|
|
|
func _make_mesh() -> ArrayMesh:
|
|
if piece_type == PieceType.GABLE_WALL and custom_points.size() >= 2:
|
|
return _make_gable_wall_mesh()
|
|
if piece_type == PieceType.GABLE_WALL_RIGHT and custom_points.size() >= 2:
|
|
return _make_right_gable_wall_mesh()
|
|
if (piece_type == PieceType.ROOF_GABLE or piece_type == PieceType.ROOF_SHED or piece_type == PieceType.ROOF_PYRAMID) and custom_points.size() >= 2:
|
|
return _make_roof_mesh()
|
|
if piece_type == PieceType.STAIRS and custom_points.size() >= 2:
|
|
return _make_stair_mesh()
|
|
if piece_type == PieceType.FENCE and custom_points.size() >= 2:
|
|
return _make_fence_mesh()
|
|
if custom_points.size() >= 2 and piece_type == PieceType.WALL:
|
|
return _make_custom_wall_mesh()
|
|
if custom_points.size() >= 3 and piece_type == PieceType.FLOOR:
|
|
return _make_custom_floor_mesh()
|
|
if custom_points.size() >= 3 and (piece_type == PieceType.FOUNDATION_SQUARE or piece_type == PieceType.FOUNDATION_TRIANGLE):
|
|
return _make_custom_foundation_mesh()
|
|
var vertices := PackedVector3Array()
|
|
var normals := PackedVector3Array()
|
|
var s := unit_size
|
|
var polygon: Array[Vector2]
|
|
match piece_type:
|
|
PieceType.FOUNDATION_TRIANGLE:
|
|
polygon = [Vector2(-s * 0.5, -s * 0.5), Vector2(s * 0.5, -s * 0.5), Vector2(-s * 0.5, s * 0.5)]
|
|
_:
|
|
polygon = [Vector2(-s * 0.5, -s * 0.5), Vector2(s * 0.5, -s * 0.5), Vector2(s * 0.5, s * 0.5), Vector2(-s * 0.5, s * 0.5)]
|
|
var y0 := -thickness * 0.5 if centered_origin else 0.0
|
|
var y1 := thickness * 0.5 if centered_origin else thickness
|
|
if piece_type == PieceType.WALL:
|
|
polygon = [Vector2(-s * 0.5, -thickness * 0.5), Vector2(s * 0.5, -thickness * 0.5), Vector2(s * 0.5, thickness * 0.5), Vector2(-s * 0.5, thickness * 0.5)]
|
|
y0 = -s * 0.5 if centered_origin else 0.0
|
|
y1 = s * 0.5 if centered_origin else s
|
|
if piece_type == PieceType.FOUNDATION_SQUARE or piece_type == PieceType.FOUNDATION_TRIANGLE:
|
|
var hatch_mesh := _make_hatch_surface_mesh(polygon, y1, y0)
|
|
if hatch_mesh != null:
|
|
return hatch_mesh
|
|
for i in range(1, polygon.size() - 1):
|
|
_add_tri(vertices, normals, Vector3(polygon[0].x, y1, polygon[0].y), Vector3(polygon[i + 1].x, y1, polygon[i + 1].y), Vector3(polygon[i].x, y1, polygon[i].y))
|
|
_add_tri(vertices, normals, Vector3(polygon[0].x, y0, polygon[0].y), Vector3(polygon[i].x, y0, polygon[i].y), Vector3(polygon[i + 1].x, y0, polygon[i + 1].y))
|
|
for i in range(polygon.size()):
|
|
var j := (i + 1) % polygon.size()
|
|
var a := polygon[i]
|
|
var b := polygon[j]
|
|
_add_tri(vertices, normals, Vector3(a.x, y0, a.y), Vector3(b.x, y0, b.y), Vector3(b.x, y1, b.y))
|
|
_add_tri(vertices, normals, Vector3(a.x, y0, a.y), Vector3(b.x, y1, b.y), Vector3(a.x, y1, a.y))
|
|
var arrays: Array = []
|
|
arrays.resize(Mesh.ARRAY_MAX)
|
|
arrays[Mesh.ARRAY_VERTEX] = vertices
|
|
arrays[Mesh.ARRAY_NORMAL] = normals
|
|
var result := ArrayMesh.new()
|
|
result.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
|
|
return result
|
|
|
|
func _make_gable_wall_mesh() -> ArrayMesh:
|
|
var vertices := PackedVector3Array()
|
|
var normals := PackedVector3Array()
|
|
var base_points := _get_centered_gable_base() if center_align else PackedVector3Array([custom_points[0], custom_points[1]])
|
|
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() * thickness * 0.5
|
|
var peak := (a + b) * 0.5 + Vector3.UP * rise
|
|
var front_a := a + side
|
|
var front_b := b + side
|
|
var front_peak := peak + side
|
|
var back_a := a - side
|
|
var back_b := b - side
|
|
var back_peak := peak - side
|
|
_add_tri(vertices, normals, front_a, front_b, front_peak)
|
|
_add_tri(vertices, normals, back_b, back_a, back_peak)
|
|
_add_quad(vertices, normals, front_a, back_a, back_b, front_b)
|
|
_add_quad(vertices, normals, front_b, back_b, back_peak, front_peak)
|
|
_add_quad(vertices, normals, front_peak, back_peak, back_a, front_a)
|
|
return _array_mesh(vertices, normals)
|
|
|
|
func _make_right_gable_wall_mesh() -> ArrayMesh:
|
|
var vertices := PackedVector3Array()
|
|
var normals := PackedVector3Array()
|
|
var base_points := _get_centered_gable_base() if center_align else PackedVector3Array([custom_points[0], custom_points[1]])
|
|
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() * thickness * 0.5
|
|
var peak := b + Vector3.UP * rise
|
|
var front_a := a + side
|
|
var front_b := b + side
|
|
var front_peak := peak + side
|
|
var back_a := a - side
|
|
var back_b := b - side
|
|
var back_peak := peak - side
|
|
_add_tri(vertices, normals, front_a, front_b, front_peak)
|
|
_add_tri(vertices, normals, back_peak, back_b, back_a)
|
|
_add_quad(vertices, normals, front_a, back_a, back_b, front_b)
|
|
_add_quad(vertices, normals, front_b, back_b, back_peak, front_peak)
|
|
_add_quad(vertices, normals, front_peak, back_peak, back_a, front_a)
|
|
return _array_mesh(vertices, normals)
|
|
|
|
func _make_roof_mesh() -> ArrayMesh:
|
|
if piece_type == PieceType.ROOF_SHED and custom_points.size() == 2:
|
|
return _make_shed_roof_mesh()
|
|
if custom_points.size() >= 4:
|
|
return _make_oriented_roof_mesh()
|
|
var vertices := PackedVector3Array()
|
|
var normals := PackedVector3Array()
|
|
var first: Vector3 = custom_points[0]
|
|
var second: Vector3 = custom_points[1]
|
|
var min_x := minf(first.x, second.x)
|
|
var max_x := maxf(first.x, second.x)
|
|
var min_z := minf(first.z, second.z)
|
|
var max_z := maxf(first.z, second.z)
|
|
var span_x := max_x - min_x
|
|
var span_z := max_z - min_z
|
|
if span_x < 0.02 or span_z < 0.02:
|
|
return _array_mesh(vertices, normals)
|
|
var panels: Array = []
|
|
if piece_type == PieceType.ROOF_GABLE:
|
|
if span_x <= span_z:
|
|
var ridge_x := (min_x + max_x) * 0.5
|
|
panels.append([Vector3(min_x, 0.0, min_z), Vector3(min_x, 0.0, max_z), Vector3(ridge_x, rise, max_z), Vector3(ridge_x, rise, min_z)])
|
|
panels.append([Vector3(ridge_x, rise, min_z), Vector3(ridge_x, rise, max_z), Vector3(max_x, 0.0, max_z), Vector3(max_x, 0.0, min_z)])
|
|
else:
|
|
var ridge_z := (min_z + max_z) * 0.5
|
|
panels.append([Vector3(min_x, 0.0, min_z), Vector3(max_x, 0.0, min_z), Vector3(max_x, rise, ridge_z), Vector3(min_x, rise, ridge_z)])
|
|
panels.append([Vector3(min_x, rise, ridge_z), Vector3(max_x, rise, ridge_z), Vector3(max_x, 0.0, max_z), Vector3(min_x, 0.0, max_z)])
|
|
else:
|
|
if span_x <= span_z:
|
|
var low_x := first.x
|
|
var high_x := second.x
|
|
panels.append([Vector3(low_x, 0.0, min_z), Vector3(low_x, 0.0, max_z), Vector3(high_x, rise, max_z), Vector3(high_x, rise, min_z)])
|
|
else:
|
|
var low_z := first.z
|
|
var high_z := second.z
|
|
panels.append([Vector3(min_x, 0.0, low_z), Vector3(max_x, 0.0, low_z), Vector3(max_x, rise, high_z), Vector3(min_x, rise, high_z)])
|
|
for panel in panels:
|
|
_append_solid_panel(vertices, normals, panel)
|
|
return _array_mesh(vertices, normals)
|
|
|
|
func _make_shed_roof_mesh() -> ArrayMesh:
|
|
var vertices := PackedVector3Array()
|
|
var normals := PackedVector3Array()
|
|
var low: Vector3 = custom_points[0]
|
|
var high: Vector3 = custom_points[1]
|
|
var slope := high - low
|
|
var horizontal := Vector3(slope.x, 0.0, slope.z)
|
|
if horizontal.length_squared() < 0.0001 or roof_width <= 0.0:
|
|
return _array_mesh(vertices, normals)
|
|
var across := Vector3(horizontal.z, 0.0, -horizontal.x).normalized() * roof_width * 0.5
|
|
var panel := [low - across, low + across, high + across, high - across]
|
|
_append_solid_panel(vertices, normals, panel)
|
|
return _array_mesh(vertices, normals)
|
|
|
|
func _make_oriented_roof_mesh() -> ArrayMesh:
|
|
var vertices := PackedVector3Array()
|
|
var normals := PackedVector3Array()
|
|
var p0: Vector3 = custom_points[0]
|
|
var p1: Vector3 = custom_points[1]
|
|
var p2: Vector3 = custom_points[2]
|
|
var p3: Vector3 = custom_points[3]
|
|
var edge_01 := Vector2(p1.x - p0.x, p1.z - p0.z).length()
|
|
var edge_12 := Vector2(p2.x - p1.x, p2.z - p1.z).length()
|
|
var panels: Array = []
|
|
if piece_type == PieceType.ROOF_GABLE:
|
|
if edge_01 <= edge_12:
|
|
var ridge_01 := (p0 + p1) * 0.5 + Vector3.UP * rise
|
|
var ridge_23 := (p2 + p3) * 0.5 + Vector3.UP * rise
|
|
panels.append([p0, p3, ridge_23, ridge_01])
|
|
panels.append([ridge_01, ridge_23, p2, p1])
|
|
else:
|
|
var ridge_12 := (p1 + p2) * 0.5 + Vector3.UP * rise
|
|
var ridge_30 := (p3 + p0) * 0.5 + Vector3.UP * rise
|
|
panels.append([p0, p1, ridge_12, ridge_30])
|
|
panels.append([ridge_30, ridge_12, p2, p3])
|
|
elif piece_type == PieceType.ROOF_PYRAMID:
|
|
var peak := (p0 + p1 + p2 + p3) * 0.25 + Vector3.UP * rise
|
|
panels.append([p0, p1, peak])
|
|
panels.append([p1, p2, peak])
|
|
panels.append([p2, p3, peak])
|
|
panels.append([p3, p0, peak])
|
|
else:
|
|
# The four points define the low and high edges. Keep their centers
|
|
# fixed when adjusting both edge widths in the Inspector.
|
|
var base_width := Vector2(p1.x - p0.x, p1.z - p0.z).length()
|
|
if base_width > 0.001 and roof_width > 0.0:
|
|
var direction := Vector3(p1.x - p0.x, 0.0, p1.z - p0.z).normalized()
|
|
var low_center := (p0 + p1) * 0.5
|
|
var high_center := (p2 + p3) * 0.5
|
|
var half_width := roof_width * 0.5
|
|
panels.append([
|
|
low_center - direction * half_width,
|
|
low_center + direction * half_width,
|
|
high_center + direction * half_width,
|
|
high_center - direction * half_width,
|
|
])
|
|
else:
|
|
panels.append([p0, p1, p2, p3])
|
|
for panel in panels:
|
|
_append_solid_panel(vertices, normals, panel)
|
|
return _array_mesh(vertices, normals)
|
|
|
|
func _make_stair_mesh() -> ArrayMesh:
|
|
var vertices := PackedVector3Array()
|
|
var normals := PackedVector3Array()
|
|
var start: Vector3 = custom_points[0]
|
|
var finish: Vector3 = custom_points[1]
|
|
var horizontal := Vector3(finish.x - start.x, 0.0, finish.z - start.z)
|
|
var run := horizontal.length()
|
|
var rise_amount := finish.y - start.y
|
|
if run < 0.05 or rise_amount <= 0.05 or stair_count < 1 or stair_width <= 0.0:
|
|
return _array_mesh(vertices, normals)
|
|
var forward := horizontal / run
|
|
var width_axis := Vector3(-forward.z, 0.0, forward.x) * stair_width * 0.5
|
|
var profile := PackedVector2Array([Vector2(0.0, 0.0), Vector2(run, 0.0), Vector2(run, rise_amount)])
|
|
for step_index in range(stair_count - 1, -1, -1):
|
|
var tread_start := run * float(step_index) / stair_count
|
|
var tread_end := run * float(step_index + 1) / stair_count
|
|
var tread_height := rise_amount * float(step_index + 1) / stair_count
|
|
profile.append(Vector2(tread_start, tread_height))
|
|
if step_index > 0:
|
|
profile.append(Vector2(tread_start, rise_amount * float(step_index) / stair_count))
|
|
var triangulation := Geometry2D.triangulate_polygon(profile)
|
|
for side_sign in [-1.0, 1.0]:
|
|
for triangle_index in range(0, triangulation.size(), 3):
|
|
var a := profile[triangulation[triangle_index]]
|
|
var b := profile[triangulation[triangle_index + 1]]
|
|
var c := profile[triangulation[triangle_index + 2]]
|
|
var point_a: Vector3 = start + forward * a.x + Vector3.UP * a.y + width_axis * side_sign
|
|
var point_b: Vector3 = start + forward * b.x + Vector3.UP * b.y + width_axis * side_sign
|
|
var point_c: Vector3 = start + forward * c.x + Vector3.UP * c.y + width_axis * side_sign
|
|
if side_sign > 0.0:
|
|
_add_tri(vertices, normals, point_a, point_b, point_c)
|
|
else:
|
|
_add_tri(vertices, normals, point_c, point_b, point_a)
|
|
for profile_index in range(profile.size()):
|
|
var next_index := (profile_index + 1) % profile.size()
|
|
var a := profile[profile_index]
|
|
var b := profile[next_index]
|
|
var point_a_left := start + forward * a.x + Vector3.UP * a.y - width_axis
|
|
var point_b_left := start + forward * b.x + Vector3.UP * b.y - width_axis
|
|
var point_b_right := start + forward * b.x + Vector3.UP * b.y + width_axis
|
|
var point_a_right := start + forward * a.x + Vector3.UP * a.y + width_axis
|
|
_add_quad(vertices, normals, point_a_left, point_b_left, point_b_right, point_a_right)
|
|
return _array_mesh(vertices, normals)
|
|
|
|
func _make_fence_mesh() -> ArrayMesh:
|
|
var vertices := PackedVector3Array()
|
|
var normals := PackedVector3Array()
|
|
var start: Vector3 = custom_points[0]
|
|
var finish: Vector3 = custom_points[1]
|
|
var path := finish - start
|
|
var path_length := path.length()
|
|
if path_length < 0.05 or fence_height <= 0.0:
|
|
return _array_mesh(vertices, normals)
|
|
var path_axis := path / path_length
|
|
var post_count := fence_divisions + 2
|
|
var post_positions: Array[Vector3] = []
|
|
for post_index in range(post_count):
|
|
var base_position := start.lerp(finish, float(post_index) / float(post_count - 1))
|
|
post_positions.append(base_position)
|
|
var side_axis := path_axis.cross(Vector3.UP).normalized()
|
|
if side_axis.length_squared() < 0.001:
|
|
side_axis = Vector3.FORWARD
|
|
_append_oriented_box(vertices, normals, base_position + Vector3.UP * fence_height * 0.5, path_axis, Vector3.UP, side_axis, Vector3(thickness, fence_height, thickness))
|
|
for section_index in range(post_positions.size() - 1):
|
|
var section_start := post_positions[section_index]
|
|
var section_end := post_positions[section_index + 1]
|
|
var section_axis := (section_end - section_start).normalized()
|
|
var rail_up := Vector3.UP - section_axis * section_axis.dot(Vector3.UP)
|
|
if rail_up.length_squared() < 0.001:
|
|
rail_up = Vector3.FORWARD - section_axis * section_axis.dot(Vector3.FORWARD)
|
|
rail_up = rail_up.normalized()
|
|
var rail_side := section_axis.cross(rail_up).normalized()
|
|
var section_length := section_start.distance_to(section_end)
|
|
for height_ratio in [0.28, 0.78]:
|
|
var rail_center: Vector3 = (section_start + section_end) * 0.5 + Vector3.UP * fence_height * height_ratio
|
|
_append_oriented_box(vertices, normals, rail_center, section_axis, rail_up, rail_side, Vector3(section_length, thickness, thickness))
|
|
return _array_mesh(vertices, normals)
|
|
|
|
func _append_oriented_box(vertices: PackedVector3Array, normals: PackedVector3Array, center: Vector3, axis_x: Vector3, axis_y: Vector3, axis_z: Vector3, dimensions: Vector3) -> void:
|
|
var x := axis_x.normalized() * dimensions.x * 0.5
|
|
var y := axis_y.normalized() * dimensions.y * 0.5
|
|
var z := axis_z.normalized() * dimensions.z * 0.5
|
|
var nnn := center - x - y - z
|
|
var pnn := center + x - y - z
|
|
var ppn := center + x + y - z
|
|
var npn := center - x + y - z
|
|
var nnp := center - x - y + z
|
|
var pnp := center + x - y + z
|
|
var ppp := center + x + y + z
|
|
var npp := center - x + y + z
|
|
_add_quad(vertices, normals, nnp, pnp, ppp, npp)
|
|
_add_quad(vertices, normals, pnn, nnn, npn, ppn)
|
|
_add_quad(vertices, normals, npp, ppp, ppn, npn)
|
|
_add_quad(vertices, normals, nnn, pnn, pnp, nnp)
|
|
_add_quad(vertices, normals, nnn, nnp, npp, npn)
|
|
_add_quad(vertices, normals, pnp, pnn, ppn, ppp)
|
|
|
|
func _get_centered_gable_base() -> PackedVector3Array:
|
|
var selected_a := to_global(custom_points[0])
|
|
var selected_b := to_global(custom_points[1])
|
|
var search_root: Node = get_parent()
|
|
while search_root != null and not search_root is ModularBuilderPieceGeometry:
|
|
search_root = search_root.get_parent()
|
|
if search_root == null:
|
|
return PackedVector3Array([custom_points[0], custom_points[1]])
|
|
var walls: Array[ModularBuilderPieceGeometry] = []
|
|
_collect_centering_walls(search_root, walls)
|
|
var centered_a := selected_a
|
|
var centered_b := selected_b
|
|
var nearest_a := INF
|
|
var nearest_b := INF
|
|
for wall in walls:
|
|
if wall.custom_points.size() < 2:
|
|
continue
|
|
var wall_a := wall.to_global(wall.custom_points[0] + Vector3.UP * wall.unit_size)
|
|
var wall_b := wall.to_global(wall.custom_points[1] + Vector3.UP * wall.unit_size)
|
|
var tolerance: float = wall.thickness + 0.04
|
|
for endpoint in [wall_a, wall_b]:
|
|
var distance_a := selected_a.distance_to(endpoint)
|
|
if distance_a < nearest_a and distance_a <= tolerance:
|
|
nearest_a = distance_a
|
|
centered_a = endpoint
|
|
var distance_b := selected_b.distance_to(endpoint)
|
|
if distance_b < nearest_b and distance_b <= tolerance:
|
|
nearest_b = distance_b
|
|
centered_b = endpoint
|
|
if nearest_a < INF and nearest_b < INF and centered_a.distance_to(centered_b) >= 0.02:
|
|
return PackedVector3Array([to_local(centered_a), to_local(centered_b)])
|
|
return PackedVector3Array([custom_points[0], custom_points[1]])
|
|
|
|
func _collect_centering_walls(node: Node, walls: Array[ModularBuilderPieceGeometry]) -> void:
|
|
if node is ModularBuilderPieceGeometry and node.piece_type == PieceType.WALL:
|
|
walls.append(node)
|
|
for child in node.get_children():
|
|
_collect_centering_walls(child, walls)
|
|
|
|
func _append_solid_panel(vertices: PackedVector3Array, normals: PackedVector3Array, panel: Array) -> void:
|
|
if panel.size() < 3:
|
|
return
|
|
var p0: Vector3 = panel[0]
|
|
var p1: Vector3 = panel[1]
|
|
var p2: Vector3 = panel[2]
|
|
var normal: Vector3 = (p1 - p0).cross(p2 - p0).normalized()
|
|
if normal.is_zero_approx():
|
|
return
|
|
var half_depth: Vector3 = normal * thickness * 0.5
|
|
var front: Array[Vector3] = []
|
|
var back: Array[Vector3] = []
|
|
for point in panel:
|
|
front.append(point + half_depth)
|
|
back.append(point - half_depth)
|
|
for i in range(1, panel.size() - 1):
|
|
_add_tri(vertices, normals, front[0], front[i], front[i + 1])
|
|
_add_tri(vertices, normals, back[i + 1], back[i], back[0])
|
|
for i in range(panel.size()):
|
|
var next := (i + 1) % panel.size()
|
|
_add_quad(vertices, normals, front[i], back[i], back[next], front[next])
|
|
|
|
func _make_custom_wall_mesh() -> ArrayMesh:
|
|
var vertices := PackedVector3Array()
|
|
var normals := PackedVector3Array()
|
|
var wall_points: PackedVector3Array = _get_centered_wall_points() if center_align else custom_points
|
|
var a: Vector3 = wall_points[0]
|
|
var b: Vector3 = wall_points[1]
|
|
var direction := (b - a).normalized()
|
|
var length := a.distance_to(b)
|
|
var side := Vector3(-direction.z, 0.0, direction.x).normalized() * thickness * 0.5
|
|
var height := Vector3.UP * unit_size
|
|
var openings: Array[PackedVector2Array] = []
|
|
for child in get_children():
|
|
if _is_detail_node(child) and int(child.get("detail_type")) != DETAIL_TYPE_HATCH and int(child.get("detail_type")) != DETAIL_TYPE_PILLAR:
|
|
var detail := child as Node3D
|
|
var center := Vector2((detail.position - a).dot(direction), detail.position.y - a.y)
|
|
var axis_x := Vector2(detail.transform.basis.x.dot(direction), detail.transform.basis.x.y).normalized()
|
|
var axis_y := Vector2(detail.transform.basis.y.dot(direction), detail.transform.basis.y.y).normalized()
|
|
var opening := PackedVector2Array([
|
|
center - axis_x * float(detail.get("width")) * 0.5 - axis_y * float(detail.get("height")) * 0.5,
|
|
center + axis_x * float(detail.get("width")) * 0.5 - axis_y * float(detail.get("height")) * 0.5,
|
|
center + axis_x * float(detail.get("width")) * 0.5 + axis_y * float(detail.get("height")) * 0.5,
|
|
center - axis_x * float(detail.get("width")) * 0.5 + axis_y * float(detail.get("height")) * 0.5
|
|
])
|
|
var wall_rect := PackedVector2Array([Vector2(0.0, 0.0), Vector2(length, 0.0), Vector2(length, unit_size), Vector2(0.0, unit_size)])
|
|
for clipped_opening in Geometry2D.intersect_polygons(opening, wall_rect):
|
|
if clipped_opening.size() >= 3:
|
|
openings.append(clipped_opening)
|
|
var xs: Array[float] = [0.0, length]
|
|
var ys: Array[float] = [0.0, unit_size]
|
|
for opening in openings:
|
|
for point in opening:
|
|
xs.append(clampf(point.x, 0.0, length))
|
|
ys.append(clampf(point.y, 0.0, unit_size))
|
|
xs.sort()
|
|
ys.sort()
|
|
for xi in range(xs.size() - 1):
|
|
for yi in range(ys.size() - 1):
|
|
var u0: float = xs[xi]
|
|
var u1: float = xs[xi + 1]
|
|
var v0: float = ys[yi]
|
|
var v1: float = ys[yi + 1]
|
|
var mid := Vector2((u0 + u1) * 0.5, (v0 + v1) * 0.5)
|
|
var cell := PackedVector2Array([Vector2(u0, v0), Vector2(u1, v0), Vector2(u1, v1), Vector2(u0, v1)])
|
|
var remaining: Array[PackedVector2Array] = [cell]
|
|
for opening in openings:
|
|
var next_remaining: Array[PackedVector2Array] = []
|
|
for polygon in remaining:
|
|
for clipped in Geometry2D.clip_polygons(polygon, opening):
|
|
if clipped.size() >= 3:
|
|
next_remaining.append(clipped)
|
|
remaining = next_remaining
|
|
for polygon in remaining:
|
|
var indices := Geometry2D.triangulate_polygon(polygon)
|
|
for ti in range(0, indices.size(), 3):
|
|
var p0: Vector2 = polygon[indices[ti]]
|
|
var p1: Vector2 = polygon[indices[ti + 1]]
|
|
var p2: Vector2 = polygon[indices[ti + 2]]
|
|
var f0 := a + direction * p0.x + Vector3.UP * p0.y
|
|
var f1 := a + direction * p1.x + Vector3.UP * p1.y
|
|
var f2 := a + direction * p2.x + Vector3.UP * p2.y
|
|
_add_tri(vertices, normals, f0 + side, f1 + side, f2 + side)
|
|
_add_tri(vertices, normals, f2 - side, f1 - side, f0 - side)
|
|
# Close the wall body at its ends, top, and base.
|
|
_add_quad(vertices, normals, a - side, a + side, a + side + height, a - side + height)
|
|
_add_quad(vertices, normals, b + side, b - side, b - side + height, b + side + height)
|
|
_add_quad(vertices, normals, a + side + height, b + side + height, b - side + height, a - side + height)
|
|
_add_quad(vertices, normals, a - side, b - side, b + side, a + side)
|
|
for opening in openings:
|
|
for edge_index in range(opening.size()):
|
|
var p0 := opening[edge_index]
|
|
var p1 := opening[(edge_index + 1) % opening.size()]
|
|
var a0 := a + direction * p0.x + Vector3.UP * p0.y
|
|
var a1 := a + direction * p1.x + Vector3.UP * p1.y
|
|
_add_quad(vertices, normals, a0 + side, a0 - side, a1 - side, a1 + side)
|
|
return _array_mesh(vertices, normals)
|
|
|
|
func _get_centered_wall_points() -> PackedVector3Array:
|
|
if custom_points.size() < 2:
|
|
return custom_points
|
|
var selected_a := to_global(custom_points[0])
|
|
var selected_b := to_global(custom_points[1])
|
|
var search_root: Node = get_parent()
|
|
while search_root != null and not search_root is ModularBuilderPieceGeometry:
|
|
search_root = search_root.get_parent()
|
|
if search_root == null:
|
|
return custom_points
|
|
var walls: Array[ModularBuilderPieceGeometry] = []
|
|
_collect_centering_walls(search_root, walls)
|
|
var centered_a := selected_a
|
|
var centered_b := selected_b
|
|
var nearest_a := INF
|
|
var nearest_b := INF
|
|
for wall in walls:
|
|
if wall == self or wall.custom_points.size() < 2:
|
|
continue
|
|
var direction := (wall.custom_points[1] - wall.custom_points[0]).normalized()
|
|
var side := Vector3(-direction.z, 0.0, direction.x).normalized() * wall.thickness * 0.5
|
|
for endpoint in wall.custom_points:
|
|
var center := wall.to_global(endpoint + Vector3.UP * wall.unit_size)
|
|
for surface_corner in [center + side, center - side]:
|
|
var distance_a := selected_a.distance_to(surface_corner)
|
|
if distance_a < nearest_a and distance_a <= wall.thickness + 0.04:
|
|
nearest_a = distance_a
|
|
centered_a = center
|
|
var distance_b := selected_b.distance_to(surface_corner)
|
|
if distance_b < nearest_b and distance_b <= wall.thickness + 0.04:
|
|
nearest_b = distance_b
|
|
centered_b = center
|
|
if nearest_a < INF and nearest_b < INF and centered_a.distance_to(centered_b) >= 0.02:
|
|
return PackedVector3Array([to_local(centered_a), to_local(centered_b)])
|
|
return custom_points
|
|
|
|
func _make_custom_floor_mesh() -> ArrayMesh:
|
|
var polygon: Array[Vector2] = []
|
|
for point in custom_points:
|
|
polygon.append(Vector2(point.x, point.z))
|
|
var hatch_mesh := _make_hatch_surface_mesh(polygon, custom_points[0].y, custom_points[0].y - thickness)
|
|
if hatch_mesh != null:
|
|
return hatch_mesh
|
|
var vertices := PackedVector3Array()
|
|
var normals := PackedVector3Array()
|
|
var points := custom_points
|
|
var bottom: Array[Vector3] = []
|
|
for point in points:
|
|
bottom.append(point - Vector3.UP * thickness)
|
|
for i in range(1, points.size() - 1):
|
|
_add_tri(vertices, normals, points[0], points[i + 1], points[i])
|
|
_add_tri(vertices, normals, bottom[0], bottom[i], bottom[i + 1])
|
|
for i in range(points.size()):
|
|
var j := (i + 1) % points.size()
|
|
_add_quad(vertices, normals, bottom[i], bottom[j], points[j], points[i])
|
|
return _array_mesh(vertices, normals)
|
|
|
|
func _make_hatch_surface_mesh(polygon: Array[Vector2], top_y: float, bottom_y: float) -> ArrayMesh:
|
|
var openings: Array[PackedVector2Array] = []
|
|
for child in get_children():
|
|
if _is_detail_node(child) and int(child.get("detail_type")) == DETAIL_TYPE_HATCH:
|
|
var detail := child as Node3D
|
|
var center := Vector2(detail.position.x, detail.position.z)
|
|
var axis_x := Vector2(detail.transform.basis.x.x, detail.transform.basis.x.z).normalized()
|
|
var axis_y := Vector2(detail.transform.basis.z.x, detail.transform.basis.z.z).normalized()
|
|
var opening := PackedVector2Array([
|
|
center - axis_x * float(detail.get("width")) * 0.5 - axis_y * float(detail.get("height")) * 0.5,
|
|
center + axis_x * float(detail.get("width")) * 0.5 - axis_y * float(detail.get("height")) * 0.5,
|
|
center + axis_x * float(detail.get("width")) * 0.5 + axis_y * float(detail.get("height")) * 0.5,
|
|
center - axis_x * float(detail.get("width")) * 0.5 + axis_y * float(detail.get("height")) * 0.5
|
|
])
|
|
openings.append(opening)
|
|
if openings.is_empty() or polygon.size() < 3:
|
|
return null
|
|
var min_x := polygon[0].x
|
|
var max_x := polygon[0].x
|
|
var min_z := polygon[0].y
|
|
var max_z := polygon[0].y
|
|
var xs: Array[float] = []
|
|
var zs: Array[float] = []
|
|
for point in polygon:
|
|
min_x = minf(min_x, point.x)
|
|
max_x = maxf(max_x, point.x)
|
|
min_z = minf(min_z, point.y)
|
|
max_z = maxf(max_z, point.y)
|
|
xs.append(point.x)
|
|
zs.append(point.y)
|
|
for opening in openings:
|
|
for point in opening:
|
|
xs.append(clampf(point.x, min_x, max_x))
|
|
zs.append(clampf(point.y, min_z, max_z))
|
|
xs.sort()
|
|
zs.sort()
|
|
var vertices := PackedVector3Array()
|
|
var normals := PackedVector3Array()
|
|
var source_polygon := PackedVector2Array()
|
|
for point in polygon:
|
|
source_polygon.append(point)
|
|
for xi in range(xs.size() - 1):
|
|
for zi in range(zs.size() - 1):
|
|
var x0: float = xs[xi]
|
|
var x1: float = xs[xi + 1]
|
|
var z0: float = zs[zi]
|
|
var z1: float = zs[zi + 1]
|
|
if x1 - x0 < 0.0001 or z1 - z0 < 0.0001:
|
|
continue
|
|
var cell := PackedVector2Array([Vector2(x0, z0), Vector2(x1, z0), Vector2(x1, z1), Vector2(x0, z1)])
|
|
var remaining: Array[PackedVector2Array] = []
|
|
for clipped_surface in Geometry2D.intersect_polygons(source_polygon, cell):
|
|
remaining.append(clipped_surface)
|
|
for opening in openings:
|
|
var next_remaining: Array[PackedVector2Array] = []
|
|
for surface_polygon in remaining:
|
|
for clipped_surface in Geometry2D.clip_polygons(surface_polygon, opening):
|
|
if clipped_surface.size() >= 3:
|
|
next_remaining.append(clipped_surface)
|
|
remaining = next_remaining
|
|
for clipped_surface in remaining:
|
|
var triangulated := Geometry2D.triangulate_polygon(clipped_surface)
|
|
for ti in range(0, triangulated.size(), 3):
|
|
var a: Vector2 = clipped_surface[triangulated[ti]]
|
|
var b: Vector2 = clipped_surface[triangulated[ti + 1]]
|
|
var c: Vector2 = clipped_surface[triangulated[ti + 2]]
|
|
_add_tri(vertices, normals, Vector3(a.x, top_y, a.y), Vector3(c.x, top_y, c.y), Vector3(b.x, top_y, b.y))
|
|
_add_tri(vertices, normals, Vector3(a.x, bottom_y, a.y), Vector3(b.x, bottom_y, b.y), Vector3(c.x, bottom_y, c.y))
|
|
for i in range(polygon.size()):
|
|
var j := (i + 1) % polygon.size()
|
|
var a := polygon[i]
|
|
var b := polygon[j]
|
|
_add_quad(vertices, normals, Vector3(a.x, bottom_y, a.y), Vector3(b.x, bottom_y, b.y), Vector3(b.x, top_y, b.y), Vector3(a.x, top_y, a.y))
|
|
for opening in openings:
|
|
var visible_edges := Geometry2D.intersect_polygons(opening, source_polygon)
|
|
for visible_opening in visible_edges:
|
|
for edge_index in range(visible_opening.size()):
|
|
var p0 := visible_opening[edge_index]
|
|
var p1 := visible_opening[(edge_index + 1) % visible_opening.size()]
|
|
_add_quad(vertices, normals, Vector3(p0.x, bottom_y, p0.y), Vector3(p0.x, top_y, p0.y), Vector3(p1.x, top_y, p1.y), Vector3(p1.x, bottom_y, p1.y))
|
|
return _array_mesh(vertices, normals)
|
|
|
|
func _is_detail_node(node: Node) -> bool:
|
|
if not node is Node3D or node.get_script() == null:
|
|
return false
|
|
return node.get_script().resource_path == DETAIL_SCRIPT_PATH
|
|
|
|
func _make_custom_foundation_mesh() -> ArrayMesh:
|
|
var polygon: Array[Vector2] = []
|
|
for point in custom_points:
|
|
polygon.append(Vector2(point.x, point.z))
|
|
var bottom_y := -thickness * 0.5 if centered_origin else 0.0
|
|
var top_y := thickness * 0.5 if centered_origin else thickness
|
|
var hatch_mesh := _make_hatch_surface_mesh(polygon, top_y, bottom_y)
|
|
if hatch_mesh != null:
|
|
return hatch_mesh
|
|
var vertices := PackedVector3Array()
|
|
var normals := PackedVector3Array()
|
|
for i in range(1, custom_points.size() - 1):
|
|
var a := custom_points[0]
|
|
var b := custom_points[i]
|
|
var c := custom_points[i + 1]
|
|
_add_tri(vertices, normals, Vector3(a.x, top_y, a.z), Vector3(c.x, top_y, c.z), Vector3(b.x, top_y, b.z))
|
|
_add_tri(vertices, normals, Vector3(a.x, bottom_y, a.z), Vector3(b.x, bottom_y, b.z), Vector3(c.x, bottom_y, c.z))
|
|
for i in range(custom_points.size()):
|
|
var j := (i + 1) % custom_points.size()
|
|
var a := custom_points[i]
|
|
var b := custom_points[j]
|
|
_add_quad(vertices, normals, Vector3(a.x, bottom_y, a.z), Vector3(b.x, bottom_y, b.z), Vector3(b.x, top_y, b.z), Vector3(a.x, top_y, a.z))
|
|
return _array_mesh(vertices, normals)
|
|
|
|
func _add_quad(v: PackedVector3Array, n: PackedVector3Array, a: Vector3, b: Vector3, c: Vector3, d: Vector3) -> void:
|
|
_add_tri(v, n, a, b, c)
|
|
_add_tri(v, n, a, c, d)
|
|
|
|
func _array_mesh(v: PackedVector3Array, n: PackedVector3Array) -> ArrayMesh:
|
|
var arrays: Array = []
|
|
arrays.resize(Mesh.ARRAY_MAX)
|
|
arrays[Mesh.ARRAY_VERTEX] = v
|
|
arrays[Mesh.ARRAY_NORMAL] = n
|
|
var result := ArrayMesh.new()
|
|
result.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
|
|
return result
|
|
|
|
func _add_tri(v: PackedVector3Array, n: PackedVector3Array, a: Vector3, b: Vector3, c: Vector3) -> void:
|
|
var normal := (b - a).cross(c - a).normalized()
|
|
v.append(a)
|
|
v.append(b)
|
|
v.append(c)
|
|
n.append(normal)
|
|
n.append(normal)
|
|
n.append(normal)
|
|
# Also generate reversed faces to show both sides.
|
|
# This keeps floors and foundations visible from below with any material.
|
|
v.append(c)
|
|
v.append(b)
|
|
v.append(a)
|
|
n.append(-normal)
|
|
n.append(-normal)
|
|
n.append(-normal)
|