math2d 1.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f3a494b2da989404d96ad8034d4b68f1ee7fa8280ce6f10e673e193fba05dc3b
4
+ data.tar.gz: 8f137444c95142d170c666456419ed07a601dd01f5593492ab0131909d3d3195
5
+ SHA512:
6
+ metadata.gz: d2a54a7a4cdfb1a84553a5fe8ec22c4cfb1680273ce69d98c5979d0eec65cae88bb4072fa69aebf0b44a9d1f6d67c5a291d3207d44ff6e1c926b685007db8a98
7
+ data.tar.gz: b0a32b1a0b168ff16e6c8e0aec72e828df677035e031f4870fb2c62994f8b9dfa37d86be0d8738bf6816d6645ab9f6e8a3296600468b61ad4ed852f3abb9e598
@@ -0,0 +1,158 @@
1
+ # Namespace for the Math2D library
2
+ module Math2D
3
+ # A collection of useful Mathematical tools in 2D space
4
+ #
5
+ # @author Ualace Henrique <ualacehenrique@hotmail.com>
6
+ # @note Most if not all methods descriptions here present come from the p5.js website.
7
+ # @see https://p5js.org/
8
+ module Utils2D
9
+ # Half the mathematical constant PI.
10
+ HALF_PI = Math::PI / 2
11
+ # A quarter of the mathematical constant PI.
12
+ QUARTER_PI = Math::PI / 4
13
+ # Twice the mathematical constant PI.
14
+ TWO_PI = Math::PI * 2
15
+
16
+ # Returns +angle+ radians in degrees.
17
+ #
18
+ # @param [Numeric] angle
19
+ # @return [Float]
20
+ def self.to_deg(angle)
21
+ (180 * angle) / Math::PI
22
+ end
23
+
24
+ # Returns +angle+ degrees in radians.
25
+ #
26
+ # @param [Numeric] angle
27
+ # @return [Float]
28
+ def self.to_rad(angle)
29
+ (Math::PI * angle) / 180
30
+ end
31
+
32
+ # Returns the distance between two cartesian points.
33
+ #
34
+ # @param [Numeric] x1
35
+ # @param [Numeric] y1
36
+ # @param [Numeric] x2
37
+ # @param [Numeric] y2
38
+ # @return [Float]
39
+ def self.distance(x1, y1, x2, y2)
40
+ Math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
41
+ end
42
+
43
+ # Calculates a number between two numbers at a specific increment.
44
+ # The amt parameter is the amount to interpolate between the two
45
+ # values where 0.0 equal to the first point, 0.1 is very near the
46
+ # first point, 0.5 is half-way in between, and 1.0 is equal to the
47
+ # second point.
48
+ #
49
+ # @param [Numeric] a
50
+ # @param [Numeric] b
51
+ # @return [Float]
52
+ def self.lerp(a, b, amt)
53
+ (b - a) * (3.0 - amt * 2.0) * amt * amt + a
54
+ end
55
+
56
+ # Re-maps a number from one range (+a1+..+a2+) to another (+b1+..+b2+).
57
+ #
58
+ # @param [Numeric] value
59
+ # @param [Numeric] a1
60
+ # @param [Numeric] a2
61
+ # @param [Numeric] b1
62
+ # @param [Numeric] b2
63
+ # @return [Float]
64
+ def self.map(value, a1, a2, b1, b2)
65
+ raise ArgumentError, 'Division by 0 - a1 cannot be equal to a2' if a2 == a1
66
+
67
+ slope = 1.0 * (b2 - b1) / (a2 - a1)
68
+ b1 + slope * (value - a1)
69
+ end
70
+
71
+ # Normalizes a number from another range (+a+..+b+) into a value between 0 and 1.
72
+ #
73
+ # @param [Numeric] value
74
+ # @param [Numeric] a
75
+ # @param [Numeric] b
76
+ # @return [Float]
77
+ def self.normalize(value, a, b)
78
+ map(value, a, b, 0.0, 1.0)
79
+ end
80
+
81
+ # Constrains a value +x+ between a minimum value +a+ and maximum value +b+.
82
+ #
83
+ # @param [Numeric] x
84
+ # @param [Numeric] a
85
+ # @param [Numeric] b
86
+ # @return [Numeric]
87
+ def self.constrain(x, a, b)
88
+ [[x, a].max, b].min
89
+ end
90
+
91
+ class << self
92
+ alias clamp constrain
93
+ end
94
+
95
+ # Returns the Perlin noise value at specified coordinates.
96
+ # Perlin noise is a random sequence generator producing a more naturally
97
+ # ordered, harmonic succession of numbers compared to the standard rand() method.
98
+ # The main difference to the rand() method is that Perlin noise is defined in an
99
+ # infinite n-dimensional space where each pair of coordinates corresponds to a fixed
100
+ # semi-random value. Utils2D can compute 1D and 2D noise, depending on the number of
101
+ # coordinates given. The resulting value will always be between 0.0 and 1.0.
102
+ # @see https://en.wikipedia.org/wiki/Perlin_noise
103
+ #
104
+ # @param [Numeric] x
105
+ # @param [Numeric] y
106
+ # @return [Float]
107
+ def self.noise(x, y = 0)
108
+ x0 = x.to_i
109
+ x1 = x0 + 1
110
+ y0 = y.to_i
111
+ y1 = y0 + 1
112
+
113
+ sx = x - x0.to_f
114
+ sy = y - y0.to_f
115
+
116
+ n0 = dot_grid_gradient(x0, y0, x, y)
117
+ n1 = dot_grid_gradient(x1, y0, x, y)
118
+ ix0 = lerp(n0, n1, sx)
119
+
120
+ n0 = dot_grid_gradient(x0, y1, x, y)
121
+ n1 = dot_grid_gradient(x1, y1, x, y)
122
+ ix1 = lerp(n0, n1, sx)
123
+ lerp(ix0, ix1, sy)
124
+ end
125
+
126
+ # If no argument is passed, randomly generates a greyscale RGB array.
127
+ # Otherwise, returns a greyscale array with that argument normalized.
128
+ #
129
+ # @param [Numeric] val
130
+ # @return [Array<Float>]
131
+ def self.grayscale(val = nil)
132
+ c = if val
133
+ normalize(val, 0, 255).abs
134
+ else
135
+ rand
136
+ end
137
+ [c, c, c, 1.0]
138
+ end
139
+
140
+ private
141
+
142
+ # @private
143
+ # @see https://en.wikipedia.org/wiki/Perlin_noise
144
+ def self.random_gradient(ix, iy)
145
+ random = 2920.0 * Math.sin(ix * 21_942.0 + iy * 171_324.0 + 8912.0) * Math.cos(ix * 23_157.0 * iy * 217_832.0 + 9758.0)
146
+ [Math.cos(random), Math.sin(random)]
147
+ end
148
+
149
+ # @private
150
+ # @see https://en.wikipedia.org/wiki/Perlin_noise
151
+ def self.dot_grid_gradient(ix, iy, x, y)
152
+ gradient = random_gradient(ix, iy)
153
+ dx = x - ix.to_f
154
+ dy = y - iy.to_f
155
+ (dx * gradient[0]) + (dy * gradient[1])
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,340 @@
1
+ # Namespace for the Math2D gem
2
+ module Math2D
3
+ # Vector2D
4
+ #
5
+ # - An implementation of various 2-dimensional vector methods.
6
+ #
7
+ # @author Ualace Henrique <ualacehenrique@hotmail.com>
8
+ # @note MOST methods return a NEW Vector2D instead of changing
9
+ # self and returning it, so be careful.
10
+ # @attr [Numeric] x The x coordinate of the Vector
11
+ # @attr [Numeric] y The y coordinate of the Vector
12
+ class Vector2D
13
+ attr_accessor :x, :y
14
+
15
+ # Creates a new vector (x, y).
16
+ #
17
+ # @param [Numeric] x
18
+ # @param [Numeric] y
19
+ # @return [Vector2D]
20
+ def initialize(x = 0, y = 0)
21
+ @x = x
22
+ @y = y
23
+ end
24
+
25
+ # Sets the +x+ and +y+ components of the vector.
26
+ # Each argument is optional, so you can change a single component
27
+ # and keep the other one's current value.
28
+ #
29
+ # @param [Numeric] x
30
+ # @param [Numeric] y
31
+ # @return [Vector2D] self
32
+ def set(x = self.x, y = self.y)
33
+ @x = x
34
+ @y = y
35
+ self
36
+ end
37
+
38
+ # Shorthand for writing Vector2D.new(0, 0).
39
+ #
40
+ # @return [Vector2D]
41
+ def self.zero
42
+ Vector2D.new(0, 0)
43
+ end
44
+
45
+ # Shorthand for writing Vector2D.new(1, 1).
46
+ #
47
+ # @return [Vector2D]
48
+ def self.one
49
+ Vector2D.new(1, 1)
50
+ end
51
+
52
+ # Shorthand for writing Vector2D.new(0, -1).
53
+ # NOTE: the y-axis is inverted as per Ruby2D's y-axis orientation
54
+ #
55
+ # @return [Vector2D]
56
+ def self.up
57
+ Vector2D.new(0, -1)
58
+ end
59
+
60
+ # Shorthand for writing Vector2D.new(0, 1).
61
+ #
62
+ # @return [Vector2D]
63
+ def self.down
64
+ Vector2D.new(0, 1)
65
+ end
66
+
67
+ # Shorthand for writing Vector2D.new(-1, 0).
68
+ #
69
+ # @return [Vector2D]
70
+ def self.left
71
+ Vector2D.new(-1, 0)
72
+ end
73
+
74
+ # Shorthand for writing Vector2D.new(1, 0).
75
+ #
76
+ # @return [Vector2D]
77
+ def self.right
78
+ Vector2D.new(1, 0)
79
+ end
80
+
81
+ # Negates both x and y values of +self+ and returns a new Vector2D.
82
+ #
83
+ # @return [Vector2D]
84
+ def -@
85
+ Vector2D.new(-@x, -@y)
86
+ end
87
+
88
+ alias negate -@
89
+
90
+ # Adds +self+ to another vector or to a scalar.
91
+ #
92
+ # @param [Numeric, Vector2D] other
93
+ # @return [Vector2D]
94
+ def +(other)
95
+ return Vector2D.new(@x + other.x, @y + other.y) if other.instance_of?(Vector2D)
96
+
97
+ Vector2D.new(@x + other, @y + other)
98
+ end
99
+
100
+ # Subtracts +self+ to another vector or to a scalar.
101
+ #
102
+ # @param [Numeric, Vector2D] other
103
+ # @return [Vector2D]
104
+ def -(other)
105
+ return Vector2D.new(@x - other.x, @y - other.y) if other.instance_of?(Vector2D)
106
+
107
+ Vector2D.new(@x - other, @y - other)
108
+ end
109
+
110
+ # Multiplies +self+ by another vector or by a scalar.
111
+ #
112
+ # @param [Numeric, Vector2D] other
113
+ # @return [Vector2D]
114
+ def *(other)
115
+ return Vector2D.new(@x * other.x, @y * other.y) if other.instance_of?(Vector2D)
116
+
117
+ Vector2D.new(@x * other, @y * other)
118
+ end
119
+
120
+ # Divides +self+ by another vector or by a scalar.
121
+ #
122
+ # @param [Numeric, Vector2D] other
123
+ # @return [Vector2D]
124
+ def /(other)
125
+ return Vector2D.new(@x / other.x, @y / other.y) if other.instance_of?(Vector2D)
126
+
127
+ Vector2D.new(@x / other, @y / other)
128
+ end
129
+
130
+ # Calculates the dot product between +self+ and +other+, where:
131
+ # A.B (A dot B) = (Ax * Bx) + (Ay * By)
132
+ #
133
+ # @param [Vector2D] other
134
+ # @return [Numeric]
135
+ def dot(other)
136
+ (@x * other.x) + (@y * other.y)
137
+ end
138
+
139
+ # Calculates the cross product between +self+ and +other+, where:
140
+ # AxB (A cross B) = (Ax * By) - (Bx * Ay)
141
+ # HOWEVER, the cross product is NOT defined in a 2D space, so the operation
142
+ # simply returns the magnitude of the resulting cross-product 3D vector.
143
+ #
144
+ # @param [Vector2D] other
145
+ # @return [Numeric]
146
+ def cross(other)
147
+ (@x * other.y) - (other.x * @y)
148
+ end
149
+
150
+ # Returns the magnitude squared of +self+.
151
+ #
152
+ # @return [Numeric]
153
+ def squared
154
+ (@x**2) + (@y**2)
155
+ end
156
+
157
+ # Returns the magnitude of +self+.
158
+ #
159
+ # @return [Float]
160
+ def magnitude
161
+ Math.sqrt(@x**2 + @y**2)
162
+ end
163
+
164
+ # Returns the Euclidean distance between +self+ and +other+.
165
+ #
166
+ # @param [Vector2D] other
167
+ # @return [Float]
168
+ def distance(other)
169
+ Math.sqrt((other.x - @x)**2 + (other.y - @y)**2)
170
+ end
171
+
172
+ # Returns the ratio (x / y) of +self+.
173
+ #
174
+ # @return [Float]
175
+ def ratio
176
+ x.to_f / y
177
+ end
178
+
179
+ # Limit the magnitude of +self+ to +max+ and returns a new vector.
180
+ #
181
+ # @param [Numeric] max
182
+ # @return [Vector2D]
183
+ def limit(max)
184
+ msq = squared
185
+ vec = self
186
+ if msq > (max**2)
187
+ vec /= Math.sqrt(msq)
188
+ vec *= max
189
+ end
190
+ vec
191
+ end
192
+
193
+ # Constrains the magnitude of +self+ between a minimum value +a+ and maximum value +b+.
194
+ #
195
+ # @note I haven't experienced this with other methods (yet), so I'm only going to document this
196
+ # here: you may end up with a broken magnitude (1.99999999 instead of 2, for example),
197
+ # so always remember to check and round according to your need.
198
+ # @param [Numeric] a
199
+ # @param [Numeric] b
200
+ # @return [Vector2D]
201
+ def constrain(a, b)
202
+ mag = magnitude
203
+ v = Vector2D.one
204
+ if mag > b
205
+ v.set_magnitude(b)
206
+ elsif mag < a
207
+ v.set_magnitude(a)
208
+ end
209
+ end
210
+
211
+ alias clamp constrain
212
+
213
+ # Sets the magnitude of +self+ to +new_mag+.
214
+ #
215
+ # @param [Numeric] new_mag
216
+ # @return [Vector2D]
217
+ def set_magnitude(new_mag)
218
+ mag = magnitude
219
+ mag = mag.zero? ? Float::INFINITY : mag
220
+ Vector2D.new((@x * new_mag) / mag, (@y * new_mag) / mag)
221
+ end
222
+
223
+ # Normalizes +self+ (set the magnitude to 1).
224
+ # +unit+ is an alias for this method.
225
+ #
226
+ # @return [Vector2D]
227
+ def normalize
228
+ set_magnitude(1)
229
+ end
230
+
231
+ alias unit normalize
232
+
233
+ # Returns true if the magnitude of +self+ is equal to 1, false otherwise.
234
+ # +unit?+ is an alias for this method.
235
+ #
236
+ # @return [Boolean]
237
+ def normalized?
238
+ magnitude == 1
239
+ end
240
+
241
+ alias unit? normalized?
242
+
243
+ # Returns the x-heading angle of +self+ in radians.
244
+ # The x-heading angle is the angle formed between +self+ and the x-axis.
245
+ #
246
+ # @return [Float]
247
+ def heading
248
+ Math.atan2(@y.to_f, @x)
249
+ end
250
+
251
+ # Returns the y-heading angle of +self+ in radians.
252
+ # The y-heading angle is the angle formed between +self+ and the y-axis.
253
+ #
254
+ # @return [Float]
255
+ def y_heading
256
+ Utils2D::HALF_PI - heading
257
+ end
258
+
259
+ # Returns a new Vector2D from a given angle +theta+ with length +len+.
260
+ #
261
+ # @param [Numeric] theta
262
+ # @param [Numeric] len
263
+ # @return [Vector2D]
264
+ def self.from_angle(theta, len = 1)
265
+ Vector2D.new(len * Math.cos(theta), len * Math.sin(theta))
266
+ end
267
+
268
+ # Returns the angle between +self+ and +other+ in radians.
269
+ #
270
+ # @param [Vector2D] other
271
+ # @return [Float]
272
+ def angle_between(other)
273
+ Math.acos((@x * other.x + @y * other.y) / (magnitude * other.magnitude))
274
+ end
275
+
276
+ # Rotates +self+ +angle+ radians and returns it as a new Vector2D.
277
+ #
278
+ # @param [Numeric] angle
279
+ # @return [Vector2D]
280
+ def rotate(angle)
281
+ Vector2D.new(@x * Math.cos(angle) - @y * Math.sin(angle),
282
+ @x * Math.sin(angle) + @y * Math.cos(angle))
283
+ end
284
+
285
+ # Linear interpolate +self+ and +other+ with an amount +amt+.
286
+ #
287
+ # @param [Numeric, Vector2D] other
288
+ # @param [Numeric] amt
289
+ # @return [Vector2D]
290
+ def lerp(other, amt)
291
+ self + (other - self) * amt
292
+ end
293
+
294
+ # Reflects +self+ and returns it as a new Vector2D.
295
+ # +other+ is the normal of the plane where +self+ is reflected.
296
+ #
297
+ # @param [Vector2D] other
298
+ # @return [Vector2D]
299
+ def reflect(other)
300
+ other = other.normalize
301
+ dot_prod = other.dot(self)
302
+ x = @x - dot_prod * other.x * 2
303
+ y = @y - dot_prod * other.y * 2
304
+ Vector2D.new(x, y)
305
+ end
306
+
307
+ # Returns a new Vector2D with random components but magnitude equal to 1.
308
+ #
309
+ # @return [Vector2D]
310
+ def self.random
311
+ theta = rand(-Utils2D::TWO_PI..Utils2D::TWO_PI)
312
+ Vector2D.new(Math.cos(theta), Math.sin(theta))
313
+ end
314
+
315
+ # Converts +self+ to an array.
316
+ #
317
+ # @return [Array<Numeric>]
318
+ def to_a
319
+ [@x, @y]
320
+ end
321
+
322
+ # Converts +self+ to a string.
323
+ #
324
+ # @return [String]
325
+ def to_s
326
+ to_a.to_s
327
+ end
328
+
329
+ # Returns a new Vector2D from an array +arr+.
330
+ # If the array is bigger than 2 elements, only the first 2 will be considered.
331
+ #
332
+ # @param [Array<Numeric>] arr
333
+ # @return [Vector2D]
334
+ def self.to_vector(arr)
335
+ raise ArgumentError, '`arr` must be an Array' if arr.class != Array
336
+
337
+ Vector2D.new(arr[0], arr[1])
338
+ end
339
+ end
340
+ end
data/lib/math2d.rb ADDED
@@ -0,0 +1,9 @@
1
+ require_relative 'math2d/vector2d'
2
+ require_relative 'math2d/utils2d'
3
+
4
+ # Namespace for the Math2D library
5
+ #
6
+ # @author Ualace Henrique <ualacehenrique@hotmail.com>
7
+ module Math2D; end
8
+
9
+ include Math2D
metadata ADDED
@@ -0,0 +1,45 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: math2d
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ualace Henrique
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-07-25 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A collection of useful Mathematical and Vector tools in 2D space
14
+ email:
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/math2d.rb
20
+ - lib/math2d/utils2d.rb
21
+ - lib/math2d/vector2d.rb
22
+ homepage: https://rubygems.org/gems/math2d
23
+ licenses:
24
+ - MIT
25
+ metadata: {}
26
+ post_install_message:
27
+ rdoc_options: []
28
+ require_paths:
29
+ - lib
30
+ required_ruby_version: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ required_rubygems_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ requirements: []
41
+ rubygems_version: 3.2.16
42
+ signing_key:
43
+ specification_version: 4
44
+ summary: Math2D
45
+ test_files: []