Created
April 30, 2022 22:27
-
-
Save Einlander/e3fbb214f2624bcf5f6b136696adb2e3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# TODO: Make sure it can handle multiple spawn locations | |
# TODO: Add the ability to trace back along player nav_path to find furthest point to use as distance traveled | |
# TODO: Add ability to get a point from a percentage along the nav_path | |
# TODO: Add ability to look forward n distance from where the intersect or player is | |
# TODO: Add ability to check if player can travel to path without obstacles. if not find another near segment to check from. | |
# TODO: Add ability to get path ahead from vector3 to distance | |
extends Node | |
var director:Node | |
export(Color) var path_color = Color.red | |
export(Color) var path_color2 = Color.blue | |
export(Color) var path_color3 = Color.green | |
var path_material = SpatialMaterial.new() | |
var path_material2 = SpatialMaterial.new() | |
var path_material3 = SpatialMaterial.new() | |
var path_geometry = ImmediateGeometry.new() | |
var path_geometry2 = ImmediateGeometry.new() | |
var path_geometry3 = ImmediateGeometry.new() | |
var path_geometry4 = ImmediateGeometry.new() | |
func _ready() -> void: | |
#create an immediate geometry node | |
add_child(path_geometry) | |
add_child(path_geometry2) | |
add_child(path_geometry3) | |
add_child(path_geometry4) | |
path_material.albedo_color = path_color | |
# path_material.vertex_color_use_as_albedo = false | |
path_material.params_line_width = 1 | |
path_material.params_point_size = 2 | |
path_material.flags_unshaded = true | |
path_material.flags_no_depth_test = true | |
path_material.params_grow = true | |
path_material.params_grow_amount = 2 | |
path_material2.albedo_color = path_color2 | |
# path_material2.vertex_color_use_as_albedo = false | |
path_material2.params_line_width = 1 | |
path_material2.params_point_size = 2 | |
path_material2.flags_unshaded = true | |
path_material2.flags_no_depth_test = true | |
path_material2.params_grow = true | |
path_material2.params_grow_amount = 2 | |
path_material3.albedo_color = path_color3 | |
# path_material3.vertex_color_use_as_albedo = false | |
path_material3.params_line_width = 1 | |
path_material3.params_point_size = 2 | |
path_material3.flags_unshaded = true | |
path_material3.flags_no_depth_test = true | |
path_material3.params_grow = true | |
path_material3.params_grow_amount = 2 | |
#needs more robust checking | |
director = get_node("/root").get_child(0).get_node("_director") | |
if director == null: | |
dprint("Path Manager:: Director not found, disabled.") | |
pass | |
# Starting point. Called by director | |
func initialize(nav_node:Navigation, path_hint_node:Spatial,path_zone_node:Spatial) -> Array: | |
#get waypoints | |
var waypoints = validate_waypoints(get_waypoints_from_node(path_hint_node)) | |
var output_path = generate_path(nav_node, waypoints) | |
dprint(["Is path valid?",validate_path(output_path,waypoints,path_zone_node)]) | |
_draw_path(output_path,path_geometry,path_geometry2,path_material,path_material2) | |
# _draw_path2(build_path(nav_node, waypoints),path_geometry,path_material) | |
return output_path | |
pass | |
# takes a parent node with child waypoint nodes. Finds all waypoints | |
# Gives and array of waypoint nodes | |
func get_waypoints_from_node(path_hint_node:Spatial) -> Array: | |
var child_nodes = path_hint_node.get_children() | |
var path_nodes = [] | |
for node in child_nodes: | |
if node is Waypoint: | |
path_nodes.append(node) | |
return path_nodes | |
pass | |
func validate_path(path:Array, waypoints:Array,path_zone_node:Spatial) -> bool: | |
var aa:Array = validate_waypoints(waypoints) | |
if aa.size() <= 0: | |
#It is missing at least 1 of the required waypoints | |
return false | |
var start_node:Waypoint | |
var end_node:Waypoint | |
for waypoint in aa: | |
if waypoint.name == "_player_spawn": | |
start_node = waypoint | |
if waypoint.name == "_safe_room": | |
end_node = waypoint | |
var bb = get_path_zones(path_zone_node) | |
if is_valid_path_zones(bb) == false: | |
#It is missing at least 1 of the required zones | |
return false | |
var start_zone:Area | |
var end_zone:Area | |
for zone in bb: | |
if zone.name == "_player_spawn": | |
start_zone = zone | |
if zone.name == "_safe_room": | |
end_zone = zone | |
if point_in_area(start_node.global_transform.origin,start_zone) == false: | |
return false | |
if point_in_area(end_node.global_transform.origin,end_zone) == false: | |
return false | |
#check if first and last points are in safe zones | |
if path.size()>1: | |
if point_in_area(path[0],start_zone) == false: | |
return false | |
if point_in_area(path[path.size()-1],end_zone) == false: | |
return false | |
else: | |
return false | |
return true | |
# Takes a list of waypoints, finds required waypoints, then traverses them to see if they go from begginging | |
# to the end. | |
# Gives an array of Waypoint Nodes | |
func validate_waypoints(waypoints:Array) -> Array : | |
var start_node:Spatial = null | |
for waypoint in waypoints: | |
if waypoint.name == "_player_spawn": | |
start_node = waypoint | |
break | |
if start_node == null: | |
dprint("_player_spawn NOT found!") | |
return [] | |
var visited_nodes: Array = [] | |
var next_node:Waypoint = start_node | |
while true: | |
if (visited_nodes.find(next_node)) < 0: | |
visited_nodes.append(next_node) | |
if next_node.next_node() != null: | |
next_node = next_node.next_node() | |
else: | |
dprint("Node ordering Complete") | |
break | |
else: | |
dprint("Looping, Halting") | |
break | |
if next_node.name != "_safe_room": | |
dprint("_safe_room NOT found!") | |
return [] | |
return visited_nodes | |
# Takes validated waypoints and connects them together using the navigation mesh. | |
# Gives an array of Vector3 | |
func generate_path(nav_node:Navigation, waypoints:Array) -> Array: | |
var points = validate_waypoints(waypoints) | |
var path = [] | |
for i in range(points.size()-1): | |
var start = points[i].global_transform.origin | |
var end = points[i+1].global_transform.origin | |
var local_path = nav_node.get_simple_path(start - nav_node.global_transform.origin ,end - nav_node.global_transform.origin) | |
if local_path.size() > 0: | |
# path.append_array(local_path) | |
for point in local_path: | |
path.append(point) | |
else: | |
path.append(end) | |
# print(local_path.size()) | |
path = optimize_path(path) | |
return path | |
# Checks an Area node to see if a point is inside it | |
func point_in_area(point:Vector3,area:Area, collide_with_bodies:bool = false, collide_with_areas: bool = true) -> bool: | |
var _shape = SphereShape.new() | |
var _temp_spatial = Spatial.new() | |
add_child(_temp_spatial) | |
var _space_state = _temp_spatial.get_world().direct_space_state | |
var _query = PhysicsShapeQueryParameters.new() | |
_shape.radius = .00001 | |
_query.shape_rid = _shape.get_rid() | |
_query.transform.origin = point | |
_query.collide_with_bodies = collide_with_bodies | |
_query.collide_with_areas = collide_with_areas | |
_query.exclude = [_shape.get_rid()] | |
var _result = _space_state.intersect_shape(_query) | |
remove_child(_temp_spatial) | |
_temp_spatial.queue_free() | |
for item in _result: | |
if item.collider == area: | |
return true | |
return false | |
# Optimize Path | |
# Removes consecutive linear points (repeated vector3's in a row), and merges line segments with the similar normals | |
# path:Array-> An array of Vector3 | |
# remove_duplicates:bool = true -> Should the repeated Vector3's be removed? | |
# margin:float-> The level of floating point precision when joining the line segments. | |
func optimize_path(path:Array,remove_duplicates:bool = true, margin = .01): | |
# return optimize_path2(path) | |
#fail early | |
if path.size() < 1: | |
return path | |
var uniq = [] | |
#remove linear duplicates | |
if remove_duplicates == true: | |
uniq.append(path[0]) | |
for _point in path: | |
if _point != uniq[uniq.size()-1]: | |
uniq.append(_point) | |
else: | |
uniq = path | |
#join straight lines | |
var last_normal:Vector3 | |
var optimized_list = [] | |
var current_normal:Vector3 | |
for i in range(1,uniq.size()): | |
var start:Vector3 = uniq[i-1] | |
var end:Vector3 = uniq[i] | |
current_normal = stepify_vector(start.direction_to(end),margin) | |
if (last_normal != current_normal): | |
optimized_list.append(start) | |
last_normal = current_normal | |
optimized_list.append(uniq[uniq.size()-1]) | |
return optimized_list | |
# Takes a Vector3 and a float and changes the decimal precision | |
# Gives a Vector3 | |
func stepify_vector(Vector:Vector3, step) -> Vector3: | |
return Vector.snapped(Vector3.ONE *step) | |
# Print function that links to director, tells where text is comming from | |
func dprint(args=[]) -> void: | |
if director != null: | |
director.dprint(str("Path Generator::",args)) | |
else: | |
print(str("Path Generator::",args)) | |
func _draw_path2(path: Array,geometry:ImmediateGeometry,material:SpatialMaterial) -> void: | |
geometry.clear() | |
geometry.set_material_override(material) | |
geometry.begin(Mesh.PRIMITIVE_LINES, null) | |
for i in range(0, path.size()-1): | |
geometry.add_vertex(path[i]) | |
geometry.add_vertex(path[i+1]) | |
geometry.end() | |
func _draw_path(path: Array,geometry0:ImmediateGeometry,geometry1:ImmediateGeometry,material0:SpatialMaterial,material1:SpatialMaterial) -> void: | |
geometry0.clear() | |
geometry1.clear() | |
var aa = true | |
geometry0.set_material_override(material0) | |
geometry0.begin(Mesh.PRIMITIVE_LINES, null) | |
geometry1.set_material_override(material1) | |
geometry1.begin(Mesh.PRIMITIVE_LINES, null) | |
for i in range(0, path.size()-1 ): | |
if aa== true: | |
geometry0.add_vertex(path[i]) | |
geometry0.add_vertex(path[i+1]) | |
else: | |
geometry1.add_vertex(path[i]) | |
geometry1.add_vertex(path[i+1]) | |
aa = !aa | |
geometry0.end() | |
geometry1.end() | |
func optimize_path2(path:Array,remove_duplicates = true) -> Array: | |
#fail early | |
if path.size() <= 1: | |
return path | |
var uniq = [] | |
#remove linear duplicates | |
if remove_duplicates == true: | |
uniq.append(path[0]) | |
for _point in path: | |
if _point != uniq[uniq.size()-1]: | |
uniq.append(_point) | |
else: | |
uniq = path | |
#join straight lines | |
var current_normal:Vector3 | |
var last_normal:Vector3 | |
var optimized_list = [] | |
# print(uniq.size()) | |
for i in range(1,uniq.size()): | |
var start:Vector3 = uniq[i-1] | |
var end:Vector3 = uniq[i] | |
current_normal = stepify_vector(start.direction_to(end),.01) | |
# print(current_normal , last_normal == current_normal, [start, end] ) | |
if (last_normal != current_normal): | |
optimized_list.append(start) | |
last_normal = current_normal | |
optimized_list.append(uniq[uniq.size()-1]) | |
return optimized_list | |
func get_path_zones(_path_zone_node:Spatial) -> Array: | |
var child_nodes = _path_zone_node.get_children() | |
var zone_nodes = [] | |
for node in child_nodes: | |
if node is Area: | |
zone_nodes.append(node) | |
return zone_nodes | |
func is_valid_path_zones(_path_zones:Array) -> bool: | |
var found_nodes = [] | |
for node in _path_zones: | |
match node.name: | |
"_player_spawn": | |
# dprint("spawn zone found") | |
found_nodes.append(node) | |
"_safe_room": | |
found_nodes.append(node) | |
# dprint("safe zone found") | |
if found_nodes.size() != 2: | |
return false | |
# dprint("Start and end zones found") | |
return true | |
func path_length(path:Array) -> float: | |
var distance = 0.0 | |
for i in range(0, path.size()-1): | |
distance += path[i].distance_to(path[i+1]) | |
return distance | |
func get_point_from_distance(path_array:Array,distance:float) -> Vector3: | |
var distance_clamped = clamp(distance,0,path_length(path_array)) | |
var working_distance:float = 0 | |
var normal:Vector3 | |
var start_point:Vector3 | |
for i in range(0, path_array.size()-1): | |
var temp_distance = path_array[i].distance_to(path_array[i+1]) | |
if working_distance + temp_distance >= distance_clamped: | |
normal = path_array[i].direction_to(path_array[i+1]) | |
start_point = path_array[i] | |
break | |
else: | |
working_distance += temp_distance | |
return start_point + ((distance_clamped - working_distance) * normal) | |
func get_point_ahead(path_array:Array, start_distance:float, distance_ahead:float) -> Vector3: | |
return get_point_from_distance(path_array,start_distance+distance_ahead) | |
pass | |
# Gets distance strictly along the path | |
func get_distance_from_point(path_array:Array, global_point:Vector3) -> float: | |
#get the distance from each segment | |
var seg_list = direct_distance_from_each_segment( path_array, global_point) | |
#find the segment with the shortest distance | |
var current_segment = find_nearest_euclidean_segment(seg_list) | |
var intersect = get_nearest_point_on_line_segment(path_array[current_segment],path_array[current_segment+1],global_point) | |
var completed_distance = 0.0 | |
if current_segment>0: | |
for i in range(0, current_segment): | |
completed_distance += path_array[i].distance_to(path_array[i+1]) | |
completed_distance += path_array[current_segment].distance_to(intersect) | |
else: | |
completed_distance = path_array[current_segment].distance_to(intersect) | |
return (completed_distance) | |
# Measures distance of global_point to closest point in each segment | |
func direct_distance_from_each_segment(path_flow:Array, global_point:Vector3): | |
var seg_list = [] | |
for i in range(0, path_flow.size()-1): | |
var intersect:Vector3 = get_nearest_point_on_line_segment(path_flow[i],path_flow[i+1],global_point) | |
seg_list.append(intersect.distance_to(global_point)) | |
return seg_list | |
func find_nearest_euclidean_segment(segment_distances:Array): | |
var min_dist | |
var current_seg = 0 | |
min_dist = segment_distances[0] | |
for i in range(0, segment_distances.size()): | |
if (segment_distances[i] <min_dist): | |
current_seg = i | |
min_dist = segment_distances[i] | |
return current_seg | |
pass | |
# Measures distance of global_point to closest point in each segment | |
func get_nearest_point_on_line_segment(start:Vector3, end:Vector3, point:Vector3): | |
#https://forum.unity.com/threads/how-do-i-find-the-closest-point-on-a-line.340058/#post-5839648 | |
var linedir = start.direction_to(end) | |
var v = point - start | |
var d = v.dot(linedir) | |
d = clamp(d, 0.0, start.distance_to(end)) | |
return start + linedir * d | |
pass |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment