Godot Version
4.4
Question
Hi all,
I have a script to put objects at a Path3d line. See here:
@tool
extends Path3D
@export var fence_scene1 = preload("res://fence.tscn")
@export var fence_scene2 = preload("res://fence2.tscn")
@export var spacing = 0.3
func _ready():
# Zorg ervoor dat de node "VisibleItems" bestaat
if !$VisibleItems3:
var visible_items = Node3D.new()
visible_items.name = "VisibleItems3"
add_child(visible_items)
spawn_fences()
func spawn_fences():
# Verwijder bestaande hekken van "VisibleItems"
for child in $VisibleItems3.get_children():
child.queue_free()
var current_distance = 0.0
while current_distance < curve.get_baked_length():
var pos = curve.sample_baked(current_distance)
var up_vector = curve.sample_baked_up_vector(current_distance)
# Maak een nieuw hek, willekeurig gekozen tussen fence_scene1 en fence_scene2
var fence_instance
if randf() < 0.5:
fence_instance = fence_scene1.instantiate()
else:
fence_instance = fence_scene2.instantiate()
fence_instance.global_transform.origin = pos
fence_instance.look_at(pos + curve.sample_baked_up_vector(current_distance), up_vector)
# Willekeurige horizontale rotatie
var random_rotation = randf_range(0.0, 2.0 * PI)
fence_instance.rotate_y(random_rotation)
$VisibleItems3.add_child(fence_instance)
# Verhoog de afstand met 0.2 meter
current_distance += spacing
func randf_range(min_val: float, max_val: float) -> float:
return min_val + (max_val - min_val) * randf()
I added RAYCAST to the script so the objects have to follow the terrain under it when the height is different but then the objects on the Path3d are gone, see the script:
@tool
extends Path3D
@export var fence_scene1 = preload("res://fence.tscn")
@export var fence_scene2 = preload("res://fence2.tscn")
@export var spacing = 0.3
@export var raycast_length = 1000 # Lengte van de raycast
@export var raycast_collision_mask = 1 # Laag waarop de raycast checkt
func _ready():
# Zorg ervoor dat de node "VisibleItems" bestaat
if !$VisibleItems3:
var visible_items = Node3D.new()
visible_items.name = "VisibleItems3"
add_child(visible_items)
spawn_fences()
func spawn_fences():
# Verwijder bestaande hekken van "VisibleItems"
for child in $VisibleItems3.get_children():
child.queue_free()
var current_distance = 0.0
while current_distance < curve.get_baked_length():
var pos = curve.sample_baked(current_distance)
var up_vector = curve.sample_baked_up_vector(current_distance)
# Pas raycast toe om positie aan te passen op basis van onderliggende objecten
var hit_position = perform_raycast(pos)
if hit_position != null:
pos = hit_position
# Maak een nieuw hek, willekeurig gekozen tussen fence_scene1 en fence_scene2
var fence_instance
if randf() < 0.5:
fence_instance = fence_scene1.instantiate()
else:
fence_instance = fence_scene2.instantiate()
fence_instance.global_transform.origin = pos
fence_instance.look_at(pos + curve.sample_baked_up_vector(current_distance), up_vector)
# Willekeurige horizontale rotatie
var random_rotation = randf_range(0.0, 2.0 * PI)
fence_instance.rotate_y(random_rotation)
$VisibleItems3.add_child(fence_instance)
# Verhoog de afstand met de opgegeven spacing
current_distance += spacing
func perform_raycast(from_position: Vector3) -> Vector3:
var space_state = get_world_3d().direct_space_state
var raycast_params = {
"from": from_position,
"to": from_position + Vector3.DOWN * raycast_length,
"collision_mask": raycast_collision_mask
}
var result = space_state.intersect_ray(raycast_params)
if result:
return result.position # Positie van de hit teruggeven
else:
return from_position # Retourneer de oorspronkelijke positie als er geen hit is
func randf_range(min_val: float, max_val: float) -> float:
return min_val + (max_val - min_val) * randf()
Is there somethging Wrong in the script with the Raycast option? The terrain is made with TERRAIN3d
I got it working, for anyone who want to use this for a road or other path3d things, here is the script:
@tool
extends Path3D
@export var fence_scene1 = preload("res://fence.tscn")
@export var fence_scene2 = preload("res://fence2.tscn")
@export var terrain: Node3D # Koppel je Terrain3D-node
@export var spacing = 0.3
@export var height_offset = -0.1 # Offset om zweven te corrigeren
var is_updating = false # Voorkomt oneindige loops
func _ready():
if !$VisibleItems3:
var visible_items = Node3D.new()
visible_items.name = "VisibleItems3"
add_child(visible_items)
curve.connect("changed", Callable(self, "_on_curve_changed"))
adjust_curve_with_terrain_data()
spawn_fences()
func _on_curve_changed():
if is_updating:
return
is_updating = true
adjust_curve_with_terrain_data()
spawn_fences()
is_updating = false
func adjust_curve_with_terrain_data():
if terrain == null or terrain.data == null:
push_error("❌ Geen geldig Terrain3D gekoppeld!")
return
if curve.point_count == 0:
push_error("❌ Path3D heeft geen punten! Voeg punten toe.")
return
print("🔎 Path3D heeft", curve.point_count, "punten. Start hoogte-aanpassing...")
var adjusted_points = []
for i in range(curve.point_count):
var point = curve.get_point_position(i)
var terrain_height = get_terrain_height(point)
if !is_nan(terrain_height) and !is_inf(terrain_height):
terrain_height += height_offset # Pas offset toe
adjusted_points.append(Vector3(point.x, terrain_height, point.z))
print("✅ Nieuw punt toegevoegd:", Vector3(point.x, terrain_height, point.z))
else:
push_error("⚠️ Ongeldige hoogte ontvangen voor punt: " + str(point) + ", val terug op oorspronkelijke hoogte.")
adjusted_points.append(point)
# Verwijder oude punten PAS na het verzamelen van de nieuwe!
curve.clear_points()
for new_point in adjusted_points:
curve.add_point(new_point)
print("📌 Toegevoegd aan curve:", new_point)
func get_terrain_height(global_position: Vector3) -> float:
print("🌍 Controleren Terrain3DRegion voor positie:", global_position)
if terrain == null or terrain.data == null:
push_error("❌ Geen geldig Terrain3D gekoppeld!")
return global_position.y # Houd oorspronkelijke hoogte aan bij fout
# Zet lokale positie om naar globale positie
global_position = to_global(global_position)
print("🔎 Global position na conversie:", global_position)
var height: float = terrain.data.get_height(global_position) * terrain.scale.y
print("✅ Ontvangen hoogte:", height)
if is_nan(height) or is_inf(height):
push_error("⚠️ Ongeldige hoogte ontvangen voor " + str(global_position) + ", val terug op 0.")
return 0.0
return height
func spawn_fences():
if terrain == null:
push_error("❌ Geen geldig Terrain3D gekoppeld!")
return
for child in $VisibleItems3.get_children():
child.queue_free()
var current_distance = 0.0
while current_distance < curve.get_baked_length():
var pos = curve.sample_baked(current_distance)
var up_vector = curve.sample_baked_up_vector(current_distance)
# Pas de hoogte van het terrein toe
pos = to_global(pos) # Zorg dat positie correct is
print("🔎 Fence global position:", pos)
var terrain_height = get_terrain_height(pos)
if !is_nan(terrain_height) and !is_inf(terrain_height):
pos.y = terrain_height + height_offset # Offset correctie
var fence_instance = (fence_scene1 if randf() < 0.5 else fence_scene2).instantiate()
fence_instance.global_transform.origin = pos
fence_instance.look_at(pos + up_vector, up_vector)
fence_instance.rotate_y(randf_range(0.0, 2.0 * PI))
$VisibleItems3.add_child(fence_instance)
current_distance += spacing
func randf_range(min_val: float, max_val: float) -> float:
return min_val + (max_val - min_val) * randf()
You have to set the terrain3d and the height offset is for if the path3d is not exactly on the terrain. In my case it followed heights but was to high above the terrain, when adjusting this it works fine. After change this setting reload your saved scene and when adding points it automatically changes to the good height.
A new version because it was not fully working yet, this one is stable:
@tool
extends Path3D
@export var fence_scene1 = preload("res://fence.tscn")
@export var fence_scene2 = preload("res://fence2.tscn")
@export var terrain: Node3D
@export var spacing = 0.3:
set(value):
spacing = value
if Engine.is_editor_hint() and is_inside_tree():
spawn_fences() # 🔁 Herbouw hekjes bij wijziging in editor
@export var height_offset = 0.0: # Zet standaard hoogte offset op 0 (geen zweven)
set(value):
height_offset = value
if Engine.is_editor_hint() and is_inside_tree():
spawn_fences() # 🔁 Herbouw hekjes bij wijziging in editor
@export var enable_random_rotation := true:
set(value):
enable_random_rotation = value
if Engine.is_editor_hint() and is_inside_tree():
spawn_fences() # 🔁 Herbouw hekjes bij wijziging in editor
var is_updating = false
func _ready():
if !$VisibleItems3:
var visible_items = Node3D.new()
visible_items.name = "VisibleItems3"
add_child(visible_items)
curve.connect("changed", Callable(self, "_on_curve_changed"))
adjust_curve_with_terrain_data()
spawn_fences()
func _on_curve_changed():
if is_updating:
return
is_updating = true
adjust_curve_with_terrain_data()
spawn_fences()
is_updating = false
func adjust_curve_with_terrain_data():
if terrain == null or terrain.data == null:
push_error("❌ Geen geldig Terrain3D gekoppeld!")
return
if curve.point_count == 0:
push_error("❌ Path3D heeft geen punten! Voeg punten toe.")
return
print("🔎 Path3D heeft", curve.point_count, "punten. Start hoogte-aanpassing...")
for i in range(curve.point_count):
var point = curve.get_point_position(i)
var terrain_height = get_terrain_height(point)
if !is_nan(terrain_height) and !is_inf(terrain_height):
var new_point = Vector3(point.x, terrain_height + height_offset, point.z)
curve.set_point_position(i, new_point)
print("✅ Punt aangepast:", new_point)
else:
push_error("⚠️ Ongeldige hoogte voor punt " + str(point) + ", behoud originele hoogte.")
func get_terrain_height(global_position: Vector3) -> float:
print("🌍 Controleren Terrain3DRegion voor positie:", global_position)
if terrain == null or terrain.data == null:
push_error("❌ Geen geldig Terrain3D gekoppeld!")
return global_position.y # Als er geen terrein is, behoud de oorspronkelijke hoogte
global_position = to_global(global_position)
print("🔎 Global position na conversie:", global_position)
var height: float = terrain.data.get_height(global_position) * terrain.scale.y
print("✅ Ontvangen hoogte:", height)
if is_nan(height) or is_inf(height):
push_error("⚠️ Ongeldige hoogte ontvangen voor " + str(global_position) + ", val terug op 0.")
return 0.0
return height
func spawn_fences():
if terrain == null:
push_error("❌ Geen geldig Terrain3D gekoppeld!")
return
for child in $VisibleItems3.get_children():
child.queue_free()
var current_distance = 0.0
while current_distance < curve.get_baked_length():
var pos = curve.sample_baked(current_distance)
var next_pos = curve.sample_baked(current_distance + 0.1)
pos = to_global(pos)
next_pos = to_global(next_pos)
var terrain_height = get_terrain_height(pos)
if !is_nan(terrain_height) and !is_inf(terrain_height):
pos.y = terrain_height + height_offset # Pas de hoogte van het hek aan op de terrain hoogte
var direction = (next_pos - pos).normalized()
direction.y = 0
var fence_instance = (fence_scene1 if randf() < 0.5 else fence_scene2).instantiate()
if direction.length() > 0:
var basis = Basis().looking_at(direction, Vector3.UP)
var transform = Transform3D(basis, pos)
fence_instance.transform = transform
else:
fence_instance.position = pos
$VisibleItems3.add_child(fence_instance)
if enable_random_rotation:
fence_instance.rotate_y(randf_range(0.0, 2.0 * PI))
current_distance += spacing
func randf_range(min_val: float, max_val: float) -> float:
return min_val + (max_val - min_val) * randf()