<reponame>InformaticFreak/vector-operations<filename>src/vectometry/__init__.py<gh_stars>1-10

from math import sqrt, acos, degrees


__project__ = "vectometry"
__version__ = "2021.9"
__author__ = "InformaticFreak"
__description__ = "A Python library for simple use of common point, vector, line and plane operations in 3-dimensional space as well as for 2-dimensions."


class Point:
	"""Point object represents a point in 3-dimensional space, means x, y and z coordinates
	
	Accepts initialization formats:
	 - List/tuple with three/two real numbers
	 - Three/Two single real numbers
	"""
	def __init__(self, *args):
		x, y, z = None, None, None
		if len(args) == 1:
			p = args[0]
			if isinstance(p, (list, tuple)):
				if len(p) == 3:
					x, y, z = p
				elif len(p) == 2:
					x, y = p
					z = 0
		elif len(args) == 2:
			x, y, z = args[0], args[1], 0
		elif len(args) == 3:
			x, y, z = args[0], args[1], args[2]
		if isinstance(x, (int, float)) and isinstance(y, (int, float)) and isinstance(z, (int, float)):
			self.__point = (x, y, z)
		else:
			raise TypeError
	
	def x(self, value=None):
		"""Returns x-coordinate value and set it to specific value, if it is given"""
		if value is not None:
			if isinstance(value, (int, float)):
				self.__point = (value, self.__point[1], self.__point[2])
			else:
				raise TypeError
		return self.__point[0]
	def y(self, value=None):
		"""Returns y-coordinate value and set it to specific value, if it is given"""
		if value is not None:
			if isinstance(value, (int, float)):
				self.__point = (self.__point[0], value, self.__point[2])
			else:
				raise TypeError
		return self.__point[1]
	def z(self, value=None):
		"""Returns z-coordinate value and set it to specific value, if it is given"""
		if value is not None:
			if isinstance(value, (int, float)):
				self.__point = (self.__point[0], self.__point[1], value)
			else:
				raise TypeError
		return self.__point[2]
	
	def copy(self):
		"""Returns an independent Point object as a copy of the original"""
		return Point(self.x(), self.y(), self.z())
	
	def __str__(self):
		"""Returns a string representing the Point object"""
		return self.__repr__()
	def __repr__(self):
		"""Returns a string representing the Point object"""
		return f"{self.__class__.__name__}({self.x()}, {self.y()}, {self.z()})"
	
	def __len__(self):
		"""Returns the amount of dimensions (always three)"""
		return len(self.__point)
	def __iter__(self):
		"""Returns an iterable object including the coordinates"""
		return iter(self.__point)
	
	def __add__(self, other):
		"""Returns a Vector object as the negated displacement of both Point objects"""
		return add(self, other)
	def __sub__(self, other):
		"""Returns a Vector object as the displacement of both Point objects"""
		return sub(self, other)	
	
	def __neg__(self):
		"""Returns a Point object with negated coordinates"""
		return neg(self)
	def __pos__(self):
		"""Returns the Point object"""
		return self
	def __round__(self, n=0):
		"""Returns a Point object, but the coordinates are rounded to the given decimal digits, default is zero decimal digits"""
		return Point(
			round(self.x(), n),
			round(self.y(), n),
			round(self.z(), n)
		)
	
	def __eq__(self, other):
		"""Returns True if every coordinate of both Point objects are equal, else False"""
		return self.x() == other.x() and self.y() == other.y() and self.z() == other.z()
	def __ne__(self, other):
		"""Returns True if not every coordinate of both Point objects are equal, else False"""
		return not self.__eq__(other)


class Vector:
	"""Vector object represents a vector in 3-dimensional space, means x, y and z coordinates.
	
	Accepts initialization formats:
	 - Point object (location vector)
	 - List/tuple with three/two real numbers
	 - Two Point objects (vector from first Point object to second Point object)
	 - Three/Two single real numbers
	"""
	def __init__(self, *args):
		if len(args) == 1:
			p = args[0]
			if type(p) is Point:
				self.__point = p.copy()
			elif isinstance(p, (list, tuple)):
				self.__point = Point(p)
			else:
				raise TypeError
		elif len(args) == 2:
			p1, p2 = args[0], args[1]
			if type(p1) is Point and type(p2) is Point:
				p = sub(p2, p1)
				self.__point = p.point()
			elif isinstance(p1, (int, float)) and isinstance(p2, (int, float)):
				self.__point = Point(p1, p2, 0)
			else:
				raise TypeError
		elif len(args) == 3:
			self.__point = Point(*args)
		else:
			raise TypeError
	
	def x(self, value=None):
		"""Returns x-coordinate value and set it to specific value, if it is given"""
		return self.__point.x(value)
	def y(self, value=None):
		"""Returns y-coordinate value and set it to specific value, if it is given"""
		return self.__point.y(value)
	def z(self, value=None):
		"""Returns z-coordinate value and set it to specific value, if it is given"""
		return self.__point.z(value)
	def point(self):
		"""Returns the coordinates to which the vector pointing as a reference to the Point object"""
		return self.__point
	
	def copy(self):
		"""Returns an independent Vector object as a copy of the original"""
		return Vector(self.point().copy())
	
	def __str__(self):
		"""Returns a string representing the Vector object"""
		return self.__repr__()
	def __repr__(self):
		"""Returns a string representing the Vector object"""
		return f"{self.__class__.__name__}({self.x()}, {self.y()}, {self.z()})"
	
	def __len__(self):
		"""Returns the amount of dimensions (always three)"""
		return len(self.__point)
	def __iter__(self):
		"""Returns an iterable object including the coordinates"""
		return iter(self.__point)
	
	def __add__(self, other):
		"""Returns the addition of two Vector objects"""
		return add(self, other)
	def __sub__(self, other):
		"""Returns the difference of two Vector objects"""
		return sub(self, other)
	def __mod__(self, other):
		"""Returns the cross product of two Vector objects"""
		return cross(self, other)
	def __truediv__(self, other):
		"""Returns the multiplication of a Vector object by the reciprocal of the real number"""
		return div(self, other)
	def __rmul__(self, other):
		"""Returns the multiplication of a Vector object by a real number or by another Vector object, means the dot product"""
		return self.__mul__(other)
	def __mul__(self, other):
		"""Returns the multiplication of a Vector object by a real number or by another Vector object, means the dot product"""
		if type(other) is int or type(other) is float: 
			return mul(self, other)
		elif type(other) is Vector:
			return dot(self, other)
		else:
			raise TypeError
	
	def __neg__(self):
		"""Returns a Vector object with negated coordinates"""
		return neg(self)
	def __pos__(self):
		"""Returns the Vector object"""
		return self
	def __abs__(self):
		"""Returns the magnitude of the Vector object as a real number"""
		return norm(self)
	def __round__(self, n=0):
		"""Returns a Vector object, but the coordinates are rounded to the given decimal digits, default is zero decimal digits"""
		return Vector(
			round(self.x(), n),
			round(self.y(), n),
			round(self.z(), n)
		)
	
	def __eq__(self, other):
		"""Returns True if both points on which the vectors points are the same (same lenght and same direction), else False"""
		return self.point() == other.point()
	def __ne__(self, other):
		"""Returns True if both points on which the vectors pointing are not the same, else False"""
		return not self.__eq__(other)


class Line:
	"""Line object represents a straight line in 3-dimensional space, means x, y and z coordinates.
	
	Accepts initialization equations:
	(Upper case letters represents Points/Vectors and lower case letters represents real numbers)
	 - [P]	Parameter equation		0 = R + s*D - X			{R as Point; D as Vector}	(default mode)
									0 = R + s*(D-R) - X		{R,D as Point}
	"""
	def __init__(self, fmt="P", **kwargs):
		if type(fmt) is str:
			fmt = fmt.upper()
			if fmt == "P":
				if type(kwargs.get("R")) is Point:
					R = kwargs["R"]
				else:
					raise TypeError
				if type(kwargs.get("D")) is Point:
					D = sub(kwargs["D"], R)
				elif type(kwargs.get("D")) is Vector:
					D = kwargs["D"]
				else:
					raise TypeError
			else:
				raise ValueError
		else:
			raise TypeError
		self.__R = R
		self.__D = D
	
	def R(self, value=None):
		"""Returns the receptor Point R as real number and set it to specific value, if it is given"""
		if value is not None:
			if type(value) is Vector:
				self.__R = value
			else:
				raise TypeError
		return self.__R
	def D(self, value=None):
		"""Returns the direction Vector D and set it to specific value, if it is given"""
		if value is not None:
			if isinstance(value, (int, float)):
				self.__D = value
			else:
				raise TypeError
		return self.__D
	
	def copy(self):
		"""Returns an independent Plane object as a copy of the original"""
		return Line("P", R=self.R().copy(), D=self.D().copy())
	
	def __str__(self):
		"""Returns a string representing the Line object"""
		return self.__repr__()
	def __repr__(self):
		"""Returns a string representing the Line object"""
		return f"{self.__class__.__name__}('P', R={self.R()}, D={self.D()})"
		
	def __round__(self, n=0):
		"""Returns a Line object, but the coordinates of the receptor point R and direction Vector D are rounded to the given decimal digits, default is zero decimal digits"""
		return Line("P", R=round(self.R(), n), D=round(self.D(), n))
	
	def __eq__(self, other):
		"""Returns True if both Lines are identical to each other (means parallel and no distance), else False"""
		return is_collinear(self.D(), other.D()) and distance(self, other) == 0
	def __ne__(self, other):
		"""Returns True if both Lines are not identical to each other, else False"""
		return not self.__eq__(other)
	
	def __call__(self, X):
		"""Insert the given Point into the line function \'R + s*D - X\' and return the result for the parameter s. If the result is zero, the given Point is not located in the line."""
		if type(X) is Point:
			s1 = (X.x() - self.R.x()) / self.D.x()
			s2 = (X.y() - self.R.y()) / self.D.y()
			s3 = (X.z() - self.R.z()) / self.D.z()
			if s1 == s2 == s3:
				return s1
			else:
				return 0
		elif type(X) is Vector:
			return self.__call__(X.point())
		else:
			raise TypeError


class Plane:
	"""Plane object represents a plane/layer in 3-dimensional space, means x, y and z coordinates.
	
	Accepts initialization equations:
	(Upper case letters represents Points/Vectors and lower case letters represents real numbers)
	 - [P]	Parameter equation	0 = R + m*D + t*S - X						{R as Point; D,S as Vector}					(default mode)
								0 = R + m*(D-R) + t*(S-R) - X				{R,D,S as Point}
	 - [C]	Coordinate equation	0 = Nx*Xx + Ny*Xy + Nz*Xz + n0 = N ° X + n0	{N as Vector(Nx, Ny, Nz); n0 as int/float}
	 - [N]	Normal equation		0 = N ° (X-R)								{N as Vector; R as Point}
	"""
	def __init__(self, fmt="P", **kwargs):
		if type(fmt) is str:
			fmt = fmt.upper()
			if fmt == "P":
				if type(kwargs.get("R")) is Point:
					R = kwargs["R"]
				else:
					raise TypeError
				if type(kwargs.get("D")) is type(kwargs.get("S")) is Point:
					D = sub(kwargs["D"], R)
					S = sub(kwargs["S"], R)
				elif type(kwargs.get("D")) is type(kwargs.get("S")) is Vector:
					D = kwargs["D"]
					S = kwargs["S"]
				else:
					raise TypeError
				N = cross(D, S)
			elif fmt == "C":
				if type(kwargs.get("N")) is Vector and isinstance(kwargs.get("n0"), (int, float)):
					N = kwargs["N"]
					n0 = kwargs["n0"]
					x, y, z = 0, 0, 0
					if N.x() != 0:
						x = n0 / N.x()
					elif N.y() != 0:
						y = n0 / N.y()
					elif N.z() != 0:
						z = n0 / N.z()
					else:
						raise ZeroDivisionError
					R = Point(x, y, z)
				else:
					raise TypeError
			elif fmt == "N":
				if type(kwargs.get("N")) is Vector and type(kwargs.get("R")) is Point:
					N = kwargs["N"]
					R = kwargs["R"]
				else:
					raise TypeError
			else:
				raise ValueError
		else:
			raise TypeError
		self.__N = N
		self.__R = R
	
	def N(self, value=None):
		"""Returns the normal Vector N and set it to specific value, if it is given"""
		if value is not None:
			if type(value) is Vector:
				self.__N = value
			else:
				raise TypeError
		return self.__N
	def R(self, value=None):
		"""Returns the receptor Point R and set it to specific value, if it is given"""
		if value is not None:
			if type(value) is Point:
				self.__R = value
			else:
				raise TypeError
		return self.__R
	
	def fmt(self, fmt="P"):
		"""Returns for one of the initialization formats the attributes of the Plane as dictionary"""
		fmt = fmt.upper()
		if fmt == "P":
			return {
				# "R": Point(0, 0, dot(self.N(), self.R()) / self.N().z()),
				"R": self.R(),
				"D": Vector(1, 0, -(self.N().x() / self.N().z())),
				"S": Vector(0, 1, -(self.N().y() / self.N().z()))
			}
		elif fmt == "C":
			return {
				"N": self.N(),
				"n0": dot(self.N(), Vector(self.R()))
			}
		elif fmt == "N":
			return {
				"N": self.N(),
				"R": self.R()
			}
		else:
			raise ValueError
	
	def copy(self):
		"""Returns an independent Plane object as a copy of the original"""
		return Plane("N", N=self.N().copy(), R=self.R().copy())
	
	def __str__(self):
		"""Returns a string representing the Plane object"""
		return self.__repr__()
	def __repr__(self):
		"""Returns a string representing the Plane object"""
		return f"{self.__class__.__name__}('N', N={self.N()}, R={self.R()})"
		
	def __round__(self, n=0):
		"""Returns a Plane object, but the coordinates of the normal Vector N and receptor Point R are rounded to the given decimal digits, default is zero decimal digits"""
		return Plane("N", N=round(self.N(), n), R=round(self.R(), n))
	
	def __eq__(self, other):
		"""Returns True if both Planes are identical to each other (means parallel and no distance), else False"""
		return is_collinear(self.N(), other.N()) and distance(self, other) == 0
	def __ne__(self, other):
		"""Returns True if both Planes are not identical to each other, else False"""
		return not self.__eq__(other)
	
	def __call__(self, X):
		"""Insert the given Point into the plane function \'N ° (X-R)\' and return the result of it. If the result is zero, the given Point is located in the plane."""
		if type(X) is Point:
			return dot(self.N(), sub(X, self.R()))
		elif type(X) is Vector:
			return self.__call__(X.point())
		else:
			raise TypeError


def norm(a):
	"""Returns the magnitude of the Vector object as a real number"""
	if type(a) is Vector:
		return sqrt(sum([ p**2 for p in a ]))
	else:
		raise TypeError
def unit(a):
	"""Returns the unit vector of the Vector object, it means a vector with the same direction but a magnitude of one"""
	if type(a) is Vector:
		return div(a, norm(a))
	else:
		raise TypeError
def neg(a):
	"""Returns the given object (Point/Vector) with negated coordinates"""
	if type(a) is Vector:
		return Vector([ -p for p in a ])
	elif type(a) is Point:
		return Point([ -p for p in a ])
	else:
		raise TypeError

def add(a, b):
	"""Cases:
	 - Two Vector objects: Returns the addition of two Vector objects
	 - Two Point objects: Returns a Vector object as the negated displacement of both Point objects
	"""
	if ( type(a) is type(b) is Vector ) or ( type(a) is type(b) is Point ):
		return Vector(
			a.x()+b.x(),
			a.y()+b.y(),
			a.z()+b.z()
		)
	else:
		raise TypeError
def sub(a, b):
	"""Cases:
	 - Two Vector objects: Returns the difference of two Vector objects
	 - Two Point objects: Returns a Vector object as the displacement of both Point objects
	"""
	return add(a, neg(b))

def mul(a, b):
	"""Returns the multiplication of a Vector object by a real number"""
	if isinstance(a, (int, float)) and type(b) is Vector:
		return mul(b, a)
	elif isinstance(b, (int, float)) and type(a) is Vector:
		return Vector([ p*b for p in a ])
	else:
		raise TypeError
def div(a, b):
	"""Returns the multiplication of a Vector object by the reciprocal of the real number"""
	if isinstance(b, (int, float)):
		return mul(a, 1/b)
	else:
		raise TypeError

def cross(a, b):
	"""Returns the cross product of two Vector objects as a Vector object"""
	if type(a) is Vector and type(b) is Vector:
		return Vector(
			a.y()*b.z() - b.y()*a.z(),
			a.z()*b.x() - b.z()*a.x(),
			a.x()*b.y() - b.x()*a.y()
		)
	else:
		raise TypeError
def dot(a, b):
	"""Returns the dot product of two Vector objects as a real number"""
	if type(a) is Vector and type(b) is Vector:
		return a.x()*b.x() + a.y()*b.y() + a.z()*b.z()
	else:
		raise TypeError

def det(a, b, c):
	"""Returns the determinant of three Vector objects as a real number"""
	return dot(cross(a, b), c)

def spate(a, b, c):
	"""Returns the volume of a parallelepiped spanned by three Vector objects as a positive real number including zero"""
	return abs(det(a, b, c))
def area(a, b):
	"""Returns the area of a parallelogram spanned by two Vector objects as a position real number including zero"""
	return norm(cross(a, b))

def angle(a, b, mode="DEG"):
	"""Returns the smaller angle between two vectometry objects as a real number, except Point objects
	Mode:
	 - In degree with mode \'DEG\'	(default mode)
	 - In radian with mode \'RAD\'
	"""
	if type(a) is type(b) is Vector:
		rad_angle = acos(dot(a, b) / (norm(a) * norm(b)))
	elif type(a) is type(b) is Line:
		return angle(a.D(), b.D())
	elif type(a) is type(b) is Plane:
		return angle(a.N(), b.N())
	elif type(a) is Line and type(b) is Vector:
		return angle(a.D(), b)
	elif type(a) is Plane and type(b) is Vector:
		return angle(a.N(), b)
	elif type(a) is Line and type(b) is Plane:
		return angle(a.D(), b.N())
	else:
		raise TypeError
	mode = mode.upper()
	if mode == "DEG":
		return degrees(rad_angle)
	elif mode == "RAD":
		return rad_angle
	else:
		raise ValueError

def is_complanar(a, b, c):
	"""Returns True if all three Vector objects are in the same plane, else False"""
	return spate(a, b, c) == 0
def is_collinear(a, b):
	"""Returns True if both Vector objects are parallel or anti-parallel to each other, else False"""
	return abs(dot(a, b)) == norm(a) * norm(b)
def is_orthogonal(a, b):
	"""Returns True if both Vector objects are perpendicular to each other, else False"""
	return dot(a, b) == 0

def distance(a, b):
	"""Returns the distance between two vectometry objects, except Vector objects"""
	if type(a) is type(b) is Point:
		return norm(Vector(a, b))
	elif type(a) is type(b) is Plane:
		if is_collinear(a.N(), b.N()):
			return distance(a, b.R())
	elif type(a) is type(b) is Line:
		if is_collinear(a.D(), b.D()):
			# https://www.mathebibel.de/abstand-paralleler-geraden
			pass
		elif "WINDSCHIEF": #engl.: warped?
			e = Plane("N", N=cross(a.D(), b.D()), R=a.R())
			return distance(e, b.R())
	elif type(a) is Line and type(b) is Plane:
		if is_orthogonal(a.D(), b.N()):
			return distance(b, a.R())
	elif type(a) is Plane and type(b) is Line:
		return distance(b, a)
	elif type(a) is Line and type(b) is Point:
		if a(b) == 0:
			# https://www.mathebibel.de/abstand-punkt-gerade
			e = Plane("N", N=a.D(), R=b)
	elif type(a) is Point and type(b) is Line:
		return distance(b, a)
	elif type(a) is Plane and type(b) is Point:
		e = a.copy()
		e.N(unit(e.N()))
		return abs(e(b))
	elif type(a) is Point and type(b) is Plane:
		return distance(b, a)
	else:
		raise TypeError

def intersect(a, b):
	"""Returns a list of Points defining the intersection Point/Line/Plane of two vectometry objects, except Vector objects, else an empty list"""
	return []

from math import sqrt, acos, degrees


__project__ = "vectometry"
__version__ = "2021.9"
__author__ = "InformaticFreak"
__description__ = "A Python library for simple use of common point, vector, line and plane operations in 3-dimensional space as well as for 2-dimensions."


class Point:
	"""Point object represents a point in 3-dimensional space, means x, y and z coordinates
	
	Accepts initialization formats:
	 - List/tuple with three/two real numbers
	 - Three/Two single real numbers
	"""
	def __init__(self, *args):
		x, y, z = None, None, None
		if len(args) == 1:
			p = args[0]
			if isinstance(p, (list, tuple)):
				if len(p) == 3:
					x, y, z = p
				elif len(p) == 2:
					x, y = p
					z = 0
		elif len(args) == 2:
			x, y, z = args[0], args[1], 0
		elif len(args) == 3:
			x, y, z = args[0], args[1], args[2]
		if isinstance(x, (int, float)) and isinstance(y, (int, float)) and isinstance(z, (int, float)):
			self.__point = (x, y, z)
		else:
			raise TypeError
	
	def x(self, value=None):
		"""Returns x-coordinate value and set it to specific value, if it is given"""
		if value is not None:
			if isinstance(value, (int, float)):
				self.__point = (value, self.__point[1], self.__point[2])
			else:
				raise TypeError
		return self.__point[0]
	def y(self, value=None):
		"""Returns y-coordinate value and set it to specific value, if it is given"""
		if value is not None:
			if isinstance(value, (int, float)):
				self.__point = (self.__point[0], value, self.__point[2])
			else:
				raise TypeError
		return self.__point[1]
	def z(self, value=None):
		"""Returns z-coordinate value and set it to specific value, if it is given"""
		if value is not None:
			if isinstance(value, (int, float)):
				self.__point = (self.__point[0], self.__point[1], value)
			else:
				raise TypeError
		return self.__point[2]
	
	def copy(self):
		"""Returns an independent Point object as a copy of the original"""
		return Point(self.x(), self.y(), self.z())
	
	def __str__(self):
		"""Returns a string representing the Point object"""
		return self.__repr__()
	def __repr__(self):
		"""Returns a string representing the Point object"""
		return f"{self.__class__.__name__}({self.x()}, {self.y()}, {self.z()})"
	
	def __len__(self):
		"""Returns the amount of dimensions (always three)"""
		return len(self.__point)
	def __iter__(self):
		"""Returns an iterable object including the coordinates"""
		return iter(self.__point)
	
	def __add__(self, other):
		"""Returns a Vector object as the negated displacement of both Point objects"""
		return add(self, other)
	def __sub__(self, other):
		"""Returns a Vector object as the displacement of both Point objects"""
		return sub(self, other)	
	
	def __neg__(self):
		"""Returns a Point object with negated coordinates"""
		return neg(self)
	def __pos__(self):
		"""Returns the Point object"""
		return self
	def __round__(self, n=0):
		"""Returns a Point object, but the coordinates are rounded to the given decimal digits, default is zero decimal digits"""
		return Point(
			round(self.x(), n),
			round(self.y(), n),
			round(self.z(), n)
		)
	
	def __eq__(self, other):
		"""Returns True if every coordinate of both Point objects are equal, else False"""
		return self.x() == other.x() and self.y() == other.y() and self.z() == other.z()
	def __ne__(self, other):
		"""Returns True if not every coordinate of both Point objects are equal, else False"""
		return not self.__eq__(other)


class Vector:
	"""Vector object represents a vector in 3-dimensional space, means x, y and z coordinates.
	
	Accepts initialization formats:
	 - Point object (location vector)
	 - List/tuple with three/two real numbers
	 - Two Point objects (vector from first Point object to second Point object)
	 - Three/Two single real numbers
	"""
	def __init__(self, *args):
		if len(args) == 1:
			p = args[0]
			if type(p) is Point:
				self.__point = p.copy()
			elif isinstance(p, (list, tuple)):
				self.__point = Point(p)
			else:
				raise TypeError
		elif len(args) == 2:
			p1, p2 = args[0], args[1]
			if type(p1) is Point and type(p2) is Point:
				p = sub(p2, p1)
				self.__point = p.point()
			elif isinstance(p1, (int, float)) and isinstance(p2, (int, float)):
				self.__point = Point(p1, p2, 0)
			else:
				raise TypeError
		elif len(args) == 3:
			self.__point = Point(*args)
		else:
			raise TypeError
	
	def x(self, value=None):
		"""Returns x-coordinate value and set it to specific value, if it is given"""
		return self.__point.x(value)
	def y(self, value=None):
		"""Returns y-coordinate value and set it to specific value, if it is given"""
		return self.__point.y(value)
	def z(self, value=None):
		"""Returns z-coordinate value and set it to specific value, if it is given"""
		return self.__point.z(value)
	def point(self):
		"""Returns the coordinates to which the vector pointing as a reference to the Point object"""
		return self.__point
	
	def copy(self):
		"""Returns an independent Vector object as a copy of the original"""
		return Vector(self.point().copy())
	
	def __str__(self):
		"""Returns a string representing the Vector object"""
		return self.__repr__()
	def __repr__(self):
		"""Returns a string representing the Vector object"""
		return f"{self.__class__.__name__}({self.x()}, {self.y()}, {self.z()})"
	
	def __len__(self):
		"""Returns the amount of dimensions (always three)"""
		return len(self.__point)
	def __iter__(self):
		"""Returns an iterable object including the coordinates"""
		return iter(self.__point)
	
	def __add__(self, other):
		"""Returns the addition of two Vector objects"""
		return add(self, other)
	def __sub__(self, other):
		"""Returns the difference of two Vector objects"""
		return sub(self, other)
	def __mod__(self, other):
		"""Returns the cross product of two Vector objects"""
		return cross(self, other)
	def __truediv__(self, other):
		"""Returns the multiplication of a Vector object by the reciprocal of the real number"""
		return div(self, other)
	def __rmul__(self, other):
		"""Returns the multiplication of a Vector object by a real number or by another Vector object, means the dot product"""
		return self.__mul__(other)
	def __mul__(self, other):
		"""Returns the multiplication of a Vector object by a real number or by another Vector object, means the dot product"""
		if type(other) is int or type(other) is float: 
			return mul(self, other)
		elif type(other) is Vector:
			return dot(self, other)
		else:
			raise TypeError
	
	def __neg__(self):
		"""Returns a Vector object with negated coordinates"""
		return neg(self)
	def __pos__(self):
		"""Returns the Vector object"""
		return self
	def __abs__(self):
		"""Returns the magnitude of the Vector object as a real number"""
		return norm(self)
	def __round__(self, n=0):
		"""Returns a Vector object, but the coordinates are rounded to the given decimal digits, default is zero decimal digits"""
		return Vector(
			round(self.x(), n),
			round(self.y(), n),
			round(self.z(), n)
		)
	
	def __eq__(self, other):
		"""Returns True if both points on which the vectors points are the same (same lenght and same direction), else False"""
		return self.point() == other.point()
	def __ne__(self, other):
		"""Returns True if both points on which the vectors pointing are not the same, else False"""
		return not self.__eq__(other)


class Line:
	"""Line object represents a straight line in 3-dimensional space, means x, y and z coordinates.
	
	Accepts initialization equations:
	(Upper case letters represents Points/Vectors and lower case letters represents real numbers)
	 - [P]	Parameter equation		0 = R + s*D - X			{R as Point; D as Vector}	(default mode)
									0 = R + s*(D-R) - X		{R,D as Point}
	"""
	def __init__(self, fmt="P", **kwargs):
		if type(fmt) is str:
			fmt = fmt.upper()
			if fmt == "P":
				if type(kwargs.get("R")) is Point:
					R = kwargs["R"]
				else:
					raise TypeError
				if type(kwargs.get("D")) is Point:
					D = sub(kwargs["D"], R)
				elif type(kwargs.get("D")) is Vector:
					D = kwargs["D"]
				else:
					raise TypeError
			else:
				raise ValueError
		else:
			raise TypeError
		self.__R = R
		self.__D = D
	
	def R(self, value=None):
		"""Returns the receptor Point R as real number and set it to specific value, if it is given"""
		if value is not None:
			if type(value) is Vector:
				self.__R = value
			else:
				raise TypeError
		return self.__R
	def D(self, value=None):
		"""Returns the direction Vector D and set it to specific value, if it is given"""
		if value is not None:
			if isinstance(value, (int, float)):
				self.__D = value
			else:
				raise TypeError
		return self.__D
	
	def copy(self):
		"""Returns an independent Plane object as a copy of the original"""
		return Line("P", R=self.R().copy(), D=self.D().copy())
	
	def __str__(self):
		"""Returns a string representing the Line object"""
		return self.__repr__()
	def __repr__(self):
		"""Returns a string representing the Line object"""
		return f"{self.__class__.__name__}('P', R={self.R()}, D={self.D()})"
		
	def __round__(self, n=0):
		"""Returns a Line object, but the coordinates of the receptor point R and direction Vector D are rounded to the given decimal digits, default is zero decimal digits"""
		return Line("P", R=round(self.R(), n), D=round(self.D(), n))
	
	def __eq__(self, other):
		"""Returns True if both Lines are identical to each other (means parallel and no distance), else False"""
		return is_collinear(self.D(), other.D()) and distance(self, other) == 0
	def __ne__(self, other):
		"""Returns True if both Lines are not identical to each other, else False"""
		return not self.__eq__(other)
	
	def __call__(self, X):
		"""Insert the given Point into the line function \'R + s*D - X\' and return the result for the parameter s. If the result is zero, the given Point is not located in the line."""
		if type(X) is Point:
			s1 = (X.x() - self.R.x()) / self.D.x()
			s2 = (X.y() - self.R.y()) / self.D.y()
			s3 = (X.z() - self.R.z()) / self.D.z()
			if s1 == s2 == s3:
				return s1
			else:
				return 0
		elif type(X) is Vector:
			return self.__call__(X.point())
		else:
			raise TypeError


class Plane:
	"""Plane object represents a plane/layer in 3-dimensional space, means x, y and z coordinates.
	
	Accepts initialization equations:
	(Upper case letters represents Points/Vectors and lower case letters represents real numbers)
	 - [P]	Parameter equation	0 = R + m*D + t*S - X						{R as Point; D,S as Vector}					(default mode)
								0 = R + m*(D-R) + t*(S-R) - X				{R,D,S as Point}
	 - [C]	Coordinate equation	0 = Nx*Xx + Ny*Xy + Nz*Xz + n0 = N ° X + n0	{N as Vector(Nx, Ny, Nz); n0 as int/float}
	 - [N]	Normal equation		0 = N ° (X-R)								{N as Vector; R as Point}
	"""
	def __init__(self, fmt="P", **kwargs):
		if type(fmt) is str:
			fmt = fmt.upper()
			if fmt == "P":
				if type(kwargs.get("R")) is Point:
					R = kwargs["R"]
				else:
					raise TypeError
				if type(kwargs.get("D")) is type(kwargs.get("S")) is Point:
					D = sub(kwargs["D"], R)
					S = sub(kwargs["S"], R)
				elif type(kwargs.get("D")) is type(kwargs.get("S")) is Vector:
					D = kwargs["D"]
					S = kwargs["S"]
				else:
					raise TypeError
				N = cross(D, S)
			elif fmt == "C":
				if type(kwargs.get("N")) is Vector and isinstance(kwargs.get("n0"), (int, float)):
					N = kwargs["N"]
					n0 = kwargs["n0"]
					x, y, z = 0, 0, 0
					if N.x() != 0:
						x = n0 / N.x()
					elif N.y() != 0:
						y = n0 / N.y()
					elif N.z() != 0:
						z = n0 / N.z()
					else:
						raise ZeroDivisionError
					R = Point(x, y, z)
				else:
					raise TypeError
			elif fmt == "N":
				if type(kwargs.get("N")) is Vector and type(kwargs.get("R")) is Point:
					N = kwargs["N"]
					R = kwargs["R"]
				else:
					raise TypeError
			else:
				raise ValueError
		else:
			raise TypeError
		self.__N = N
		self.__R = R
	
	def N(self, value=None):
		"""Returns the normal Vector N and set it to specific value, if it is given"""
		if value is not None:
			if type(value) is Vector:
				self.__N = value
			else:
				raise TypeError
		return self.__N
	def R(self, value=None):
		"""Returns the receptor Point R and set it to specific value, if it is given"""
		if value is not None:
			if type(value) is Point:
				self.__R = value
			else:
				raise TypeError
		return self.__R
	
	def fmt(self, fmt="P"):
		"""Returns for one of the initialization formats the attributes of the Plane as dictionary"""
		fmt = fmt.upper()
		if fmt == "P":
			return {
				# "R": Point(0, 0, dot(self.N(), self.R()) / self.N().z()),
				"R": self.R(),
				"D": Vector(1, 0, -(self.N().x() / self.N().z())),
				"S": Vector(0, 1, -(self.N().y() / self.N().z()))
			}
		elif fmt == "C":
			return {
				"N": self.N(),
				"n0": dot(self.N(), Vector(self.R()))
			}
		elif fmt == "N":
			return {
				"N": self.N(),
				"R": self.R()
			}
		else:
			raise ValueError
	
	def copy(self):
		"""Returns an independent Plane object as a copy of the original"""
		return Plane("N", N=self.N().copy(), R=self.R().copy())
	
	def __str__(self):
		"""Returns a string representing the Plane object"""
		return self.__repr__()
	def __repr__(self):
		"""Returns a string representing the Plane object"""
		return f"{self.__class__.__name__}('N', N={self.N()}, R={self.R()})"
		
	def __round__(self, n=0):
		"""Returns a Plane object, but the coordinates of the normal Vector N and receptor Point R are rounded to the given decimal digits, default is zero decimal digits"""
		return Plane("N", N=round(self.N(), n), R=round(self.R(), n))
	
	def __eq__(self, other):
		"""Returns True if both Planes are identical to each other (means parallel and no distance), else False"""
		return is_collinear(self.N(), other.N()) and distance(self, other) == 0
	def __ne__(self, other):
		"""Returns True if both Planes are not identical to each other, else False"""
		return not self.__eq__(other)
	
	def __call__(self, X):
		"""Insert the given Point into the plane function \'N ° (X-R)\' and return the result of it. If the result is zero, the given Point is located in the plane."""
		if type(X) is Point:
			return dot(self.N(), sub(X, self.R()))
		elif type(X) is Vector:
			return self.__call__(X.point())
		else:
			raise TypeError


def norm(a):
	"""Returns the magnitude of the Vector object as a real number"""
	if type(a) is Vector:
		return sqrt(sum([ p**2 for p in a ]))
	else:
		raise TypeError
def unit(a):
	"""Returns the unit vector of the Vector object, it means a vector with the same direction but a magnitude of one"""
	if type(a) is Vector:
		return div(a, norm(a))
	else:
		raise TypeError
def neg(a):
	"""Returns the given object (Point/Vector) with negated coordinates"""
	if type(a) is Vector:
		return Vector([ -p for p in a ])
	elif type(a) is Point:
		return Point([ -p for p in a ])
	else:
		raise TypeError

def add(a, b):
	"""Cases:
	 - Two Vector objects: Returns the addition of two Vector objects
	 - Two Point objects: Returns a Vector object as the negated displacement of both Point objects
	"""
	if ( type(a) is type(b) is Vector ) or ( type(a) is type(b) is Point ):
		return Vector(
			a.x()+b.x(),
			a.y()+b.y(),
			a.z()+b.z()
		)
	else:
		raise TypeError
def sub(a, b):
	"""Cases:
	 - Two Vector objects: Returns the difference of two Vector objects
	 - Two Point objects: Returns a Vector object as the displacement of both Point objects
	"""
	return add(a, neg(b))

def mul(a, b):
	"""Returns the multiplication of a Vector object by a real number"""
	if isinstance(a, (int, float)) and type(b) is Vector:
		return mul(b, a)
	elif isinstance(b, (int, float)) and type(a) is Vector:
		return Vector([ p*b for p in a ])
	else:
		raise TypeError
def div(a, b):
	"""Returns the multiplication of a Vector object by the reciprocal of the real number"""
	if isinstance(b, (int, float)):
		return mul(a, 1/b)
	else:
		raise TypeError

def cross(a, b):
	"""Returns the cross product of two Vector objects as a Vector object"""
	if type(a) is Vector and type(b) is Vector:
		return Vector(
			a.y()*b.z() - b.y()*a.z(),
			a.z()*b.x() - b.z()*a.x(),
			a.x()*b.y() - b.x()*a.y()
		)
	else:
		raise TypeError
def dot(a, b):
	"""Returns the dot product of two Vector objects as a real number"""
	if type(a) is Vector and type(b) is Vector:
		return a.x()*b.x() + a.y()*b.y() + a.z()*b.z()
	else:
		raise TypeError

def det(a, b, c):
	"""Returns the determinant of three Vector objects as a real number"""
	return dot(cross(a, b), c)

def spate(a, b, c):
	"""Returns the volume of a parallelepiped spanned by three Vector objects as a positive real number including zero"""
	return abs(det(a, b, c))
def area(a, b):
	"""Returns the area of a parallelogram spanned by two Vector objects as a position real number including zero"""
	return norm(cross(a, b))

def angle(a, b, mode="DEG"):
	"""Returns the smaller angle between two vectometry objects as a real number, except Point objects
	Mode:
	 - In degree with mode \'DEG\'	(default mode)
	 - In radian with mode \'RAD\'
	"""
	if type(a) is type(b) is Vector:
		rad_angle = acos(dot(a, b) / (norm(a) * norm(b)))
	elif type(a) is type(b) is Line:
		return angle(a.D(), b.D())
	elif type(a) is type(b) is Plane:
		return angle(a.N(), b.N())
	elif type(a) is Line and type(b) is Vector:
		return angle(a.D(), b)
	elif type(a) is Plane and type(b) is Vector:
		return angle(a.N(), b)
	elif type(a) is Line and type(b) is Plane:
		return angle(a.D(), b.N())
	else:
		raise TypeError
	mode = mode.upper()
	if mode == "DEG":
		return degrees(rad_angle)
	elif mode == "RAD":
		return rad_angle
	else:
		raise ValueError

def is_complanar(a, b, c):
	"""Returns True if all three Vector objects are in the same plane, else False"""
	return spate(a, b, c) == 0
def is_collinear(a, b):
	"""Returns True if both Vector objects are parallel or anti-parallel to each other, else False"""
	return abs(dot(a, b)) == norm(a) * norm(b)
def is_orthogonal(a, b):
	"""Returns True if both Vector objects are perpendicular to each other, else False"""
	return dot(a, b) == 0

def distance(a, b):
	"""Returns the distance between two vectometry objects, except Vector objects"""
	if type(a) is type(b) is Point:
		return norm(Vector(a, b))
	elif type(a) is type(b) is Plane:
		if is_collinear(a.N(), b.N()):
			return distance(a, b.R())
	elif type(a) is type(b) is Line:
		if is_collinear(a.D(), b.D()):
			# https://www.mathebibel.de/abstand-paralleler-geraden
			pass
		elif "WINDSCHIEF": #engl.: warped?
			e = Plane("N", N=cross(a.D(), b.D()), R=a.R())
			return distance(e, b.R())
	elif type(a) is Line and type(b) is Plane:
		if is_orthogonal(a.D(), b.N()):
			return distance(b, a.R())
	elif type(a) is Plane and type(b) is Line:
		return distance(b, a)
	elif type(a) is Line and type(b) is Point:
		if a(b) == 0:
			# https://www.mathebibel.de/abstand-punkt-gerade
			e = Plane("N", N=a.D(), R=b)
	elif type(a) is Point and type(b) is Line:
		return distance(b, a)
	elif type(a) is Plane and type(b) is Point:
		e = a.copy()
		e.N(unit(e.N()))
		return abs(e(b))
	elif type(a) is Point and type(b) is Plane:
		return distance(b, a)
	else:
		raise TypeError

def intersect(a, b):
	"""Returns a list of Points defining the intersection Point/Line/Plane of two vectometry objects, except Vector objects, else an empty list"""
	return []
Point object represents a point in 3-dimensional space, means x, y and z coordinates
 	
 	Accepts initialization formats:
 	 - List/tuple with three/two real numbers
 	 - Three/Two single real numbers Returns x-coordinate value and set it to specific value, if it is given Returns y-coordinate value and set it to specific value, if it is given Returns z-coordinate value and set it to specific value, if it is given Returns an independent Point object as a copy of the original Returns a string representing the Point object Returns a string representing the Point object Returns the amount of dimensions (always three) Returns an iterable object including the coordinates Returns a Vector object as the negated displacement of both Point objects Returns a Vector object as the displacement of both Point objects Returns a Point object with negated coordinates Returns the Point object Returns a Point object, but the coordinates are rounded to the given decimal digits, default is zero decimal digits Returns True if every coordinate of both Point objects are equal, else False Returns True if not every coordinate of both Point objects are equal, else False Vector object represents a vector in 3-dimensional space, means x, y and z coordinates.
 	
 	Accepts initialization formats:
 	 - Point object (location vector)
 	 - List/tuple with three/two real numbers
 	 - Two Point objects (vector from first Point object to second Point object)
 	 - Three/Two single real numbers Returns x-coordinate value and set it to specific value, if it is given Returns y-coordinate value and set it to specific value, if it is given Returns z-coordinate value and set it to specific value, if it is given Returns the coordinates to which the vector pointing as a reference to the Point object Returns an independent Vector object as a copy of the original Returns a string representing the Vector object Returns a string representing the Vector object Returns the amount of dimensions (always three) Returns an iterable object including the coordinates Returns the addition of two Vector objects Returns the difference of two Vector objects Returns the cross product of two Vector objects Returns the multiplication of a Vector object by the reciprocal of the real number Returns the multiplication of a Vector object by a real number or by another Vector object, means the dot product Returns the multiplication of a Vector object by a real number or by another Vector object, means the dot product Returns a Vector object with negated coordinates Returns the Vector object Returns the magnitude of the Vector object as a real number Returns a Vector object, but the coordinates are rounded to the given decimal digits, default is zero decimal digits Returns True if both points on which the vectors points are the same (same lenght and same direction), else False Returns True if both points on which the vectors pointing are not the same, else False Line object represents a straight line in 3-dimensional space, means x, y and z coordinates.
 	
 	Accepts initialization equations:
 	(Upper case letters represents Points/Vectors and lower case letters represents real numbers)
 	 - [P]	Parameter equation		0 = R + s*D - X			{R as Point; D as Vector}	(default mode)
 									0 = R + s*(D-R) - X		{R,D as Point} Returns the receptor Point R as real number and set it to specific value, if it is given Returns the direction Vector D and set it to specific value, if it is given Returns an independent Plane object as a copy of the original Returns a string representing the Line object Returns a string representing the Line object Returns a Line object, but the coordinates of the receptor point R and direction Vector D are rounded to the given decimal digits, default is zero decimal digits Returns True if both Lines are identical to each other (means parallel and no distance), else False Returns True if both Lines are not identical to each other, else False Insert the given Point into the line function \'R + s*D - X\' and return the result for the parameter s. If the result is zero, the given Point is not located in the line. Plane object represents a plane/layer in 3-dimensional space, means x, y and z coordinates.
 	
 	Accepts initialization equations:
 	(Upper case letters represents Points/Vectors and lower case letters represents real numbers)
 	 - [P]	Parameter equation	0 = R + m*D + t*S - X						{R as Point; D,S as Vector}					(default mode)
 								0 = R + m*(D-R) + t*(S-R) - X				{R,D,S as Point}
 	 - [C]	Coordinate equation	0 = Nx*Xx + Ny*Xy + Nz*Xz + n0 = N ° X + n0	{N as Vector(Nx, Ny, Nz); n0 as int/float}
 	 - [N]	Normal equation		0 = N ° (X-R)								{N as Vector; R as Point} Returns the normal Vector N and set it to specific value, if it is given Returns the receptor Point R and set it to specific value, if it is given Returns for one of the initialization formats the attributes of the Plane as dictionary # "R": Point(0, 0, dot(self.N(), self.R()) / self.N().z()), Returns an independent Plane object as a copy of the original Returns a string representing the Plane object Returns a string representing the Plane object Returns a Plane object, but the coordinates of the normal Vector N and receptor Point R are rounded to the given decimal digits, default is zero decimal digits Returns True if both Planes are identical to each other (means parallel and no distance), else False Returns True if both Planes are not identical to each other, else False Insert the given Point into the plane function \'N ° (X-R)\' and return the result of it. If the result is zero, the given Point is located in the plane. Returns the magnitude of the Vector object as a real number Returns the unit vector of the Vector object, it means a vector with the same direction but a magnitude of one Returns the given object (Point/Vector) with negated coordinates Cases:
 	 - Two Vector objects: Returns the addition of two Vector objects
 	 - Two Point objects: Returns a Vector object as the negated displacement of both Point objects Cases:
 	 - Two Vector objects: Returns the difference of two Vector objects
 	 - Two Point objects: Returns a Vector object as the displacement of both Point objects Returns the multiplication of a Vector object by a real number Returns the multiplication of a Vector object by the reciprocal of the real number Returns the cross product of two Vector objects as a Vector object Returns the dot product of two Vector objects as a real number Returns the determinant of three Vector objects as a real number Returns the volume of a parallelepiped spanned by three Vector objects as a positive real number including zero Returns the area of a parallelogram spanned by two Vector objects as a position real number including zero Returns the smaller angle between two vectometry objects as a real number, except Point objects
 	Mode:
 	 - In degree with mode \'DEG\'	(default mode)
 	 - In radian with mode \'RAD\' Returns True if all three Vector objects are in the same plane, else False Returns True if both Vector objects are parallel or anti-parallel to each other, else False Returns True if both Vector objects are perpendicular to each other, else False Returns the distance between two vectometry objects, except Vector objects # https://www.mathebibel.de/abstand-paralleler-geraden #engl.: warped? # https://www.mathebibel.de/abstand-punkt-gerade Returns a list of Points defining the intersection Point/Line/Plane of two vectometry objects, except Vector objects, else an empty list