orbit 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +14 -0
- data/README.md +16 -0
- data/lib/orbit.rb +27 -0
- data/lib/orbit/demo.rb +152 -0
- data/lib/orbit/eci.rb +130 -0
- data/lib/orbit/geocentric_coordinates.rb +37 -0
- data/lib/orbit/julian.rb +153 -0
- data/lib/orbit/norad_base.rb +261 -0
- data/lib/orbit/norad_sdp4.rb +4 -0
- data/lib/orbit/norad_sgp4.rb +101 -0
- data/lib/orbit/orbit.rb +163 -0
- data/lib/orbit/orbit_globals.rb +128 -0
- data/lib/orbit/satellite.rb +40 -0
- data/lib/orbit/site.rb +115 -0
- data/lib/orbit/tle.rb +127 -0
- data/lib/orbit/topocentric_horizon_coordinates.rb +33 -0
- data/lib/orbit/vector.rb +56 -0
- data/orbit.gemspec +17 -0
- metadata +67 -0
@@ -0,0 +1,128 @@
|
|
1
|
+
module Orbit
|
2
|
+
|
3
|
+
class OrbitGlobals
|
4
|
+
|
5
|
+
PI = 3.141592653589793
|
6
|
+
TWO_PI = 2.0 * OrbitGlobals::PI
|
7
|
+
RADS_PER_DEGREE = OrbitGlobals::PI / 180.0
|
8
|
+
DEGREES_PER_RAD = 180.0 / OrbitGlobals::PI
|
9
|
+
|
10
|
+
GM = 398601.2 # Earth gravitational constant, km^3/sec^2
|
11
|
+
GEO_SYNC_ALT = 42241.892 # km
|
12
|
+
EARTHDIAM = 12800.0 # km
|
13
|
+
DAYSIDEREAL = (23 * 3600) + (56 * 60) + 4.09 # sec
|
14
|
+
DAYSOLAR = (24 * 3600.0) # sec
|
15
|
+
|
16
|
+
AE = 1.0
|
17
|
+
AU = 149597870.0 # Astronomical unit (km) (IAU 76)
|
18
|
+
SR = 696000.0 # Solar radius (km) (IAU 76)
|
19
|
+
XKMPER = 6378.135 # Earth equatorial radius - kilometers (WGS '72)
|
20
|
+
F = 1.0 / 298.26 # Earth flattening (WGS '72)
|
21
|
+
GE = 398600.8 # Earth gravitational constant (WGS '72)
|
22
|
+
J2 = 1.0826158E-3 # J2 harmonic (WGS '72)
|
23
|
+
J3 = -2.53881E-6 # J3 harmonic (WGS '72)
|
24
|
+
J4 = -1.65597E-6 # J4 harmonic (WGS '72)
|
25
|
+
CK2 = OrbitGlobals::J2 / 2.0
|
26
|
+
CK4 = -3.0 * OrbitGlobals::J4 / 8.0
|
27
|
+
XJ3 = OrbitGlobals::J3
|
28
|
+
QO = OrbitGlobals::AE + 120.0 / OrbitGlobals::XKMPER
|
29
|
+
S = OrbitGlobals::AE + 78.0 / OrbitGlobals::XKMPER
|
30
|
+
MIN_PER_DAY = 1440.0 # Minutes per day (solar)
|
31
|
+
SEC_PER_DAY = 86400.0 # Seconds per day (solar)
|
32
|
+
OMEGAE = 1.00273790934 # Earth rotation per sidereal day
|
33
|
+
XKE = Math.sqrt(3600.0 * OrbitGlobals::GE /
|
34
|
+
(OrbitGlobals::XKMPER * OrbitGlobals::XKMPER * OrbitGlobals::XKMPER)) # sqrt(ge) ER^3/min^2
|
35
|
+
QOMS2T = ((OrbitGlobals::QO - OrbitGlobals::S) ** 4.0) #(QO - S)^4 ER^4
|
36
|
+
|
37
|
+
|
38
|
+
def self.sqr( n )
|
39
|
+
n ** 2
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.deg_to_rad( deg )
|
43
|
+
deg * RADS_PER_DEGREE;
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.rad_to_deg( rad )
|
47
|
+
rad * DEGREES_PER_RAD;
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
def self.fmod2p(arg)
|
52
|
+
modu = (arg % TWO_PI);
|
53
|
+
|
54
|
+
if (modu < 0.0)
|
55
|
+
modu += TWO_PI
|
56
|
+
end
|
57
|
+
|
58
|
+
return modu
|
59
|
+
end
|
60
|
+
|
61
|
+
# // ///////////////////////////////////////////////////////////////////////////
|
62
|
+
# // Globals.AcTan()
|
63
|
+
# // ArcTangent of sin(x) / cos(x). The advantage of this function over arctan()
|
64
|
+
# // is that it returns the correct quadrant of the angle.
|
65
|
+
def self.actan( sinx, cosx)
|
66
|
+
ret = nil
|
67
|
+
|
68
|
+
if (cosx == 0.0)
|
69
|
+
if (sinx > 0.0)
|
70
|
+
ret = PI / 2.0
|
71
|
+
else
|
72
|
+
ret = 3.0 * PI / 2.0
|
73
|
+
end
|
74
|
+
else
|
75
|
+
if (cosx > 0.0)
|
76
|
+
ret = Math.atan(sinx / cosx)
|
77
|
+
else
|
78
|
+
ret = PI + Math.atan(sinx / cosx)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
return ret
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.time_to_gmst(t)
|
86
|
+
jd = t.to_date.jd - 0.5
|
87
|
+
seconds = (t.hour * 3600) + (t.min * 60) + (t.sec).to_f + (t.subsec).to_f
|
88
|
+
fraction_of_day = seconds / 86400.0
|
89
|
+
|
90
|
+
jd += fraction_of_day
|
91
|
+
|
92
|
+
#puts "jd: #{jd}"
|
93
|
+
|
94
|
+
ut = (jd + 0.5 ) % 1.0;
|
95
|
+
jd = jd - ut
|
96
|
+
tu = (jd - 2451545.0) / 36525.0
|
97
|
+
gmst = 24110.54841 + tu * (8640184.812866 + tu * (0.093104 - tu * 6.2E-6));
|
98
|
+
gmst = ( gmst + 86400.0 * 1.00273790934 * ut ) % 86400.0
|
99
|
+
if (gmst < 0.0)
|
100
|
+
gmst += 86400.0 # "wrap" negative modulo value
|
101
|
+
end
|
102
|
+
|
103
|
+
gmst = (OrbitGlobals::TWO_PI * (gmst / 86400.0))
|
104
|
+
|
105
|
+
# puts "gmst: #{gmst}"
|
106
|
+
|
107
|
+
gmst
|
108
|
+
end
|
109
|
+
|
110
|
+
# /////////////////////////////////////////////////////////////////////
|
111
|
+
# ToLmst()
|
112
|
+
# Calculate Local Mean Sidereal Time for given longitude (for this date).
|
113
|
+
# The longitude is assumed to be in radians measured west from Greenwich.
|
114
|
+
# The return value is the angle, in radians, measuring eastward from the
|
115
|
+
# Vernal Equinox to the given longitude.
|
116
|
+
def self.time_to_lmst (t, lon)
|
117
|
+
gmst = OrbitGlobals.time_to_gmst( t )
|
118
|
+
lmst = ( gmst + lon ) % TWO_PI
|
119
|
+
|
120
|
+
# puts "long: #{lon}"
|
121
|
+
# puts "gmst: #{gmst}"
|
122
|
+
# puts "lmst: #{lmst}"
|
123
|
+
|
124
|
+
lmst
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Orbit
|
2
|
+
class Satellite
|
3
|
+
|
4
|
+
attr_accessor :tle
|
5
|
+
attr_accessor :orbit
|
6
|
+
|
7
|
+
def initialize( tle_string )
|
8
|
+
@tle = Tle.new(tle_string)
|
9
|
+
@orbit = Orbit.new(@tle)
|
10
|
+
end
|
11
|
+
|
12
|
+
def eci_position_at_time( time )
|
13
|
+
since_epoch = ( time.utc.to_f - @tle.epoch.to_f )
|
14
|
+
|
15
|
+
eci_position_at_seconds_since_epoch( since_epoch )
|
16
|
+
end
|
17
|
+
|
18
|
+
def eci_position_at_seconds_since_epoch( time_since_epoch )
|
19
|
+
t = time_since_epoch / 60.0
|
20
|
+
|
21
|
+
p = @orbit.get_position( t ) #For whatever reason this is decimal minutes
|
22
|
+
end
|
23
|
+
|
24
|
+
def position_at_time( time )
|
25
|
+
seconds_since_epoch = ( time.utc.to_f - @tle.epoch.to_f )
|
26
|
+
position_at_seconds_since_epoch( seconds_since_epoch )
|
27
|
+
end
|
28
|
+
|
29
|
+
def position_at_seconds_since_epoch( time_since_epoch )
|
30
|
+
eci_position_at_seconds_since_epoch( time_since_epoch ).to_geo
|
31
|
+
end
|
32
|
+
|
33
|
+
def current_position
|
34
|
+
since_epoch = ( Time.new.utc.to_f - @tle.epoch.to_f )
|
35
|
+
|
36
|
+
position_at_seconds_since_epoch( since_epoch )
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
data/lib/orbit/site.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
module Orbit
|
2
|
+
|
3
|
+
class Site
|
4
|
+
|
5
|
+
attr_accessor :geo
|
6
|
+
|
7
|
+
def initialize( lat, lon, alt )
|
8
|
+
alt = alt / 1000 #km
|
9
|
+
@geo = GeocentricCoordinates.new( OrbitGlobals.deg_to_rad( lat ), OrbitGlobals.deg_to_rad( lon ), alt )
|
10
|
+
end
|
11
|
+
|
12
|
+
def latitude_rad
|
13
|
+
@geo.latitude_rad
|
14
|
+
end
|
15
|
+
|
16
|
+
def longitude_rad
|
17
|
+
@geo.longitude_rad
|
18
|
+
end
|
19
|
+
|
20
|
+
def latitude_deg
|
21
|
+
OrbitGlobals::rad_to_deg( latitude_rad )
|
22
|
+
end
|
23
|
+
|
24
|
+
def longitude_deg
|
25
|
+
OrbitGlobals::rad_to_deg( longitude_rad )
|
26
|
+
end
|
27
|
+
|
28
|
+
def altitude
|
29
|
+
@geo.altitude
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_position(date)
|
33
|
+
return Eci.new(@geo, date)
|
34
|
+
end
|
35
|
+
|
36
|
+
def view_angle_to_satellite_at_time( sat, time )
|
37
|
+
sat_position = sat.eci_position_at_time( time )
|
38
|
+
topoLook = get_look_angle( sat_position )
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_look_angle(eci)
|
42
|
+
# Calculate the ECI coordinates for this Site object at the time
|
43
|
+
# of interest.
|
44
|
+
date = eci.date
|
45
|
+
eciSite = Eci.new(@geo, date)
|
46
|
+
vecRgRate = Vector.new(eci.velocity.m_x - eciSite.velocity.m_x,
|
47
|
+
eci.velocity.m_y - eciSite.velocity.m_y,
|
48
|
+
eci.velocity.m_z - eciSite.velocity.m_z)
|
49
|
+
|
50
|
+
x = eci.position.m_x - eciSite.position.m_x
|
51
|
+
y = eci.position.m_y - eciSite.position.m_y
|
52
|
+
z = eci.position.m_z - eciSite.position.m_z
|
53
|
+
w = Math.sqrt(OrbitGlobals::sqr(x) + OrbitGlobals::sqr(y) + OrbitGlobals::sqr(z))
|
54
|
+
|
55
|
+
vecRange = Vector.new(x, y, z, w)
|
56
|
+
|
57
|
+
# The site's Local Mean Sidereal Time at the time of interest.
|
58
|
+
theta = OrbitGlobals.time_to_lmst( date, longitude_rad)
|
59
|
+
|
60
|
+
sin_lat = Math.sin(latitude_rad)
|
61
|
+
cos_lat = Math.cos(latitude_rad)
|
62
|
+
sin_theta = Math.sin(theta)
|
63
|
+
cos_theta = Math.cos(theta)
|
64
|
+
|
65
|
+
top_s = sin_lat * cos_theta * vecRange.m_x +
|
66
|
+
sin_lat * sin_theta * vecRange.m_y -
|
67
|
+
cos_lat * vecRange.m_z
|
68
|
+
top_e = -sin_theta * vecRange.m_x +
|
69
|
+
cos_theta * vecRange.m_y
|
70
|
+
top_z = cos_lat * cos_theta * vecRange.m_x +
|
71
|
+
cos_lat * sin_theta * vecRange.m_y +
|
72
|
+
sin_lat * vecRange.m_z
|
73
|
+
az = Math.atan(-top_e / top_s)
|
74
|
+
|
75
|
+
if (top_s > 0.0)
|
76
|
+
az += OrbitGlobals::PI
|
77
|
+
end
|
78
|
+
|
79
|
+
if (az < 0.0)
|
80
|
+
az += 2.0 * OrbitGlobals::PI
|
81
|
+
end
|
82
|
+
|
83
|
+
el = Math.asin(top_z / vecRange.m_w)
|
84
|
+
rate = (vecRange.m_x * vecRgRate.m_x +
|
85
|
+
vecRange.m_y * vecRgRate.m_y +
|
86
|
+
vecRange.m_z * vecRgRate.m_z) / vecRange.m_w
|
87
|
+
|
88
|
+
topo = TopocentricHorizonCoordinates.new(az, # azimuth, radians
|
89
|
+
el, # elevation, radians
|
90
|
+
vecRange.m_w, # range, km
|
91
|
+
rate) # rate, km / sec
|
92
|
+
#if WANT_ATMOSPHERIC_CORRECTION
|
93
|
+
# Elevation correction for atmospheric refraction.
|
94
|
+
# Reference: Astronomical Algorithms by Jean Meeus, pp. 101-104
|
95
|
+
# Note: Correction is meaningless when apparent elevation is below horizon
|
96
|
+
topo.elevation += OrbitGlobals::deg_to_rad((1.02 /
|
97
|
+
Math.tan(OrbitGlobals::deg_to_rad(OrbitGlobals::rad_to_deg(el) + 10.3 /
|
98
|
+
(OrbitGlobals::rad_to_deg(el) + 5.11)))) / 60.0)
|
99
|
+
if (topo.elevation < 0.0)
|
100
|
+
topo.elevation = el # Reset to true elevation
|
101
|
+
end
|
102
|
+
|
103
|
+
if (topo.elevation > (OrbitGlobals::PI / 2))
|
104
|
+
topo.elevation = (OrbitGlobals::PI / 2)
|
105
|
+
end
|
106
|
+
#endif
|
107
|
+
return topo
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
data/lib/orbit/tle.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
module Orbit
|
2
|
+
|
3
|
+
class Tle
|
4
|
+
|
5
|
+
attr_accessor :tle_string
|
6
|
+
attr_accessor :norad_num
|
7
|
+
attr_accessor :intl_desc
|
8
|
+
attr_accessor :set_number # TLE set number
|
9
|
+
attr_accessor :epoch_year # Epoch: Last two digits of year
|
10
|
+
attr_accessor :epoch_day # Epoch: Fractional Julian Day of year
|
11
|
+
attr_accessor :orbit_at_epoch # Orbit at epoch
|
12
|
+
attr_accessor :inclination # Inclination
|
13
|
+
attr_accessor :raan # R.A. ascending node
|
14
|
+
attr_accessor :eccentricity # Eccentricity
|
15
|
+
attr_accessor :arg_perigee # Argument of perigee
|
16
|
+
attr_accessor :mean_anomaly # Mean anomaly
|
17
|
+
attr_accessor :mean_motion # Mean motion
|
18
|
+
attr_accessor :mean_motion_dt # First time derivative of mean motion
|
19
|
+
attr_accessor :mean_motion_dt2 # Second time derivative of mean motion
|
20
|
+
attr_accessor :bstar_drag # BSTAR Drag
|
21
|
+
|
22
|
+
|
23
|
+
# ISS (ZARYA)
|
24
|
+
# 1 25544U 98067A 08264.51782528 −.00002182 00000-0 -11606-4 0 2927
|
25
|
+
# 2 25544 51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537
|
26
|
+
|
27
|
+
FIELD_COLUMNS = {
|
28
|
+
norad_num: [1, 3,7],
|
29
|
+
classification: [1, 8,8],
|
30
|
+
launch_year: [1, 10,11],
|
31
|
+
launch_num: [1, 12,14],
|
32
|
+
launch_piece: [1, 15,17],
|
33
|
+
epoch_year: [1, 19,20],
|
34
|
+
epoch_day: [1, 21,32],
|
35
|
+
mean_motion_dt: [1, 34,43],
|
36
|
+
mean_motion_dt2: [1, 45,52],
|
37
|
+
bstar_drag: [1, 54,61],
|
38
|
+
number_zero: [1, 63,63],
|
39
|
+
element_num: [1, 65,68],
|
40
|
+
checksum_1: [1, 69,69],
|
41
|
+
inclination: [2, 9,16],
|
42
|
+
raan: [2, 18,25],
|
43
|
+
eccentricity: [2, 27,33],
|
44
|
+
arg_perigee: [2, 35,42],
|
45
|
+
mean_anomaly: [2, 44,51],
|
46
|
+
mean_motion: [2, 53,63],
|
47
|
+
revolution_num: [2, 64,68],
|
48
|
+
checksum_2: [2, 69,69]
|
49
|
+
}
|
50
|
+
|
51
|
+
|
52
|
+
def initialize( s )
|
53
|
+
@tle_string = s
|
54
|
+
|
55
|
+
# puts @tle_string
|
56
|
+
|
57
|
+
@norad_num = get_field( :norad_num )
|
58
|
+
@classification = get_field( :classification )
|
59
|
+
@launch_year = get_field( :launch_year )
|
60
|
+
@launch_num = get_field( :launch_num )
|
61
|
+
@launch_piece = get_field( :launch_piece )
|
62
|
+
@mean_motion_dt = get_field( :mean_motion_dt ).to_f
|
63
|
+
@mean_motion_dt2 = exp_to_float( "0." + get_field( :mean_motion_dt2 ) ).to_f
|
64
|
+
@bstar_drag = exp_to_float( "0." + get_field( :bstar_drag ) ).to_f
|
65
|
+
@element_num = get_field( :element_num ).to_f
|
66
|
+
@inclination = get_field( :inclination ).to_f
|
67
|
+
@raan = get_field( :raan ).to_f
|
68
|
+
@eccentricity = exp_to_float( "0." + get_field( :eccentricity ) ).to_f
|
69
|
+
@arg_perigee = get_field( :arg_perigee ).to_f
|
70
|
+
@mean_anomaly = get_field( :mean_anomaly ).to_f
|
71
|
+
@mean_motion = get_field( :mean_motion ).to_f
|
72
|
+
@revolution_num = get_field( :revolution_num ).to_f
|
73
|
+
end
|
74
|
+
|
75
|
+
def exp_to_float( exp )
|
76
|
+
# puts "exp: #{exp}"
|
77
|
+
parts = exp.split( "-" )
|
78
|
+
exp_part = -0.1
|
79
|
+
|
80
|
+
if parts.count < 2
|
81
|
+
parts = exp.split( " " )
|
82
|
+
exp_part = 0.1
|
83
|
+
end
|
84
|
+
|
85
|
+
float = parts[0].to_f * ( exp_part ** parts[1].to_f )
|
86
|
+
end
|
87
|
+
|
88
|
+
def get_field( field )
|
89
|
+
lines = @tle_string.split( "\n" )
|
90
|
+
|
91
|
+
line_num = FIELD_COLUMNS[field][0]
|
92
|
+
substring_start = FIELD_COLUMNS[field][1] - 1 # Convert to zero-base
|
93
|
+
substring_end = FIELD_COLUMNS[field][2] - 1 # Convert to zero-base
|
94
|
+
|
95
|
+
lines[line_num][substring_start..substring_end].strip
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_s
|
99
|
+
hash = {}
|
100
|
+
instance_variables.each {|var| hash[var.to_s.delete("@")] = instance_variable_get(var) }
|
101
|
+
|
102
|
+
hash.to_s
|
103
|
+
end
|
104
|
+
|
105
|
+
def epoch
|
106
|
+
epoch_year = get_field( :epoch_year ).to_f
|
107
|
+
epoch_day = get_field( :epoch_day ).to_f
|
108
|
+
|
109
|
+
epoch_year = epoch_year < 57 ? ( epoch_year + 2000 ) : ( epoch_year + 1900 )
|
110
|
+
|
111
|
+
epoch = Time.at( Time.utc( epoch_year ).to_i + ( ( epoch_day - 1 ) * OrbitGlobals::SEC_PER_DAY ) )
|
112
|
+
|
113
|
+
epoch = epoch.utc
|
114
|
+
|
115
|
+
# puts "epoch_year: #{epoch_year}"
|
116
|
+
# puts "epoch_day: #{epoch_day}"
|
117
|
+
# puts "epoch: #{epoch}"
|
118
|
+
|
119
|
+
epoch
|
120
|
+
end
|
121
|
+
|
122
|
+
# def epoch_julian
|
123
|
+
# epoch.
|
124
|
+
# end
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Orbit
|
2
|
+
|
3
|
+
class TopocentricHorizonCoordinates
|
4
|
+
#/ <summary>
|
5
|
+
#/ The azimuth, in radians.
|
6
|
+
#/ </summary>
|
7
|
+
attr_accessor :azimuth
|
8
|
+
|
9
|
+
#/ <summary>
|
10
|
+
#/ The elevation, in radians.
|
11
|
+
#/ </summary>
|
12
|
+
attr_accessor :elevation
|
13
|
+
|
14
|
+
#/ <summary>
|
15
|
+
#/ The range, in kilometers.
|
16
|
+
#/ </summary>
|
17
|
+
attr_accessor :range
|
18
|
+
|
19
|
+
#/ <summary>
|
20
|
+
#/ The range rate, in kilometers per second.
|
21
|
+
#/ A negative value means "towards observer".
|
22
|
+
#/ </summary>
|
23
|
+
attr_accessor :range_rate
|
24
|
+
|
25
|
+
def initialize( az, el, r, rr )
|
26
|
+
@azimuth = az
|
27
|
+
@elevation = el
|
28
|
+
@range = r
|
29
|
+
@range_rate = rr
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
data/lib/orbit/vector.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
module Orbit
|
2
|
+
|
3
|
+
class Vector
|
4
|
+
attr_accessor :m_x
|
5
|
+
attr_accessor :m_y
|
6
|
+
attr_accessor :m_z
|
7
|
+
attr_accessor :m_w
|
8
|
+
|
9
|
+
def initialize( x = nil, y = nil, z = nil, w = nil )
|
10
|
+
@m_x = x
|
11
|
+
@m_y = y
|
12
|
+
@m_z = z
|
13
|
+
@m_w = w
|
14
|
+
end
|
15
|
+
|
16
|
+
# ##################################/
|
17
|
+
# Multiply each component in the vector by 'factor'.
|
18
|
+
def mul(factor)
|
19
|
+
#puts "m_x: #{@m_x}, factor: #{factor}"
|
20
|
+
|
21
|
+
@m_x = @m_x * factor
|
22
|
+
@m_y *= factor
|
23
|
+
@m_z *= factor
|
24
|
+
@m_w *= (factor).abs if @m_w
|
25
|
+
end
|
26
|
+
|
27
|
+
# ##################################/
|
28
|
+
# Subtract a vector from this one.
|
29
|
+
def sub(vec)
|
30
|
+
@m_x -= vec.m_x
|
31
|
+
@m_y -= vec.m_y
|
32
|
+
@m_z -= vec.m_z
|
33
|
+
@m_w -= vec.m_w
|
34
|
+
end
|
35
|
+
|
36
|
+
# ##################################/
|
37
|
+
# Calculate the angle between this vector and another
|
38
|
+
def angle(vec)
|
39
|
+
return Math.acos(dot(vec) / (magnitude() * vec.magnitude()))
|
40
|
+
end
|
41
|
+
|
42
|
+
# ##################################/
|
43
|
+
def magnitude
|
44
|
+
return Math.sqrt((@m_x * @m_x) + (@m_y * @m_y) + (@m_z * @m_z))
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
# ##################################/
|
49
|
+
# Return the dot product
|
50
|
+
def dot(vec)
|
51
|
+
return (@m_x * vec.m_x) + (@m_y * vec.m_y) + (@m_z * vec.m_z)
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|