extends Node3D @export_group("Riferimenti Base") @export var path_treno: Path3D @export_group("I Tuoi Biomi") @export var lista_biomi: Array[DatiBioma] @export_group("Impostazioni Griglia e Aree") @export var dimensione_chunk: float = 20.0 @export var raggio_visivo: int = 3 @export var scala_distretti: float = 0.05 @export var materiale_filo_palo: ShaderMaterial @export_range(0.01, 1.0) var spessore_filo: float = 0.05 @export_range(0.0, 5.0) var forza_vento_fili: float = 1.0 var connessioni_pali: Dictionary = {} var scacchiera: Dictionary = {} var ultima_pos_griglia_treno: Vector2i = Vector2i(999999, 999999) var generatore_rumore: FastNoiseLite var generatore_altitudine: FastNoiseLite var bioma_manuale_attivo: DatiBioma = null var dropdown_biomi: OptionButton var lbl_bioma: Label var lbl_distanza: Label func _ready() -> void: if lista_biomi.is_empty() or path_treno == null: return generatore_rumore = FastNoiseLite.new() generatore_rumore.noise_type = FastNoiseLite.TYPE_PERLIN generatore_rumore.seed = randi() generatore_rumore.frequency = scala_distretti generatore_altitudine = FastNoiseLite.new() generatore_altitudine.noise_type = FastNoiseLite.TYPE_PERLIN generatore_altitudine.seed = randi() generatore_altitudine.frequency = scala_distretti * 0.5 _crea_ui_gps() _aggiorna_set_pieces() print("✅ Generatore a Distretti Dinamici Pronto.") func _crea_ui_gps() -> void: var canvas = CanvasLayer.new() canvas.layer = 10 add_child(canvas) var margin = MarginContainer.new() margin.set_anchors_preset(Control.PRESET_FULL_RECT) margin.add_theme_constant_override("margin_top", 20) margin.add_theme_constant_override("margin_right", 20) canvas.add_child(margin) var vbox = VBoxContainer.new() vbox.size_flags_horizontal = Control.SIZE_SHRINK_END margin.add_child(vbox) lbl_bioma = Label.new() lbl_bioma.add_theme_font_size_override("font_size", 10) lbl_bioma.add_theme_color_override("font_color", Color(1, 0.9, 0.4)) lbl_bioma.add_theme_color_override("font_outline_color", Color.BLACK) lbl_bioma.add_theme_constant_override("outline_size", 3) lbl_bioma.text = "📍 Avvio GPS..." vbox.add_child(lbl_bioma) lbl_distanza = Label.new() lbl_distanza.add_theme_font_size_override("font_size", 8) lbl_distanza.add_theme_color_override("font_outline_color", Color.BLACK) lbl_distanza.add_theme_constant_override("outline_size", 3) lbl_distanza.text = "Calcolo tragitto..." vbox.add_child(lbl_distanza) var hbox = HBoxContainer.new() vbox.add_child(hbox) dropdown_biomi = OptionButton.new() dropdown_biomi.add_item("Dinamico (Procedurale)") for bioma in lista_biomi: dropdown_biomi.add_item(bioma.nome_bioma) dropdown_biomi.item_selected.connect(_on_dropdown_biomi_item_selected) hbox.add_child(dropdown_biomi) _radar_bioma_treno() func _aggiorna_set_pieces() -> void: var set_pieces = get_tree().get_nodes_in_group("set_pieces") for sp in set_pieces: if not "bioma_esclusivo" in sp or sp.bioma_esclusivo == "": continue var bioma_locale = "" if bioma_manuale_attivo != null: bioma_locale = bioma_manuale_attivo.nome_bioma else: var grid_x = roundi(sp.global_position.x / dimensione_chunk) var grid_z = roundi(sp.global_position.z / dimensione_chunk) bioma_locale = _get_nome_bioma_procedurale(generatore_rumore.get_noise_2d(grid_x, grid_z)) if bioma_locale == sp.bioma_esclusivo: sp.show() sp.process_mode = Node.PROCESS_MODE_INHERIT else: sp.hide() sp.process_mode = Node.PROCESS_MODE_DISABLED func _on_dropdown_biomi_item_selected(indice: int) -> void: if indice == 0: bioma_manuale_attivo = null else: bioma_manuale_attivo = lista_biomi[indice - 1] _distruggi_e_rigenera_mondo() func _distruggi_e_rigenera_mondo() -> void: _aggiorna_set_pieces() for pos in scacchiera.keys(): var cella = scacchiera[pos] if cella["tipo"] == "bioma": if cella.has("nodo") and is_instance_valid(cella["nodo"]): cella["nodo"].queue_free() scacchiera.clear() connessioni_pali.clear() ultima_pos_griglia_treno = Vector2i(999999, 999999) _radar_bioma_treno() ultima_pos_griglia_treno = Vector2i(999999, 999999) _radar_bioma_treno() func _physics_process(_delta: float) -> void: if path_treno == null or path_treno.treno_istanziato == null: return var pos_treno = path_treno.treno_istanziato.global_position var grid_x = roundi(pos_treno.x / dimensione_chunk) var grid_z = roundi(pos_treno.z / dimensione_chunk) var pos_attuale = Vector2i(grid_x, grid_z) if pos_attuale != ultima_pos_griglia_treno: ultima_pos_griglia_treno = pos_attuale _genera_attorno_al_treno(pos_attuale) _pulisci_chunk_lontani(pos_attuale) _radar_bioma_treno() if materiale_filo_palo and materiale_filo_palo is ShaderMaterial: materiale_filo_palo.set_shader_parameter("forza_vento", forza_vento_fili) # ========================================================== # ===== MOTORE DI RICERCA PROFONDA MULTI-CHUNK ============= # ========================================================== # Setaccia l'intero albero e colleziona tutti i nodi InfoChunk func _trova_tutti_nodi_info(radice: Node, lista: Array[Node]) -> void: if radice == null: return if radice.has_method("get_dati_ruotati") or "ha_palo_luce" in radice: lista.append(radice) for figlio in radice.get_children(): _trova_tutti_nodi_info(figlio, lista) func _genera_attorno_al_treno(centro: Vector2i) -> void: for x in range(-raggio_visivo, raggio_visivo + 1): for z in range(-raggio_visivo, raggio_visivo + 1): var pos_griglia = centro + Vector2i(x, z) if not scacchiera.has(pos_griglia): var ce_ostacolo = _registra_cella_con_laser(pos_griglia) if not ce_ostacolo: _piazza_bioma_compatibile(pos_griglia) func _scegli_catalogo_per_cella(pos_griglia: Vector2i) -> Array[PackedScene]: if bioma_manuale_attivo != null: return bioma_manuale_attivo.chunk_disponibili var valore = generatore_rumore.get_noise_2d(pos_griglia.x, pos_griglia.y) var valore_normalizzato = (valore + 1.0) / 2.0 var indice = clamp(int(valore_normalizzato * lista_biomi.size()), 0, lista_biomi.size() - 1) return lista_biomi[indice].chunk_disponibili func _get_nome_bioma_procedurale(valore: float) -> String: if bioma_manuale_attivo != null: return bioma_manuale_attivo.nome_bioma var valore_normalizzato = (valore + 1.0) / 2.0 var indice = clamp(int(valore_normalizzato * lista_biomi.size()), 0, lista_biomi.size() - 1) return lista_biomi[indice].nome_bioma func _registra_cella_con_laser(pos_griglia: Vector2i) -> bool: if scacchiera.has(pos_griglia): return true var pos_mondo = Vector3(pos_griglia.x * dimensione_chunk, 0, pos_griglia.y * dimensione_chunk) var space_state = get_world_3d().direct_space_state var origine_raggio = Vector3(pos_mondo.x, 50.0, pos_mondo.z) var fine_raggio = Vector3(pos_mondo.x, -10.0, pos_mondo.z) var query = PhysicsRayQueryParameters3D.create(origine_raggio, fine_raggio) var result = space_state.intersect_ray(query) if result.size() > 0: var collider = result["collider"] var radice_chunk = collider var lista_info: Array[Node] = [] # 1. Risaliamo l'albero per trovare il "Genitore" che contiene le logiche while radice_chunk != null and radice_chunk != get_tree().root: _trova_tutti_nodi_info(radice_chunk, lista_info) if lista_info.size() > 0: break radice_chunk = radice_chunk.get_parent() var nodo_info_corretto = null # 2. SEZIONE MULTI-CHUNK: Scegliamo l'InfoChunk fisicamente più vicino al raggio laser in questa cella if lista_info.size() > 0: var min_distanza = 999999.0 var pos_centro_cella = Vector3(pos_mondo.x, 0, pos_mondo.z) for info in lista_info: if info is Node3D: # Calcoliamo la distanza 2D var pos_info = Vector3(info.global_position.x, 0, info.global_position.z) var dist = pos_centro_cella.distance_to(pos_info) if dist < min_distanza: min_distanza = dist nodo_info_corretto = info # Fallback di sicurezza: prendiamo il primo trovato se il calcolo fallisce if nodo_info_corretto == null and lista_info.size() > 0: nodo_info_corretto = lista_info[0] var uscite_trovate = {"nord": false, "est": false, "sud": false, "ovest": false} var altezze_trovate = {"nord": 0, "est": 0, "sud": 0, "ovest": 0} var ha_il_palo = false # 3. Estraiamo i dati dal VERO InfoChunk di QUESTA precisa cella if nodo_info_corretto != null: if nodo_info_corretto.has_method("get_dati_ruotati"): # Leggiamo la rotazione direttamente dal nodo info corretto var scatti_rotazione = roundi(rad_to_deg(nodo_info_corretto.global_rotation.y) / -90.0) var dati = nodo_info_corretto.get_dati_ruotati(scatti_rotazione) uscite_trovate = dati["connessioni"] altezze_trovate = dati["altezze"] if "ha_palo_luce" in nodo_info_corretto: ha_il_palo = nodo_info_corretto.ha_palo_luce # 4. Registriamo sulla scacchiera questo specifico pezzetto di mondo scacchiera[pos_griglia] = { "tipo": "ostacolo", "uscite": uscite_trovate, "altezze": altezze_trovate, "nodo": radice_chunk, "info": nodo_info_corretto, "ha_palo": ha_il_palo } if ha_il_palo: _collega_pali_luce(pos_griglia) return true return false func _piazza_bioma_compatibile(pos_griglia: Vector2i) -> void: var req_conn_nord = _richiede_connessione(pos_griglia + Vector2i(0, -1), "sud") var req_conn_est = _richiede_connessione(pos_griglia + Vector2i(1, 0), "ovest") var req_conn_sud = _richiede_connessione(pos_griglia + Vector2i(0, 1), "nord") var req_conn_ovest = _richiede_connessione(pos_griglia + Vector2i(-1, 0), "est") var req_alt_nord = _richiede_altezza(pos_griglia + Vector2i(0, -1), "sud") var req_alt_est = _richiede_altezza(pos_griglia + Vector2i(1, 0), "ovest") var req_alt_sud = _richiede_altezza(pos_griglia + Vector2i(0, 1), "nord") var req_alt_ovest = _richiede_altezza(pos_griglia + Vector2i(-1, 0), "est") var rumore_alt = generatore_altitudine.get_noise_2d(pos_griglia.x, pos_griglia.y) var altitudine_target = clamp(roundi((rumore_alt + 1.0) / 2.0), 0, 1) var catalogo_zona = _scegli_catalogo_per_cella(pos_griglia) var candidati_validi = [] for scena in catalogo_zona: var test_chunk = scena.instantiate() var lista_test: Array[Node] = [] _trova_tutti_nodi_info(test_chunk, lista_test) var info_test = lista_test[0] if lista_test.size() > 0 else null if info_test != null and info_test.has_method("get_dati_ruotati"): for rot in range(4): var dati = info_test.get_dati_ruotati(rot) var u_conn = dati["connessioni"] var u_alt = dati["altezze"] var match_conn_n = (req_conn_nord == -1) or ((req_conn_nord == 1) == u_conn["nord"]) var match_conn_e = (req_conn_est == -1) or ((req_conn_est == 1) == u_conn["est"]) var match_conn_s = (req_conn_sud == -1) or ((req_conn_sud == 1) == u_conn["sud"]) var match_conn_o = (req_conn_ovest == -1) or ((req_conn_ovest == 1) == u_conn["ovest"]) var match_alt_n = (req_alt_nord == -1) or (req_alt_nord == u_alt["nord"]) var match_alt_e = (req_alt_est == -1) or (req_alt_est == u_alt["est"]) var match_alt_s = (req_alt_sud == -1) or (req_alt_sud == u_alt["sud"]) var match_alt_o = (req_alt_ovest == -1) or (req_alt_ovest == u_alt["ovest"]) var diff_n = abs(u_alt["nord"] - altitudine_target) if req_alt_nord == -1 else 0 var diff_e = abs(u_alt["est"] - altitudine_target) if req_alt_est == -1 else 0 var diff_s = abs(u_alt["sud"] - altitudine_target) if req_alt_sud == -1 else 0 var diff_o = abs(u_alt["ovest"] - altitudine_target) if req_alt_ovest == -1 else 0 var salti_eccessivi = diff_n > 1 or diff_e > 1 or diff_s > 1 or diff_o > 1 if match_conn_n and match_conn_e and match_conn_s and match_conn_o and match_alt_n and match_alt_e and match_alt_s and match_alt_o and not salti_eccessivi: var score = 0 if req_alt_nord == -1 and u_alt["nord"] == altitudine_target: score += 1 if req_alt_est == -1 and u_alt["est"] == altitudine_target: score += 1 if req_alt_sud == -1 and u_alt["sud"] == altitudine_target: score += 1 if req_alt_ovest == -1 and u_alt["ovest"] == altitudine_target: score += 1 candidati_validi.append({"scena": scena, "rotazione": rot, "dati": dati, "score": score}) test_chunk.queue_free() if candidati_validi.size() > 0: var max_score = -1 for c in candidati_validi: if c.score > max_score: max_score = c.score var candidati_migliori = [] for c in candidati_validi: if c.score == max_score: candidati_migliori.append(c) var scelto = candidati_migliori.pick_random() var nuovo_chunk = scelto.scena.instantiate() add_child(nuovo_chunk) nuovo_chunk.position = Vector3(pos_griglia.x * dimensione_chunk, 0, pos_griglia.y * dimensione_chunk) nuovo_chunk.rotation.y = scelto.rotazione * (-PI / 2.0) var lista_nuovo: Array[Node] = [] _trova_tutti_nodi_info(nuovo_chunk, lista_nuovo) var info_nuovo = lista_nuovo[0] if lista_nuovo.size() > 0 else null var ha_il_palo = false if info_nuovo != null and "ha_palo_luce" in info_nuovo: ha_il_palo = info_nuovo.ha_palo_luce scacchiera[pos_griglia] = { "tipo": "bioma", "uscite": scelto.dati["connessioni"], "altezze": scelto.dati["altezze"], "nodo": nuovo_chunk, "info": info_nuovo, "ha_palo": ha_il_palo } if ha_il_palo: _collega_pali_luce(pos_griglia) else: var backup = catalogo_zona[0].instantiate() add_child(backup) backup.position = Vector3(pos_griglia.x * dimensione_chunk, 0, pos_griglia.y * dimensione_chunk) var lista_backup: Array[Node] = [] _trova_tutti_nodi_info(backup, lista_backup) var info_backup = lista_backup[0] if lista_backup.size() > 0 else null var altezze_sicure = { "nord": req_alt_nord if req_alt_nord != -1 else altitudine_target, "est": req_alt_est if req_alt_est != -1 else altitudine_target, "sud": req_alt_sud if req_alt_sud != -1 else altitudine_target, "ovest": req_alt_ovest if req_alt_ovest != -1 else altitudine_target } scacchiera[pos_griglia] = { "tipo": "bioma", "uscite": {"nord":false, "est":false, "sud":false, "ovest":false}, "altezze": altezze_sicure, "nodo": backup, "info": info_backup, "ha_palo": false } func _richiede_connessione(pos_vicino: Vector2i, lato_richiesto: String) -> int: if not scacchiera.has(pos_vicino): _registra_cella_con_laser(pos_vicino) if scacchiera.has(pos_vicino) and scacchiera[pos_vicino].has("uscite"): return 1 if scacchiera[pos_vicino]["uscite"][lato_richiesto] else 0 return -1 func _richiede_altezza(pos_vicino: Vector2i, lato_richiesto: String) -> int: if scacchiera.has(pos_vicino) and scacchiera[pos_vicino].has("altezze"): return scacchiera[pos_vicino]["altezze"][lato_richiesto] return -1 func _radar_bioma_treno() -> void: if path_treno == null or path_treno.curve == null or path_treno.treno_istanziato == null: return if bioma_manuale_attivo != null: if lbl_bioma: lbl_bioma.text = bioma_manuale_attivo.nome_bioma if lbl_distanza: lbl_distanza.text = "Override manuale (Infinita)" return var pos_treno = path_treno.treno_istanziato.global_position var grid_x = roundi(pos_treno.x / dimensione_chunk) var grid_z = roundi(pos_treno.z / dimensione_chunk) var bioma_attuale = _get_nome_bioma_procedurale(generatore_rumore.get_noise_2d(grid_x, grid_z)) var progresso_attuale = path_treno.progresso_treno var lunghezza_totale = path_treno.curve.get_baked_length() var distanza_cambio = 0.0 var prossimo_bioma = "" for step in range(1, 31): var progresso_futuro = wrapf(progresso_attuale + (step * dimensione_chunk), 0.0, lunghezza_totale) var pos_futura_locale = path_treno.curve.sample_baked(progresso_futuro, true) var pos_futura_globale = path_treno.to_global(pos_futura_locale) var f_x = roundi(pos_futura_globale.x / dimensione_chunk) var f_z = roundi(pos_futura_globale.z / dimensione_chunk) var bioma_futuro = _get_nome_bioma_procedurale(generatore_rumore.get_noise_2d(f_x, f_z)) if bioma_futuro != bioma_attuale: distanza_cambio = step * dimensione_chunk prossimo_bioma = bioma_futuro break if lbl_bioma and lbl_distanza: lbl_bioma.text = bioma_attuale if prossimo_bioma != "": lbl_distanza.text = str(distanza_cambio) + "m ➔ " + prossimo_bioma else: lbl_distanza.text = "Nessun cambio nei prossimi 600m" func _pulisci_chunk_lontani(centro_attuale: Vector2i) -> void: var celle_da_rimuovere = [] var margine_sicurezza = 2 for pos_griglia in scacchiera.keys(): var cella = scacchiera[pos_griglia] if cella["tipo"] == "ostacolo": continue var dist_x = abs(pos_griglia.x - centro_attuale.x) var dist_z = abs(pos_griglia.y - centro_attuale.y) if dist_x > raggio_visivo + margine_sicurezza or dist_z > raggio_visivo + margine_sicurezza: if cella.has("nodo") and is_instance_valid(cella["nodo"]): # Questo distruggerà tutto il chunk, inclusi i fili ad esso agganciati cella["nodo"].queue_free() celle_da_rimuovere.append(pos_griglia) for pos in celle_da_rimuovere: scacchiera.erase(pos) # ========================================================== # ============ NUOVE FUNZIONI FILI DELLA LUCE ============== # ========================================================== func _ottieni_pali(nodo_info: Node, lato: String) -> Array[Node3D]: var pali: Array[Node3D] = [] if lato == "sx": if "attacchi_sx" in nodo_info and nodo_info.attacchi_sx != null: for p in nodo_info.attacchi_sx: if is_instance_valid(p): pali.append(p) if pali.is_empty() and "nodo_attacco_sx" in nodo_info and is_instance_valid(nodo_info.get("nodo_attacco_sx")): pali.append(nodo_info.get("nodo_attacco_sx")) else: if "attacchi_dx" in nodo_info and nodo_info.attacchi_dx != null: for p in nodo_info.attacchi_dx: if is_instance_valid(p): pali.append(p) if pali.is_empty() and "nodo_attacco_dx" in nodo_info and is_instance_valid(nodo_info.get("nodo_attacco_dx")): pali.append(nodo_info.get("nodo_attacco_dx")) return pali func _collega_pali_luce(pos_griglia_nuovo: Vector2i) -> void: var cella_nuova = scacchiera[pos_griglia_nuovo] var radice_nuovo = cella_nuova["nodo"] var info_nuovo = cella_nuova["info"] if info_nuovo == null: return if info_nuovo is Node3D: info_nuovo.force_update_transform() var sx_nuovi = _ottieni_pali(info_nuovo, "sx") var dx_nuovi = _ottieni_pali(info_nuovo, "dx") if sx_nuovi.is_empty() or dx_nuovi.is_empty(): return var id_nuovo = radice_nuovo.get_instance_id() if not connessioni_pali.has(id_nuovo): connessioni_pali[id_nuovo] = 0 if connessioni_pali[id_nuovo] >= 2: return var p_sx_mio_best = null; var p_dx_mio_best = null var p_sx_tuo_best = null; var p_dx_tuo_best = null var miglior_vicino_radice = null var miglior_distanza = 999999.0 var raggio_ricerca = 3 for x in range(-raggio_ricerca, raggio_ricerca + 1): for z in range(-raggio_ricerca, raggio_ricerca + 1): if x == 0 and z == 0: continue var pos_vicino = pos_griglia_nuovo + Vector2i(x, z) if scacchiera.has(pos_vicino) and scacchiera[pos_vicino].get("ha_palo", false): var radice_vicino = scacchiera[pos_vicino]["nodo"] var info_vicino = scacchiera[pos_vicino]["info"] if not is_instance_valid(radice_vicino) or radice_vicino == radice_nuovo or info_vicino == null: continue var id_vicino = radice_vicino.get_instance_id() var conn_vicino = connessioni_pali.get(id_vicino, 0) if conn_vicino >= 2: continue if info_vicino is Node3D: info_vicino.force_update_transform() var sx_vicini = _ottieni_pali(info_vicino, "sx") var dx_vicini = _ottieni_pali(info_vicino, "dx") if sx_vicini.is_empty() or dx_vicini.is_empty(): continue for i_m in range(sx_nuovi.size()): for i_t in range(sx_vicini.size()): var c_mio = (sx_nuovi[i_m].global_position + dx_nuovi[i_m].global_position) / 2.0 var c_tuo = (sx_vicini[i_t].global_position + dx_vicini[i_t].global_position) / 2.0 var dist = c_mio.distance_to(c_tuo) if dist < miglior_distanza: miglior_distanza = dist miglior_vicino_radice = radice_vicino p_sx_mio_best = sx_nuovi[i_m] p_dx_mio_best = dx_nuovi[i_m] p_sx_tuo_best = sx_vicini[i_t] p_dx_tuo_best = dx_vicini[i_t] if miglior_vicino_radice == null: for x in range(-raggio_ricerca, raggio_ricerca + 1): for z in range(-raggio_ricerca, raggio_ricerca + 1): if x == 0 and z == 0: continue var pos_vicino = pos_griglia_nuovo + Vector2i(x, z) if scacchiera.has(pos_vicino) and scacchiera[pos_vicino].get("ha_palo", false): var radice_vicino = scacchiera[pos_vicino]["nodo"] var info_vicino = scacchiera[pos_vicino]["info"] if not is_instance_valid(radice_vicino) or radice_vicino == radice_nuovo or info_vicino == null: continue if info_vicino is Node3D: info_vicino.force_update_transform() var sx_vicini = _ottieni_pali(info_vicino, "sx") var dx_vicini = _ottieni_pali(info_vicino, "dx") if sx_vicini.is_empty() or dx_vicini.is_empty(): continue for i_m in range(sx_nuovi.size()): for i_t in range(sx_vicini.size()): var c_mio = (sx_nuovi[i_m].global_position + dx_nuovi[i_m].global_position) / 2.0 var c_tuo = (sx_vicini[i_t].global_position + dx_vicini[i_t].global_position) / 2.0 var dist = c_mio.distance_to(c_tuo) if dist < miglior_distanza: miglior_distanza = dist miglior_vicino_radice = radice_vicino p_sx_mio_best = sx_nuovi[i_m] p_dx_mio_best = dx_nuovi[i_m] p_sx_tuo_best = sx_vicini[i_t] p_dx_tuo_best = dx_vicini[i_t] var max_dist = dimensione_chunk * 8.0 if miglior_vicino_radice != null and miglior_distanza < max_dist: var dist_dritto = p_sx_mio_best.global_position.distance_to(p_sx_tuo_best.global_position) + p_dx_mio_best.global_position.distance_to(p_dx_tuo_best.global_position) var dist_incrocio = p_sx_mio_best.global_position.distance_to(p_dx_tuo_best.global_position) + p_dx_mio_best.global_position.distance_to(p_sx_tuo_best.global_position) if dist_dritto <= dist_incrocio: _disegna_parabola(p_sx_mio_best.global_position, p_sx_tuo_best.global_position, radice_nuovo) _disegna_parabola(p_dx_mio_best.global_position, p_dx_tuo_best.global_position, radice_nuovo) else: _disegna_parabola(p_sx_mio_best.global_position, p_dx_tuo_best.global_position, radice_nuovo) _disegna_parabola(p_dx_mio_best.global_position, p_sx_tuo_best.global_position, radice_nuovo) connessioni_pali[id_nuovo] += 1 var id_vicino = miglior_vicino_radice.get_instance_id() if not connessioni_pali.has(id_vicino): connessioni_pali[id_vicino] = 0 connessioni_pali[id_vicino] += 1 func _disegna_parabola(p1: Vector3, p2: Vector3, genitore: Node3D) -> void: var segmenti = 15 var abbassamento = 1.5 var punti_curva = [] var sag_factors = [] for i in range(segmenti + 1): var t = float(i) / float(segmenti) var pos_globale = p1.lerp(p2, t) var gravita = abbassamento * (1.0 - pow(2.0 * t - 1.0, 2.0)) pos_globale.y -= gravita punti_curva.append(genitore.to_local(pos_globale)) sag_factors.append(sin(t * PI)) var st = SurfaceTool.new() st.begin(Mesh.PRIMITIVE_TRIANGLES) var lati = 4 var raggio = spessore_filo / 2.0 for i in range(punti_curva.size() - 1): var p_corrente = punti_curva[i] var p_prossimo = punti_curva[i+1] var sag_corrente = sag_factors[i] var sag_prossimo = sag_factors[i+1] var dir = (p_prossimo - p_corrente).normalized() var up = Vector3.UP var right = dir.cross(up).normalized() if right.length_squared() < 0.01: right = dir.cross(Vector3.RIGHT).normalized() up = right.cross(dir).normalized() for s in range(lati): var ang1 = (float(s) / lati) * TAU var ang2 = (float((s + 1) % lati) / lati) * TAU var offset1 = (right * cos(ang1) + up * sin(ang1)) * raggio var offset2 = (right * cos(ang2) + up * sin(ang2)) * raggio st.set_color(Color(sag_corrente, 0, 0, 1)) st.add_vertex(p_corrente + offset1) st.set_color(Color(sag_corrente, 0, 0, 1)) st.add_vertex(p_corrente + offset2) st.set_color(Color(sag_prossimo, 0, 0, 1)) st.add_vertex(p_prossimo + offset1) st.set_color(Color(sag_corrente, 0, 0, 1)) st.add_vertex(p_corrente + offset2) st.set_color(Color(sag_prossimo, 0, 0, 1)) st.add_vertex(p_prossimo + offset2) st.set_color(Color(sag_prossimo, 0, 0, 1)) st.add_vertex(p_prossimo + offset1) var nodo_filo = MeshInstance3D.new() nodo_filo.mesh = st.commit() if materiale_filo_palo: nodo_filo.material_override = materiale_filo_palo genitore.add_child(nodo_filo)