steering_behaviors 1.0.4 → 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +3 -0
- data/README.md +2 -6
- data/lib/steering_behaviors.rb +6 -0
- data/lib/steering_behaviors/align.rb +6 -6
- data/lib/steering_behaviors/arrive.rb +11 -12
- data/lib/steering_behaviors/broadside.rb +8 -8
- data/lib/steering_behaviors/common.rb +61 -0
- data/lib/steering_behaviors/evade.rb +9 -9
- data/lib/steering_behaviors/flee.rb +4 -4
- data/lib/steering_behaviors/match.rb +5 -5
- data/lib/steering_behaviors/orthogonal.rb +10 -10
- data/lib/steering_behaviors/pursue.rb +12 -14
- data/lib/steering_behaviors/seek.rb +6 -6
- data/lib/steering_behaviors/separation.rb +60 -0
- data/lib/steering_behaviors/steering.rb +17 -15
- data/lib/steering_behaviors/vector.rb +12 -7
- data/lib/steering_behaviors/wander.rb +6 -6
- data/test/vector_test.rb +52 -0
- metadata +3 -2
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -12,7 +12,7 @@ The steering behaviors expect to operate on a 'kinematic' thing. That is, the st
|
|
12
12
|
|
13
13
|
## Supported stering behaviors
|
14
14
|
|
15
|
-
The library supports these steering behaviors:
|
15
|
+
The library supports a variety of steering behaviors. Some of these are the 'canonical' steering behaviors documented at Craig Reynold's website (see References, below). Others are behaviors documented in game programming books or behaviors that I have found useful for my own game programming needs:
|
16
16
|
|
17
17
|
* Seek: aim for and reach the specified point at top speed
|
18
18
|
* Flee: head away from the specified point at top speed (the opposite of Seek)
|
@@ -20,11 +20,7 @@ The library supports these steering behaviors:
|
|
20
20
|
* Pursue: given a moving target, anticipate its future position and intercept the target intelligently
|
21
21
|
* Evade: given a moving target, anticipate its future position and avoid the target intelligently (the opposite of Pursue)
|
22
22
|
* Wander: move about the plain in a 'random walk' way
|
23
|
-
|
24
|
-
These are all standard steering behaviors documented at Craig Reynold's website (see References, below).
|
25
|
-
|
26
|
-
Additionally, this library supports some other useful behaviors:
|
27
|
-
|
23
|
+
* Separate: predictively steer to avoid collision with another agent, based on the closest point of approach
|
28
24
|
* Align: align my course with the target's course, without altering my speed
|
29
25
|
* Match: match my course and speed to those of the target
|
30
26
|
* Broadside: expose my side to the target in the manner of a ship's broadside; useful for orbiting or exposing weapons, say
|
data/lib/steering_behaviors.rb
CHANGED
@@ -12,6 +12,12 @@ require 'steering_behaviors/match.rb'
|
|
12
12
|
require 'steering_behaviors/orthogonal.rb'
|
13
13
|
require 'steering_behaviors/pursue.rb'
|
14
14
|
require 'steering_behaviors/seek.rb'
|
15
|
+
require 'steering_behaviors/separation.rb'
|
15
16
|
require 'steering_behaviors/steering.rb'
|
16
17
|
require 'steering_behaviors/vector.rb'
|
17
18
|
require 'steering_behaviors/wander.rb'
|
19
|
+
|
20
|
+
|
21
|
+
SteeringBehaviors::VEC_ZERO = SteeringBehaviors::Vector.new(0, 0)
|
22
|
+
SteeringBehaviors::VEC_UP_NEGY = SteeringBehaviors::Vector.new(0, -1)
|
23
|
+
SteeringBehaviors::VEC_UP_POSY = SteeringBehaviors::Vector.new(0, 1)
|
@@ -11,15 +11,15 @@ class SteeringBehaviors::Align
|
|
11
11
|
# Align with a moving target by observing its course.
|
12
12
|
#
|
13
13
|
# * *Args* :
|
14
|
-
# - +
|
15
|
-
# - +
|
14
|
+
# - +character_kinematic+ -> kinematic of "our" character that is moving and aligning
|
15
|
+
# - +other_kinematic+ -> kinematic of the thing to align with
|
16
16
|
# * *Returns* :
|
17
|
-
# -
|
17
|
+
# - a steering force
|
18
18
|
#
|
19
|
-
def self.steer(
|
20
|
-
desired_velocity =
|
19
|
+
def self.steer(character_kinematic, other_kinematic)
|
20
|
+
desired_velocity = other_kinematic.heading_vec * character_kinematic.velocity_vec.length
|
21
21
|
|
22
|
-
return desired_velocity -
|
22
|
+
return desired_velocity - character_kinematic.velocity_vec
|
23
23
|
end
|
24
24
|
|
25
25
|
end
|
@@ -7,31 +7,30 @@
|
|
7
7
|
# the terms found in the "LICENSE" file included with the framework.
|
8
8
|
|
9
9
|
class SteeringBehaviors::Arrive
|
10
|
-
TARGET_RADIUS=0
|
11
|
-
|
12
10
|
# Arrive 'gently' at the goal position by decelerating smoothly.
|
13
11
|
#
|
14
12
|
# * *Args* :
|
15
|
-
# - +
|
16
|
-
# - +goal_position+ -> a Vector of position
|
17
|
-
# - +slow_radius+ -> don't begin decelerating until inside this radius; max speed outside
|
13
|
+
# - +character_kinematic+ -> kinematic of "our" character that is moving and arriving
|
14
|
+
# - +goal_position+ -> a Vector of position at which to arrive
|
15
|
+
# - +slow_radius+ -> don't begin decelerating until inside this radius; maintain max speed outside
|
16
|
+
# - +arrive_radius+ -> consider ourselves 'arrived' when inside this radius
|
18
17
|
# * *Returns* :
|
19
18
|
# - a steering force
|
20
19
|
#
|
21
|
-
def self.steer(
|
22
|
-
to_target = goal_position -
|
20
|
+
def self.steer(character_kinematic, goal_position, slow_radius, arrive_radius=0)
|
21
|
+
to_target = goal_position - character_kinematic.position_vec
|
23
22
|
dist = to_target.length
|
24
23
|
|
25
|
-
if dist <
|
26
|
-
return SteeringBehaviors::
|
24
|
+
if dist < arrive_radius
|
25
|
+
return SteeringBehaviors::VEC_ZERO
|
27
26
|
elsif dist > slow_radius
|
28
|
-
desired_speed =
|
27
|
+
desired_speed = character_kinematic.max_speed
|
29
28
|
else
|
30
|
-
desired_speed =
|
29
|
+
desired_speed = character_kinematic.max_speed * dist / slow_radius
|
31
30
|
end
|
32
31
|
|
33
32
|
desired_vel = to_target.normalize * desired_speed
|
34
33
|
|
35
|
-
return desired_vel -
|
34
|
+
return desired_vel - character_kinematic.velocity_vec
|
36
35
|
end
|
37
36
|
end
|
@@ -12,24 +12,24 @@ class SteeringBehaviors::Broadside
|
|
12
12
|
# exposing weapons, orbiting, etc.
|
13
13
|
#
|
14
14
|
# * *Args* :
|
15
|
-
# - +
|
16
|
-
# - +
|
15
|
+
# - +character_kinematic+ -> kinematic of "our" character that is moving and broadsiding
|
16
|
+
# - +other_kinematic+ -> kinematic of thing to broadside to
|
17
17
|
# * *Returns* :
|
18
18
|
# - a steering force
|
19
19
|
#
|
20
|
-
def self.steer(
|
21
|
-
to_quarry = (
|
20
|
+
def self.steer(character_kinematic, other_kinematic)
|
21
|
+
to_quarry = (other_kinematic.position_vec - character_kinematic.position_vec).normalize
|
22
22
|
option_a = SteeringBehaviors::Vector.new(to_quarry.y, -to_quarry.x)
|
23
23
|
option_b = SteeringBehaviors::Vector.new(-to_quarry.y, to_quarry.x)
|
24
24
|
|
25
|
-
da = option_a.delta(
|
26
|
-
db = option_b.delta(
|
25
|
+
da = option_a.delta(character_kinematic.heading_vec)
|
26
|
+
db = option_b.delta(character_kinematic.heading_vec)
|
27
27
|
|
28
28
|
best_hdg_vec = (da < db ? option_a : option_b)
|
29
29
|
|
30
|
-
desired_velocity = best_hdg_vec.normalize *
|
30
|
+
desired_velocity = best_hdg_vec.normalize * character_kinematic.velocity_vec.length
|
31
31
|
|
32
|
-
return desired_velocity -
|
32
|
+
return desired_velocity - character_kinematic.velocity_vec
|
33
33
|
end
|
34
34
|
|
35
35
|
end
|
@@ -8,6 +8,67 @@
|
|
8
8
|
|
9
9
|
module SteeringBehaviors::Common
|
10
10
|
|
11
|
+
# Given two kinematics, determine how many seconds until their Closest Point of Approach (CPA)
|
12
|
+
#
|
13
|
+
# * *Args* :
|
14
|
+
# - +character_kinematic+ -> kinematic of "our" character that is moving
|
15
|
+
# - +other_kinematic+ -> kinematic of the other mover
|
16
|
+
# * *Returns* :
|
17
|
+
# - time in seconds until CPA; may be negative (for 'in the past')
|
18
|
+
#
|
19
|
+
def compute_nearest_approach_time(character_kinematic, other_kinematic)
|
20
|
+
relative_vel_vec = other_kinematic.velocity_vec - character_kinematic.velocity_vec
|
21
|
+
relative_speed = relative_vel_vec.length
|
22
|
+
|
23
|
+
return 0 if relative_speed==0
|
24
|
+
|
25
|
+
relative_tangent_vec = relative_vel_vec / relative_speed
|
26
|
+
relative_position_vec = character_kinematic.position_vec - other_kinematic.position_vec
|
27
|
+
|
28
|
+
projection = relative_tangent_vec.dot(relative_position_vec)
|
29
|
+
return projection / relative_speed
|
30
|
+
end
|
31
|
+
|
32
|
+
# Given two kinematics, determine where they will be at the time of closest approach.
|
33
|
+
#
|
34
|
+
# * *Args* :
|
35
|
+
# - +character_kinematic+ -> kinematic of "our" character that is moving
|
36
|
+
# - +other_kinematic+ -> kinematic of the other mover
|
37
|
+
# * *Returns* :
|
38
|
+
# - a tuple consisting of (time until CPA), (character position at CPA), (other position at CPA)
|
39
|
+
#
|
40
|
+
def compute_nearest_approach(character_kinematic, other_kinematic)
|
41
|
+
# How long until it happens?
|
42
|
+
cpa_time = compute_nearest_approach_time(character_kinematic, other_kinematic)
|
43
|
+
|
44
|
+
# How far will the two kinematics go in that time?
|
45
|
+
char_travel_vec = character_kinematic.velocity_vec * cpa_time
|
46
|
+
other_travel_vec = other_kinematic.velocity_vec * cpa_time
|
47
|
+
|
48
|
+
# Project forward from current positions...
|
49
|
+
char_pos_vec = char_travel_vec + character_kinematic.position_vec
|
50
|
+
other_pos_vec = other_travel_vec + other_kinematic.position_vec
|
51
|
+
|
52
|
+
return [cpa_time, char_pos_vec, other_pos_vec]
|
53
|
+
end
|
54
|
+
|
55
|
+
# Given two kinematics, determine how close the two agents will be at their
|
56
|
+
# time & place of closest approach. Largely a convenience method, since you
|
57
|
+
# could call compute_nearest_approach() and do the same subtraction
|
58
|
+
# just as easily.
|
59
|
+
#
|
60
|
+
# * *Args* :
|
61
|
+
# - +character_kinematic+ -> kinematic of "our" character that is moving
|
62
|
+
# - +other_kinematic+ -> kinematic of the other mover
|
63
|
+
# * *Returns* :
|
64
|
+
# - the distance between the two agents at their closest approach
|
65
|
+
#
|
66
|
+
def compute_nearest_approach_distance(character_kinematic, other_kinematic)
|
67
|
+
cpa_time, char_pos_vec, other_pos_vec = compute_nearest_approach(character_kinematic, other_kinematic)
|
68
|
+
|
69
|
+
(char_pos_vec - other_pos_vec).length
|
70
|
+
end
|
71
|
+
|
11
72
|
# A support routine used by Pursue and Evade. Observes how 'forward' the
|
12
73
|
# target is, and how 'parallel' its course is to our own.
|
13
74
|
#
|
@@ -14,29 +14,29 @@ class SteeringBehaviors::Evade
|
|
14
14
|
# See http://www.red3d.com/cwr/steer/
|
15
15
|
#
|
16
16
|
# * *Args* :
|
17
|
-
# - +
|
18
|
-
# - +
|
17
|
+
# - +character_kinematic+ -> kinematic of "our" character that is moving and evading
|
18
|
+
# - +other_kinematic+ -> kinematic of the thing to evade
|
19
19
|
# * *Returns* :
|
20
20
|
# - a steering force
|
21
21
|
#
|
22
|
-
def self.steer(
|
23
|
-
offset = enemy_kinematic.position_vec -
|
22
|
+
def self.steer(character_kinematic, enemy_kinematic)
|
23
|
+
offset = enemy_kinematic.position_vec - character_kinematic.position_vec
|
24
24
|
direct_distance = offset.length
|
25
25
|
unit_offset = offset / direct_distance
|
26
26
|
|
27
|
-
parallelness =
|
28
|
-
forwardness = unit_offset.dot(
|
27
|
+
parallelness = character_kinematic.heading_vec.dot(enemy_kinematic.heading_vec)
|
28
|
+
forwardness = unit_offset.dot(character_kinematic.heading_vec)
|
29
29
|
|
30
30
|
gen, tf = compute_time_factor(forwardness, parallelness)
|
31
31
|
|
32
|
-
direct_travel_time = direct_distance /
|
33
|
-
direct_travel_time_2 = direct_distance / (
|
32
|
+
direct_travel_time = direct_distance / character_kinematic.speed
|
33
|
+
direct_travel_time_2 = direct_distance / (character_kinematic.speed + enemy_kinematic.speed)
|
34
34
|
estimated_time_enroute = direct_travel_time_2 * tf
|
35
35
|
|
36
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
37
|
predicted_pos_vec = enemy_kinematic.position_vec + (enemy_kinematic.velocity_vec * estimated_time_enroute)
|
38
38
|
|
39
|
-
return [predicted_pos_vec, SteeringBehaviors::Flee.steer(
|
39
|
+
return [predicted_pos_vec, SteeringBehaviors::Flee.steer(character_kinematic, predicted_pos_vec)]
|
40
40
|
end
|
41
41
|
|
42
42
|
end
|
@@ -12,13 +12,13 @@ class SteeringBehaviors::Flee
|
|
12
12
|
# See http://www.red3d.com/cwr/steer/
|
13
13
|
#
|
14
14
|
# * *Args* :
|
15
|
-
# - +
|
15
|
+
# - +character_kinematic+ -> kinematic of "our" character that is moving and fleeing
|
16
16
|
# - +flee_position+ -> the position-vector that we want to flee from
|
17
17
|
# * *Returns* :
|
18
18
|
# - the calculated steering force
|
19
19
|
#
|
20
|
-
def self.steer(
|
21
|
-
|
22
|
-
|
20
|
+
def self.steer(character_kinematic, flee_position)
|
21
|
+
desired_velocity = (character_kinematic.position_vec - flee_position).normalize * character_kinematic.max_speed
|
22
|
+
force = (desired_velocity - character_kinematic.velocity_vec)
|
23
23
|
end
|
24
24
|
end
|
@@ -11,15 +11,15 @@ class SteeringBehaviors::Match
|
|
11
11
|
# Matches the target's course and speed.
|
12
12
|
#
|
13
13
|
# * *Args* :
|
14
|
-
# - +
|
15
|
-
# - +
|
14
|
+
# - +character_kinematic+ -> kinematic of "our" character that is moving and matching
|
15
|
+
# - +other_kinematic+ -> kinematic of the thing to match
|
16
16
|
# * *Returns* :
|
17
17
|
# - the steering force
|
18
18
|
#
|
19
|
-
def self.steer(
|
20
|
-
desired_velocity =
|
19
|
+
def self.steer(character_kinematic, other_kinematic)
|
20
|
+
desired_velocity = other_kinematic.heading_vec * other_kinematic.velocity_vec.length
|
21
21
|
|
22
|
-
return desired_velocity -
|
22
|
+
return desired_velocity - character_kinematic.velocity_vec
|
23
23
|
end
|
24
24
|
|
25
25
|
end
|
@@ -11,23 +11,23 @@ class SteeringBehaviors::Orthogonal
|
|
11
11
|
# Moves at a 90-degree angle to the target's course.
|
12
12
|
#
|
13
13
|
# * *Args* :
|
14
|
-
# - +
|
15
|
-
# - +
|
14
|
+
# - +character_kinematic+ -> kinematic of "our" character that is moving
|
15
|
+
# - +other_kinematic+ -> kinematic of the thing to match
|
16
16
|
# * *Returns* :
|
17
|
-
# -
|
17
|
+
# - a steering force
|
18
18
|
#
|
19
|
-
def self.steer(
|
20
|
-
option_a = SteeringBehaviors::Vector.new(
|
21
|
-
option_b = SteeringBehaviors::Vector.new(-
|
19
|
+
def self.steer(character_kinematic, other_kinematic)
|
20
|
+
option_a = SteeringBehaviors::Vector.new(other_kinematic.heading_vec.y, -other_kinematic.heading_vec.x)
|
21
|
+
option_b = SteeringBehaviors::Vector.new(-other_kinematic.heading_vec.y, other_kinematic.heading_vec.x)
|
22
22
|
|
23
|
-
da = option_a.delta(
|
24
|
-
db = option_b.delta(
|
23
|
+
da = option_a.delta(character_kinematic.heading_vec)
|
24
|
+
db = option_b.delta(character_kinematic.heading_vec)
|
25
25
|
|
26
26
|
best_hdg_vec = (da < db ? option_a : option_b)
|
27
27
|
|
28
|
-
desired_velocity = best_hdg_vec *
|
28
|
+
desired_velocity = best_hdg_vec * character_kinematic.velocity_vec.length
|
29
29
|
|
30
|
-
return desired_velocity -
|
30
|
+
return desired_velocity - character_kinematic.velocity_vec
|
31
31
|
end
|
32
32
|
|
33
33
|
end
|
@@ -14,31 +14,29 @@ class SteeringBehaviors::Pursue
|
|
14
14
|
# See http://www.red3d.com/cwr/steer/
|
15
15
|
#
|
16
16
|
# * *Args* :
|
17
|
-
# - +
|
18
|
-
# - +
|
17
|
+
# - +character_kinematic+ -> kinematic of "our" character that is moving and pursuing
|
18
|
+
# - +other_kinematic+ -> kinematic of the thing to pursue
|
19
19
|
# * *Returns* :
|
20
|
-
# -
|
21
|
-
# * *Raises* :
|
22
|
-
# - ++ ->
|
20
|
+
# - a steering force
|
23
21
|
#
|
24
|
-
def self.steer(
|
25
|
-
offset =
|
22
|
+
def self.steer(character_kinematic, other_kinematic)
|
23
|
+
offset = other_kinematic.position_vec - character_kinematic.position_vec
|
26
24
|
direct_distance = offset.length
|
27
25
|
unit_offset = offset / direct_distance
|
28
26
|
|
29
|
-
parallelness =
|
30
|
-
forwardness = unit_offset.dot(
|
27
|
+
parallelness = character_kinematic.heading_vec.dot(other_kinematic.heading_vec)
|
28
|
+
forwardness = unit_offset.dot(character_kinematic.heading_vec)
|
31
29
|
|
32
30
|
gen, tf = compute_time_factor(forwardness, parallelness)
|
33
31
|
|
34
|
-
direct_travel_time = direct_distance /
|
35
|
-
direct_travel_time_2 = direct_distance / (
|
32
|
+
direct_travel_time = direct_distance / character_kinematic.speed
|
33
|
+
direct_travel_time_2 = direct_distance / (character_kinematic.speed + other_kinematic.speed)
|
36
34
|
estimated_time_enroute = direct_travel_time_2 * tf
|
37
|
-
|
38
35
|
# 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 =
|
36
|
+
predicted_pos_vec = other_kinematic.position_vec + (other_kinematic.velocity_vec * estimated_time_enroute)
|
40
37
|
|
41
|
-
|
38
|
+
# puts "#{compute_nearest_approach_distance(character_kinematic, other_kinematic)} in #{compute_nearest_approach_time(character_kinematic, other_kinematic)} sec"
|
39
|
+
return [predicted_pos_vec, SteeringBehaviors::Seek.steer(character_kinematic, predicted_pos_vec)]
|
42
40
|
end
|
43
41
|
|
44
42
|
|
@@ -12,13 +12,13 @@ class SteeringBehaviors::Seek
|
|
12
12
|
# See http://www.red3d.com/cwr/steer/
|
13
13
|
#
|
14
14
|
# * *Args* :
|
15
|
-
# - +
|
16
|
-
# - +goal_position+ -> the position-vector
|
15
|
+
# - +character_kinematic+ -> kinematic of "our" character that is moving and seeking
|
16
|
+
# - +goal_position+ -> the position-vector that we want to seek
|
17
17
|
# * *Returns* :
|
18
|
-
# - the
|
18
|
+
# - the steering force
|
19
19
|
#
|
20
|
-
def self.steer(
|
21
|
-
desired_velocity = (goal_position -
|
22
|
-
desired_velocity -
|
20
|
+
def self.steer(character_kinematic, goal_position)
|
21
|
+
desired_velocity = (goal_position - character_kinematic.position_vec).normalize * character_kinematic.max_speed
|
22
|
+
desired_velocity - character_kinematic.velocity_vec
|
23
23
|
end
|
24
24
|
end
|
@@ -0,0 +1,60 @@
|
|
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::Separation
|
10
|
+
extend SteeringBehaviors::Common
|
11
|
+
|
12
|
+
# Steer to avoid another kinematic by a certain radius.
|
13
|
+
#
|
14
|
+
# * *Args* :
|
15
|
+
# - +character_kinematic+ -> kinematic of "our" character that is moving and separating
|
16
|
+
# - +other_kinematic+ -> kinematic of the thing to separate from
|
17
|
+
# - +danger_radius+ -> attempt to avoid closest approach of less than this value
|
18
|
+
# * *Returns* :
|
19
|
+
# - a steering force
|
20
|
+
#
|
21
|
+
def self.steer(character_kinematic, other_kinematic, danger_radius)
|
22
|
+
cpa_time, char_pos_at_cpa, other_pos_at_cpa = compute_nearest_approach(character_kinematic, other_kinematic)
|
23
|
+
|
24
|
+
# Do nothing if the CPA is in the past, or if we won't breach the danger radius
|
25
|
+
return SteeringBehaviors::VEC_ZERO if cpa_time < 0
|
26
|
+
cpa_dist = (char_pos_at_cpa - other_pos_at_cpa).length
|
27
|
+
return SteeringBehaviors::VEC_ZERO if cpa_dist > danger_radius
|
28
|
+
|
29
|
+
parallelness = character_kinematic.heading_vec.dot(other_kinematic.heading_vec)
|
30
|
+
|
31
|
+
side_vec = character_kinematic.heading_vec.perpendicular
|
32
|
+
val = 0
|
33
|
+
|
34
|
+
if parallelness < -0.707
|
35
|
+
# anti-parallel, head-on paths
|
36
|
+
# steer away from the threat's future posn
|
37
|
+
offset_vec = other_pos_at_cpa - character_kinematic.position_vec
|
38
|
+
side_dot = offset_vec.dot(side_vec)
|
39
|
+
val = (side_dot > 0 ? -1.0 : 1.0)
|
40
|
+
# puts "Head-on, steering away from future pos"
|
41
|
+
elsif parallelness > 0.707
|
42
|
+
# parallel paths; steer away from threat
|
43
|
+
offset_vec = other_kinematic.position_vec - character_kinematic.position_vec
|
44
|
+
side_dot = offset_vec.dot(side_vec)
|
45
|
+
val = (side_dot > 0 ? -1.0 : 1.0)
|
46
|
+
# puts "Parallel, steering away from other guy"
|
47
|
+
else
|
48
|
+
# perpendicular paths; steer behind threat
|
49
|
+
if other_kinematic.speed <= character_kinematic.speed
|
50
|
+
side_dot = side_vec.dot(other_kinematic.velocity_vec)
|
51
|
+
val = (side_dot > 0 ? -1.0 : 1.0)
|
52
|
+
# puts "Perpendicular, steering behind other guy"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
return side_vec * val
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -13,41 +13,43 @@ class SteeringBehaviors::Steering
|
|
13
13
|
# directly alters the provided Mobile component.
|
14
14
|
#
|
15
15
|
# * *Args* :
|
16
|
-
# - +
|
16
|
+
# - +character_kinematic+ -> "our" character that is moving
|
17
17
|
# - +steering_force+ -> force vector supplied by a steering behavior
|
18
18
|
# - +delta+ -> time delta (in seconds) used for scaling the result
|
19
19
|
#
|
20
|
-
def self.feel_the_force(
|
21
|
-
|
20
|
+
def self.feel_the_force(character_kinematic, steering_force, delta, accelerative=true)
|
21
|
+
return if steering_force.nil?
|
22
22
|
|
23
|
-
|
24
|
-
desired_velocity = kinematic.velocity_vec + (acceleration * delta)
|
23
|
+
acceleration = steering_force / character_kinematic.mass
|
25
24
|
|
26
|
-
|
25
|
+
# Compute the new, proposed velocity vector.
|
26
|
+
desired_velocity = character_kinematic.velocity_vec + (acceleration * delta)
|
27
|
+
desired_velocity.truncate!(character_kinematic.speed) if !accelerative
|
28
|
+
desired_velocity.truncate!(character_kinematic.max_speed)
|
27
29
|
|
28
30
|
# If this timeslice's proposed velocity-vector exceeds the turn rate,
|
29
31
|
# come up with a revised velociy-vec that doesn't exceed the rate -- and use that.
|
30
|
-
angle = Math.acos
|
31
|
-
max_course_change =
|
32
|
+
angle = Math.acos character_kinematic.heading_vec.clamped_dot(desired_velocity.normalize)
|
33
|
+
max_course_change = character_kinematic.max_turn * delta
|
32
34
|
|
33
35
|
if angle.abs > max_course_change
|
34
|
-
direction = SteeringBehaviors::Vector.sign(
|
35
|
-
limited_crse =
|
36
|
+
direction = SteeringBehaviors::Vector.sign(character_kinematic.velocity_vec, desired_velocity) # -1==CCW, 1==CW
|
37
|
+
limited_crse = character_kinematic.heading_vec.radians - max_course_change * direction
|
36
38
|
|
37
39
|
# printf "Current %0.4f. Angle %0.4f %s exceeds max change %0.4f. Desired course [%0.4f], limited course [%0.4f]\n",
|
38
|
-
#
|
40
|
+
# character_kinematic.heading_vec.radians,
|
39
41
|
# angle,
|
40
42
|
# (direction==1 ? 'CW' : 'CCW'),
|
41
43
|
# max_course_change,
|
42
44
|
# desired_velocity.radians,
|
43
45
|
# limited_crse
|
44
46
|
|
45
|
-
|
46
|
-
Math.sin(limited_crse) *
|
47
|
-
Math.cos(limited_crse) *
|
47
|
+
character_kinematic.velocity_vec = SteeringBehaviors::Vector.new(
|
48
|
+
Math.sin(limited_crse) * character_kinematic.speed,
|
49
|
+
Math.cos(limited_crse) * character_kinematic.speed
|
48
50
|
)
|
49
51
|
else
|
50
|
-
|
52
|
+
character_kinematic.velocity_vec = desired_velocity
|
51
53
|
end
|
52
54
|
end
|
53
55
|
|
@@ -90,9 +90,19 @@ class SteeringBehaviors::Vector
|
|
90
90
|
self
|
91
91
|
end
|
92
92
|
|
93
|
+
# A pure and correct dot product routine.
|
93
94
|
# A · B = A.x * B.x + A.y * B.y
|
94
95
|
def dot(b)
|
95
96
|
val = @x*b.x + @y*b.y
|
97
|
+
end
|
98
|
+
|
99
|
+
# Unlike the 'pure' dot product above, this one ensures a -1.0..1.0 return value.
|
100
|
+
# If you're doing dot products of normalized vectors and expecting a pure -1.0..1.0
|
101
|
+
# return value for Math.acos() purposes, use this routine to avoid "Numerical argument
|
102
|
+
# is out of domain" errors that creep in due to rounding errors.
|
103
|
+
def clamped_dot(b)
|
104
|
+
val = self.dot(b)
|
105
|
+
|
96
106
|
val = 1.0 if val > 1.0
|
97
107
|
val = -1.0 if val < -1.0
|
98
108
|
|
@@ -104,13 +114,9 @@ class SteeringBehaviors::Vector
|
|
104
114
|
end
|
105
115
|
|
106
116
|
def compass_bearing(y_down_more_positive=false)
|
107
|
-
|
108
|
-
up = SteeringBehaviors::Vector.new(0, -1)
|
109
|
-
else
|
110
|
-
up = SteeringBehaviors::Vector.new(0, 1)
|
111
|
-
end
|
117
|
+
up = ( y_down_more_positive ? SteeringBehaviors::VEC_UP_NEGY : SteeringBehaviors::VEC_UP_POSY)
|
112
118
|
|
113
|
-
theta = Math.acos(self.normalize.
|
119
|
+
theta = Math.acos(self.normalize.clamped_dot(up))
|
114
120
|
|
115
121
|
theta *= -1 if @x < 0
|
116
122
|
degs = SteeringBehaviors::Vector.rad2deg(theta)
|
@@ -197,5 +203,4 @@ class SteeringBehaviors::Vector
|
|
197
203
|
def to_s
|
198
204
|
format("Vector {[%.7f, %.7f] len %0.7f}", @x, @y, length)
|
199
205
|
end
|
200
|
-
|
201
206
|
end
|
@@ -12,15 +12,15 @@ class SteeringBehaviors::Wander
|
|
12
12
|
# See http://www.red3d.com/cwr/steer/
|
13
13
|
#
|
14
14
|
# * *Args* :
|
15
|
-
# - +
|
15
|
+
# - +character_kinematic+ -> kinematic of "our" character that is moving and wandering
|
16
16
|
# - +erraticism+ -> how erratic the wandering effect will be
|
17
17
|
# * *Returns* :
|
18
|
-
# - the
|
18
|
+
# - the steering force
|
19
19
|
#
|
20
|
-
def self.steer(
|
21
|
-
|
22
|
-
|
20
|
+
def self.steer(character_kinematic, erraticism)
|
21
|
+
character_kinematic.steering_target += SteeringBehaviors::Vector.new(rand(-1.0..1.0)*erraticism, rand(-1.0..1.0)*erraticism)
|
22
|
+
character_kinematic.steering_target.normalize!
|
23
23
|
|
24
|
-
|
24
|
+
character_kinematic.steering_target.rotate(character_kinematic.heading_vec.radians)
|
25
25
|
end
|
26
26
|
end
|
data/test/vector_test.rb
CHANGED
@@ -196,6 +196,14 @@ class VectorTest < MiniTest::Unit::TestCase
|
|
196
196
|
@v2 = SteeringBehaviors::Vector.new(1.0, 0.5)
|
197
197
|
assert_equal(1.0, @v1.dot(@v2))
|
198
198
|
|
199
|
+
@v1 = SteeringBehaviors::Vector.new(1.0, 1.0)
|
200
|
+
@v2 = SteeringBehaviors::Vector.new(1.0, 1.0)
|
201
|
+
assert_equal(2.0, @v1.dot(@v2))
|
202
|
+
|
203
|
+
@v1 = SteeringBehaviors::Vector.new(0.708, 0.707)
|
204
|
+
@v2 = SteeringBehaviors::Vector.new(0.707, 0.707)
|
205
|
+
assert_equal(1.000405, @v1.dot(@v2))
|
206
|
+
|
199
207
|
@v1 = SteeringBehaviors::Vector.new(1.0, 0)
|
200
208
|
@v2 = SteeringBehaviors::Vector.new(0, 1.0)
|
201
209
|
assert_equal(0, @v1.dot(@v2))
|
@@ -224,8 +232,52 @@ class VectorTest < MiniTest::Unit::TestCase
|
|
224
232
|
@v2 = SteeringBehaviors::Vector.new(0.866, 0.5)
|
225
233
|
@v2.normalize!
|
226
234
|
assert_equal(0.86603810567665, @v1.dot(@v2))
|
235
|
+
|
236
|
+
@v1 = SteeringBehaviors::Vector.new(500, 500)
|
237
|
+
@v2 = SteeringBehaviors::Vector.new(-14, 75)
|
238
|
+
assert_equal(30500, @v1.dot(@v2))
|
239
|
+
|
240
|
+
@v1 = SteeringBehaviors::Vector.new(-0.5, -0.5)
|
241
|
+
@v2 = SteeringBehaviors::Vector.new(2.2, 2.2)
|
242
|
+
assert_equal(-2.2, @v1.dot(@v2))
|
243
|
+
end
|
244
|
+
|
245
|
+
def test_clamped_dot_product
|
246
|
+
@v1 = SteeringBehaviors::Vector.new(0.5, 1)
|
247
|
+
@v2 = SteeringBehaviors::Vector.new(1.0, 0.5)
|
248
|
+
assert_equal(1.0, @v1.clamped_dot(@v2))
|
249
|
+
|
250
|
+
@v1 = SteeringBehaviors::Vector.new(0.708, 0.707)
|
251
|
+
@v2 = SteeringBehaviors::Vector.new(0.707, 0.707)
|
252
|
+
assert_equal(1.0, @v1.clamped_dot(@v2))
|
253
|
+
|
254
|
+
@v1 = SteeringBehaviors::Vector.new(0, 1.0)
|
255
|
+
@v2 = SteeringBehaviors::Vector.new(-1.0, 0)
|
256
|
+
assert_equal(0, @v1.clamped_dot(@v2))
|
257
|
+
|
258
|
+
@v1 = SteeringBehaviors::Vector.new(0, 1.0)
|
259
|
+
@v2 = SteeringBehaviors::Vector.new(0.707, 0.707)
|
260
|
+
@v2.normalize!
|
261
|
+
assert_equal(0.7071067811865476, @v1.clamped_dot(@v2))
|
262
|
+
|
263
|
+
@v1 = SteeringBehaviors::Vector.new(1.0, 0)
|
264
|
+
@v2 = SteeringBehaviors::Vector.new(-0.707, 0.707)
|
265
|
+
@v2.normalize!
|
266
|
+
assert_equal(-0.7071067811865476, @v1.clamped_dot(@v2))
|
267
|
+
|
268
|
+
@v1 = SteeringBehaviors::Vector.new(1.0, 0)
|
269
|
+
@v2 = SteeringBehaviors::Vector.new(-0.707, -0.707)
|
270
|
+
@v2.normalize!
|
271
|
+
assert_equal(-0.7071067811865476, @v1.clamped_dot(@v2))
|
272
|
+
|
273
|
+
@v1 = SteeringBehaviors::Vector.new(0.5, 0.866)
|
274
|
+
@v1.normalize!
|
275
|
+
@v2 = SteeringBehaviors::Vector.new(0.866, 0.5)
|
276
|
+
@v2.normalize!
|
277
|
+
assert_equal(0.86603810567665, @v1.clamped_dot(@v2))
|
227
278
|
end
|
228
279
|
|
280
|
+
|
229
281
|
def test_perpendicular
|
230
282
|
@v2 = @v.perpendicular
|
231
283
|
assert_equal(1.0, @v2.x)
|
metadata
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
name: steering_behaviors
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 1.0.
|
5
|
+
version: 1.0.5
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Chris Powell
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-08-
|
12
|
+
date: 2013-08-11 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: |
|
15
15
|
If you're building a game, you need your game agents and characters to move on their own. A standard way of doing this is with 'steering behaviors'. The seminal paper by Craig Reynolds established a core set of steering behaviors that could be utilized for a variety of common movement tasks and natural behaviors. This Ruby library can accomplish many/most of those tasks for your Ruby / JRuby game. The basic behaviors can be layered for more complicated and advanced behaviors, such as flocking and crowd movement.
|
@@ -29,6 +29,7 @@ files:
|
|
29
29
|
- lib/steering_behaviors/orthogonal.rb
|
30
30
|
- lib/steering_behaviors/pursue.rb
|
31
31
|
- lib/steering_behaviors/seek.rb
|
32
|
+
- lib/steering_behaviors/separation.rb
|
32
33
|
- lib/steering_behaviors/steering.rb
|
33
34
|
- lib/steering_behaviors/vector.rb
|
34
35
|
- lib/steering_behaviors/wander.rb
|