Class: Sevgi::Geometry::Point

Inherits:
Data
  • Object
show all
Includes:
Comparable
Defined in:
lib/sevgi/geometry/point.rb,
lib/sevgi/geometry/point.rb,
lib/sevgi/geometry/equation.rb

Overview

Immutable point in SVG/screen coordinates.

Use Point[x, y] to create a point from two coordinates. Public geometry methods that expect a point also accept [x, y]; explicit Point values are most useful when a result will be transformed, compared, or reused.

Examples:

Measure and rotate a point in screen coordinates

point = Sevgi::Geometry::Point[3, 4]
Sevgi::Geometry::Point.length(Sevgi::Geometry::Origin, point) # => 5.0
point.rotate(90).approx.deconstruct # => [-4.0, 3.0]

Compare positions in screen coordinates

upper = Sevgi::Geometry::Point[4, 2]
lower = Sevgi::Geometry::Point[4, 8]
upper.above?(lower) # => true
upper.left?([6, 2]) # => true

See Also:

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(x:, y:) ⇒ void

Creates a point.

Parameters:

  • x (Numeric)

    x coordinate

  • y (Numeric)

    y coordinate

Raises:



201
# File 'lib/sevgi/geometry/point.rb', line 201

def initialize(x:, y:) = super(x: Real[:x, x], y: Real[:y, y])

Instance Attribute Details

#xFloat (readonly)

Returns x coordinate.

Returns:

  • (Float)

    x coordinate



150
151
152
# File 'lib/sevgi/geometry/point.rb', line 150

def x
  @x
end

#yFloat (readonly)

Returns y coordinate.

Returns:

  • (Float)

    y coordinate



150
151
152
# File 'lib/sevgi/geometry/point.rb', line 150

def y
  @y
end

Class Method Details

.[](x, y) ⇒ Sevgi::Geometry::Point

Creates a point from two coordinates.

Examples:

Create a point with mathematical notation

Sevgi::Geometry::Point[3, 5]

Parameters:

  • x (Numeric)

    x coordinate

  • y (Numeric)

    y coordinate

Returns:

Raises:



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/sevgi/geometry/point.rb', line 150

Point = Data.define(:x, :y) do
  include Comparable
  include Affinity

  # @!attribute [r] x
  #   @return [Float] x coordinate
  # @!attribute [r] y
  #   @return [Float] y coordinate

  # Returns the screen-space angle from one point to another.
  # @param starting [Sevgi::Geometry::Point, Array<Numeric>] start point
  # @param ending [Sevgi::Geometry::Point, Array<Numeric>] end point
  # @return [Float] clockwise angle in degrees
  # @raise [Sevgi::Geometry::Error] when either point cannot be coerced
  def self.angle(starting, ending)
    starting, ending = Tuples[Point, starting, ending]
    F.atan2(ending.y - starting.y, ending.x - starting.x)
  end

  # Compares two points with optional numeric precision.
  # @param this [Sevgi::Geometry::Point, Array<Numeric>] first point
  # @param that [Sevgi::Geometry::Point, Array<Numeric>] second point
  # @param precision [Integer, nil] decimal precision, or nil for the current function default
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when either point cannot be coerced
  def self.eq?(this, that, precision: nil)
    this, that = Tuples[self, this, that]
    F.eq?(this.x, that.x, precision:) && F.eq?(this.y, that.y, precision:)
  end

  # Returns the Euclidean distance between two points.
  # @param starting [Sevgi::Geometry::Point, Array<Numeric>] start point
  # @param ending [Sevgi::Geometry::Point, Array<Numeric>] end point
  # @return [Float]
  # @raise [Sevgi::Geometry::Error] when either point cannot be coerced
  def self.length(starting, ending)
    starting, ending = Tuples[Point, starting, ending]
    ::Math.sqrt(((starting.y - ending.y) ** 2) + ((starting.x - ending.x) ** 2))
  end

  # Returns the origin point.
  # @return [Sevgi::Geometry::Point]
  def self.origin
    new(x: 0.0, y: 0.0)
  end

  # Creates a point.
  # @param x [Numeric] x coordinate
  # @param y [Numeric] y coordinate
  # @return [void]
  # @raise [Sevgi::Geometry::Error] when a coordinate is not a finite Numeric
  def initialize(x:, y:) = super(x: Real[:x, x], y: Real[:y, y])

  # Compares points by x, then y.
  # @param other [Object] point or two-item coordinate array to compare
  # @return [Integer, nil] comparison result, or nil when other is not a valid point value
  def <=>(other)
    deconstruct <=> Tuple[Point, other].deconstruct
  rescue Error
    nil
  end

  # Reports whether this point is at or above another point in screen coordinates.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def above?(other) = y <= Tuple[Point, other].y

  # Returns a point rounded to precision.
  # @param precision [Integer, nil] decimal precision, or nil for the current function default
  # @return [Sevgi::Geometry::Point]
  def approx(precision = nil) = with(x: F.approx(x, precision), y: F.approx(y, precision))

  # Reports whether this point is at or below another point in screen coordinates.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def below?(other) = y >= Tuple[Point, other].y

  # Compares this point with optional numeric precision.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @param precision [Integer, nil] decimal precision, or nil for the current function default
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def eq?(other, precision: nil) = self.class.eq?(self, other, precision:)

  # Reports strict point equality.
  # @param other [Object] object to compare
  # @return [Boolean]
  def eql?(other) = self.class == other.class && deconstruct == other.deconstruct

  # Returns a hash compatible with strict equality.
  # @return [Integer]
  def hash = [self.class, *deconstruct].hash

  # Reports whether this point is at or left of another point.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def left?(other) = x <= Tuple[Point, other].x

  # Reports whether this point is at or right of another point.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def right?(other) = x >= Tuple[Point, other].x

  # Formats point coordinates for SVG coordinate-list attributes.
  # @return [String]
  def to_cs = "#{F.approx(x)},#{F.approx(y)}"

  # Formats the point for display.
  # @return [String]
  def to_s = "(#{to_cs})"

  alias_method :==, :eql?
end

.angle(starting, ending) ⇒ Float

Returns the screen-space angle from one point to another.

Parameters:

Returns:

  • (Float)

    clockwise angle in degrees

Raises:



164
165
166
167
# File 'lib/sevgi/geometry/point.rb', line 164

def self.angle(starting, ending)
  starting, ending = Tuples[Point, starting, ending]
  F.atan2(ending.y - starting.y, ending.x - starting.x)
end

.eq?(this, that, precision: nil) ⇒ Boolean

Compares two points with optional numeric precision.

Parameters:

  • this (Sevgi::Geometry::Point, Array<Numeric>)

    first point

  • that (Sevgi::Geometry::Point, Array<Numeric>)

    second point

  • precision (Integer, nil) (defaults to: nil)

    decimal precision, or nil for the current function default

Returns:

  • (Boolean)

Raises:



175
176
177
178
# File 'lib/sevgi/geometry/point.rb', line 175

def self.eq?(this, that, precision: nil)
  this, that = Tuples[self, this, that]
  F.eq?(this.x, that.x, precision:) && F.eq?(this.y, that.y, precision:)
end

.length(starting, ending) ⇒ Float

Returns the Euclidean distance between two points.

Parameters:

Returns:

  • (Float)

Raises:



185
186
187
188
# File 'lib/sevgi/geometry/point.rb', line 185

def self.length(starting, ending)
  starting, ending = Tuples[Point, starting, ending]
  ::Math.sqrt(((starting.y - ending.y) ** 2) + ((starting.x - ending.x) ** 2))
end

.originSevgi::Geometry::Point

Returns the origin point.



192
193
194
# File 'lib/sevgi/geometry/point.rb', line 192

def self.origin
  new(x: 0.0, y: 0.0)
end

Instance Method Details

#<=>(other) ⇒ Integer?

Compares points by x, then y.

Parameters:

  • other (Object)

    point or two-item coordinate array to compare

Returns:

  • (Integer, nil)

    comparison result, or nil when other is not a valid point value



206
207
208
209
210
# File 'lib/sevgi/geometry/point.rb', line 206

def <=>(other)
  deconstruct <=> Tuple[Point, other].deconstruct
rescue Error
  nil
end

#above?(other) ⇒ Boolean

Reports whether this point is at or above another point in screen coordinates.

Parameters:

Returns:

  • (Boolean)

Raises:



216
# File 'lib/sevgi/geometry/point.rb', line 216

def above?(other) = y <= Tuple[Point, other].y

#approx(precision = nil) ⇒ Sevgi::Geometry::Point

Returns a point rounded to precision.

Parameters:

  • precision (Integer, nil) (defaults to: nil)

    decimal precision, or nil for the current function default

Returns:



221
# File 'lib/sevgi/geometry/point.rb', line 221

def approx(precision = nil) = with(x: F.approx(x, precision), y: F.approx(y, precision))

#below?(other) ⇒ Boolean

Reports whether this point is at or below another point in screen coordinates.

Parameters:

Returns:

  • (Boolean)

Raises:



227
# File 'lib/sevgi/geometry/point.rb', line 227

def below?(other) = y >= Tuple[Point, other].y

#eq?(other, precision: nil) ⇒ Boolean

Compares this point with optional numeric precision.

Parameters:

  • other (Sevgi::Geometry::Point, Array<Numeric>)

    point to compare

  • precision (Integer, nil) (defaults to: nil)

    decimal precision, or nil for the current function default

Returns:

  • (Boolean)

Raises:



234
# File 'lib/sevgi/geometry/point.rb', line 234

def eq?(other, precision: nil) = self.class.eq?(self, other, precision:)

#eql?(other) ⇒ Boolean Also known as: ==

Reports strict point equality.

Parameters:

  • other (Object)

    object to compare

Returns:

  • (Boolean)


239
# File 'lib/sevgi/geometry/point.rb', line 239

def eql?(other) = self.class == other.class && deconstruct == other.deconstruct

#equation(angle) ⇒ Sevgi::Geometry::Equation::Linear

Returns the linear equation passing through this point at an angle.

Examples:

Create axis-aligned equations through a point

point = Sevgi::Geometry::Point[4, 3]
point.equation(0).y(20)  # => 3.0
point.equation(90).x(20) # => 4.0

Parameters:

  • angle (Numeric)

    clockwise angle in degrees

Returns:



120
121
122
123
124
125
# File 'lib/sevgi/geometry/equation.rb', line 120

def equation(angle)
  return Equation.horizontal(y) if F.zero?(angle % 180.0)
  return Equation.vertical(x) if F.zero?(angle % 90.0)

  Equation.diagonal(slope: (slope = F.tan(angle)), intercept: y - (slope * x))
end

#hashInteger

Returns a hash compatible with strict equality.

Returns:

  • (Integer)


243
# File 'lib/sevgi/geometry/point.rb', line 243

def hash = [self.class, *deconstruct].hash

#left?(other) ⇒ Boolean

Reports whether this point is at or left of another point.

Parameters:

Returns:

  • (Boolean)

Raises:



249
# File 'lib/sevgi/geometry/point.rb', line 249

def left?(other) = x <= Tuple[Point, other].x

#reflect(x: true, y: true) ⇒ Sevgi::Geometry::Point

Returns a point reflected across the selected axes.

Parameters:

  • x (Boolean) (defaults to: true)

    reflect across the x-axis

  • y (Boolean) (defaults to: true)

    reflect across the y-axis

Returns:

Raises:



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/sevgi/geometry/point.rb', line 150

Point = Data.define(:x, :y) do
  include Comparable
  include Affinity

  # @!attribute [r] x
  #   @return [Float] x coordinate
  # @!attribute [r] y
  #   @return [Float] y coordinate

  # Returns the screen-space angle from one point to another.
  # @param starting [Sevgi::Geometry::Point, Array<Numeric>] start point
  # @param ending [Sevgi::Geometry::Point, Array<Numeric>] end point
  # @return [Float] clockwise angle in degrees
  # @raise [Sevgi::Geometry::Error] when either point cannot be coerced
  def self.angle(starting, ending)
    starting, ending = Tuples[Point, starting, ending]
    F.atan2(ending.y - starting.y, ending.x - starting.x)
  end

  # Compares two points with optional numeric precision.
  # @param this [Sevgi::Geometry::Point, Array<Numeric>] first point
  # @param that [Sevgi::Geometry::Point, Array<Numeric>] second point
  # @param precision [Integer, nil] decimal precision, or nil for the current function default
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when either point cannot be coerced
  def self.eq?(this, that, precision: nil)
    this, that = Tuples[self, this, that]
    F.eq?(this.x, that.x, precision:) && F.eq?(this.y, that.y, precision:)
  end

  # Returns the Euclidean distance between two points.
  # @param starting [Sevgi::Geometry::Point, Array<Numeric>] start point
  # @param ending [Sevgi::Geometry::Point, Array<Numeric>] end point
  # @return [Float]
  # @raise [Sevgi::Geometry::Error] when either point cannot be coerced
  def self.length(starting, ending)
    starting, ending = Tuples[Point, starting, ending]
    ::Math.sqrt(((starting.y - ending.y) ** 2) + ((starting.x - ending.x) ** 2))
  end

  # Returns the origin point.
  # @return [Sevgi::Geometry::Point]
  def self.origin
    new(x: 0.0, y: 0.0)
  end

  # Creates a point.
  # @param x [Numeric] x coordinate
  # @param y [Numeric] y coordinate
  # @return [void]
  # @raise [Sevgi::Geometry::Error] when a coordinate is not a finite Numeric
  def initialize(x:, y:) = super(x: Real[:x, x], y: Real[:y, y])

  # Compares points by x, then y.
  # @param other [Object] point or two-item coordinate array to compare
  # @return [Integer, nil] comparison result, or nil when other is not a valid point value
  def <=>(other)
    deconstruct <=> Tuple[Point, other].deconstruct
  rescue Error
    nil
  end

  # Reports whether this point is at or above another point in screen coordinates.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def above?(other) = y <= Tuple[Point, other].y

  # Returns a point rounded to precision.
  # @param precision [Integer, nil] decimal precision, or nil for the current function default
  # @return [Sevgi::Geometry::Point]
  def approx(precision = nil) = with(x: F.approx(x, precision), y: F.approx(y, precision))

  # Reports whether this point is at or below another point in screen coordinates.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def below?(other) = y >= Tuple[Point, other].y

  # Compares this point with optional numeric precision.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @param precision [Integer, nil] decimal precision, or nil for the current function default
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def eq?(other, precision: nil) = self.class.eq?(self, other, precision:)

  # Reports strict point equality.
  # @param other [Object] object to compare
  # @return [Boolean]
  def eql?(other) = self.class == other.class && deconstruct == other.deconstruct

  # Returns a hash compatible with strict equality.
  # @return [Integer]
  def hash = [self.class, *deconstruct].hash

  # Reports whether this point is at or left of another point.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def left?(other) = x <= Tuple[Point, other].x

  # Reports whether this point is at or right of another point.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def right?(other) = x >= Tuple[Point, other].x

  # Formats point coordinates for SVG coordinate-list attributes.
  # @return [String]
  def to_cs = "#{F.approx(x)},#{F.approx(y)}"

  # Formats the point for display.
  # @return [String]
  def to_s = "(#{to_cs})"

  alias_method :==, :eql?
end

#right?(other) ⇒ Boolean

Reports whether this point is at or right of another point.

Parameters:

Returns:

  • (Boolean)

Raises:



255
# File 'lib/sevgi/geometry/point.rb', line 255

def right?(other) = x >= Tuple[Point, other].x

#rotate(a) ⇒ Sevgi::Geometry::Point

Returns a point rotated around the origin.

Parameters:

  • a (Numeric)

    clockwise angle in degrees

Returns:

Raises:



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/sevgi/geometry/point.rb', line 150

Point = Data.define(:x, :y) do
  include Comparable
  include Affinity

  # @!attribute [r] x
  #   @return [Float] x coordinate
  # @!attribute [r] y
  #   @return [Float] y coordinate

  # Returns the screen-space angle from one point to another.
  # @param starting [Sevgi::Geometry::Point, Array<Numeric>] start point
  # @param ending [Sevgi::Geometry::Point, Array<Numeric>] end point
  # @return [Float] clockwise angle in degrees
  # @raise [Sevgi::Geometry::Error] when either point cannot be coerced
  def self.angle(starting, ending)
    starting, ending = Tuples[Point, starting, ending]
    F.atan2(ending.y - starting.y, ending.x - starting.x)
  end

  # Compares two points with optional numeric precision.
  # @param this [Sevgi::Geometry::Point, Array<Numeric>] first point
  # @param that [Sevgi::Geometry::Point, Array<Numeric>] second point
  # @param precision [Integer, nil] decimal precision, or nil for the current function default
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when either point cannot be coerced
  def self.eq?(this, that, precision: nil)
    this, that = Tuples[self, this, that]
    F.eq?(this.x, that.x, precision:) && F.eq?(this.y, that.y, precision:)
  end

  # Returns the Euclidean distance between two points.
  # @param starting [Sevgi::Geometry::Point, Array<Numeric>] start point
  # @param ending [Sevgi::Geometry::Point, Array<Numeric>] end point
  # @return [Float]
  # @raise [Sevgi::Geometry::Error] when either point cannot be coerced
  def self.length(starting, ending)
    starting, ending = Tuples[Point, starting, ending]
    ::Math.sqrt(((starting.y - ending.y) ** 2) + ((starting.x - ending.x) ** 2))
  end

  # Returns the origin point.
  # @return [Sevgi::Geometry::Point]
  def self.origin
    new(x: 0.0, y: 0.0)
  end

  # Creates a point.
  # @param x [Numeric] x coordinate
  # @param y [Numeric] y coordinate
  # @return [void]
  # @raise [Sevgi::Geometry::Error] when a coordinate is not a finite Numeric
  def initialize(x:, y:) = super(x: Real[:x, x], y: Real[:y, y])

  # Compares points by x, then y.
  # @param other [Object] point or two-item coordinate array to compare
  # @return [Integer, nil] comparison result, or nil when other is not a valid point value
  def <=>(other)
    deconstruct <=> Tuple[Point, other].deconstruct
  rescue Error
    nil
  end

  # Reports whether this point is at or above another point in screen coordinates.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def above?(other) = y <= Tuple[Point, other].y

  # Returns a point rounded to precision.
  # @param precision [Integer, nil] decimal precision, or nil for the current function default
  # @return [Sevgi::Geometry::Point]
  def approx(precision = nil) = with(x: F.approx(x, precision), y: F.approx(y, precision))

  # Reports whether this point is at or below another point in screen coordinates.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def below?(other) = y >= Tuple[Point, other].y

  # Compares this point with optional numeric precision.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @param precision [Integer, nil] decimal precision, or nil for the current function default
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def eq?(other, precision: nil) = self.class.eq?(self, other, precision:)

  # Reports strict point equality.
  # @param other [Object] object to compare
  # @return [Boolean]
  def eql?(other) = self.class == other.class && deconstruct == other.deconstruct

  # Returns a hash compatible with strict equality.
  # @return [Integer]
  def hash = [self.class, *deconstruct].hash

  # Reports whether this point is at or left of another point.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def left?(other) = x <= Tuple[Point, other].x

  # Reports whether this point is at or right of another point.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def right?(other) = x >= Tuple[Point, other].x

  # Formats point coordinates for SVG coordinate-list attributes.
  # @return [String]
  def to_cs = "#{F.approx(x)},#{F.approx(y)}"

  # Formats the point for display.
  # @return [String]
  def to_s = "(#{to_cs})"

  alias_method :==, :eql?
end

#scale(sx, sy = Undefined) ⇒ Sevgi::Geometry::Point

Returns a point scaled from the origin.

Parameters:

  • sx (Numeric)

    x scale factor

  • sy (Numeric, Sevgi::Undefined) (defaults to: Undefined)

    y scale factor, defaulting to sx

Returns:

Raises:



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/sevgi/geometry/point.rb', line 150

Point = Data.define(:x, :y) do
  include Comparable
  include Affinity

  # @!attribute [r] x
  #   @return [Float] x coordinate
  # @!attribute [r] y
  #   @return [Float] y coordinate

  # Returns the screen-space angle from one point to another.
  # @param starting [Sevgi::Geometry::Point, Array<Numeric>] start point
  # @param ending [Sevgi::Geometry::Point, Array<Numeric>] end point
  # @return [Float] clockwise angle in degrees
  # @raise [Sevgi::Geometry::Error] when either point cannot be coerced
  def self.angle(starting, ending)
    starting, ending = Tuples[Point, starting, ending]
    F.atan2(ending.y - starting.y, ending.x - starting.x)
  end

  # Compares two points with optional numeric precision.
  # @param this [Sevgi::Geometry::Point, Array<Numeric>] first point
  # @param that [Sevgi::Geometry::Point, Array<Numeric>] second point
  # @param precision [Integer, nil] decimal precision, or nil for the current function default
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when either point cannot be coerced
  def self.eq?(this, that, precision: nil)
    this, that = Tuples[self, this, that]
    F.eq?(this.x, that.x, precision:) && F.eq?(this.y, that.y, precision:)
  end

  # Returns the Euclidean distance between two points.
  # @param starting [Sevgi::Geometry::Point, Array<Numeric>] start point
  # @param ending [Sevgi::Geometry::Point, Array<Numeric>] end point
  # @return [Float]
  # @raise [Sevgi::Geometry::Error] when either point cannot be coerced
  def self.length(starting, ending)
    starting, ending = Tuples[Point, starting, ending]
    ::Math.sqrt(((starting.y - ending.y) ** 2) + ((starting.x - ending.x) ** 2))
  end

  # Returns the origin point.
  # @return [Sevgi::Geometry::Point]
  def self.origin
    new(x: 0.0, y: 0.0)
  end

  # Creates a point.
  # @param x [Numeric] x coordinate
  # @param y [Numeric] y coordinate
  # @return [void]
  # @raise [Sevgi::Geometry::Error] when a coordinate is not a finite Numeric
  def initialize(x:, y:) = super(x: Real[:x, x], y: Real[:y, y])

  # Compares points by x, then y.
  # @param other [Object] point or two-item coordinate array to compare
  # @return [Integer, nil] comparison result, or nil when other is not a valid point value
  def <=>(other)
    deconstruct <=> Tuple[Point, other].deconstruct
  rescue Error
    nil
  end

  # Reports whether this point is at or above another point in screen coordinates.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def above?(other) = y <= Tuple[Point, other].y

  # Returns a point rounded to precision.
  # @param precision [Integer, nil] decimal precision, or nil for the current function default
  # @return [Sevgi::Geometry::Point]
  def approx(precision = nil) = with(x: F.approx(x, precision), y: F.approx(y, precision))

  # Reports whether this point is at or below another point in screen coordinates.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def below?(other) = y >= Tuple[Point, other].y

  # Compares this point with optional numeric precision.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @param precision [Integer, nil] decimal precision, or nil for the current function default
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def eq?(other, precision: nil) = self.class.eq?(self, other, precision:)

  # Reports strict point equality.
  # @param other [Object] object to compare
  # @return [Boolean]
  def eql?(other) = self.class == other.class && deconstruct == other.deconstruct

  # Returns a hash compatible with strict equality.
  # @return [Integer]
  def hash = [self.class, *deconstruct].hash

  # Reports whether this point is at or left of another point.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def left?(other) = x <= Tuple[Point, other].x

  # Reports whether this point is at or right of another point.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def right?(other) = x >= Tuple[Point, other].x

  # Formats point coordinates for SVG coordinate-list attributes.
  # @return [String]
  def to_cs = "#{F.approx(x)},#{F.approx(y)}"

  # Formats the point for display.
  # @return [String]
  def to_s = "(#{to_cs})"

  alias_method :==, :eql?
end

#skew(ax, ay = Undefined) ⇒ Sevgi::Geometry::Point

Returns a point skewed from the origin.

Parameters:

  • ax (Numeric)

    x-axis skew angle in degrees

  • ay (Numeric, Sevgi::Undefined) (defaults to: Undefined)

    y-axis skew angle in degrees, defaulting to ax

Returns:

Raises:



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/sevgi/geometry/point.rb', line 150

Point = Data.define(:x, :y) do
  include Comparable
  include Affinity

  # @!attribute [r] x
  #   @return [Float] x coordinate
  # @!attribute [r] y
  #   @return [Float] y coordinate

  # Returns the screen-space angle from one point to another.
  # @param starting [Sevgi::Geometry::Point, Array<Numeric>] start point
  # @param ending [Sevgi::Geometry::Point, Array<Numeric>] end point
  # @return [Float] clockwise angle in degrees
  # @raise [Sevgi::Geometry::Error] when either point cannot be coerced
  def self.angle(starting, ending)
    starting, ending = Tuples[Point, starting, ending]
    F.atan2(ending.y - starting.y, ending.x - starting.x)
  end

  # Compares two points with optional numeric precision.
  # @param this [Sevgi::Geometry::Point, Array<Numeric>] first point
  # @param that [Sevgi::Geometry::Point, Array<Numeric>] second point
  # @param precision [Integer, nil] decimal precision, or nil for the current function default
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when either point cannot be coerced
  def self.eq?(this, that, precision: nil)
    this, that = Tuples[self, this, that]
    F.eq?(this.x, that.x, precision:) && F.eq?(this.y, that.y, precision:)
  end

  # Returns the Euclidean distance between two points.
  # @param starting [Sevgi::Geometry::Point, Array<Numeric>] start point
  # @param ending [Sevgi::Geometry::Point, Array<Numeric>] end point
  # @return [Float]
  # @raise [Sevgi::Geometry::Error] when either point cannot be coerced
  def self.length(starting, ending)
    starting, ending = Tuples[Point, starting, ending]
    ::Math.sqrt(((starting.y - ending.y) ** 2) + ((starting.x - ending.x) ** 2))
  end

  # Returns the origin point.
  # @return [Sevgi::Geometry::Point]
  def self.origin
    new(x: 0.0, y: 0.0)
  end

  # Creates a point.
  # @param x [Numeric] x coordinate
  # @param y [Numeric] y coordinate
  # @return [void]
  # @raise [Sevgi::Geometry::Error] when a coordinate is not a finite Numeric
  def initialize(x:, y:) = super(x: Real[:x, x], y: Real[:y, y])

  # Compares points by x, then y.
  # @param other [Object] point or two-item coordinate array to compare
  # @return [Integer, nil] comparison result, or nil when other is not a valid point value
  def <=>(other)
    deconstruct <=> Tuple[Point, other].deconstruct
  rescue Error
    nil
  end

  # Reports whether this point is at or above another point in screen coordinates.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def above?(other) = y <= Tuple[Point, other].y

  # Returns a point rounded to precision.
  # @param precision [Integer, nil] decimal precision, or nil for the current function default
  # @return [Sevgi::Geometry::Point]
  def approx(precision = nil) = with(x: F.approx(x, precision), y: F.approx(y, precision))

  # Reports whether this point is at or below another point in screen coordinates.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def below?(other) = y >= Tuple[Point, other].y

  # Compares this point with optional numeric precision.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @param precision [Integer, nil] decimal precision, or nil for the current function default
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def eq?(other, precision: nil) = self.class.eq?(self, other, precision:)

  # Reports strict point equality.
  # @param other [Object] object to compare
  # @return [Boolean]
  def eql?(other) = self.class == other.class && deconstruct == other.deconstruct

  # Returns a hash compatible with strict equality.
  # @return [Integer]
  def hash = [self.class, *deconstruct].hash

  # Reports whether this point is at or left of another point.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def left?(other) = x <= Tuple[Point, other].x

  # Reports whether this point is at or right of another point.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def right?(other) = x >= Tuple[Point, other].x

  # Formats point coordinates for SVG coordinate-list attributes.
  # @return [String]
  def to_cs = "#{F.approx(x)},#{F.approx(y)}"

  # Formats the point for display.
  # @return [String]
  def to_s = "(#{to_cs})"

  alias_method :==, :eql?
end

#skew_x(a) ⇒ Sevgi::Geometry::Point

Returns a point skewed along x.

Parameters:

  • a (Numeric)

    skew angle in degrees

Returns:

Raises:



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/sevgi/geometry/point.rb', line 150

Point = Data.define(:x, :y) do
  include Comparable
  include Affinity

  # @!attribute [r] x
  #   @return [Float] x coordinate
  # @!attribute [r] y
  #   @return [Float] y coordinate

  # Returns the screen-space angle from one point to another.
  # @param starting [Sevgi::Geometry::Point, Array<Numeric>] start point
  # @param ending [Sevgi::Geometry::Point, Array<Numeric>] end point
  # @return [Float] clockwise angle in degrees
  # @raise [Sevgi::Geometry::Error] when either point cannot be coerced
  def self.angle(starting, ending)
    starting, ending = Tuples[Point, starting, ending]
    F.atan2(ending.y - starting.y, ending.x - starting.x)
  end

  # Compares two points with optional numeric precision.
  # @param this [Sevgi::Geometry::Point, Array<Numeric>] first point
  # @param that [Sevgi::Geometry::Point, Array<Numeric>] second point
  # @param precision [Integer, nil] decimal precision, or nil for the current function default
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when either point cannot be coerced
  def self.eq?(this, that, precision: nil)
    this, that = Tuples[self, this, that]
    F.eq?(this.x, that.x, precision:) && F.eq?(this.y, that.y, precision:)
  end

  # Returns the Euclidean distance between two points.
  # @param starting [Sevgi::Geometry::Point, Array<Numeric>] start point
  # @param ending [Sevgi::Geometry::Point, Array<Numeric>] end point
  # @return [Float]
  # @raise [Sevgi::Geometry::Error] when either point cannot be coerced
  def self.length(starting, ending)
    starting, ending = Tuples[Point, starting, ending]
    ::Math.sqrt(((starting.y - ending.y) ** 2) + ((starting.x - ending.x) ** 2))
  end

  # Returns the origin point.
  # @return [Sevgi::Geometry::Point]
  def self.origin
    new(x: 0.0, y: 0.0)
  end

  # Creates a point.
  # @param x [Numeric] x coordinate
  # @param y [Numeric] y coordinate
  # @return [void]
  # @raise [Sevgi::Geometry::Error] when a coordinate is not a finite Numeric
  def initialize(x:, y:) = super(x: Real[:x, x], y: Real[:y, y])

  # Compares points by x, then y.
  # @param other [Object] point or two-item coordinate array to compare
  # @return [Integer, nil] comparison result, or nil when other is not a valid point value
  def <=>(other)
    deconstruct <=> Tuple[Point, other].deconstruct
  rescue Error
    nil
  end

  # Reports whether this point is at or above another point in screen coordinates.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def above?(other) = y <= Tuple[Point, other].y

  # Returns a point rounded to precision.
  # @param precision [Integer, nil] decimal precision, or nil for the current function default
  # @return [Sevgi::Geometry::Point]
  def approx(precision = nil) = with(x: F.approx(x, precision), y: F.approx(y, precision))

  # Reports whether this point is at or below another point in screen coordinates.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def below?(other) = y >= Tuple[Point, other].y

  # Compares this point with optional numeric precision.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @param precision [Integer, nil] decimal precision, or nil for the current function default
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def eq?(other, precision: nil) = self.class.eq?(self, other, precision:)

  # Reports strict point equality.
  # @param other [Object] object to compare
  # @return [Boolean]
  def eql?(other) = self.class == other.class && deconstruct == other.deconstruct

  # Returns a hash compatible with strict equality.
  # @return [Integer]
  def hash = [self.class, *deconstruct].hash

  # Reports whether this point is at or left of another point.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def left?(other) = x <= Tuple[Point, other].x

  # Reports whether this point is at or right of another point.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def right?(other) = x >= Tuple[Point, other].x

  # Formats point coordinates for SVG coordinate-list attributes.
  # @return [String]
  def to_cs = "#{F.approx(x)},#{F.approx(y)}"

  # Formats the point for display.
  # @return [String]
  def to_s = "(#{to_cs})"

  alias_method :==, :eql?
end

#skew_y(a) ⇒ Sevgi::Geometry::Point

Returns a point skewed along y.

Parameters:

  • a (Numeric)

    skew angle in degrees

Returns:

Raises:



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/sevgi/geometry/point.rb', line 150

Point = Data.define(:x, :y) do
  include Comparable
  include Affinity

  # @!attribute [r] x
  #   @return [Float] x coordinate
  # @!attribute [r] y
  #   @return [Float] y coordinate

  # Returns the screen-space angle from one point to another.
  # @param starting [Sevgi::Geometry::Point, Array<Numeric>] start point
  # @param ending [Sevgi::Geometry::Point, Array<Numeric>] end point
  # @return [Float] clockwise angle in degrees
  # @raise [Sevgi::Geometry::Error] when either point cannot be coerced
  def self.angle(starting, ending)
    starting, ending = Tuples[Point, starting, ending]
    F.atan2(ending.y - starting.y, ending.x - starting.x)
  end

  # Compares two points with optional numeric precision.
  # @param this [Sevgi::Geometry::Point, Array<Numeric>] first point
  # @param that [Sevgi::Geometry::Point, Array<Numeric>] second point
  # @param precision [Integer, nil] decimal precision, or nil for the current function default
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when either point cannot be coerced
  def self.eq?(this, that, precision: nil)
    this, that = Tuples[self, this, that]
    F.eq?(this.x, that.x, precision:) && F.eq?(this.y, that.y, precision:)
  end

  # Returns the Euclidean distance between two points.
  # @param starting [Sevgi::Geometry::Point, Array<Numeric>] start point
  # @param ending [Sevgi::Geometry::Point, Array<Numeric>] end point
  # @return [Float]
  # @raise [Sevgi::Geometry::Error] when either point cannot be coerced
  def self.length(starting, ending)
    starting, ending = Tuples[Point, starting, ending]
    ::Math.sqrt(((starting.y - ending.y) ** 2) + ((starting.x - ending.x) ** 2))
  end

  # Returns the origin point.
  # @return [Sevgi::Geometry::Point]
  def self.origin
    new(x: 0.0, y: 0.0)
  end

  # Creates a point.
  # @param x [Numeric] x coordinate
  # @param y [Numeric] y coordinate
  # @return [void]
  # @raise [Sevgi::Geometry::Error] when a coordinate is not a finite Numeric
  def initialize(x:, y:) = super(x: Real[:x, x], y: Real[:y, y])

  # Compares points by x, then y.
  # @param other [Object] point or two-item coordinate array to compare
  # @return [Integer, nil] comparison result, or nil when other is not a valid point value
  def <=>(other)
    deconstruct <=> Tuple[Point, other].deconstruct
  rescue Error
    nil
  end

  # Reports whether this point is at or above another point in screen coordinates.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def above?(other) = y <= Tuple[Point, other].y

  # Returns a point rounded to precision.
  # @param precision [Integer, nil] decimal precision, or nil for the current function default
  # @return [Sevgi::Geometry::Point]
  def approx(precision = nil) = with(x: F.approx(x, precision), y: F.approx(y, precision))

  # Reports whether this point is at or below another point in screen coordinates.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def below?(other) = y >= Tuple[Point, other].y

  # Compares this point with optional numeric precision.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @param precision [Integer, nil] decimal precision, or nil for the current function default
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def eq?(other, precision: nil) = self.class.eq?(self, other, precision:)

  # Reports strict point equality.
  # @param other [Object] object to compare
  # @return [Boolean]
  def eql?(other) = self.class == other.class && deconstruct == other.deconstruct

  # Returns a hash compatible with strict equality.
  # @return [Integer]
  def hash = [self.class, *deconstruct].hash

  # Reports whether this point is at or left of another point.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def left?(other) = x <= Tuple[Point, other].x

  # Reports whether this point is at or right of another point.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def right?(other) = x >= Tuple[Point, other].x

  # Formats point coordinates for SVG coordinate-list attributes.
  # @return [String]
  def to_cs = "#{F.approx(x)},#{F.approx(y)}"

  # Formats the point for display.
  # @return [String]
  def to_s = "(#{to_cs})"

  alias_method :==, :eql?
end

#to_csString

Formats point coordinates for SVG coordinate-list attributes.

Returns:

  • (String)


259
# File 'lib/sevgi/geometry/point.rb', line 259

def to_cs = "#{F.approx(x)},#{F.approx(y)}"

#to_sString

Formats the point for display.

Returns:

  • (String)


263
# File 'lib/sevgi/geometry/point.rb', line 263

def to_s = "(#{to_cs})"

#translate(dx, dy = Undefined) ⇒ Sevgi::Geometry::Point

Returns a translated point.

Parameters:

  • dx (Numeric)

    x offset

  • dy (Numeric, Sevgi::Undefined) (defaults to: Undefined)

    y offset, defaulting to dx

Returns:

Raises:



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/sevgi/geometry/point.rb', line 150

Point = Data.define(:x, :y) do
  include Comparable
  include Affinity

  # @!attribute [r] x
  #   @return [Float] x coordinate
  # @!attribute [r] y
  #   @return [Float] y coordinate

  # Returns the screen-space angle from one point to another.
  # @param starting [Sevgi::Geometry::Point, Array<Numeric>] start point
  # @param ending [Sevgi::Geometry::Point, Array<Numeric>] end point
  # @return [Float] clockwise angle in degrees
  # @raise [Sevgi::Geometry::Error] when either point cannot be coerced
  def self.angle(starting, ending)
    starting, ending = Tuples[Point, starting, ending]
    F.atan2(ending.y - starting.y, ending.x - starting.x)
  end

  # Compares two points with optional numeric precision.
  # @param this [Sevgi::Geometry::Point, Array<Numeric>] first point
  # @param that [Sevgi::Geometry::Point, Array<Numeric>] second point
  # @param precision [Integer, nil] decimal precision, or nil for the current function default
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when either point cannot be coerced
  def self.eq?(this, that, precision: nil)
    this, that = Tuples[self, this, that]
    F.eq?(this.x, that.x, precision:) && F.eq?(this.y, that.y, precision:)
  end

  # Returns the Euclidean distance between two points.
  # @param starting [Sevgi::Geometry::Point, Array<Numeric>] start point
  # @param ending [Sevgi::Geometry::Point, Array<Numeric>] end point
  # @return [Float]
  # @raise [Sevgi::Geometry::Error] when either point cannot be coerced
  def self.length(starting, ending)
    starting, ending = Tuples[Point, starting, ending]
    ::Math.sqrt(((starting.y - ending.y) ** 2) + ((starting.x - ending.x) ** 2))
  end

  # Returns the origin point.
  # @return [Sevgi::Geometry::Point]
  def self.origin
    new(x: 0.0, y: 0.0)
  end

  # Creates a point.
  # @param x [Numeric] x coordinate
  # @param y [Numeric] y coordinate
  # @return [void]
  # @raise [Sevgi::Geometry::Error] when a coordinate is not a finite Numeric
  def initialize(x:, y:) = super(x: Real[:x, x], y: Real[:y, y])

  # Compares points by x, then y.
  # @param other [Object] point or two-item coordinate array to compare
  # @return [Integer, nil] comparison result, or nil when other is not a valid point value
  def <=>(other)
    deconstruct <=> Tuple[Point, other].deconstruct
  rescue Error
    nil
  end

  # Reports whether this point is at or above another point in screen coordinates.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def above?(other) = y <= Tuple[Point, other].y

  # Returns a point rounded to precision.
  # @param precision [Integer, nil] decimal precision, or nil for the current function default
  # @return [Sevgi::Geometry::Point]
  def approx(precision = nil) = with(x: F.approx(x, precision), y: F.approx(y, precision))

  # Reports whether this point is at or below another point in screen coordinates.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def below?(other) = y >= Tuple[Point, other].y

  # Compares this point with optional numeric precision.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @param precision [Integer, nil] decimal precision, or nil for the current function default
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def eq?(other, precision: nil) = self.class.eq?(self, other, precision:)

  # Reports strict point equality.
  # @param other [Object] object to compare
  # @return [Boolean]
  def eql?(other) = self.class == other.class && deconstruct == other.deconstruct

  # Returns a hash compatible with strict equality.
  # @return [Integer]
  def hash = [self.class, *deconstruct].hash

  # Reports whether this point is at or left of another point.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def left?(other) = x <= Tuple[Point, other].x

  # Reports whether this point is at or right of another point.
  # @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
  # @return [Boolean]
  # @raise [Sevgi::Geometry::Error] when other cannot be coerced
  def right?(other) = x >= Tuple[Point, other].x

  # Formats point coordinates for SVG coordinate-list attributes.
  # @return [String]
  def to_cs = "#{F.approx(x)},#{F.approx(y)}"

  # Formats the point for display.
  # @return [String]
  def to_s = "(#{to_cs})"

  alias_method :==, :eql?
end