Class: Gifenc::Geometry::Point

Inherits:
Object
  • Object
show all
Defined in:
lib/geometry.rb

Overview

Represents a point in the plane. It's essentially a wrapper for an Float array with 2 elements (the coordinates) and many geometric methods that aid working with them. It is used indistinctly for both points and vectors, and will be denoted as such throughout the code, depending on which interpretation is more relevant.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(x, y) ⇒ Point

Create a new point given its coordinates. The coordinates are assumed to be the Cartesian coordinates with respect to the same axes as the desired image, and they need not be integers (though they'll be casted as such when actually drawing them).

Parameters:

  • x (Float)

    The X coordinate of the point.

  • y (Float)

    The Y coordinate of the point.



71
72
73
74
# File 'lib/geometry.rb', line 71

def initialize(x, y)
  @x = x.to_f
  @y = y.to_f
end

Instance Attribute Details

#xInteger

The X coordinate of the point.

Returns:

  • (Integer)

    X coordinate.



21
22
23
# File 'lib/geometry.rb', line 21

def x
  @x
end

#yInteger

The Y coordinate of the point.

Returns:

  • (Integer)

    Y coordinate.



25
26
27
# File 'lib/geometry.rb', line 25

def y
  @y
end

Class Method Details

.parse(point, sys = :cartesian) ⇒ Point

Parse a point from an arbitrary argument. It accepts either:

  • A point object, in which case it returns itself.
  • An array, in which case it creates a new point whose coordinates are the values of the array.

Parameters:

  • point (Point, Array<Integer>)

    The parameter to parse the point from.

  • sys (Symbol) (defaults to: :cartesian)

    The coordinate system to use for parsing the coordinates. It may be :cartesian or :polar.

Returns:

  • (Point)

    The parsed point object.

Raises:



54
55
56
57
58
59
60
61
62
63
# File 'lib/geometry.rb', line 54

def self.parse(point, sys = :cartesian)
  if point.is_a?(Point)
    point
  elsif point.is_a?(Array)
    point = polar2rect(*point) if sys == :polar
    new(*point)
  else
    raise Exception::GeometryError, "Couldn't parse point from argument."
  end
end

.polar2rect(mod, arg) ⇒ Array<Float>

Convert polar coordinates to rectangular (Cartesian) coordinates.

Parameters:

  • mod (Float)

    The point's module (euclidean norm).

  • arg (Float)

    The point's argument (angle with respect to the positive X axis).

Returns:

  • (Array<Float>)

    The corresponding Cartesian coordinates.



32
33
34
# File 'lib/geometry.rb', line 32

def self.polar2rect(mod, arg)
  [mod * Math.cos(arg), mod * Math.sin(arg)]
end

.rect2polar(x, y) ⇒ Array<Float>

Convert rectangular (Cartesian) coordinates to polar coordinates.

Parameters:

  • x (Float)

    The point's X coordinate.

  • y (Float)

    The point's Y coordinate.

Returns:

  • (Array<Float>)

    The corresponding polar coordinates.



40
41
42
# File 'lib/geometry.rb', line 40

def self.rect2polar(x, y)
  [(x ** 2  + y ** 2) ** 0.5, Math.atan2(y, x)]
end

Instance Method Details

#&(p) ⇒ Point

Compute the midpoint between this point and the given one.

Parameters:

  • p (Point)

    The other point.

Returns:

  • (Point)

    The midpoint.



148
149
150
# File 'lib/geometry.rb', line 148

def &(p)
  (self + Point.parse(p)) / 2
end

#*(arg) ⇒ Point, Float Also known as: scale

Scale a point or compute the dot product of two points.

  • If arg is Numeric, the point will be scaled by that factor. The return value will then be a new Point.
  • If arg is a Point, the scalar product of the two points will be computed. The return value will then be a Float.

Parameters:

  • arg (Numeric, Point)

    The factor to scale the point.

Returns:

  • (Point, Float)

    The scaled point or the scalar product.



113
114
115
116
117
118
119
120
# File 'lib/geometry.rb', line 113

def *(arg)
  if Numeric === arg
    Point.new(@x * arg, @y * arg)
  else
    p = Point.parse(arg)
    @x * p.x + @y * p.y
  end
end

#+(p) ⇒ Point Also known as: translate

Add another point to this one.

Parameters:

  • p (Point)

    The other point.

Returns:

  • (Point)

    The new point.



79
80
81
82
# File 'lib/geometry.rb', line 79

def +(p)
  p = Point.parse(p)
  Point.new(@x + p.x, @y + p.y)
end

#+@Point

Make all coordinates positive. This is equivalent to reflecting the point about the coordinate axes until it is in the first quadrant.

Returns:

  • (Point)

    The new point.



87
88
89
# File 'lib/geometry.rb', line 87

def +@
  Point.new(@x.abs, @y.abs)
end

#-(p) ⇒ Point

Subtract another point to this one.

Parameters:

  • p (Point)

    The other point.

Returns:

  • (Point)

    The new point.



94
95
96
97
# File 'lib/geometry.rb', line 94

def -(p)
  p = Point.parse(p)
  Point.new(@x - p.x, @y - p.y)
end

#-@Point

Take the opposite point with respect to the origin. This is equivalent to performing half a rotation about the origin.

Returns:

  • (Point)

    The new point.



102
103
104
# File 'lib/geometry.rb', line 102

def -@
  Point.new(-@x, -@y)
end

#/(s) ⇒ Point

Scale the point by the inverse of a factor.

Parameters:

  • arg (Numeric, Point)

    The factor to scale the point.

Returns:

  • (Point)

    The new point.



125
126
127
# File 'lib/geometry.rb', line 125

def /(s)
  Point.new(@x / s, @y / s)
end

#^(p) ⇒ Point

Reflect the point with respect to the given one.

Parameters:

  • p (Point)

    The other point.

Returns:

  • (Point)

    The new reflected point.



141
142
143
# File 'lib/geometry.rb', line 141

def ^(p)
  self + (Point.parse(p) - self) * 2
end

#angle(p) ⇒ Float

Find the angle between this point and the given one. The angle will be in the interval [0, PI].

Parameters:

  • p (Point)

    The other point.

Returns:

  • (Float)

    Angle between the points.



320
321
322
323
# File 'lib/geometry.rb', line 320

def angle(p)
  p = Point.parse(p)
  Math.acos((self * p) / (norm * p.norm))
end

#argFloat

Return the angle (argument) of the point. It is expressed in radian, between -PI and PI.

Returns:

  • (Float)

    Angle of the point.



306
307
308
# File 'lib/geometry.rb', line 306

def arg
  Math.atan2(@y, @x)
end

#ceilPoint

Convert to integer point by taking the ceiling part of the coordinates.

Returns:

  • (Point)

    The new point.

See Also:



434
435
436
# File 'lib/geometry.rb', line 434

def ceil
  Point.new(@x.ceil, @y.ceil)
end

#coords_cartesianArray<Float> Also known as: coords

Return the standard (Cartesian) coordinates of the point. This consists on the X and Y values.

Returns:

  • (Array<Float>)

    Cartesian coordinates of the point.



155
156
157
# File 'lib/geometry.rb', line 155

def coords_cartesian
  [@x, @y]
end

#coords_polarArray<Float> Also known as: polar

Return the polar coordinates of the point. This consists on the module (euclidean norm) and argument (angle between -PI and PI).

Returns:

  • (Array<Float>)

    Polar coordinates of the point.



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

def coords_polar
  [mod, arg]
end

#distance(p) ⇒ Object

Compute the Euclidean distance between two points.

Parameters:

  • p (Point)

    The other point.

Returns:



260
261
262
# File 'lib/geometry.rb', line 260

def distance(p)
  (self - Point.parse(p)).norm
end

#floorPoint

Convert to integer point by taking the floor part of the coordinates.

Returns:

  • (Point)

    The new point.

See Also:



425
426
427
# File 'lib/geometry.rb', line 425

def floor
  Point.new(@x.floor, @y.floor)
end

#negative?(p) ⇒ Boolean

Find whether the points are negatively aligned. This means that their scalar product is negative, and implies that they form an obtuse angle, i.e., they go roughly in the opposite direction.

Parameters:

  • p (Point)

    The other point.

Returns:

  • (Boolean)

    Whether the points are negatively aligned.



356
357
358
# File 'lib/geometry.rb', line 356

def negative?(p)
  self * p < 0
end

#normFloat Also known as: norm_2, mod

Shortcut to compute the euclidean norm of the vector.

Returns:

  • (Float)

    The euclidean norm of the vector.

See Also:



206
207
208
# File 'lib/geometry.rb', line 206

def norm
  norm_p(2)
end

#norm_1Float

Shortcut to compute the 1-norm of the vector.

Returns:

  • (Float)

    The 1-norm of the vector.

See Also:



199
200
201
# File 'lib/geometry.rb', line 199

def norm_1
  (@x.abs + @y.abs).to_f
end

#norm_infFloat

Shortcut to compute the infinity (maximum) norm of the vector.

Returns:

  • (Float)

    The infinity norm of the vector.

See Also:



216
217
218
# File 'lib/geometry.rb', line 216

def norm_inf
  [@x.abs, @y.abs].max.to_f
end

#norm_p(p = 2) ⇒ Float

Compute the p-norm of the vector. It should be p>0.

Parameters:

  • p (Float) (defaults to: 2)

    The parameter of the norm.

Returns:

  • (Float)

    The p-norm of the vector.

See Also:



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

def norm_p(p = 2)
  (@x.abs ** p + @y.abs ** p) ** (1.0 / p)
end

#normal_leftPoint

Compute the left-hand (CCW) normal vector.

Returns:

  • (Point)

    The left-hand normal vector.

See Also:



173
174
175
# File 'lib/geometry.rb', line 173

def normal_left
  Point.new(@y, -@x)
end

#normal_rightPoint Also known as: normal

Compute the right-hand (CW) normal vector.

Returns:

  • (Point)

    The right-hand normal vector.

See Also:



180
181
182
# File 'lib/geometry.rb', line 180

def normal_right
  Point.new(-@y, @x)
end

#normalizePoint Also known as: normalize_2

Shotcut to normalize the vector with respect to the euclidean norm.

Returns:

  • (Point)

    The normalized vector.

Raises:

See Also:



243
244
245
# File 'lib/geometry.rb', line 243

def normalize
  normalize_p(2)
end

#normalize_1Point

Shotcut to normalize the vector with respect to the 1-norm.

Returns:

  • (Point)

    The normalized vector.

Raises:

See Also:



235
236
237
# File 'lib/geometry.rb', line 235

def normalize_1
  normalize_p(1)
end

#normalize_infPoint

Shotcut to normalize the vector with respect to the infinity norm.

Returns:

  • (Point)

    The normalized vector.

Raises:

See Also:



253
254
255
# File 'lib/geometry.rb', line 253

def normalize_inf
  normalize_gen(norm_inf)
end

#normalize_p(p) ⇒ Point

Normalize the vector with respect to the p-norm. It should be p>0.

Parameters:

  • p (Float)

    The parameter of the norm.

Returns:

  • (Point)

    The normalized vector.

Raises:

See Also:



227
228
229
# File 'lib/geometry.rb', line 227

def normalize_p(p)
  normalize_gen(norm_p(p))
end

#orthogonal?(p) ⇒ Boolean Also known as: perpendicular?

Find whether the given point is perpendicular to this one.

Parameters:

  • p (Point)

    The other point.

Returns:

  • (Boolean)

    Whether the points are orthogonal.



328
329
330
# File 'lib/geometry.rb', line 328

def orthogonal?(p)
  (self * Point.parse(p)).abs < PRECISION
end

#parallel?(p) ⇒ Boolean

Find whether the given point / vector is parallel (proportional) to this one.

Parameters:

  • p (Point)

    The other point.

Returns:

  • (Boolean)

    Whether the points are parallel.



338
339
340
# File 'lib/geometry.rb', line 338

def parallel?(p)
  angle(p).abs < PRECISION
end

#positive?(p) ⇒ Boolean

Find whether the points are positively aligned. This means that their scalar product is positive, and implies that they form an acute angle, i.e., they go roughly in the same direction.

Parameters:

  • p (Point)

    The other point.

Returns:

  • (Boolean)

    Whether the points are positively aligned.



347
348
349
# File 'lib/geometry.rb', line 347

def positive?(p)
  self * p > 0
end

#project(p1: nil, p2: nil, direction: nil, angle: nil) ⇒ Point

Project the point onto a line. The line might be supplied by providing either of the following 3 options:

  • Two different points from the line.
  • A point and a direction vector (not necessarily normalized).
  • A point and an angle. At least one point is therefore always required.

Parameters:

  • p1 (Point) (defaults to: nil)

    A point on the line.

  • p2 (Point) (defaults to: nil)

    Another point on the line.

  • direction (Point) (defaults to: nil)

    The direction vector of the line.

  • angle (Float) (defaults to: nil)

    The angle of the line, in radians.

Returns:

  • (Point)

    The projected point on the line.

Raises:



277
278
279
280
281
282
283
# File 'lib/geometry.rb', line 277

def project(p1: nil, p2: nil, direction: nil, angle: nil)
  raise Exception::GeometryError, "Couldn't determine line,\
    at least one point must be supplied." if !p1 && !p2
  point = Point.parse(p1 || p2)
  direction = Geometry.direction(p1: p1, p2: p2, angle: angle) unless direction
  point - ((point - self) | direction)
end

#reflect(t = 1, p1: nil, p2: nil, direction: nil, angle: nil) ⇒ Point

Reflect the point with respect to a line. The line might be supplied by providing either of the following 3 options:

  • Two different points from the line.
  • A point and a direction vector (not necessarily normalized).
  • A point and an angle. At least one point is therefore always required.

Parameters:

  • t (Float) (defaults to: 1)

    Proportion of reflection to perform. For example:

    • t = 1 will perform the full reflection.
    • t = 0 will land on the line.
    • t = -1 will not move the point.
  • p1 (Point) (defaults to: nil)

    A point on the line.

  • p2 (Point) (defaults to: nil)

    Another point on the line.

  • direction (Point) (defaults to: nil)

    The direction vector of the line.

  • angle (Float) (defaults to: nil)

    The angle of the line, in radians.

Returns:

  • (Point)

    The reflected point with respect to the line.

Raises:



298
299
300
301
# File 'lib/geometry.rb', line 298

def reflect(t = 1, p1: nil, p2: nil, direction: nil, angle: nil)
  proj = self.project(p1: p1, p2: p2, direction: direction, angle: angle)
  self + (proj - self) * (t + 1)
end

#rotate(angle, center = ORIGIN) ⇒ Point

Rotate the point by a certain angle about a given center.

Parameters:

  • angle (Float)

    The angle to rotate the point, in radians.

  • center (Point) (defaults to: ORIGIN)

    The point to rotate about.

Returns:

  • (Point)

    The new point.



364
365
366
367
368
369
370
371
372
373
# File 'lib/geometry.rb', line 364

def rotate(angle, center = ORIGIN)
  center = Point.parse(center)
  x_old = @x - center.x
  y_old = @y - center.y
  sin = Math.sin(angle)
  cos = Math.cos(angle)
  x = x_old * cos - y_old * sin
  y = x_old * sin + y_old * cos
  Point.new(x + center.x, y + center.y)
end

#rotate_180(center = ORIGIN) ⇒ Point

Shortcut to rotate the point 180 degrees about a given center.

Parameters:

  • center (Point) (defaults to: ORIGIN)

    The point to rotate about.

Returns:

  • (Point)

    The new point.



393
394
395
# File 'lib/geometry.rb', line 393

def rotate_180(center = ORIGIN)
  rotate(Math::PI, center)
end

#rotate_left(center = ORIGIN) ⇒ Point

Shortcut to rotate the point 90 degrees counterclockwise about a given center.

Parameters:

  • center (Point) (defaults to: ORIGIN)

    The point to rotate about.

Returns:

  • (Point)

    The new point.



379
380
381
# File 'lib/geometry.rb', line 379

def rotate_left(center = ORIGIN)
  rotate(-Math::PI / 2, center)
end

#rotate_right(center = ORIGIN) ⇒ Point

Shortcut to rotate the point 90 degrees clockwise about a given center.

Parameters:

  • center (Point) (defaults to: ORIGIN)

    The point to rotate about.

Returns:

  • (Point)

    The new point.



386
387
388
# File 'lib/geometry.rb', line 386

def rotate_right(center = ORIGIN)
  rotate(Math::PI / 2, center)
end

#roundPoint

Convert to integer point by rounding the coordinates.

Returns:

  • (Point)

    The new point.

See Also:



405
406
407
# File 'lib/geometry.rb', line 405

def round
  Point.new(@x.round, @y.round)
end

#to_iPoint Also known as: truncate

Convert to integer point by taking the integer part of the coordinates.

Returns:

  • (Point)

    The new point.

See Also:



414
415
416
# File 'lib/geometry.rb', line 414

def to_i
  Point.new(@x.to_i, @y.to_i)
end

#to_sString

Format the point's coordinates in the usual form.

Returns:

  • (String)

    The formatted point.



440
441
442
# File 'lib/geometry.rb', line 440

def to_s
  "(#{@x}, #{@y})"
end

#zero?Boolean

Whether the point is null, i.e., close enough to the origin.

Returns:

  • (Boolean)

    Whether the point is (close enough to) the origin.



312
313
314
# File 'lib/geometry.rb', line 312

def zero?
  norm < PRECISION
end

#|(p) ⇒ Point

Project the point onto the given vector. The supplied vector need not be unitary, as it will be normalized automatically.

Parameters:

  • p (Point)

    The other point.

Returns:

  • (Point)

    The new projected point.



133
134
135
136
# File 'lib/geometry.rb', line 133

def |(p)
  u = Point.parse(p).normalize
  u * (self * u)
end