Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- require 'geometry'
- module Kernel
- # Create a 2D Point
- def XY(x,y)
- Geometry::Point.xy(x, y)
- end
- # Create a 3D Point
- def XYZ(x,y,z)
- Geometry::Point.xyz(x, y, z)
- end
- end
- module Geometry
- # immutable
- # Represents a point in an n-dimensional space
- class Point < Entity
- class <<self
- # create a 2-dimensional point
- def xy(x, y)
- new(x, y)
- end
- # create a 3-dimensional point
- def xyz(x, y, z)
- new(x, y, z)
- end
- end
- intersects_with(Point) { |a,b|
- a =~ b
- }
- def initialize(*components)
- raise TypeError, "Not numeric component" unless components.all? { |c| Numeric === c }
- @components = components
- @components.freeze
- end
- # Value-equality of this point with another
- # Use =~ if you deal with floats
- # Tests if all components of the same dimension are == to the other
- def ==(point)
- raise DimensionError, "Dimension mismatch, #{dimensions} vs. #{point.dimensions}" unless dimensions == point.dimensions
- point.to_a == @components
- end
- # Float-equality of this point with another (see Numeric#=~)
- # Tests if all components of the same dimension are =~ to the other
- def =~(point)
- raise DimensionError, "Dimension mismatch, #{dimensions} vs. #{point.dimensions}" unless dimensions == point.dimensions
- @components.zip(point.to_a).all? { |(a,b)| a =~ b }
- end
- def include?(entity)
- entity.is_a?(Point) ? self =~ entity : false
- end
- # Array-like access to the components of this point
- def [](*index)
- @components[*index]
- end
- # Returns component at index 0
- def x
- @components[0]
- end
- # Returns component at index 1
- def y
- @components[1]
- end
- # Returns component at index 2
- def z
- @components[2]
- end
- # The dimensions of this point, e.g. Point.new(1,2,3).dimensions # => 3
- def dimensions
- @components.length
- end
- # The center of a point is self
- def center
- dup
- end
- # Returns a point of given dimension. If new point has more
- # dimensions than the old one the additional dimensions are filled
- # with zeros. If it has less, the are truncated.
- def redim(d, fill=0)
- Point[*(@components[0,d]+Array.new([d-dimensions,0].max, fill))]
- end
- # add a vector, get a new point
- def +(vector)
- raise ArgumentError, "Can only add vectors" unless Geometry::Vector === vector
- raise DimensionError, "Dimension mismatch, #{dimensions} vs. #{vector.dimensions}" unless dimensions == vector.dimensions
- Point.new(*@components.zip(vector.to_a).map { |(a,b)| a+b })
- end
- # subtract a vector, get a new point
- # subtract a point, get a vector
- def -(vector)
- raise ArgumentError, "Can only subtract vectors or points" unless Vector === vector || Point === vector
- raise DimensionError, "Dimension mismatch, #{dimensions} vs. #{vector.dimensions}" unless dimensions == vector.dimensions
- if vector.is_a?(Point) then
- Vector.new(*@components.zip(vector.to_a).map { |(a,b)| a-b })
- else
- Point.new(*@components.zip(vector.to_a).map { |(a,b)| a-b })
- end
- end
- # Returns the direct distance from point to point
- def distance_to(point)
- vector_to(point).magnitude
- end
- # Returns the vector needed to go from self to point
- def vector_to(point)
- raise ArgumentError, "#{point} is not a Point" unless Point === point
- raise DimensionError, "Dimension mismatch, #{dimensions} vs. #{vector.dimensions}" unless dimensions == point.dimensions
- Vector[*@components.zip(point.to_a).map { |(a,b)| b-a }]
- end
- # Returns the vector needed to go from point to self
- def vector_from(point)
- raise ArgumentError, "#{point} is not a Point" unless Point === point
- raise DimensionError, "Dimension mismatch, #{dimensions} vs. #{vector.dimensions}" unless dimensions == point.dimensions
- Vector[*@components.zip(point.to_a).map { |(a,b)| a-b }]
- end
- # Returns a point moved/translated by given vector
- def moved_by(vector)
- raise ArgumentError, "Can only add vectors" unless Geometry::Vector === vector
- raise DimensionError, "Dimension mismatch, #{dimensions} vs. #{vector.dimensions}" unless dimensions == vector.dimensions
- Point.new(*@components.zip(vector.to_a).map { |(a,b)| a+b })
- end
- # Returns a point mirrored at given line
- def mirrored_at(line)
- super
- end
- # Returns a point rotated around given point by given amount (radian)
- def rotated_around(point, by_degree=Math::PI)
- raise ArgumentError, "#{point} is not a Point" unless Point === point
- raise ArgumentError, "#{by_degree} is not Numeric" unless Numeric === by_degree
- raise DimensionError, "Dimension mismatch, #{dimensions} vs. #{vector.dimensions}" unless dimensions == point.dimensions
- raise DimensionError, "rotate_around is only defined for 2 dimensions" unless dimensions == 2
- point.moved_by(point.vector_to(self).rotated_by(by_degree))
- end
- # Returns a point rotated by given amount (radian) around given point
- def rotated_by(degree, around_point=XY(0,0))
- rotated_around(around_point, degree)
- end
- # Position vector of this point
- def to_vector
- Vector[*@components]
- end
- # A point can always become converted to a point :)
- def to_point
- dup
- end
- # The components of the point as array
- def to_a
- @components.dup
- end
- # String representation of point
- def to_s
- @components.map { |c| c.inspect }.join(", ")
- end
- def inspect # :nodoc:
- "#<Point #{self}>"
- end
- def dup # :nodoc:
- Point.new(*@components)
- end
- end
- end
Add Comment
Please, Sign In to add comment