Skip to content

Instantly share code, notes, and snippets.

@caspercasanova
Last active October 30, 2024 08:10
Show Gist options
  • Select an option

  • Save caspercasanova/5f8b927c86df2cadfa5cd7d71f7bfd19 to your computer and use it in GitHub Desktop.

Select an option

Save caspercasanova/5f8b927c86df2cadfa5cd7d71f7bfd19 to your computer and use it in GitHub Desktop.

The script essentially:

  • generates random towers in bounding box
  • sorts towers by centroid difference
  • connects towers with a curve
  • increases points on curve
  • instances sandbag meshes on curve
  • rotates sandbags to face outwards

Heres an image of my scenes and result

To use it just click these in order

  • regenerate perimeter towes
  • regenerate connect path
  • regenerate sandbag wall

image

Result

image

Base Scene

image

Sandbag Scene

image

Tower Scene

image

With 10 Towers

image

@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()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment