diff --git a/project/project.godot b/project/project.godot index c5b5251..fc258be 100644 --- a/project/project.godot +++ b/project/project.godot @@ -19,7 +19,7 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://src/behaviors/GSTArrive.gd" }, { -"base": "GSTSteeringBehavior", +"base": "GSTGroupBehavior", "class": "GSTAvoidCollisions", "language": "GDScript", "path": "res://src/behaviors/GSTAvoidCollisions.gd" @@ -29,7 +29,7 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://src/behaviors/GSTBlend.gd" }, { -"base": "GSTSteeringBehavior", +"base": "GSTGroupBehavior", "class": "GSTCohesion", "language": "GDScript", "path": "res://src/behaviors/GSTCohesion.gd" @@ -54,6 +54,11 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://src/behaviors/GSTFollowPath.gd" }, { +"base": "GSTSteeringBehavior", +"class": "GSTGroupBehavior", +"language": "GDScript", +"path": "res://src/GSTGroupBehavior.gd" +}, { "base": "GSTProximity", "class": "GSTInfiniteProximity", "language": "GDScript", @@ -99,7 +104,7 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://src/behaviors/GSTSeek.gd" }, { -"base": "GSTSteeringBehavior", +"base": "GSTGroupBehavior", "class": "GSTSeparation", "language": "GDScript", "path": "res://src/behaviors/GSTSeparation.gd" @@ -120,9 +125,9 @@ _global_script_classes=[ { "path": "res://src/GSTTargetAcceleration.gd" }, { "base": "Reference", -"class": "Utils", +"class": "GSTUtils", "language": "GDScript", -"path": "res://src/Utils.gd" +"path": "res://src/GSTUtils.gd" } ] _global_script_class_icons={ "GSTAgentLocation": "", @@ -134,6 +139,7 @@ _global_script_class_icons={ "GSTFace": "", "GSTFlee": "", "GSTFollowPath": "", +"GSTGroupBehavior": "", "GSTInfiniteProximity": "", "GSTLookWhereYouGo": "", "GSTMatchOrientation": "", @@ -147,7 +153,7 @@ _global_script_class_icons={ "GSTSteeringAgent": "", "GSTSteeringBehavior": "", "GSTTargetAcceleration": "", -"Utils": "" +"GSTUtils": "" } [application] diff --git a/project/src/GSTGroupBehavior.gd b/project/src/GSTGroupBehavior.gd new file mode 100644 index 0000000..5a38843 --- /dev/null +++ b/project/src/GSTGroupBehavior.gd @@ -0,0 +1,16 @@ +extends GSTSteeringBehavior +class_name GSTGroupBehavior +# Extended behavior that features a Proximity group for group-based behaviors. + + +var proximity: GSTProximity + +var _callback: = funcref(self, "report_neighbor") + + +func _init(agent: GSTSteeringAgent, proximity: GSTProximity).(agent) -> void: + self.proximity = proximity + + +func report_neighbor(neighbor: GSTSteeringAgent) -> bool: + return false diff --git a/project/src/GSTPath.gd b/project/src/GSTPath.gd index 7fbb733..4277025 100644 --- a/project/src/GSTPath.gd +++ b/project/src/GSTPath.gd @@ -1,60 +1,69 @@ extends Reference class_name GSTPath +# Represents a path made up of Vector3 waypoints, split into path segments for use by path +# following algorithms. + +# # Keeping it updated requires calling `create_path` to update the path. -var segments: Array -var is_open: bool +var open: bool var path_length: float -var nearest_point_on_segment: Vector3 -var nearest_point_on_path: Vector3 +var _segments: Array + +var _nearest_point_on_segment: Vector3 +var _nearest_point_on_path: Vector3 func _init(waypoints: Array, is_open: = false) -> void: self.is_open = is_open - _create_path(waypoints) - nearest_point_on_segment = waypoints[0] - nearest_point_on_path = waypoints[0] + create_path(waypoints) + _nearest_point_on_segment = waypoints[0] + _nearest_point_on_path = waypoints[0] -func get_start_point() -> Vector3: - return segments.front().begin - - -func get_end_point() -> Vector3: - return segments.back().end - - -func calculate_point_segment_distance_squared(a: Vector3, b: Vector3, c: Vector3) -> float: - nearest_point_on_segment = a - var ab: = b - a - var ab_length_squared: = ab.length_squared() - if ab_length_squared != 0: - var t = (c - a).dot(ab) / ab_length_squared - nearest_point_on_segment += ab * clamp(t, 0, 1) +func create_path(waypoints: Array) -> void: + if not waypoints or waypoints.size() < 2: + printerr("Waypoints cannot be null and must contain at least two (2) waypoints.") - return nearest_point_on_segment.distance_squared_to(c) + _segments = [] + path_length = 0 + var current: Vector3 = _segments[0] + var previous: Vector3 + + for i in range(1, waypoints.size(), 1): + previous = current + if i < waypoints.size(): + current = waypoints[i] + elif open: + break + else: + current = waypoints[0] + var segment: = GSTSegment.new(previous, current) + path_length += segment.length + segment.cumulative_length = path_length + _segments.append(segment) func calculate_distance(agent_current_position: Vector3, path_parameter: Dictionary) -> float: var smallest_distance_squared: float = INF var nearest_segment: GSTSegment - for i in range(segments.size()): - var segment: GSTSegment = segments[i] - var distance_squared: = calculate_point_segment_distance_squared( + for i in range(_segments.size()): + var segment: GSTSegment = _segments[i] + var distance_squared: = _calculate_point_segment_distance_squared( segment.begin, segment.end, agent_current_position) if distance_squared < smallest_distance_squared: - nearest_point_on_path = nearest_point_on_segment + _nearest_point_on_path = _nearest_point_on_segment smallest_distance_squared = distance_squared nearest_segment = segment path_parameter.segment_index = i var length_on_path: = ( nearest_segment.cumulative_length - - nearest_point_on_path.distance_to(nearest_segment.end)) + _nearest_point_on_path.distance_to(nearest_segment.end)) path_parameter.distance = length_on_path @@ -62,7 +71,7 @@ func calculate_distance(agent_current_position: Vector3, path_parameter: Diction func calculate_target_position(param: Dictionary, target_distance: float) -> Vector3: - if is_open: + if open: target_distance = clamp(target_distance, 0, path_length) else: if target_distance < 0: @@ -71,8 +80,8 @@ func calculate_target_position(param: Dictionary, target_distance: float) -> Vec target_distance = fmod(target_distance, path_length) var desired_segment: GSTSegment - for i in range(segments.size()): - var segment: GSTSegment = segments[i] + for i in range(_segments.size()): + var segment: GSTSegment = _segments[i] if segment.cumulative_length >= target_distance: desired_segment = segment break @@ -84,27 +93,23 @@ func calculate_target_position(param: Dictionary, target_distance: float) -> Vec (distance / desired_segment.length) + desired_segment.end) -func _create_path(waypoints: Array) -> void: - if not waypoints or waypoints.size() < 2: - printerr("Waypoints cannot be null and must contain at least two (2) waypoints.") +func get_start_point() -> Vector3: + return _segments.front().begin + + +func get_end_point() -> Vector3: + return _segments.back().end + + +func _calculate_point_segment_distance_squared(start: Vector3, end: Vector3, position: Vector3) -> float: + _nearest_point_on_segment = start + var start_end: = end - start + var start_end_length_squared: = start_end.length_squared() + if start_end_length_squared != 0: + var t = (position - start).dot(start_end) / start_end_length_squared + _nearest_point_on_segment += start_end * clamp(t, 0, 1) - segments = [] - path_length = 0 - var current: Vector3 = segments[0] - var previous: Vector3 - - for i in range(1, waypoints.size(), 1): - previous = current - if i < waypoints.size(): - current = waypoints[i] - elif is_open: - break - else: - current = waypoints[0] - var segment: = GSTSegment.new(previous, current) - path_length += segment.length - segment.cumulative_length = path_length - segments.append(segment) + return _nearest_point_on_segment.distance_squared_to(position) class GSTSegment: diff --git a/project/src/GSTSteeringAgent.gd b/project/src/GSTSteeringAgent.gd index c8a17b9..68c787a 100644 --- a/project/src/GSTSteeringAgent.gd +++ b/project/src/GSTSteeringAgent.gd @@ -1,6 +1,6 @@ extends GSTAgentLocation class_name GSTSteeringAgent -# Extended agent data type that adds velocity and speed data. +# Extended agent data type that adds velocity, speed, and size data var zero_linear_speed_threshold: = 0.01 diff --git a/project/src/Utils.gd b/project/src/GSTUtils.gd similarity index 93% rename from project/src/Utils.gd rename to project/src/GSTUtils.gd index 9433861..b366ff5 100644 --- a/project/src/Utils.gd +++ b/project/src/GSTUtils.gd @@ -1,4 +1,4 @@ -class_name Utils +class_name GSTUtils # Useful math and utility functions to complement Godot's own. diff --git a/project/src/behaviors/GSTArrive.gd b/project/src/behaviors/GSTArrive.gd index 5d5ab59..eeb84c0 100644 --- a/project/src/behaviors/GSTArrive.gd +++ b/project/src/behaviors/GSTArrive.gd @@ -30,7 +30,7 @@ func _arrive(acceleration: GSTTargetAcceleration, target_position: Vector3) -> G desired_velocity = (desired_velocity - agent.linear_velocity) * 1.0 / time_to_reach - acceleration.linear = Utils.clampedv3(desired_velocity, agent.max_linear_acceleration) + acceleration.linear = GSTUtils.clampedv3(desired_velocity, agent.max_linear_acceleration) acceleration.angular = 0 return acceleration diff --git a/project/src/behaviors/GSTAvoidCollisions.gd b/project/src/behaviors/GSTAvoidCollisions.gd index 3dad94c..d4ed8d2 100644 --- a/project/src/behaviors/GSTAvoidCollisions.gd +++ b/project/src/behaviors/GSTAvoidCollisions.gd @@ -1,9 +1,8 @@ -extends GSTSteeringBehavior +extends GSTGroupBehavior class_name GSTAvoidCollisions # Behavior that steers the agent to avoid obstacles lying in its path, approximated by a sphere. -var proximity: GSTProximity var first_neighbor: GSTSteeringAgent var shortest_time: float var first_minimum_separation: float @@ -12,8 +11,8 @@ var first_relative_position: Vector3 var first_relative_velocity: Vector3 -func _init(agent: GSTSteeringAgent, proximity: GSTProximity).(agent) -> void: - self.proximity = proximity +func _init(agent: GSTSteeringAgent, proximity: GSTProximity).(agent, proximity) -> void: + pass func _calculate_steering(acceleration: GSTTargetAcceleration) -> GSTTargetAcceleration: @@ -22,7 +21,7 @@ func _calculate_steering(acceleration: GSTTargetAcceleration) -> GSTTargetAccele first_minimum_separation = 0 first_distance = 0 - var neighbor_count: = proximity.find_neighbors(funcref(self, "_report_neighbor")) + var neighbor_count: = proximity.find_neighbors(_callback) if neighbor_count == 0 or not first_neighbor: acceleration.set_zero() @@ -39,7 +38,7 @@ func _calculate_steering(acceleration: GSTTargetAcceleration) -> GSTTargetAccele return acceleration -func _report_neighbor(neighbor: GSTSteeringAgent) -> bool: +func report_neighbor(neighbor: GSTSteeringAgent) -> bool: var relative_position: = neighbor.position - agent.position var relative_velocity: = neighbor.linear_velocity - agent.linear_velocity var relative_speed_squared: = relative_velocity.length_squared() diff --git a/project/src/behaviors/GSTBlend.gd b/project/src/behaviors/GSTBlend.gd index 7c76308..33f0699 100644 --- a/project/src/behaviors/GSTBlend.gd +++ b/project/src/behaviors/GSTBlend.gd @@ -38,7 +38,7 @@ func _calculate_steering(blended_accel: GSTTargetAcceleration) -> GSTTargetAccel blended_accel.add_scaled_accel(_accel, bw.weight) - blended_accel.linear = Utils.clampedv3(blended_accel.linear, agent.max_linear_acceleration) + blended_accel.linear = GSTUtils.clampedv3(blended_accel.linear, agent.max_linear_acceleration) blended_accel.angular = min(blended_accel.angular, agent.max_angular_acceleration) return blended_accel diff --git a/project/src/behaviors/GSTCohesion.gd b/project/src/behaviors/GSTCohesion.gd index 08cecee..12fe0ac 100644 --- a/project/src/behaviors/GSTCohesion.gd +++ b/project/src/behaviors/GSTCohesion.gd @@ -1,27 +1,26 @@ -extends GSTSteeringBehavior +extends GSTGroupBehavior class_name GSTCohesion # Group behavior that produces linear acceleration that attempts to move the agent towards the # center of mass of the agents in the area defined by the defined Proximity. var center_of_mass: Vector3 -var proximity: GSTProximity -func _init(agent: GSTSteeringAgent, proximity: GSTProximity).(agent) -> void: - self.proximity = proximity +func _init(agent: GSTSteeringAgent, proximity: GSTProximity).(agent, proximity) -> void: + pass func _calculate_steering(acceleration: GSTTargetAcceleration) -> GSTTargetAcceleration: acceleration.set_zero() center_of_mass = Vector3.ZERO - var neighbor_count = proximity.find_neighbors(funcref(self, "_report_neighbor")) + var neighbor_count = proximity.find_neighbors(_callback) if neighbor_count > 0: center_of_mass *= 1.0 / neighbor_count acceleration.linear = (center_of_mass - agent.position).normalized() * agent.max_linear_acceleration return acceleration -func _report_neighbor(neighbor: GSTSteeringAgent) -> bool: +func report_neighbor(neighbor: GSTSteeringAgent) -> bool: center_of_mass += neighbor.position return true diff --git a/project/src/behaviors/GSTSeparation.gd b/project/src/behaviors/GSTSeparation.gd index f7fcf67..9d9a573 100644 --- a/project/src/behaviors/GSTSeparation.gd +++ b/project/src/behaviors/GSTSeparation.gd @@ -1,4 +1,4 @@ -extends GSTSteeringBehavior +extends GSTGroupBehavior class_name GSTSeparation # Group behavior that produces acceleration repelling from the other neighbors that are in the # immediate area defined by the given `GSTProximity`. @@ -11,21 +11,20 @@ class_name GSTSeparation var decay_coefficient: = 1.0 var acceleration: GSTTargetAcceleration -var proximity: GSTProximity -func _init(agent: GSTSteeringAgent, proximity: GSTProximity).(agent) -> void: - self.proximity = proximity +func _init(agent: GSTSteeringAgent, proximity: GSTProximity).(agent, proximity) -> void: + pass func _calculate_steering(acceleration: GSTTargetAcceleration) -> GSTTargetAcceleration: acceleration.set_zero() self.acceleration = acceleration - proximity.find_neighbors(funcref(self, "_report_neighbor")) + proximity.find_neighbors(_callback) return acceleration -func _report_neighbor(neighbor: GSTSteeringAgent) -> bool: +func report_neighbor(neighbor: GSTSteeringAgent) -> bool: var to_agent: = agent.position - neighbor.position var distance_squared: = to_agent.length_squared() diff --git a/project/src/proximities/GSTInfiniteProximity.gd b/project/src/proximities/GSTInfiniteProximity.gd index 07ffbfd..0dd9b17 100644 --- a/project/src/proximities/GSTInfiniteProximity.gd +++ b/project/src/proximities/GSTInfiniteProximity.gd @@ -1,6 +1,6 @@ extends GSTProximity class_name GSTInfiniteProximity -# Specifies any agent that is in the specified list as being neighbors with the owner agent. +# Determines any agent that is in the specified list as being neighbors with the owner agent. func _init(agent: GSTSteeringAgent, agents: Array).(agent, agents) -> void: diff --git a/project/src/proximities/GSTProximity.gd b/project/src/proximities/GSTProximity.gd index 9eb5f9d..f746410 100644 --- a/project/src/proximities/GSTProximity.gd +++ b/project/src/proximities/GSTProximity.gd @@ -1,6 +1,7 @@ extends Reference class_name GSTProximity -# Defines an area that is used by group behaviors to find and process the owner's neighbors. +# Defines a way to determine any agent that is in the specified list as being neighbors with the +# owner agent. var agent: GSTSteeringAgent diff --git a/project/src/proximities/GSTRadiusProximity.gd b/project/src/proximities/GSTRadiusProximity.gd index 2e5bbd8..300fd11 100644 --- a/project/src/proximities/GSTRadiusProximity.gd +++ b/project/src/proximities/GSTRadiusProximity.gd @@ -1,7 +1,7 @@ extends GSTProximity class_name GSTRadiusProximity -# Specifies any agent that is in the specified list as being neighbors with the owner agent if they -# lie within the specified radius. +# Determines any agent that is in the specified list as being neighbors with the owner agent if +# they lie within the specified radius. var radius: = 0.0 @@ -34,7 +34,7 @@ func find_neighbors(callback: FuncRef) -> int: var range_to: = radius + current_agent.bounding_radius if distance_squared < range_to * range_to: - if callback.call_func(current_agent) == true: + if callback.call_func(current_agent): current_agent.tagged = true neighbor_count += 1 continue @@ -45,7 +45,7 @@ func find_neighbors(callback: FuncRef) -> int: var current_agent = agents[i] as GSTSteeringAgent if current_agent != agent and current_agent.tagged: - if callback.call_func(current_agent) == true: + if callback.call_func(current_agent): neighbor_count += 1 return neighbor_count