467 lines
13 KiB
GDScript
467 lines
13 KiB
GDScript
extends Node3D
|
|
class_name TrackGenerator
|
|
|
|
## Generatore procedurale di binari ferroviari
|
|
|
|
# Configurazione binari
|
|
@export var track_width: float = 2.0
|
|
@export var rail_height: float = 0.15
|
|
@export var rail_width: float = 0.1
|
|
@export var sleeper_spacing: float = 1.0
|
|
@export var sleeper_width: float = 2.4
|
|
@export var sleeper_height: float = 0.1
|
|
@export var sleeper_depth: float = 0.25
|
|
|
|
# Configurazione generazione
|
|
@export var segment_length: float = 20.0
|
|
@export var curve_points: int = 24
|
|
@export var min_curve_radius: float = 50.0
|
|
@export var max_curve_radius: float = 80.0
|
|
@export var max_curve_angle: float = 30.0
|
|
@export var straight_probability: float = 0.5
|
|
@export var segments_ahead: int = 12
|
|
@export var segments_behind: int = 4
|
|
|
|
# Materiali
|
|
var rail_material: StandardMaterial3D
|
|
var sleeper_material: StandardMaterial3D
|
|
var ballast_material: StandardMaterial3D
|
|
|
|
# Stato del tracciato
|
|
var segments: Array = []
|
|
var total_length: float = 0.0
|
|
|
|
# Posizione corrente della "testa" del binario
|
|
var head_position: Vector3 = Vector3.ZERO
|
|
var head_direction: Vector3 = Vector3(0, 0, -1) # Avanti in Godot
|
|
|
|
# Per curve più naturali
|
|
var last_curve_dir: int = 0
|
|
|
|
|
|
func _ready() -> void:
|
|
_setup_materials()
|
|
_generate_initial_track()
|
|
|
|
|
|
func _setup_materials() -> void:
|
|
rail_material = StandardMaterial3D.new()
|
|
rail_material.albedo_color = Color(0.35, 0.35, 0.4)
|
|
rail_material.metallic = 0.8
|
|
rail_material.roughness = 0.3
|
|
|
|
sleeper_material = StandardMaterial3D.new()
|
|
sleeper_material.albedo_color = Color(0.4, 0.28, 0.18)
|
|
sleeper_material.roughness = 0.9
|
|
|
|
ballast_material = StandardMaterial3D.new()
|
|
ballast_material.albedo_color = Color(0.55, 0.5, 0.45)
|
|
ballast_material.roughness = 1.0
|
|
|
|
|
|
func _generate_initial_track() -> void:
|
|
for i in range(segments_ahead):
|
|
_add_next_segment()
|
|
|
|
|
|
func _add_next_segment() -> void:
|
|
var is_curve = randf() > straight_probability
|
|
|
|
var segment: Dictionary
|
|
if is_curve:
|
|
segment = _create_curve_segment()
|
|
else:
|
|
segment = _create_straight_segment()
|
|
|
|
# Crea la mesh visiva
|
|
_create_segment_mesh(segment)
|
|
|
|
# Aggiorna la testa del binario per il prossimo segmento
|
|
head_position = segment["end_position"]
|
|
head_direction = segment["end_direction"]
|
|
total_length += segment["length"]
|
|
|
|
segments.append(segment)
|
|
|
|
|
|
func _create_straight_segment() -> Dictionary:
|
|
var length = segment_length
|
|
var end_pos = head_position + head_direction * length
|
|
|
|
# Genera punti lungo il segmento dritto
|
|
var points: Array = []
|
|
var tangents: Array = []
|
|
var num_points = 10
|
|
|
|
for i in range(num_points + 1):
|
|
var t = float(i) / float(num_points)
|
|
var pos = head_position.lerp(end_pos, t)
|
|
points.append(pos)
|
|
tangents.append(head_direction)
|
|
|
|
return {
|
|
"start_position": head_position,
|
|
"end_position": end_pos,
|
|
"start_direction": head_direction,
|
|
"end_direction": head_direction,
|
|
"length": length,
|
|
"start_distance": total_length,
|
|
"is_curve": false,
|
|
"path_points": points,
|
|
"path_tangents": tangents,
|
|
"mesh_instance": null
|
|
}
|
|
|
|
|
|
func _create_curve_segment() -> Dictionary:
|
|
var radius = randf_range(min_curve_radius, max_curve_radius)
|
|
var angle_deg = randf_range(15.0, max_curve_angle)
|
|
|
|
# Alterna direzione curve per percorso naturale
|
|
var curve_dir: int
|
|
if last_curve_dir == 0:
|
|
curve_dir = 1 if randf() > 0.5 else -1
|
|
else:
|
|
curve_dir = -last_curve_dir if randf() > 0.3 else last_curve_dir
|
|
last_curve_dir = curve_dir
|
|
|
|
# Calcola il centro della curva
|
|
var right = head_direction.cross(Vector3.UP).normalized()
|
|
var center = head_position + right * radius * curve_dir
|
|
|
|
# Lunghezza dell'arco
|
|
var angle_rad = deg_to_rad(angle_deg)
|
|
var arc_length = radius * angle_rad
|
|
|
|
# Genera punti lungo la curva
|
|
var points: Array = []
|
|
var tangents: Array = []
|
|
|
|
for i in range(curve_points + 1):
|
|
var t = float(i) / float(curve_points)
|
|
var current_angle = angle_rad * t * curve_dir
|
|
|
|
# Posizione: ruota il vettore dal centro al punto iniziale
|
|
var start_to_center = head_position - center
|
|
var rotated_vec = _rotate_y(start_to_center, current_angle)
|
|
var pos = center + rotated_vec
|
|
|
|
# Tangente: ruota la direzione iniziale
|
|
var tangent = _rotate_y(head_direction, current_angle)
|
|
|
|
points.append(pos)
|
|
tangents.append(tangent)
|
|
|
|
var end_pos = points[points.size() - 1]
|
|
var end_dir = tangents[tangents.size() - 1]
|
|
|
|
return {
|
|
"start_position": head_position,
|
|
"end_position": end_pos,
|
|
"start_direction": head_direction,
|
|
"end_direction": end_dir,
|
|
"length": arc_length,
|
|
"start_distance": total_length,
|
|
"is_curve": true,
|
|
"path_points": points,
|
|
"path_tangents": tangents,
|
|
"mesh_instance": null
|
|
}
|
|
|
|
|
|
func _rotate_y(vec: Vector3, angle: float) -> Vector3:
|
|
var cos_a = cos(angle)
|
|
var sin_a = sin(angle)
|
|
return Vector3(
|
|
vec.x * cos_a - vec.z * sin_a,
|
|
vec.y,
|
|
vec.x * sin_a + vec.z * cos_a
|
|
)
|
|
|
|
|
|
func _create_segment_mesh(segment: Dictionary) -> void:
|
|
var mesh_instance = MeshInstance3D.new()
|
|
var array_mesh = ArrayMesh.new()
|
|
|
|
var rail_arrays = _generate_rails_mesh(segment)
|
|
if rail_arrays[Mesh.ARRAY_VERTEX].size() > 0:
|
|
array_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, rail_arrays)
|
|
array_mesh.surface_set_material(array_mesh.get_surface_count() - 1, rail_material)
|
|
|
|
var sleeper_arrays = _generate_sleepers_mesh(segment)
|
|
if sleeper_arrays[Mesh.ARRAY_VERTEX].size() > 0:
|
|
array_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, sleeper_arrays)
|
|
array_mesh.surface_set_material(array_mesh.get_surface_count() - 1, sleeper_material)
|
|
|
|
var ballast_arrays = _generate_ballast_mesh(segment)
|
|
if ballast_arrays[Mesh.ARRAY_VERTEX].size() > 0:
|
|
array_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, ballast_arrays)
|
|
array_mesh.surface_set_material(array_mesh.get_surface_count() - 1, ballast_material)
|
|
|
|
mesh_instance.mesh = array_mesh
|
|
mesh_instance.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_ON
|
|
add_child(mesh_instance)
|
|
segment["mesh_instance"] = mesh_instance
|
|
|
|
|
|
func _generate_rails_mesh(segment: Dictionary) -> Array:
|
|
var vertices = PackedVector3Array()
|
|
var normals = PackedVector3Array()
|
|
var uvs = PackedVector2Array()
|
|
var indices = PackedInt32Array()
|
|
|
|
var half_track = track_width / 2.0
|
|
var half_rail = rail_width / 2.0
|
|
var path_points: Array = segment["path_points"]
|
|
var path_tangents: Array = segment["path_tangents"]
|
|
|
|
for rail_side in [-1, 1]:
|
|
var rail_offset = half_track * rail_side
|
|
var vertex_offset = vertices.size()
|
|
|
|
for i in range(path_points.size()):
|
|
var pos: Vector3 = path_points[i]
|
|
var tangent: Vector3 = path_tangents[i]
|
|
var right = tangent.cross(Vector3.UP).normalized()
|
|
|
|
var base = pos + right * rail_offset
|
|
|
|
# 4 vertici per sezione
|
|
vertices.append(base - right * half_rail)
|
|
vertices.append(base + right * half_rail)
|
|
vertices.append(base + right * half_rail + Vector3.UP * rail_height)
|
|
vertices.append(base - right * half_rail + Vector3.UP * rail_height)
|
|
|
|
for _j in range(4):
|
|
normals.append(Vector3.UP)
|
|
|
|
var u = float(i) / float(path_points.size())
|
|
uvs.append(Vector2(0, u))
|
|
uvs.append(Vector2(1, u))
|
|
uvs.append(Vector2(1, u))
|
|
uvs.append(Vector2(0, u))
|
|
|
|
# Collega le sezioni
|
|
for i in range(path_points.size() - 1):
|
|
var base_idx = vertex_offset + i * 4
|
|
|
|
# Top
|
|
indices.append(base_idx + 2)
|
|
indices.append(base_idx + 3)
|
|
indices.append(base_idx + 7)
|
|
indices.append(base_idx + 2)
|
|
indices.append(base_idx + 7)
|
|
indices.append(base_idx + 6)
|
|
|
|
# Sides
|
|
indices.append(base_idx + 0)
|
|
indices.append(base_idx + 4)
|
|
indices.append(base_idx + 7)
|
|
indices.append(base_idx + 0)
|
|
indices.append(base_idx + 7)
|
|
indices.append(base_idx + 3)
|
|
|
|
indices.append(base_idx + 1)
|
|
indices.append(base_idx + 2)
|
|
indices.append(base_idx + 6)
|
|
indices.append(base_idx + 1)
|
|
indices.append(base_idx + 6)
|
|
indices.append(base_idx + 5)
|
|
|
|
var arrays = []
|
|
arrays.resize(Mesh.ARRAY_MAX)
|
|
arrays[Mesh.ARRAY_VERTEX] = vertices
|
|
arrays[Mesh.ARRAY_NORMAL] = normals
|
|
arrays[Mesh.ARRAY_TEX_UV] = uvs
|
|
arrays[Mesh.ARRAY_INDEX] = indices
|
|
return arrays
|
|
|
|
|
|
func _generate_sleepers_mesh(segment: Dictionary) -> Array:
|
|
var vertices = PackedVector3Array()
|
|
var normals = PackedVector3Array()
|
|
var uvs = PackedVector2Array()
|
|
var indices = PackedInt32Array()
|
|
|
|
var seg_length: float = segment["length"]
|
|
var num_sleepers = max(1, int(seg_length / sleeper_spacing))
|
|
var path_points: Array = segment["path_points"]
|
|
var path_tangents: Array = segment["path_tangents"]
|
|
|
|
for i in range(num_sleepers):
|
|
var t = float(i) / float(num_sleepers)
|
|
var pos = _interpolate_path(path_points, t)
|
|
var tangent = _interpolate_path(path_tangents, t).normalized()
|
|
var right = tangent.cross(Vector3.UP).normalized()
|
|
|
|
var half_w = sleeper_width / 2.0
|
|
var half_d = sleeper_depth / 2.0
|
|
var base_idx = vertices.size()
|
|
|
|
var corners = [
|
|
pos + right * (-half_w) + tangent * (-half_d),
|
|
pos + right * half_w + tangent * (-half_d),
|
|
pos + right * half_w + tangent * half_d,
|
|
pos + right * (-half_w) + tangent * half_d,
|
|
]
|
|
|
|
# Bottom vertices
|
|
for c in corners:
|
|
vertices.append(c)
|
|
normals.append(Vector3.DOWN)
|
|
uvs.append(Vector2(0, 0))
|
|
|
|
# Top vertices
|
|
for c in corners:
|
|
vertices.append(c + Vector3.UP * sleeper_height)
|
|
normals.append(Vector3.UP)
|
|
uvs.append(Vector2(1, 1))
|
|
|
|
# Top face
|
|
indices.append(base_idx + 4)
|
|
indices.append(base_idx + 5)
|
|
indices.append(base_idx + 6)
|
|
indices.append(base_idx + 4)
|
|
indices.append(base_idx + 6)
|
|
indices.append(base_idx + 7)
|
|
|
|
# Side faces
|
|
for j in range(4):
|
|
var j_next = (j + 1) % 4
|
|
indices.append(base_idx + j)
|
|
indices.append(base_idx + j_next)
|
|
indices.append(base_idx + j_next + 4)
|
|
indices.append(base_idx + j)
|
|
indices.append(base_idx + j_next + 4)
|
|
indices.append(base_idx + j + 4)
|
|
|
|
var arrays = []
|
|
arrays.resize(Mesh.ARRAY_MAX)
|
|
arrays[Mesh.ARRAY_VERTEX] = vertices
|
|
arrays[Mesh.ARRAY_NORMAL] = normals
|
|
arrays[Mesh.ARRAY_TEX_UV] = uvs
|
|
arrays[Mesh.ARRAY_INDEX] = indices
|
|
return arrays
|
|
|
|
|
|
func _generate_ballast_mesh(segment: Dictionary) -> Array:
|
|
var vertices = PackedVector3Array()
|
|
var normals = PackedVector3Array()
|
|
var uvs = PackedVector2Array()
|
|
var indices = PackedInt32Array()
|
|
|
|
var ballast_width = sleeper_width * 1.3
|
|
var ballast_y = -0.08
|
|
var path_points: Array = segment["path_points"]
|
|
var path_tangents: Array = segment["path_tangents"]
|
|
|
|
for i in range(path_points.size()):
|
|
var pos: Vector3 = path_points[i]
|
|
var tangent: Vector3 = path_tangents[i]
|
|
var right = tangent.cross(Vector3.UP).normalized()
|
|
var half_w = ballast_width / 2.0
|
|
|
|
vertices.append(pos + right * (-half_w) + Vector3.UP * ballast_y)
|
|
vertices.append(pos + right * half_w + Vector3.UP * ballast_y)
|
|
normals.append(Vector3.UP)
|
|
normals.append(Vector3.UP)
|
|
|
|
var u = float(i) / float(path_points.size())
|
|
uvs.append(Vector2(0, u))
|
|
uvs.append(Vector2(1, u))
|
|
|
|
for i in range(path_points.size() - 1):
|
|
var base_idx = i * 2
|
|
indices.append(base_idx)
|
|
indices.append(base_idx + 1)
|
|
indices.append(base_idx + 3)
|
|
indices.append(base_idx)
|
|
indices.append(base_idx + 3)
|
|
indices.append(base_idx + 2)
|
|
|
|
var arrays = []
|
|
arrays.resize(Mesh.ARRAY_MAX)
|
|
arrays[Mesh.ARRAY_VERTEX] = vertices
|
|
arrays[Mesh.ARRAY_NORMAL] = normals
|
|
arrays[Mesh.ARRAY_TEX_UV] = uvs
|
|
arrays[Mesh.ARRAY_INDEX] = indices
|
|
return arrays
|
|
|
|
|
|
func _interpolate_path(points: Array, t: float) -> Vector3:
|
|
if points.size() < 2:
|
|
return points[0] if points.size() > 0 else Vector3.ZERO
|
|
|
|
t = clampf(t, 0.0, 1.0)
|
|
var idx = t * (points.size() - 1)
|
|
var i = int(idx)
|
|
var frac = idx - i
|
|
|
|
if i >= points.size() - 1:
|
|
return points[points.size() - 1]
|
|
|
|
var p1: Vector3 = points[i]
|
|
var p2: Vector3 = points[i + 1]
|
|
return p1.lerp(p2, frac)
|
|
|
|
|
|
func update_track(train_distance: float) -> void:
|
|
# Rimuovi segmenti vecchi
|
|
while segments.size() > 0:
|
|
var first: Dictionary = segments[0]
|
|
var seg_end = first["start_distance"] + first["length"]
|
|
|
|
if train_distance - seg_end > segment_length * segments_behind:
|
|
_remove_segment(first)
|
|
else:
|
|
break
|
|
|
|
# Aggiungi nuovi segmenti
|
|
while total_length - train_distance < segment_length * segments_ahead:
|
|
_add_next_segment()
|
|
|
|
|
|
func _remove_segment(segment: Dictionary) -> void:
|
|
var mesh: MeshInstance3D = segment["mesh_instance"]
|
|
if mesh:
|
|
mesh.queue_free()
|
|
segments.erase(segment)
|
|
|
|
|
|
func get_position_on_track(distance: float) -> Dictionary:
|
|
for segment in segments:
|
|
var seg_start: float = segment["start_distance"]
|
|
var seg_end = seg_start + segment["length"]
|
|
|
|
if distance >= seg_start and distance <= seg_end:
|
|
var seg_len: float = segment["length"]
|
|
var t = (distance - seg_start) / seg_len if seg_len > 0 else 0.0
|
|
|
|
var path_points: Array = segment["path_points"]
|
|
var path_tangents: Array = segment["path_tangents"]
|
|
|
|
return {
|
|
"position": _interpolate_path(path_points, t),
|
|
"direction": _interpolate_path(path_tangents, t).normalized(),
|
|
"valid": true
|
|
}
|
|
|
|
# Se oltre tutti i segmenti, usa l'ultimo punto
|
|
if segments.size() > 0:
|
|
var last: Dictionary = segments[segments.size() - 1]
|
|
return {
|
|
"position": last["end_position"],
|
|
"direction": last["end_direction"],
|
|
"valid": true
|
|
}
|
|
|
|
return {
|
|
"position": Vector3.ZERO,
|
|
"direction": Vector3(0, 0, -1),
|
|
"valid": false
|
|
}
|
|
|
|
|
|
func get_total_length() -> float:
|
|
return total_length
|