|
@tool |
|
extends Node3D |
|
|
|
|
|
# We call them towers, but think of them as verticies |
|
@export var number_of_towers: int = 4 |
|
@export var tower_scene: PackedScene |
|
@export var sandbag_scene: PackedScene |
|
@export var min_bounds: Vector3 = Vector3(-10, 0, -10) |
|
@export var max_bounds: Vector3 = Vector3(10, 0, 10) |
|
@export var regenerate_perimeter_towers: bool = false: |
|
set(val): |
|
regenerate_perimeter_towers = false |
|
clear_towers() |
|
generate_perimeter_towers() |
|
|
|
@export var regenerate_perimeter_path: bool = false: |
|
set(val): |
|
regenerate_perimeter_path = false |
|
clear_perimeter_path() |
|
generate_path_to_towers() |
|
|
|
@export var border_density = 1.0: |
|
set(value): |
|
border_density = value |
|
add_evenly_spaced_points(curve, border_density) |
|
|
|
@export var regenerate_sandbag_wall: bool = false: |
|
set(value): |
|
clear_sandbag_wall() |
|
generate_sandbag_wall() |
|
regenerate_sandbag_wall = false |
|
|
|
|
|
|
|
var path: Path3D |
|
var curve: Curve3D |
|
var towers: Array[Node3D] = [] |
|
var sandbags: Array[Node3D] = [] |
|
|
|
var edited_root = get_tree().edited_scene_root |
|
|
|
func generate_perimeter_towers(): |
|
print("Generating Perimeter Towers") |
|
for i in range(number_of_towers): |
|
var tower = tower_scene.instantiate() |
|
add_child(tower) |
|
tower.owner = get_tree().edited_scene_root |
|
tower.global_position = get_random_position() |
|
towers.append(tower) |
|
towers = sort_towers_clockwise(towers) |
|
|
|
|
|
func clear_towers(): |
|
towers.clear() |
|
for node in get_children(): |
|
remove_child(node) |
|
node.queue_free() |
|
|
|
|
|
# Function to sort points in clockwise order to form a convex polygon |
|
func sort_towers_clockwise(towers: Array[Node3D]) -> Array[Node3D]: |
|
# Step 1: Calculate the center point (centroid) |
|
var center = Vector3.ZERO |
|
for tower in towers: |
|
center += tower.global_position |
|
center /= towers.size() |
|
|
|
# Step 2: Calculate angles from center and store them with the points |
|
var towers_with_angles = [] |
|
for tower in towers: |
|
# Vector from center to point |
|
var rel_point = tower.global_position - center |
|
# Step 3: Calculate angle using atan2(y, x) |
|
var angle = atan2(rel_point.z, rel_point.x) # Assuming x and z plane for 3D |
|
towers_with_angles.append({"tower": tower, "angle": angle}) |
|
|
|
# Step 4: Sort points by angle in clockwise order |
|
towers_with_angles.sort_custom(_compare_angles) |
|
|
|
# Extract sorted points |
|
var sorted_towers: Array[Node3D] = [] |
|
for entry in towers_with_angles: |
|
sorted_towers.append(entry["tower"]) |
|
|
|
print(sorted_towers) |
|
return sorted_towers |
|
|
|
# Custom sort function for comparing angles |
|
func _compare_angles(a, b) -> int: |
|
return a["angle"] < b["angle"] if -1 else 1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
func generate_path_to_towers(): |
|
print("Generating Path") |
|
print(towers) |
|
|
|
# Create a new Path3D node |
|
path = Path3D.new() |
|
add_child(path) |
|
path.owner = get_tree().edited_scene_root |
|
curve = Curve3D.new() |
|
|
|
for tower in towers: |
|
curve.add_point(tower.global_position) |
|
|
|
# Close the path by adding the first point again at the end |
|
curve.add_point(curve.get_point_position(0)) |
|
|
|
path.curve = curve |
|
|
|
|
|
|
|
|
|
|
|
func clear_perimeter_path(): |
|
for node in get_children(): |
|
if node is Path3D: |
|
remove_child(node) |
|
node.queue_free() |
|
|
|
|
|
func get_random_position() -> Vector3: |
|
var random_x = randf_range(min_bounds.x, max_bounds.x) |
|
var random_y = randf_range(min_bounds.y, max_bounds.y) |
|
var random_z = randf_range(min_bounds.z, max_bounds.z) |
|
|
|
return Vector3(random_x, random_y, random_z) |
|
|
|
|
|
|
|
func add_evenly_spaced_points(curve: Curve3D, segment_length: int = 5, tolerance_length: = 20.0): |
|
if(!curve): |
|
return |
|
# Use tessellate_even_length to get points at regular intervals along the curve |
|
var even_points = curve.tessellate_even_length(segment_length, 1) |
|
|
|
## Optionally, you can add these points back into the curve or use them directly |
|
for point in even_points: |
|
curve.add_point(point) |
|
|
|
|
|
|
|
func generate_sandbag_wall2(): |
|
if(!curve): |
|
print("No Curve Exists") |
|
return |
|
print("Generating Perimeter Sandbags") |
|
var curve_points = curve.get_baked_points() |
|
|
|
for point in curve_points: |
|
print(point) |
|
var sandbag = sandbag_scene.instantiate() |
|
add_child(sandbag) |
|
sandbag.owner = edited_root |
|
sandbag.global_position = point |
|
|
|
|
|
func generate_sandbag_wall(): |
|
if !curve: |
|
print("No Curve Exists") |
|
return |
|
print("Generating Perimeter Sandbags") |
|
|
|
# Get baked points along the curve for even spacing |
|
var curve_points = curve.get_baked_points() |
|
|
|
# Iterate through each point on the curve |
|
for i in range(curve_points.size() - 1): |
|
var point = curve_points[i] |
|
var next_point = curve_points[i + 1] |
|
|
|
# Calculate the direction vector between the current and next point |
|
var direction = (next_point - point).normalized() |
|
|
|
# Define the outward direction using the cross product with the UP axis |
|
var outward_vector = direction.cross(Vector3.UP).normalized() |
|
|
|
# Calculate the upward vector to maintain orthogonality in the basis |
|
var up_vector = outward_vector.cross(direction).normalized() |
|
|
|
# Set up the basis for the sandbag rotation |
|
var sandbag_basis = Basis(direction, up_vector, outward_vector) |
|
# Rotate the basis 90 degrees around the Y-axis |
|
sandbag_basis = sandbag_basis.rotated(Vector3.UP, deg_to_rad(90)) |
|
|
|
|
|
# Instantiate and position the sandbag |
|
var sandbag = sandbag_scene.instantiate() |
|
add_child(sandbag) |
|
# This is a bug, I think I should be using the actual function call for get tree_root |
|
sandbag.owner = get_tree().edited_scene_root |
|
sandbag.global_position = point |
|
sandbag.global_transform.basis = sandbag_basis |
|
|
|
func clear_sandbag_wall(): |
|
for node in sandbags: |
|
remove_child(node) |
|
node.queue_free() |
|
|
|
sandbags.clear() |