orbit 0.8.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/.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
|