steering_behaviors 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.md +0 -0
- data/LICENSE +207 -0
- data/README.md +76 -0
- data/Rakefile +8 -0
- data/lib/steering_behaviors.rb +17 -0
- data/lib/steering_behaviors/align.rb +26 -0
- data/lib/steering_behaviors/arrive.rb +34 -0
- data/lib/steering_behaviors/broadside.rb +33 -0
- data/lib/steering_behaviors/common.rb +77 -0
- data/lib/steering_behaviors/evade.rb +42 -0
- data/lib/steering_behaviors/flee.rb +24 -0
- data/lib/steering_behaviors/match.rb +28 -0
- data/lib/steering_behaviors/orthogonal.rb +31 -0
- data/lib/steering_behaviors/pursue.rb +45 -0
- data/lib/steering_behaviors/seek.rb +24 -0
- data/lib/steering_behaviors/steering.rb +56 -0
- data/lib/steering_behaviors/vector.rb +185 -0
- data/lib/steering_behaviors/wander.rb +26 -0
- data/test/all_suite.rb +7 -0
- data/test/vector_test.rb +362 -0
- metadata +68 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
##
|
2
|
+
# Copyright 2013, Prylis Incorporated.
|
3
|
+
#
|
4
|
+
# This file is part of The Ruby Steering Behaviors Library.
|
5
|
+
# http://github.com/cpowell/steering-behaviors
|
6
|
+
# You can redistribute and/or modify this software only in accordance with
|
7
|
+
# the terms found in the "LICENSE" file included with the framework.
|
8
|
+
|
9
|
+
class SteeringBehaviors::Evade
|
10
|
+
extend SteeringBehaviors::Common
|
11
|
+
|
12
|
+
# Evade a moving target by anticipating its future position. Calculates where it thinks
|
13
|
+
# the target will be, and leverages 'flee' to calculate how to best escape.
|
14
|
+
# See http://www.red3d.com/cwr/steer/
|
15
|
+
#
|
16
|
+
# * *Args* :
|
17
|
+
# - +evading_kinematic+ -> evading kinematic
|
18
|
+
# - +enemy_kinematic+ -> kinematic of the thing to evade
|
19
|
+
# * *Returns* :
|
20
|
+
# - a steering force
|
21
|
+
#
|
22
|
+
def self.steer(evading_kinematic, enemy_kinematic)
|
23
|
+
offset = enemy_kinematic.position_vec - evading_kinematic.position_vec
|
24
|
+
direct_distance = offset.length
|
25
|
+
unit_offset = offset / direct_distance
|
26
|
+
|
27
|
+
parallelness = evading_kinematic.heading_vec.dot(enemy_kinematic.heading_vec)
|
28
|
+
forwardness = unit_offset.dot(evading_kinematic.heading_vec)
|
29
|
+
|
30
|
+
gen, tf = compute_time_factor(forwardness, parallelness)
|
31
|
+
|
32
|
+
direct_travel_time = direct_distance / evading_kinematic.speed
|
33
|
+
direct_travel_time_2 = direct_distance / (evading_kinematic.speed + enemy_kinematic.speed)
|
34
|
+
estimated_time_enroute = direct_travel_time_2 * tf
|
35
|
+
|
36
|
+
# printf "#{ipos.entity}'s target #{qpos.entity} is '#{gen}'. fness: %0.3f pness: %0.3f f: %0.3f p: %0.3f tf: %0.3f\n", forwardness, parallelness, f, p, tf
|
37
|
+
predicted_pos_vec = enemy_kinematic.position_vec + (enemy_kinematic.velocity_vec * estimated_time_enroute)
|
38
|
+
|
39
|
+
return [predicted_pos_vec, SteeringBehaviors::Flee.steer(evading_kinematic, predicted_pos_vec)]
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
##
|
2
|
+
# Copyright 2013, Prylis Incorporated.
|
3
|
+
#
|
4
|
+
# This file is part of The Ruby Steering Behaviors Library.
|
5
|
+
# http://github.com/cpowell/steering-behaviors
|
6
|
+
# You can redistribute and/or modify this software only in accordance with
|
7
|
+
# the terms found in the "LICENSE" file included with the framework.
|
8
|
+
|
9
|
+
class SteeringBehaviors::Flee
|
10
|
+
|
11
|
+
# Flee a specific position via the best possible route.
|
12
|
+
# See http://www.red3d.com/cwr/steer/
|
13
|
+
#
|
14
|
+
# * *Args* :
|
15
|
+
# - +kinematic+ -> the thing that is fleeing
|
16
|
+
# - +flee_position+ -> the position-vector that we want to flee from
|
17
|
+
# * *Returns* :
|
18
|
+
# - the calculated steering force
|
19
|
+
#
|
20
|
+
def self.steer(kinematic, flee_position)
|
21
|
+
best_velocity_to_target = (kinematic.position_vec - flee_position).normalize * kinematic.max_speed
|
22
|
+
best_velocity_to_target - kinematic.velocity_vec
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
##
|
2
|
+
# Copyright 2013, Prylis Incorporated.
|
3
|
+
#
|
4
|
+
# This file is part of The Ruby Steering Behaviors Library.
|
5
|
+
# http://github.com/cpowell/steering-behaviors
|
6
|
+
# You can redistribute and/or modify this software only in accordance with
|
7
|
+
# the terms found in the "LICENSE" file included with the framework.
|
8
|
+
|
9
|
+
class SteeringBehaviors::Match
|
10
|
+
|
11
|
+
# Matches the target's course and speed.
|
12
|
+
#
|
13
|
+
# * *Args* :
|
14
|
+
# - +hunter_kinematic+ -> my moving thing
|
15
|
+
# - +quarry_kinematic+ -> kinematic of the target
|
16
|
+
# * *Returns* :
|
17
|
+
# - the steering force
|
18
|
+
#
|
19
|
+
def self.steer(hunter_kinematic, quarry_kinematic)
|
20
|
+
course_diff = ( ( quarry_kinematic.heading_vec.radians - hunter_kinematic.heading_vec.radians + 3*Math::PI ) % (2*Math::PI) ) - Math::PI
|
21
|
+
|
22
|
+
speed_diff = quarry_kinematic.speed - hunter_kinematic.speed
|
23
|
+
|
24
|
+
target_local = SteeringBehaviors::Vector.new(course_diff * quarry_kinematic.speed, speed_diff)
|
25
|
+
target_local.rotate!(hunter_kinematic.heading_vec.radians)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
##
|
2
|
+
# Copyright 2013, Prylis Incorporated.
|
3
|
+
#
|
4
|
+
# This file is part of The Ruby Steering Behaviors Library.
|
5
|
+
# http://github.com/cpowell/steering-behaviors
|
6
|
+
# You can redistribute and/or modify this software only in accordance with
|
7
|
+
# the terms found in the "LICENSE" file included with the framework.
|
8
|
+
|
9
|
+
class SteeringBehaviors::Orthogonal
|
10
|
+
|
11
|
+
# Moves at a 90-degree angle to the target's course.
|
12
|
+
#
|
13
|
+
# * *Args* :
|
14
|
+
# - +hunter_kinematic+ -> my moving thing
|
15
|
+
# - +quarry_kinematic+ -> kinematic of the target
|
16
|
+
# * *Returns* :
|
17
|
+
# -
|
18
|
+
#
|
19
|
+
def self.steer(hunter_kinematic, quarry_kinematic)
|
20
|
+
option_a = SteeringBehaviors::Vector.new(quarry_kinematic.heading_vec.y, -quarry_kinematic.heading_vec.x)
|
21
|
+
option_b = SteeringBehaviors::Vector.new(-quarry_kinematic.heading_vec.y, quarry_kinematic.heading_vec.x)
|
22
|
+
|
23
|
+
da = option_a.delta(hunter_kinematic.heading_vec)
|
24
|
+
db = option_b.delta(hunter_kinematic.heading_vec)
|
25
|
+
|
26
|
+
best_hdg_vec = (da < db ? option_a : option_b)
|
27
|
+
|
28
|
+
desired_velocity = best_hdg_vec * hunter_kinematic.velocity_vec.length
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
##
|
2
|
+
# Copyright 2013, Prylis Incorporated.
|
3
|
+
#
|
4
|
+
# This file is part of The Ruby Steering Behaviors Library.
|
5
|
+
# http://github.com/cpowell/steering-behaviors
|
6
|
+
# You can redistribute and/or modify this software only in accordance with
|
7
|
+
# the terms found in the "LICENSE" file included with the framework.
|
8
|
+
|
9
|
+
class SteeringBehaviors::Pursue
|
10
|
+
extend SteeringBehaviors::Common
|
11
|
+
|
12
|
+
# Pursue a moving target by anticipating its future position. Calculates where it thinks
|
13
|
+
# the target will be, and leverages 'seek' to calculate how to get there.
|
14
|
+
# See http://www.red3d.com/cwr/steer/
|
15
|
+
#
|
16
|
+
# * *Args* :
|
17
|
+
# - +hunter_kinematic+ -> pursuing kinematic
|
18
|
+
# - +quarry_kinematic+ -> kinematic of the target
|
19
|
+
# * *Returns* :
|
20
|
+
# -
|
21
|
+
# * *Raises* :
|
22
|
+
# - ++ ->
|
23
|
+
#
|
24
|
+
def self.steer(hunter_kinematic, quarry_kinematic)
|
25
|
+
offset = quarry_kinematic.position_vec - hunter_kinematic.position_vec
|
26
|
+
direct_distance = offset.length
|
27
|
+
unit_offset = offset / direct_distance
|
28
|
+
|
29
|
+
parallelness = hunter_kinematic.heading_vec.dot(quarry_kinematic.heading_vec)
|
30
|
+
forwardness = unit_offset.dot(hunter_kinematic.heading_vec)
|
31
|
+
|
32
|
+
gen, tf = compute_time_factor(forwardness, parallelness)
|
33
|
+
|
34
|
+
direct_travel_time = direct_distance / hunter_kinematic.speed
|
35
|
+
direct_travel_time_2 = direct_distance / (hunter_kinematic.speed + quarry_kinematic.speed)
|
36
|
+
estimated_time_enroute = direct_travel_time_2 * tf
|
37
|
+
|
38
|
+
# printf "#{ipos.entity}'s target #{qpos.entity} is '#{gen}'. fness: %0.3f pness: %0.3f f: %0.3f p: %0.3f tf: %0.3f\n", forwardness, parallelness, f, p, tf
|
39
|
+
predicted_pos_vec = quarry_kinematic.position_vec + (quarry_kinematic.velocity_vec * estimated_time_enroute)
|
40
|
+
|
41
|
+
return [predicted_pos_vec, SteeringBehaviors::Seek.steer(hunter_kinematic, predicted_pos_vec)]
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
##
|
2
|
+
# Copyright 2013, Prylis Incorporated.
|
3
|
+
#
|
4
|
+
# This file is part of The Ruby Steering Behaviors Library.
|
5
|
+
# http://github.com/cpowell/steering-behaviors
|
6
|
+
# You can redistribute and/or modify this software only in accordance with
|
7
|
+
# the terms found in the "LICENSE" file included with the framework.
|
8
|
+
|
9
|
+
class SteeringBehaviors::Seek
|
10
|
+
|
11
|
+
# Seek a specific position; unlike 'arrive', will not slow down to stop at that pos.
|
12
|
+
# See http://www.red3d.com/cwr/steer/
|
13
|
+
#
|
14
|
+
# * *Args* :
|
15
|
+
# - +kinematic+ -> my seeking thing
|
16
|
+
# - +goal_position+ -> the position-vector where we want to go
|
17
|
+
# * *Returns* :
|
18
|
+
# - the calculated steering force
|
19
|
+
#
|
20
|
+
def self.steer(kinematic, goal_position)
|
21
|
+
desired_velocity = (goal_position - kinematic.position_vec).normalize * kinematic.max_speed
|
22
|
+
desired_velocity - kinematic.velocity_vec
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
##
|
2
|
+
# Copyright 2013, Prylis Incorporated.
|
3
|
+
#
|
4
|
+
# This file is part of The Ruby Steering Behaviors Library.
|
5
|
+
# http://github.com/cpowell/steering-behaviors
|
6
|
+
# You can redistribute and/or modify this software only in accordance with
|
7
|
+
# the terms found in the "LICENSE" file included with the framework.
|
8
|
+
|
9
|
+
class SteeringBehaviors::Steering
|
10
|
+
|
11
|
+
# Given a steering force vector, alter course and velocity accordingly.
|
12
|
+
# Takes turn rate limitations, mass, and other limits into account, and
|
13
|
+
# directly alters the provided Mobile component.
|
14
|
+
#
|
15
|
+
# * *Args* :
|
16
|
+
# - +kinematic+ -> the kinematic thing
|
17
|
+
# - +steering_force+ -> force vector supplied by a steering behavior
|
18
|
+
# - +delta+ -> time delta (in seconds) used for scaling the result
|
19
|
+
# - +accelerative+ -> whether you want the steering to change the thing's velocity (default true)
|
20
|
+
#
|
21
|
+
def self.feel_the_force(kinematic, steering_force, delta, accelerative=true) #, mobile, position)
|
22
|
+
acceleration = steering_force / kinematic.mass
|
23
|
+
|
24
|
+
# Compute the new, proposed velocity vector.
|
25
|
+
desired_velocity = kinematic.velocity_vec + (acceleration * delta)
|
26
|
+
|
27
|
+
desired_velocity.truncate!(kinematic.speed) if !accelerative
|
28
|
+
desired_velocity.truncate!(kinematic.max_speed)
|
29
|
+
|
30
|
+
# If this timeslice's proposed velocity-vector exceeds the turn rate,
|
31
|
+
# come up with a revised velociy-vec that doesn't exceed the rate -- and use that.
|
32
|
+
angle = Math.acos kinematic.heading_vec.dot(desired_velocity.normalize)
|
33
|
+
max_course_change = kinematic.max_turn * delta
|
34
|
+
|
35
|
+
if angle.abs > max_course_change
|
36
|
+
direction = SteeringBehaviors::Vector.sign(kinematic.velocity_vec, desired_velocity) # -1==CCW, 1==CW
|
37
|
+
limited_crse = kinematic.heading_vec.radians - max_course_change * direction
|
38
|
+
|
39
|
+
# printf "Current %0.4f. Angle %0.4f %s exceeds max change %0.4f. Desired course [%0.4f], limited course [%0.4f]\n",
|
40
|
+
# kinematic.heading_vec.radians,
|
41
|
+
# angle,
|
42
|
+
# (direction==1 ? 'CW' : 'CCW'),
|
43
|
+
# max_course_change,
|
44
|
+
# desired_velocity.radians,
|
45
|
+
# limited_crse
|
46
|
+
|
47
|
+
kinematic.velocity_vec = SteeringBehaviors::Vector.new(
|
48
|
+
Math.sin(limited_crse) * kinematic.speed,
|
49
|
+
Math.cos(limited_crse) * kinematic.speed
|
50
|
+
)
|
51
|
+
else
|
52
|
+
kinematic.velocity_vec = desired_velocity
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
##
|
2
|
+
# Copyright 2013, Prylis Incorporated.
|
3
|
+
#
|
4
|
+
# This file is part of The Ruby Steering Behaviors Library.
|
5
|
+
# http://github.com/cpowell/steering-behaviors
|
6
|
+
# You can redistribute and/or modify this software only in accordance with
|
7
|
+
# the terms found in the "LICENSE" file included with the framework.
|
8
|
+
|
9
|
+
class SteeringBehaviors::Vector
|
10
|
+
attr_reader :x, :y
|
11
|
+
|
12
|
+
def initialize(x=0,y=0)
|
13
|
+
@x = x.to_f
|
14
|
+
@y = y.to_f
|
15
|
+
end
|
16
|
+
|
17
|
+
def ==(other)
|
18
|
+
self.class == other.class && @x==other.x && @y==other.y
|
19
|
+
end
|
20
|
+
alias_method :eql?, :==
|
21
|
+
|
22
|
+
def x=(x)
|
23
|
+
@x = x.to_f
|
24
|
+
end
|
25
|
+
|
26
|
+
def y=(y)
|
27
|
+
@y = y.to_f
|
28
|
+
end
|
29
|
+
|
30
|
+
def length
|
31
|
+
Math.sqrt(@x**2 + @y**2)
|
32
|
+
end
|
33
|
+
|
34
|
+
def +(v)
|
35
|
+
SteeringBehaviors::Vector.new(@x + v.x, @y + v.y)
|
36
|
+
end
|
37
|
+
|
38
|
+
def /(n)
|
39
|
+
SteeringBehaviors::Vector.new(@x/n, @y/n)
|
40
|
+
end
|
41
|
+
|
42
|
+
def *(n)
|
43
|
+
SteeringBehaviors::Vector.new(@x*n, @y*n)
|
44
|
+
end
|
45
|
+
|
46
|
+
def -(v)
|
47
|
+
SteeringBehaviors::Vector.new(@x - v.x, @y - v.y)
|
48
|
+
end
|
49
|
+
|
50
|
+
def delta(other)
|
51
|
+
(( ( other.radians - self.radians + Math::PI + 2*Math::PI ) % (2*Math::PI) ) - Math::PI).abs
|
52
|
+
end
|
53
|
+
|
54
|
+
def normalize!
|
55
|
+
orig_length = length
|
56
|
+
return self if orig_length == 1.0 || orig_length == 0
|
57
|
+
|
58
|
+
@x /= orig_length
|
59
|
+
@y /= orig_length
|
60
|
+
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
def normalize
|
65
|
+
orig_length = length
|
66
|
+
return self if orig_length == 1.0 || orig_length == 0
|
67
|
+
|
68
|
+
SteeringBehaviors::Vector.new(@x/orig_length, @y/orig_length)
|
69
|
+
end
|
70
|
+
|
71
|
+
def truncate!(max)
|
72
|
+
return self if length < max
|
73
|
+
|
74
|
+
self.normalize!
|
75
|
+
self.x *= max
|
76
|
+
self.y *= max
|
77
|
+
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
# A · B = A.x * B.x + A.y * B.y
|
82
|
+
def dot(b)
|
83
|
+
val = @x*b.x + @y*b.y
|
84
|
+
val = 1.0 if val > 1.0
|
85
|
+
val = -1.0 if val < -1.0
|
86
|
+
|
87
|
+
val
|
88
|
+
end
|
89
|
+
|
90
|
+
def perpendicular
|
91
|
+
SteeringBehaviors::Vector.new(@y, -@x)
|
92
|
+
end
|
93
|
+
|
94
|
+
def compass_bearing(y_down_more_positive=false)
|
95
|
+
if y_down_more_positive
|
96
|
+
up = SteeringBehaviors::Vector.new(0, -1)
|
97
|
+
else
|
98
|
+
up = SteeringBehaviors::Vector.new(0, 1)
|
99
|
+
end
|
100
|
+
|
101
|
+
theta = Math.acos(self.normalize.dot(up))
|
102
|
+
|
103
|
+
theta *= -1 if @x < 0
|
104
|
+
degs = SteeringBehaviors::Vector.rad2deg(theta)
|
105
|
+
degs += 360 if degs < 0
|
106
|
+
|
107
|
+
degs
|
108
|
+
end
|
109
|
+
|
110
|
+
def sign(other)
|
111
|
+
SteeringBehaviors::Vector.sign(self, other)
|
112
|
+
end
|
113
|
+
|
114
|
+
def radians
|
115
|
+
theta = Math.acos(@y/length)
|
116
|
+
if @x < 0
|
117
|
+
theta *= -1
|
118
|
+
end
|
119
|
+
|
120
|
+
theta % (2 * Math::PI)
|
121
|
+
end
|
122
|
+
|
123
|
+
def from_compass_bearing!(brg)
|
124
|
+
rad = SteeringBehaviors::Vector.deg2rad(brg)
|
125
|
+
self.x = Math.sin(rad)
|
126
|
+
self.y = Math.cos(rad)
|
127
|
+
end
|
128
|
+
|
129
|
+
def rotate!(radians)
|
130
|
+
circle_cos = Math.cos(-radians)
|
131
|
+
circle_sin = Math.sin(-radians)
|
132
|
+
|
133
|
+
x_rot = circle_cos * x - circle_sin * y
|
134
|
+
y_rot = circle_sin * x + circle_cos * y
|
135
|
+
|
136
|
+
self.x, self.y = x_rot, y_rot
|
137
|
+
self
|
138
|
+
end
|
139
|
+
|
140
|
+
def rotate(radians)
|
141
|
+
circle_cos = Math.cos(-radians)
|
142
|
+
circle_sin = Math.sin(-radians)
|
143
|
+
|
144
|
+
x_rot = circle_cos * x - circle_sin * y
|
145
|
+
y_rot = circle_sin * x + circle_cos * y
|
146
|
+
|
147
|
+
SteeringBehaviors::Vector.new(x_rot, y_rot)
|
148
|
+
end
|
149
|
+
|
150
|
+
def self.sign(v1, v2)
|
151
|
+
if v1.y * v2.x > v1.x*v2.y
|
152
|
+
return -1 # clockwise
|
153
|
+
else
|
154
|
+
return 1 # anti-clockwise
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# def self.position_to_world_coords(point, heading_vec, pos)
|
159
|
+
# local_angle = heading_vec.radians + point.radians
|
160
|
+
|
161
|
+
# x = Math.sin(local_angle) * point.length
|
162
|
+
# y = Math.cos(local_angle) * point.length
|
163
|
+
|
164
|
+
# world_point = SteeringBehaviors::Vector.new(x,y) + pos
|
165
|
+
# world_point
|
166
|
+
# end
|
167
|
+
|
168
|
+
def self.deg2rad(d)
|
169
|
+
d * 0.017453292519943295 # Math::PI / 180.0
|
170
|
+
end
|
171
|
+
|
172
|
+
def self.rad2deg(r)
|
173
|
+
r * 57.29577951308232 # 180.0 / Math::PI
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.from_compass_bearing(brg)
|
177
|
+
rad = SteeringBehaviors::Vector.deg2rad(brg)
|
178
|
+
SteeringBehaviors::Vector.new(Math.sin(rad), Math.cos(rad))
|
179
|
+
end
|
180
|
+
|
181
|
+
def to_s
|
182
|
+
format("Vector {[%.7f, %.7f] len %0.7f}", @x, @y, length)
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|