vector2d 2.3.0 → 3.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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build.yml +1 -1
  3. data/.gitignore +3 -1
  4. data/.release-please-manifest.json +1 -1
  5. data/.rubocop.yml +1 -1
  6. data/.yardopts +11 -0
  7. data/CHANGELOG.md +216 -0
  8. data/Gemfile +5 -1
  9. data/Gemfile.lock +35 -5
  10. data/README.md +325 -12
  11. data/Rakefile +18 -0
  12. data/benchmark/vector_comparison.rb +129 -0
  13. data/lib/vector2d/angles.rb +315 -0
  14. data/lib/vector2d/arithmetic.rb +97 -0
  15. data/lib/vector2d/comparison.rb +188 -0
  16. data/lib/vector2d/componentwise.rb +239 -0
  17. data/lib/vector2d/constructors.rb +137 -0
  18. data/lib/vector2d/conversions.rb +139 -0
  19. data/lib/vector2d/coordinates.rb +22 -0
  20. data/lib/vector2d/deprecation.rb +16 -0
  21. data/lib/vector2d/dimensions.rb +297 -0
  22. data/lib/vector2d/interpolation.rb +191 -0
  23. data/lib/vector2d/lengths.rb +284 -0
  24. data/lib/vector2d/matrix_interop.rb +93 -0
  25. data/lib/vector2d/parsing.rb +142 -0
  26. data/lib/vector2d/projection.rb +208 -0
  27. data/lib/vector2d/version.rb +2 -1
  28. data/lib/vector2d.rb +172 -60
  29. data/spec/lib/vector2d/angles_spec.rb +441 -0
  30. data/spec/lib/vector2d/{calculations_spec.rb → arithmetic_spec.rb} +48 -58
  31. data/spec/lib/vector2d/comparison_spec.rb +358 -0
  32. data/spec/lib/vector2d/componentwise_spec.rb +300 -0
  33. data/spec/lib/vector2d/constructors_spec.rb +299 -0
  34. data/spec/lib/vector2d/conversions_spec.rb +234 -0
  35. data/spec/lib/vector2d/dimensions_spec.rb +820 -0
  36. data/spec/lib/vector2d/interpolation_spec.rb +322 -0
  37. data/spec/lib/vector2d/lengths_spec.rb +457 -0
  38. data/spec/lib/vector2d/matrix_interop_spec.rb +92 -0
  39. data/spec/lib/vector2d/parsing_spec.rb +329 -0
  40. data/spec/lib/vector2d/projection_spec.rb +306 -0
  41. data/spec/lib/vector2d_documentation_spec.rb +38 -0
  42. data/spec/lib/vector2d_immutability_spec.rb +263 -0
  43. data/spec/lib/vector2d_spec.rb +90 -50
  44. data/spec/lib/vector2d_subclassing_spec.rb +210 -0
  45. data/spec/lib/vector2d_tags_spec.rb +43 -0
  46. data/spec/spec_helper.rb +2 -0
  47. data/spec/support/doc_examples/comments.rb +65 -0
  48. data/spec/support/doc_examples/markdown.rb +50 -0
  49. data/spec/support/doc_examples.rb +94 -0
  50. data/spec/support/doc_tags.rb +151 -0
  51. data/spec/support/shared_examples/class_preserving_method.rb +10 -0
  52. data/spec/support/shared_examples/deprecated_method.rb +25 -0
  53. data/spec/support/shared_examples/parsed_vector.rb +11 -0
  54. data/vector2d.gemspec +2 -1
  55. metadata +42 -13
  56. data/.travis.yml +0 -10
  57. data/lib/vector2d/calculations.rb +0 -141
  58. data/lib/vector2d/coercions.rb +0 -64
  59. data/lib/vector2d/fitting.rb +0 -60
  60. data/lib/vector2d/properties.rb +0 -46
  61. data/lib/vector2d/transformations.rb +0 -98
  62. data/spec/lib/vector2d/coercions_spec.rb +0 -59
  63. data/spec/lib/vector2d/fitting_spec.rb +0 -70
  64. data/spec/lib/vector2d/properties_spec.rb +0 -47
  65. data/spec/lib/vector2d/transformations_spec.rb +0 -139
data/spec/spec_helper.rb CHANGED
@@ -5,3 +5,5 @@ SimpleCov.start
5
5
 
6
6
  require "vector2d"
7
7
  require "rspec/its"
8
+
9
+ Dir[File.expand_path("support/**/*.rb", __dir__)].each { |file| require file }
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DocExamples
4
+ # The examples in the doc comments under lib/. An example is an
5
+ # indented line inside a comment, and one comment is one block, named
6
+ # after the method it sits above.
7
+ #
8
+ # Tag blocks are skipped. A block runs from the first tag at the left
9
+ # margin of the comment to the next blank comment line, so the type
10
+ # lists in @param and @return are not mistaken for examples.
11
+ module Comments
12
+ COMMENT = /\A\s*#(?:\s|\z)/
13
+ INDENTED = /\A\s*#\s{3}(\s*\S.*)\z/
14
+ DEFINITION = /\A\s*def\s+(self\.)?([^\s(]+)/
15
+ TAG = /\A\s*#\s@/
16
+ BLANK = /\A\s*#\s*\z/
17
+
18
+ class << self
19
+ def blocks
20
+ Dir[File.join(ROOT, "lib/**/*.rb")].flat_map { |path| parse(path) }
21
+ end
22
+
23
+ private
24
+
25
+ def parse(path)
26
+ chunks = File.readlines(path, chomp: true).each_with_index.to_a
27
+ .chunk { |line, _| line.match?(COMMENT) }.to_a
28
+ chunks.each_with_index.filter_map do |(comment, lines), index|
29
+ block(path, lines, chunks.dig(index + 1, 1)) if comment
30
+ end
31
+ end
32
+
33
+ def block(path, lines, following)
34
+ DocExamples.block(path.delete_prefix("#{ROOT}/"), lines.first[1] + 1,
35
+ name(following), code(lines))
36
+ end
37
+
38
+ def code(lines)
39
+ untagged(lines).filter_map do |line, index|
40
+ body = line[INDENTED, 1]
41
+ [body, index] if body
42
+ end
43
+ end
44
+
45
+ # Drops the tag blocks, each running from its first tag to the
46
+ # next blank comment line.
47
+ def untagged(lines)
48
+ tagged = false
49
+ lines.reject do |line, _|
50
+ tagged = true if line.match?(TAG)
51
+ tagged &&= !line.match?(BLANK)
52
+ end
53
+ end
54
+
55
+ # The method a doc comment sits above, for naming the example.
56
+ def name(following)
57
+ code = Array(following).map(&:first).find { |line| !line.strip.empty? }
58
+ match = code&.match(DEFINITION)
59
+ return "the examples" unless match
60
+
61
+ "#{match[1] ? '.' : '#'}#{match[2]}"
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DocExamples
4
+ # The examples in the fenced Ruby blocks in the README.
5
+ #
6
+ # One section, from a heading to the next, is one block. The fenced
7
+ # blocks under a heading share a binding, the way the README reads,
8
+ # where a vector set up in one paragraph is used by the next.
9
+ module Markdown
10
+ FILE = "README.md"
11
+ HEADING = /\A##\s+(.+)\z/
12
+ FENCE = /\A```/
13
+ RUBY_FENCE = /\A```ruby\s*\z/
14
+
15
+ class << self
16
+ def blocks
17
+ lines = File.readlines(File.join(ROOT, FILE), chomp: true)
18
+ sections(lines.each_with_index.to_a)
19
+ .filter_map { |section| block(section) }
20
+ end
21
+
22
+ private
23
+
24
+ def sections(lines)
25
+ lines.slice_before { |line, _| line.match?(HEADING) }
26
+ end
27
+
28
+ def block(section)
29
+ DocExamples.block(FILE, section.first[1] + 1, name(section),
30
+ code(section))
31
+ end
32
+
33
+ def name(section)
34
+ heading = section.first[0][HEADING, 1]
35
+ heading ? "the #{heading} section" : "the examples"
36
+ end
37
+
38
+ # The lines inside the ```ruby fences, with the blank ones dropped.
39
+ def code(section)
40
+ fenced(section).reject { |line, _| line.strip.empty? }
41
+ end
42
+
43
+ def fenced(lines)
44
+ lines.slice_before { |line, _| line.match?(FENCE) }
45
+ .select { |chunk| chunk.first[0].match?(RUBY_FENCE) }
46
+ .flat_map { |chunk| chunk.drop(1) }
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ripper"
4
+
5
+ # Extracts the runnable examples from the documentation.
6
+ #
7
+ # The doc comments under lib/ and the fenced Ruby blocks in the README
8
+ # are read the same way. Lines carrying a "# =>" marker assert on the
9
+ # value of the expression to their left, the rest are setup, and the
10
+ # examples in one block share a binding:
11
+ #
12
+ # v = Vector2d(2, 3)
13
+ # v.length # => 3.6055..
14
+ #
15
+ # A marker on a line of its own applies to the line above it. A
16
+ # trailing ".." stands in for any digits, and an exception class means
17
+ # the expression is expected to raise. An example spanning several
18
+ # lines, like a class definition, is evaluated as one expression.
19
+ module DocExamples
20
+ ROOT = File.expand_path("../..", __dir__)
21
+ MARKER = /\A(.*?)\s*#\s*=>\s*(.+)\z/
22
+ EXCEPTION = /\A(?:[A-Z]\w*::)*[A-Z]\w*Error\z/
23
+ HASH_ROCKET = /:(\w+)=>/
24
+
25
+ # A single line of an example. Setup lines have no expectation.
26
+ Example = Struct.new(:location, :code, :expected) do
27
+ def expectation? = !expected.nil?
28
+
29
+ # Appends a line to an example that is not a complete expression yet.
30
+ def continue(line, expected)
31
+ self.code = "#{code}\n#{line}"
32
+ self.expected = expected
33
+ end
34
+
35
+ def raises? = expected.match?(EXCEPTION)
36
+
37
+ def exception = Object.const_get(expected)
38
+
39
+ def pattern
40
+ escaped = Regexp.escape(normalize(expected))
41
+ Regexp.new("\\A#{escaped.gsub('\.\.') { '\d*' }}\\z")
42
+ end
43
+
44
+ def inspected(value) = normalize(value.inspect)
45
+
46
+ private
47
+
48
+ # Values are compared with whitespace removed, so the documentation
49
+ # is free to align the markers, and with hashes in the {key: value}
50
+ # form Ruby 3.4 and up print.
51
+ def normalize(inspected)
52
+ inspected.delete(" ").gsub(HASH_ROCKET) { "#{Regexp.last_match(1)}:" }
53
+ end
54
+ end
55
+
56
+ # The examples from a single doc comment or README section.
57
+ Block = Struct.new(:file, :line, :name, :examples)
58
+
59
+ class << self
60
+ def blocks = Comments.blocks + Markdown.blocks
61
+
62
+ # A block built from its code lines, or nil if none of them assert
63
+ # on anything.
64
+ def block(file, line, name, lines)
65
+ examples = examples(file, lines)
66
+ return unless examples.any?(&:expectation?)
67
+
68
+ Block.new(file, line, name, examples)
69
+ end
70
+
71
+ private
72
+
73
+ def examples(file, lines)
74
+ lines.each_with_object([]) do |(body, index), found|
75
+ append(found, "#{file}:#{index + 1}", body)
76
+ end
77
+ end
78
+
79
+ def append(found, location, body)
80
+ expression, expected = body.match(MARKER)&.captures
81
+ if expected && expression.empty?
82
+ found.last.expected = expected
83
+ elsif continues?(found.last)
84
+ found.last.continue(expression || body, expected)
85
+ else
86
+ found << Example.new(location, expression || body, expected)
87
+ end
88
+ end
89
+
90
+ def continues?(example) = example && !complete?(example.code)
91
+
92
+ def complete?(code) = !Ripper.sexp(code).nil?
93
+ end
94
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yard"
4
+
5
+ # The type vocabulary the doc tags are written in.
6
+ #
7
+ # YARD tags are the only place a type is declared, so a union that
8
+ # loses a member in one comment is a silent error. These are the
9
+ # canonical spellings, and the checks below hold every tag to them.
10
+ module DocTags
11
+ ROOT = File.expand_path("../..", __dir__)
12
+
13
+ # Coordinates are the concrete real classes. Numeric is wrong: to_i,
14
+ # to_f and nan? are defined on these four, not on Numeric.
15
+ COORDINATE = %w[Integer Float Rational BigDecimal].freeze
16
+
17
+ # Everything Vector2d.parse accepts, written out once in the
18
+ # +coercible+ macro in lib/vector2d.rb.
19
+ COERCIBLE = %w[Vector2d Array String Hash Integer Float Rational
20
+ BigDecimal ::Vector ::Matrix].freeze
21
+
22
+ # The members of the parse union that are not coordinates. Naming
23
+ # two of them is what marks a parameter as taking the whole union.
24
+ SHAPES = %w[Vector2d Array String Hash ::Vector ::Matrix].freeze
25
+
26
+ # Numeric spans Complex, which is not a coordinate, and misses the
27
+ # methods the coordinate types are used for.
28
+ FORBIDDEN = %w[Numeric].freeze
29
+ TYPE_NAME = /(?:::)?[A-Z]\w*/
30
+ TAG = /\A\s*#\s@/
31
+ DEPRECATED = /\A\s*#\s@deprecated\b/
32
+ CONTINUATION = /\A\s*#\s{2,}\S/
33
+ COMMENT = /\A\s*#(?:\s|\z)/
34
+
35
+ # One tag, with its type list flattened to the names it mentions.
36
+ Tag = Struct.new(:path, :kind, :name, :types) do
37
+ def to_s = [path, "@#{kind}", name].compact.join(" ")
38
+
39
+ def count(union) = (union & types).size
40
+
41
+ def missing(union) = union - types
42
+ end
43
+
44
+ class << self
45
+ def methods
46
+ load_registry
47
+ YARD::Registry.all(:method)
48
+ .select { |m| m.visibility == :public }
49
+ .sort_by(&:path)
50
+ end
51
+
52
+ # Every parameter the method takes, keywords included.
53
+ def parameters(method)
54
+ method.parameters.map { |name, _| name.to_s.delete("*&:") }
55
+ .reject(&:empty?)
56
+ end
57
+
58
+ # The parameters with no @param, counting the ones inside an
59
+ # @overload.
60
+ def undocumented_parameters(method)
61
+ documented = method.tags(:param) +
62
+ method.tags(:overload).flat_map { |o| o.tags(:param) }
63
+ parameters(method) - documented.map(&:name)
64
+ end
65
+
66
+ def tags(method)
67
+ collect(method, method) +
68
+ method.tags(:overload).flat_map { |o| collect(method, o) }
69
+ end
70
+
71
+ # The tags naming more than one coordinate type, which have to
72
+ # name all four. One on its own is a type in its own right, as the
73
+ # Integer #ceil rounds to is.
74
+ def coordinate_tags(method)
75
+ tags(method).select { |tag| tag.count(COORDINATE) > 1 }
76
+ end
77
+
78
+ # The tags taking anything Vector2d.parse accepts, which have to
79
+ # name the whole union.
80
+ def parse_union_tags(method)
81
+ tags(method).select do |tag|
82
+ tag.kind == :param && tag.count(SHAPES) > 1
83
+ end
84
+ end
85
+
86
+ # Methods warning about their own deprecation without saying so in
87
+ # a tag.
88
+ def untagged_deprecations
89
+ load_registry
90
+ YARD::Registry.all(:method).select do |method|
91
+ method.name != :warn_deprecated &&
92
+ method.source.to_s.include?("warn_deprecated") &&
93
+ method.tag(:deprecated).nil?
94
+ end.map(&:path)
95
+ end
96
+
97
+ # Comment lines that come after the tag block of their doc
98
+ # comment. Tags close a comment, and the example extractor reads
99
+ # them as running to the end of it, so prose after them would go
100
+ # missing and type lists before them would be read as code.
101
+ # @deprecated is the exception, and opens a comment.
102
+ def prose_after_tags
103
+ sources.flat_map do |file, lines|
104
+ comment_blocks(lines).flat_map { |block| stray(file, block) }
105
+ end
106
+ end
107
+
108
+ private
109
+
110
+ def comment_blocks(lines)
111
+ lines.each_with_index.chunk { |line, _| line.match?(COMMENT) }
112
+ .select(&:first).map(&:last)
113
+ end
114
+
115
+ def stray(file, block)
116
+ start = block.index { |line, _| line.match?(TAG) && !line.match?(DEPRECATED) }
117
+ return [] unless start
118
+
119
+ block[start..]
120
+ .reject { |line, _| line.match?(TAG) || line.match?(CONTINUATION) }
121
+ .map { |_, index| "#{file}:#{index + 1}" }
122
+ end
123
+
124
+ def collect(method, holder)
125
+ %i[param return].flat_map do |kind|
126
+ holder.tags(kind).map do |tag|
127
+ Tag.new(method.path, kind, tag.name,
128
+ Array(tag.types).join(",").scan(TYPE_NAME).uniq)
129
+ end
130
+ end
131
+ end
132
+
133
+ def sources
134
+ files.map { |path| [path.delete_prefix("#{ROOT}/"), File.readlines(path, chomp: true)] }
135
+ end
136
+
137
+ # lib/vector2d.rb first, so the macros it defines are known by the
138
+ # time the modules using them are parsed.
139
+ def files
140
+ Dir[File.join(ROOT, "lib/**/*.rb")].sort_by { |path| [path.count("/"), path] }
141
+ end
142
+
143
+ def load_registry
144
+ return if @loaded
145
+
146
+ YARD::Registry.clear
147
+ YARD.parse(files, [], YARD::Logger::ERROR)
148
+ @loaded = true
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples "a class preserving method" do |method, *args|
4
+ describe "##{method}" do
5
+ it "returns an instance of the receiver's class" do
6
+ expect(vector.public_send(method, *args))
7
+ .to be_an_instance_of(vector.class)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples "a deprecated method" do |name, replacement|
4
+ around do |example|
5
+ enabled = Warning[:deprecated]
6
+ Warning[:deprecated] = true
7
+ example.run
8
+ Warning[:deprecated] = enabled
9
+ end
10
+
11
+ it "warns that the method is deprecated" do
12
+ expect { vector }
13
+ .to output(/Vector2d##{name} is deprecated/).to_stderr
14
+ end
15
+
16
+ it "points at the replacement" do
17
+ expect { vector }
18
+ .to output(/#{Regexp.escape(replacement)}/).to_stderr
19
+ end
20
+
21
+ it "is silent when deprecation warnings are disabled" do
22
+ Warning[:deprecated] = false
23
+ expect { vector }.not_to output.to_stderr
24
+ end
25
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples "a parsed vector" do |x, y|
4
+ it "receives the x property" do
5
+ expect(subject.x).to eq(x)
6
+ end
7
+
8
+ it "receives the y property" do
9
+ expect(subject.y).to eq(y)
10
+ end
11
+ end
data/vector2d.gemspec CHANGED
@@ -20,10 +20,11 @@ Gem::Specification.new do |s|
20
20
  .map { |f| File.basename(f) }
21
21
  s.require_paths = ["lib"]
22
22
 
23
- s.required_ruby_version = ">= 3.2.0"
23
+ s.required_ruby_version = ">= 3.4.0"
24
24
 
25
25
  s.metadata = {
26
26
  "bug_tracker_uri" => "https://github.com/elektronaut/vector2d/issues",
27
+ "changelog_uri" => "https://github.com/elektronaut/vector2d/blob/main/CHANGELOG.md",
27
28
  "documentation_uri" => "https://www.rubydoc.info/gems/vector2d",
28
29
  "rubygems_mfa_required" => "true",
29
30
  "source_code_uri" => "https://github.com/elektronaut/vector2d"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vector2d
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Inge Jørgensen
@@ -23,7 +23,7 @@ files:
23
23
  - ".release-please-manifest.json"
24
24
  - ".rubocop.yml"
25
25
  - ".tool-versions"
26
- - ".travis.yml"
26
+ - ".yardopts"
27
27
  - CHANGELOG.md
28
28
  - CODE_OF_CONDUCT.md
29
29
  - CONTRIBUTING.md
@@ -32,27 +32,56 @@ files:
32
32
  - LICENSE
33
33
  - README.md
34
34
  - Rakefile
35
+ - benchmark/vector_comparison.rb
35
36
  - lib/vector2d.rb
36
- - lib/vector2d/calculations.rb
37
- - lib/vector2d/coercions.rb
38
- - lib/vector2d/fitting.rb
39
- - lib/vector2d/properties.rb
40
- - lib/vector2d/transformations.rb
37
+ - lib/vector2d/angles.rb
38
+ - lib/vector2d/arithmetic.rb
39
+ - lib/vector2d/comparison.rb
40
+ - lib/vector2d/componentwise.rb
41
+ - lib/vector2d/constructors.rb
42
+ - lib/vector2d/conversions.rb
43
+ - lib/vector2d/coordinates.rb
44
+ - lib/vector2d/deprecation.rb
45
+ - lib/vector2d/dimensions.rb
46
+ - lib/vector2d/interpolation.rb
47
+ - lib/vector2d/lengths.rb
48
+ - lib/vector2d/matrix_interop.rb
49
+ - lib/vector2d/parsing.rb
50
+ - lib/vector2d/projection.rb
41
51
  - lib/vector2d/version.rb
42
52
  - release-please-config.json
43
- - spec/lib/vector2d/calculations_spec.rb
44
- - spec/lib/vector2d/coercions_spec.rb
45
- - spec/lib/vector2d/fitting_spec.rb
46
- - spec/lib/vector2d/properties_spec.rb
47
- - spec/lib/vector2d/transformations_spec.rb
53
+ - spec/lib/vector2d/angles_spec.rb
54
+ - spec/lib/vector2d/arithmetic_spec.rb
55
+ - spec/lib/vector2d/comparison_spec.rb
56
+ - spec/lib/vector2d/componentwise_spec.rb
57
+ - spec/lib/vector2d/constructors_spec.rb
58
+ - spec/lib/vector2d/conversions_spec.rb
59
+ - spec/lib/vector2d/dimensions_spec.rb
60
+ - spec/lib/vector2d/interpolation_spec.rb
61
+ - spec/lib/vector2d/lengths_spec.rb
62
+ - spec/lib/vector2d/matrix_interop_spec.rb
63
+ - spec/lib/vector2d/parsing_spec.rb
64
+ - spec/lib/vector2d/projection_spec.rb
65
+ - spec/lib/vector2d_documentation_spec.rb
66
+ - spec/lib/vector2d_immutability_spec.rb
48
67
  - spec/lib/vector2d_spec.rb
68
+ - spec/lib/vector2d_subclassing_spec.rb
69
+ - spec/lib/vector2d_tags_spec.rb
49
70
  - spec/spec_helper.rb
71
+ - spec/support/doc_examples.rb
72
+ - spec/support/doc_examples/comments.rb
73
+ - spec/support/doc_examples/markdown.rb
74
+ - spec/support/doc_tags.rb
75
+ - spec/support/shared_examples/class_preserving_method.rb
76
+ - spec/support/shared_examples/deprecated_method.rb
77
+ - spec/support/shared_examples/parsed_vector.rb
50
78
  - vector2d.gemspec
51
79
  homepage: https://github.com/elektronaut/vector2d
52
80
  licenses:
53
81
  - MIT
54
82
  metadata:
55
83
  bug_tracker_uri: https://github.com/elektronaut/vector2d/issues
84
+ changelog_uri: https://github.com/elektronaut/vector2d/blob/main/CHANGELOG.md
56
85
  documentation_uri: https://www.rubydoc.info/gems/vector2d
57
86
  rubygems_mfa_required: 'true'
58
87
  source_code_uri: https://github.com/elektronaut/vector2d
@@ -63,7 +92,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
63
92
  requirements:
64
93
  - - ">="
65
94
  - !ruby/object:Gem::Version
66
- version: 3.2.0
95
+ version: 3.4.0
67
96
  required_rubygems_version: !ruby/object:Gem::Requirement
68
97
  requirements:
69
98
  - - ">="
data/.travis.yml DELETED
@@ -1,10 +0,0 @@
1
- language: ruby
2
- sudo: false
3
- cache: bundler
4
- dist: bionic
5
- rvm:
6
- - 2.4.5
7
- - 2.6.3
8
- - 2.7.1
9
- script:
10
- - bundle exec rspec spec
@@ -1,141 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Vector2d
4
- module Calculations
5
- module ClassMethods
6
- # Calculates cross product of two vectors.
7
- #
8
- # v1 = Vector2d(2, 1)
9
- # v2 = Vector2d(2, 3)
10
- # Vector2d.cross_product(v1, v2) # => 4
11
- #
12
- def cross_product(vector1, vector2)
13
- (vector1.x * vector2.y) - (vector1.y * vector2.x)
14
- end
15
-
16
- # Calculates dot product of two vectors.
17
- #
18
- # v1 = Vector2d(2, 1)
19
- # v2 = Vector2d(2, 3)
20
- # Vector2d.dot_product(v1, v2) # => 10
21
- #
22
- def dot_product(vector1, vector2)
23
- (vector1.x * vector2.x) + (vector1.y * vector2.y)
24
- end
25
-
26
- # Calculates angle between two vectors in radians.
27
- #
28
- # v1 = Vector2d(2, 3)
29
- # v2 = Vector2d(4, 5)
30
- # Vector2d.angle_between(v1, v2) # => 0.0867..
31
- #
32
- def angle_between(vector1, vector2)
33
- one = vector1.normalized? ? vector1 : vector1.normalize
34
- two = vector2.normalized? ? vector2 : vector2.normalize
35
- Math.acos(dot_product(one, two))
36
- end
37
- end
38
-
39
- # Multiplies vectors.
40
- #
41
- # Vector2d(1, 2) * Vector2d(2, 3) # => Vector2d(2, 6)
42
- # Vector2d(1, 2) * 2 # => Vector2d(2, 4)
43
- #
44
- def *(other)
45
- calculate_each(:*, other)
46
- end
47
-
48
- # Divides vectors.
49
- #
50
- # Vector2d(4, 2) / Vector2d(2, 1) # => Vector2d(2, 2)
51
- # Vector2d(4, 2) / 2 # => Vector2d(2, 1)
52
- #
53
- def /(other)
54
- calculate_each(:/, other)
55
- end
56
-
57
- # Adds vectors.
58
- #
59
- # Vector2d(1, 2) + Vector2d(2, 3) # => Vector2d(3, 5)
60
- # Vector2d(1, 2) + 2 # => Vector2d(3, 4)
61
- #
62
- def +(other)
63
- calculate_each(:+, other)
64
- end
65
-
66
- # Subtracts vectors.
67
- #
68
- # Vector2d(2, 3) - Vector2d(2, 1) # => Vector2d(0, 2)
69
- # Vector2d(4, 3) - 1 # => Vector2d(3, 2)
70
- #
71
- def -(other)
72
- calculate_each(:-, other)
73
- end
74
-
75
- # Calculates the distance between two vectors.
76
- #
77
- # v1 = Vector2d(2, 3)
78
- # v2 = Vector2d(5, 6)
79
- # v1.distance(v2) # => 1.4142..
80
- #
81
- def distance(other)
82
- Math.sqrt(squared_distance(other))
83
- end
84
-
85
- # Calculate squared distance between vectors.
86
- #
87
- # v1 = Vector2d(2, 3)
88
- # v2 = Vector2d(5, 6)
89
- # v1.distance_squared(v2) # => 18
90
- #
91
- def squared_distance(other)
92
- v, = coerce(other)
93
- dx = v.x - x
94
- dy = v.y - y
95
- (dx * dx) + (dy * dy)
96
- end
97
-
98
- # Dot product of this vector and another vector.
99
- #
100
- # v1 = Vector2d(2, 1)
101
- # v2 = Vector2d(2, 3)
102
- # v1.dot_product(v2) # => 10
103
- #
104
- def dot_product(other)
105
- v, = coerce(other)
106
- self.class.dot_product(self, v)
107
- end
108
-
109
- # Cross product of this vector and another vector.
110
- #
111
- # v1 = Vector2d(2, 1)
112
- # v2 = Vector2d(2, 3)
113
- # v1.cross_product(v2) # => 4
114
- #
115
- def cross_product(other)
116
- v, = coerce(other)
117
- self.class.cross_product(self, v)
118
- end
119
-
120
- # Angle in radians between this vector and another vector.
121
- #
122
- # v1 = Vector2d(2, 3)
123
- # v2 = Vector2d(4, 5)
124
- # v1.angle_between(v2) # => 0.0867..
125
- #
126
- def angle_between(other)
127
- v, = coerce(other)
128
- self.class.angle_between(self, v)
129
- end
130
-
131
- private
132
-
133
- def calculate_each(method, other)
134
- v, = coerce(other)
135
- self.class.new(
136
- x.send(method, v.x),
137
- y.send(method, v.y)
138
- )
139
- end
140
- end
141
- end