Module ctsimu.primitives
Structures for linear algebra and geometry: vectors, matrices, lines and polygons.
Functions
def rotation_matrix(axis: Vector, angle: float) ‑> Matrix
-
A matrix that performs a 3D vector rotation around the given
axis
vector by the givenangle
(in rad).Note that this is only a rotation matrix; translations are not taken into account. This means that the pivot point will always be the origin of the object that you rotate, i.e., the rotation axis vector is attached to the object's origin.
Parameters
axis
:Vector
- Rotation axis. Must not be a unit vector.
angle
:float
- Rotation angle.
Returns
R
:Matrix
- Rotation matrix.
Classes
class Line2D (m: float = None, n: float = None)
-
A mathematical line in 2D space, with a slope and an offset.
y = m*x + n
Attributes
m
:float
- Slope
n
:float
- Vertical offset (i.e., intersection with y axis)
Initialize with specified slope and offset (both optional).
Parameters
m
:float
, optional- Slope
n
:float
, optional- Vertical offset (i.e., intersection with y axis)
Expand source code
class Line2D: """A mathematical line in 2D space, with a slope and an offset. `y = m*x + n` Attributes ---------- m : float Slope n : float Vertical offset (i.e., intersection with y axis) """ def __init__(self, m:float=None, n:float=None): """Initialize with specified slope and offset (both optional). Parameters ---------- m : float, optional Slope n : float, optional Vertical offset (i.e., intersection with y axis) """ self.m = m self.n = n def __str__(self): return "m={}, n={}".format(self.m, self.n) def set(self, m:float, n:float): """Set line parameters. Parameters ---------- m : float Slope n : float Vertical offset (i.e., intersection with y axis) """ self.m = m self.n = n def set_from_points(self, p0:'Vector', p1:'Vector'): """Set line slope `m` and offset `n` from two given points `p0` and `p1`. Both points are assumed to be on the line. Parameters ---------- p0 : Vector First point, given by 2D vector. For higher-dimensional vectors, only the first two coordinates are considered. p1 : Vector Second point, given by 2D vector. For higher-dimensional vectors, only the first two coordinates are considered. """ # points are defined by 2D vectors x0 = p0.x() y0 = p0.y() x1 = p1.x() y1 = p1.y() if x0 != x1: self.m = (y1-y0) / (x1-x0) self.n = y0 - self.m*x0 else: # Vertical line self.m = math.inf self.n = x0 # Store x intersection in n if line is vertical def intersection(self, v:'Line2D') -> 'Vector': """Intersection point with another line. Parameters ---------- v : Line2D Another line that intersects this line. Returns ------- intersection : Vector 2D vector that contains the coordinates of the intersection point. Raises ------ Exception : "Lines are parallel." If the lines don't intersect. """ m0 = self.m n0 = self.n m1 = v.m n1 = v.n if m0 == m1: # Lines are parallel raise Exception("Lines are parallel.") if m0 != math.inf and m1 != math.inf: xs = (n1-n0)/(m0-m1) ys = m0*xs + n0 return Vector(x=xs, y=ys) elif m0 == math.inf and m1 != math.inf: xs = n0 ys = m1*xs + n1 return Vector(x=xs, y=ys) elif m0 != math.inf and m1 == math.inf: xs = n1 ys = m0*xs + n0 return Vector(x=xs, y=ys)
Methods
def intersection(self, v: Line2D) ‑> Vector
def set(self, m: float, n: float)
-
Set line parameters.
Parameters
m
:float
- Slope
n
:float
- Vertical offset (i.e., intersection with y axis)
def set_from_points(self, p0: Vector, p1: Vector)
-
Set line slope
m
and offsetn
from two given pointsp0
andp1
. Both points are assumed to be on the line.Parameters
class Matrix (cols: int = None, rows: int = None, values: list = None, numpy_data=None)
-
Simple matrix class.
Attributes
cols
:int
- Number of matrix columns.
rows
:int
- Number of matrix rows.
n_entries
:int
- Number of matrix elements. (Computed internally whenever matrix size is set or changed.)
value
:numpy.ndarray
- NumPy array that contains the matrix values.
Initialize matrix by given size or numpy data array.
If
cols
androws
are notNone
, a matrix of the given size will be created with all elements being zero. Otherwise, the matrix will be set up from thenumpy_data
array if provided.Parameters
cols
:int
, optional- Number of matrix columns.
rows
:int
, optional- Number of matrix rows.
values
:list
, optional- List of lists to initialize matrix.
numpy_data
:numpy.ndarray
, optional- 2-dimensional NumPy array. The number of columns and rows is determined from the array.
Expand source code
class Matrix: """Simple matrix class. Attributes ---------- cols : int Number of matrix columns. rows : int Number of matrix rows. n_entries : int Number of matrix elements. (Computed internally whenever matrix size is set or changed.) value : numpy.ndarray NumPy array that contains the matrix values. """ def __init__(self, cols:int=None, rows:int=None, values:list=None, numpy_data=None): """Initialize matrix by given size or numpy data array. If `cols` and `rows` are not `None`, a matrix of the given size will be created with all elements being zero. Otherwise, the matrix will be set up from the `numpy_data` array if provided. Parameters ---------- cols : int, optional Number of matrix columns. rows : int, optional Number of matrix rows. values : list, optional List of lists to initialize matrix. numpy_data : numpy.ndarray, optional 2-dimensional NumPy array. The number of columns and rows is determined from the array. """ if values is not None: npda = numpy.array(values) self.set_numpy_data_array(npda) elif numpy_data is not None: self.set_numpy_data_array(numpy_data) elif (cols is not None) and (rows is not None): self.reset(cols=cols, rows=rows) else: raise Exception("Matrix initialization failed. Number of rows and columns must be provided, or an array of values.") def __str__(self): return f"{self.value}" def __add__(self, x): result = self.get_copy() result.add(x) return result def __sub__(self, x): result = self.get_copy() result.subtract(x) return result def __mul__(self, x): result = self.get_copy() result.multiply(x) if isinstance(x, Vector): # We need to return a vector return Vector(numpy_data=result.value) return result def __truediv__(self, x): result = self.get_copy() result.divide(x) return result def __floordiv__(self, x): result = self.get_copy() result.floor_divide(x) return result def __radd__(self, x): return self.__add__(x) def __rsub__(self, x): if isinstance(x, numbers.Number): result = self.get_copy() result.multiply(-1) result.add(x) return result def __rmul__(self, x): if isinstance(x, numbers.Number): return self.__mul__(x) def size(self) -> int: """Get the number of matrix elements. Returns ------- n_entries : int Number of matrix elements. """ return self.n_entries def same_size(self, M:'Matrix') -> bool: """Check if this matrix has the same size as the given matrix `M`. Parameters ---------- M : Matrix Returns ------- same_size : bool `True` if `M` has the same number of rows and columns as this matrix, otherwise `False`. """ if (self.cols == M.cols) and (self.rows == M.rows): return True return False def reset(self, cols:int, rows:int): """Set matrix size to given number of columns and rows, and set all matrix elements to zero. Parameters ---------- cols : int Number of matrix columns. rows : int Number of matrix rows. """ self.cols = cols self.rows = rows self.n_entries = cols*rows self.value = numpy.zeros((rows, cols), dtype=numpy.float64) def make_identity(self): """Make this an identity matrix.""" self.value = numpy.zeros((self.rows, self.cols), dtype=numpy.float64) for i in range(min(self.cols, self.rows)): self.value[i][i] = float(1) def set(self, col:int, row:int, value:float): """Set a matrix element's value. Parameters ---------- col : int The element's column position. row : int The element's row position. value : float The new value of the matrix element. """ self.value[row][col] = value def set_numpy_data_array(self, numpy_data:'numpy.ndarray'): """Set up the matrix from a given NumPy array. Gets the number of columns and rows from the array and uses the array for its values. Warning: the array content is not copied, only referenced. If you change the array afterwards, the matrix content will change as well. Use `numpy.copy()` if you want to pass a copy of your array. Parameters ---------- numpy_data : numpy.ndarray NumPy array of the new matrix values. """ self.rows = len(numpy_data) self.cols = len(numpy_data[0]) self.n_entries = self.cols * self.rows self.value = numpy_data.astype(float) def update_rows_cols(self): """Update the numbers of rows and columns based on the current shape of `self.value`.""" # Update number of rows and columns sh = self.value.shape self.rows = sh[0] if len(sh) > 1: self.cols = sh[1] else: # Created a vector self.cols = 1 def copy(self, x:'Matrix'): """Make this matrix a copy of the given matrix `x`. Parameters ---------- x : Matrix The matrix whose contents shall be copied. """ self.set_numpy_data_array(numpy_data=numpy.copy(x.value)) def get(self, col:int, row:int) -> float: """Get a matrix element's value. Parameters ---------- col : int Element's column position. row : int Element's row position. Returns ------- value : float Value of the requested matrix element. """ if len(self.value) > row: if len(self.value[row]) > col: return self.value[row][col] raise Exception(f"Matrix.get(col={col}, row={row}): requested index does not exist.") def get_copy(self) -> 'Matrix': """Get a copy of this matrix object. Returns ------- copy : Matrix Copy of this matrix object. """ new_values = numpy.copy(self.value) return Matrix(numpy_data=new_values) def add(self, x): """Add a scalar to all matrix elements, or add another compatible matrix of the same size. Parameters ---------- x : Matrix or float Matrix to be added to this matrix, or scalar to be added to all matrix elements. """ if isinstance(x, Matrix): # 'x' is another matrix: if self.same_size(x): self.value = numpy.add(self.value, x.value) else: raise Exception("Incompatible matrix sizes. Can only add matrices of same size.") elif isinstance(x, numbers.Number): # 'x' is a scalar: self.value = numpy.add(self.value, x) else: raise Exception(f"Matrix addition failed: M+A is only supported when A is of type 'Matrix' or a scalar. The given type is not supported: {type(x)}.") def subtract(self, x): """Subtract a scalar from all matrix elements, or subtract another compatible matrix. Parameters ---------- x : Matrix or float Matrix to be subtracted from this matrix, or scalar to be subtracted from all matrix elements. """ if isinstance(x, Matrix): # 'x' is another matrix: if self.same_size(x): self.value = numpy.subtract(self.value, x.value) else: raise Exception("Incompatible matrix sizes. Can only subtract matrices of same size.") elif isinstance(x, numbers.Number): # 'x' is a scalar: self.value = numpy.subtract(self.value, x) else: raise Exception(f"Matrix subtraction failed: M-A is only supported when A is of type 'Matrix' or a scalar. The given type is not supported: {type(x)}.") def multiply(self, x): """Multiply a scalar to all matrix elements, or perform matrix multiplication with a compatible matrix. Parameters ---------- x : Matrix or float Compatible matrix to be multiplied with this matrix, or scalar to be multiplied to all matrix elements. """ if isinstance(x, Matrix) or isinstance(x, Vector): # 'x' is another matrix or a vector: self.value = numpy.matmul(self.value, x.value) self.n_entries = self.value.size self.update_rows_cols() elif isinstance(x, numbers.Number): # 'x' is a scalar: self.value = numpy.multiply(self.value, x) else: raise Exception(f"Matrix multiplication failed: M*A is only supported when A is of type 'Matrix' or 'Vector' or a scalar. The given type is not supported: {type(x)}.") def divide(self, x): """Divide all matrix elements by a scalar, or perform element-wise division with a matrix of the same size. Parameters ---------- x : Matrix or float Matrix of same size for element-wise division with this matrix, or scalar to divide all matrix elements. """ if isinstance(x, Matrix): # 'x' is another matrix... element-wise division: if self.same_size(x): self.value = numpy.divide(self.value, x.value) self.n_entries = self.value.size else: raise Exception("Incompatible matrix sizes. Can only run an element-wise division for matrices of same size.") elif isinstance(x, numbers.Number): # 'x' is a scalar: self.value = numpy.divide(self.value, x) else: raise Exception(f"Matrix division failed: M/A is only supported when A is of type 'Matrix' or a scalar. The given type is not supported: {type(x)}.") def floor_divide(self, x): """Floor-divide all matrix elements by a scalar, or perform element-wise division with a matrix of the same size. Parameters ---------- x : Matrix or float Matrix of same size for element-wise floor-division with this matrix, or scalar to floor-divide all matrix elements. """ if isinstance(x, Matrix): # 'x' is another matrix... element-wise division: if self.same_size(x): self.value = numpy.floor_divide(self.value, x.value) else: raise Exception("Incompatible matrix sizes. Can only run an element-wise floor-division for matrices of same size.") elif isinstance(x, numbers.Number): # 'x' is a scalar: self.value = numpy.floor_divide(self.value, x) else: raise Exception(f"Matrix floor-division failed: M//A is only supported when A is of type 'Matrix' or a scalar. The given type is not supported: {type(x)}.") def scale(self, factor:float): """Scale matrix by a scalar factor. Parameters ---------- factor : float Factor that scales all matrix elements. """ self.value = numpy.multiply(self.value, factor)
Methods
def add(self, x)
-
Add a scalar to all matrix elements, or add another compatible matrix of the same size.
Parameters
x
:Matrix
orfloat
- Matrix to be added to this matrix, or scalar to be added to all matrix elements.
def copy(self, x: Matrix)
-
Make this matrix a copy of the given matrix
x
.Parameters
x
:Matrix
- The matrix whose contents shall be copied.
def divide(self, x)
-
Divide all matrix elements by a scalar, or perform element-wise division with a matrix of the same size.
Parameters
x
:Matrix
orfloat
- Matrix of same size for element-wise division with this matrix, or scalar to divide all matrix elements.
def floor_divide(self, x)
-
Floor-divide all matrix elements by a scalar, or perform element-wise division with a matrix of the same size.
Parameters
x
:Matrix
orfloat
- Matrix of same size for element-wise floor-division with this matrix, or scalar to floor-divide all matrix elements.
def get(self, col: int, row: int) ‑> float
-
Get a matrix element's value.
Parameters
col
:int
- Element's column position.
row
:int
- Element's row position.
Returns
value
:float
- Value of the requested matrix element.
def get_copy(self) ‑> Matrix
def make_identity(self)
-
Make this an identity matrix.
def multiply(self, x)
-
Multiply a scalar to all matrix elements, or perform matrix multiplication with a compatible matrix.
Parameters
x
:Matrix
orfloat
- Compatible matrix to be multiplied with this matrix, or scalar to be multiplied to all matrix elements.
def reset(self, cols: int, rows: int)
-
Set matrix size to given number of columns and rows, and set all matrix elements to zero.
Parameters
cols
:int
- Number of matrix columns.
rows
:int
- Number of matrix rows.
def same_size(self, M: Matrix) ‑> bool
-
Check if this matrix has the same size as the given matrix
M
.Parameters
M
:Matrix
Returns
same_size
:bool
True
ifM
has the same number of rows and columns as this matrix, otherwiseFalse
.
def scale(self, factor: float)
-
Scale matrix by a scalar factor.
Parameters
factor
:float
- Factor that scales all matrix elements.
def set(self, col: int, row: int, value: float)
-
Set a matrix element's value.
Parameters
col
:int
- The element's column position.
row
:int
- The element's row position.
value
:float
- The new value of the matrix element.
def set_numpy_data_array(self, numpy_data: numpy.ndarray)
-
Set up the matrix from a given NumPy array.
Gets the number of columns and rows from the array and uses the array for its values.
Warning: the array content is not copied, only referenced. If you change the array afterwards, the matrix content will change as well. Use
numpy.copy()
if you want to pass a copy of your array.Parameters
numpy_data
:numpy.ndarray
- NumPy array of the new matrix values.
def size(self) ‑> int
-
Get the number of matrix elements.
Returns
n_entries
:int
- Number of matrix elements.
def subtract(self, x)
-
Subtract a scalar from all matrix elements, or subtract another compatible matrix.
Parameters
x
:Matrix
orfloat
- Matrix to be subtracted from this matrix, or scalar to be subtracted from all matrix elements.
def update_rows_cols(self)
-
Update the numbers of rows and columns based on the current shape of
self.value
.
class Polygon (*points: Vector)
-
A general 2D polygon with N points in space.
Attributes
points
:list
- List of points that make up the polygon, each point represented by a
Vector
. vertex_order_CCW
:bool
True
if the vertex order is counter-clockwise (standard) orFalse
if clockwise.
Initialize with a series of points. They should be defined in counter-clockwise direction. They are objects of class
Vector
.If the points are specified in clockwise direction, the parameter
vertex_order_CCW
should be set toFalse
manually.Parameters
*points
:Vector
- An arbitrary number of points given.
Expand source code
class Polygon: """ A general 2D polygon with N points in space. Attributes ---------- points : list List of points that make up the polygon, each point represented by a `Vector`. vertex_order_CCW : bool `True` if the vertex order is counter-clockwise (standard) or `False` if clockwise. """ def __init__(self, *points:'Vector'): """ Initialize with a series of points. They should be defined in counter-clockwise direction. They are objects of class `Vector`. If the points are specified in clockwise direction, the parameter `vertex_order_CCW` should be set to `False` manually. Parameters ---------- *points : Vector An arbitrary number of points given. """ self.points = [] self._area = None # Vertices defined in counter-clockwise order (True) or clockwise order (False). self.vertex_order_CCW = True self.set(*points) def __str__(self): s = "" for i, p in enumerate(self.points): s += "P{i}: ({x}, {y})\n".format(i=i, x=p.x(), y=p.y()) return s def make_3D(self, z_component:float): """Convert all points in xy-plane from 2D vectors to 3D vectors, using the provided z_component. Parameters ---------- z_component : float The new z component for each point's 3D vector. """ for i, p in enumerate(self.points): newPoint = Vector(x=p.x(), y=p.y(), z=z_component, n=3) self.points[i] = newPoint def set(self, *points:'Vector'): """ Set polygon from an arbitrary number of points. The points should be defined in counter-clockwise direction. They are objects of class `Vector`. If the points are specified in clockwise direction, the parameter `vertex_order_CCW` should be set to `False` manually. Parameters ---------- *points : Vector An arbitrary number of points. """ self.points = [] self.points.extend(points) self._area = None def area(self) -> float: """Get the area enclosed by the polygon. Returns ------- area : float Area enclosed by the polygon. """ if self._area is None: self._calculate_area() return self._area def _calculate_area(self): """Calculate the area enclosed by the polygon. The area will be stored as an internal parameter. The function `area()` can be used to get this value. """ self._area = 0 # Split polygon into triangles and calculate area of each # triangle using the trapezoid method. if len(self.points) >= 3: # Start at first point p1 = self.points[0] x1 = p1.x() y1 = p1.y() for i in range(1, len(self.points)-1): p2 = self.points[i] p3 = self.points[i+1] x2 = p2.x() y2 = p2.y() x3 = p3.x() y3 = p3.y() self._area += 0.5 * ( (y1+y3)*(x3-x1) + (y2+y3)*(x2-x3) - (y1+y2)*(x2-x1) ) def get_bounding_box(self) -> tuple[int, int, int, int]: """Get the polygon's bounding box values. Returns ------- leftmost : float Leftmost coordinate. upmost : float Upmost coordinate. rightmost : float Rightmost coordinate. downmost : float Downmost coordinate. """ leftmost = self.points[0].x() rightmost = -1 upmost = self.points[0].y() downmost = -1 for p in self.points: if p.x() < leftmost: leftmost = math.floor(p.x()) if p.x() > rightmost: rightmost = math.ceil(p.x()) if p.y() < upmost: upmost = math.floor(p.y()) if p.y() > downmost: downmost = math.ceil(p.y()) return int(leftmost), int(upmost), int(rightmost), int(downmost) def is_inside_2D(self, point:'Vector') -> bool: """ Check if the given point is inside the polygon or on an edge. Only the xy plane is considered (2D projection). Parameters ---------- point : Vector Point coordinates to check if they are inside the polygon. Returns ------- inside : bool `True` if inside, `False` otherwise. """ x = point.x() y = point.y() if len(self.points) >= 3: p1 = self.points[0] x1 = p1.x() y1 = p1.y() # Set up sub-triangles and check if point is in any of those: for i in range(1, len(self.points)-1): p2 = self.points[i] p3 = self.points[i+1] x2 = p2.x() y2 = p2.y() x3 = p3.x() y3 = p3.y() # Calculate the barycentric coordinates of the point with respect to the triangle: D = (y2-y3)*(x1-x3) + (x3-x2)*(y1-y3) lambda1 = ((y2-y3)*(x-x3) + (x3-x2)*(y-y3)) / D lambda2 = ((y3-y1)*(x-x3) + (x1-x3)*(y-y3)) / D lambda3 = 1 - lambda1 - lambda2 #print("Is {} inside triangle? D: {}, l1: {}, l2: {}, l3: {}".format(point, D, lambda1, lambda2, lambda3)) if (lambda1>=0 and lambda2>=0 and lambda3>=0): return True return False def _inside_edge(self, edgePoint0:'Vector', edgePoint1:'Vector', vertexToTest:'Vector') -> bool: """ Helper function for clip(): decide if vertex point is on the "inside" of the clipping edge. Inside means "to the left" if vertices are in counter-clockwise direction, otherwise "to the right". """ edge = Vector(edgePoint1.x() - edgePoint0.x(), edgePoint1.y() - edgePoint0.y()) point = Vector(vertexToTest.x() - edgePoint0.x(), vertexToTest.y() - edgePoint0.y()) cpz = edge.cross_z(point) if cpz >= 0: # on the edge return True return False def clip(self, clipping_polygon:'Polygon') -> 'Polygon': """ Clips the polygon using the given clipping polygon. Implementation of the Sutherland-Hodgman clipping algorithm. The given `clipping_polygon` must be convex. Parameters ---------- clipping_polygon : Polygon Clipping polygon. The result will be a polygon that only exists within the boundaries of this clipping polygon. Returns ------- clipped : Polygon Clipped polygon. """ # Make a list of edges (lines) of the clipping polygon: outputVertices = self.points # copy.deepcopy(self.points) nPoints = len(clipping_polygon.points) for i in range(nPoints): edgePoint0 = clipping_polygon.points[i] edgePoint1 = clipping_polygon.points[int((i+1)%nPoints)] edgeLine = Line2D() edgeLine.set_from_points(p0=edgePoint0, p1=edgePoint1) inputVertices = outputVertices outputVertices = [] #print("## Clip line: {}".format(edgeLine)) for i in range(len(inputVertices)): currentPoint = inputVertices[i] previousPoint = inputVertices[(i+len(inputVertices)-1)%len(inputVertices)] #print(" Current Point: {}".format(currentPoint)) #print(" Previous Point: {}".format(previousPoint)) currentLine = Line2D() currentLine.set_from_points(p0=currentPoint, p1=previousPoint) #print(" -> current Line: {}".format(currentLine)) if self._inside_edge(edgePoint0, edgePoint1, currentPoint): #print(" Current point is inside clipping polygon.") if not self._inside_edge(edgePoint0, edgePoint1, previousPoint): try: intersectionPoint = currentLine.intersection(edgeLine) #print(" Added intersection to output list.") #print(" -> intersection: {}".format(intersectionPoint)) outputVertices.append(intersectionPoint) except: # Parallel lines -> no intersection pass #print(" Added currentPoint to output list.") outputVertices.append(currentPoint) elif self._inside_edge(edgePoint0, edgePoint1, previousPoint): #print(" Current point is not inside clipping polygon, but previous point is.") try: intersectionPoint = currentLine.intersection(edgeLine) #print(" Only added intersection point to output list.") #print(" -> intersection: {}".format(intersectionPoint)) outputVertices.append(intersectionPoint) except: pass #else: #print("Neither current nor previous point is inside clipping polygon.") #print("\n") result = Polygon(*outputVertices) return result
Methods
def area(self) ‑> float
-
Get the area enclosed by the polygon.
Returns
area
:float
- Area enclosed by the polygon.
def clip(self, clipping_polygon: Polygon) ‑> Polygon
-
Clips the polygon using the given clipping polygon.
Implementation of the Sutherland-Hodgman clipping algorithm.
The given
clipping_polygon
must be convex.Parameters
clipping_polygon
:Polygon
- Clipping polygon. The result will be a polygon that only exists within the boundaries of this clipping polygon.
Returns
clipped
:Polygon
- Clipped polygon.
def get_bounding_box(self) ‑> tuple[int, int, int, int]
-
Get the polygon's bounding box values.
Returns
leftmost
:float
- Leftmost coordinate.
upmost
:float
- Upmost coordinate.
rightmost
:float
- Rightmost coordinate.
downmost
:float
- Downmost coordinate.
def is_inside_2D(self, point: Vector) ‑> bool
-
Check if the given point is inside the polygon or on an edge. Only the xy plane is considered (2D projection).
Parameters
point
:Vector
- Point coordinates to check if they are inside the polygon.
Returns
inside
:bool
True
if inside,False
otherwise.
def make_3D(self, z_component: float)
-
Convert all points in xy-plane from 2D vectors to 3D vectors, using the provided z_component.
Parameters
z_component
:float
- The new z component for each point's 3D vector.
def set(self, *points: Vector)
-
Set polygon from an arbitrary number of points. The points should be defined in counter-clockwise direction. They are objects of class
Vector
.If the points are specified in clockwise direction, the parameter
vertex_order_CCW
should be set toFalse
manually.Parameters
*points
:Vector
- An arbitrary number of points.
class Vector (x: float = None, y: float = None, z: float = None, w: float = None, n: int = None, numpy_data=None)
-
A vector in space, arbitrary number of dimensions.
Attributes
n_entries
:int
- Number of vector elements.
value
:numpy.ndarray
- NumPy array that contains the vector elements.
Initialize vector by providing elements
x
, (y
,z
,w
) and possibly the number of elementsn
, or a NumPy data array.Parameters
x
:float
, optional- Value for first vector element.
y
:float
, optional- Value for second vector element.
z
:float
, optional- Value for third vector element.
w
:float
, optional- Value for fourth vector element.
n
:int
, optional- Number of vector elements. Must be provided if no NumPy data array
is given and if the number of vector elements cannot be automatically
determined from the given parameters
x
,y
,z
andw
(those which are set toNone
are not considered in the determination of the dimension). numpy_data
:numpy.ndarray
, optional- One-dimensional NumPy data array. Must be provided if number of vector elements
n
is not given.
Expand source code
class Vector: """A vector in space, arbitrary number of dimensions. Attributes ---------- n_entries : int Number of vector elements. value : numpy.ndarray NumPy array that contains the vector elements. """ def __init__(self, x:float=None, y:float=None, z:float=None, w:float=None, n:int=None, numpy_data=None): """Initialize vector by providing elements `x`, (`y`, `z`, `w`) and possibly the number of elements `n`, or a NumPy data array. Parameters ---------- x : float, optional Value for first vector element. y : float, optional Value for second vector element. z : float, optional Value for third vector element. w : float, optional Value for fourth vector element. n : int, optional Number of vector elements. Must be provided if no NumPy data array is given and if the number of vector elements cannot be automatically determined from the given parameters `x`, `y`, `z` and `w` (those which are set to `None` are not considered in the determination of the dimension). numpy_data : numpy.ndarray, optional One-dimensional NumPy data array. Must be provided if number of vector elements `n` is not given. """ # The following member variables are private and should not # be accessed from outside. They are invalidated whenever the # vector changes. self._unit_vector = None # the unit vector that corresponds to this vector self._length = None # vector's length self.n_entries = 3 # default: three-component vector if numpy_data is not None: # Initialize from given numpy data array: self.set_numpy_data_array(numpy_data) else: # Or from regular value arguments: if n is not None: # If the number of vector components is specified: self.n_entries = n else: # Determine number of vector components from given values: if x is not None: self.n_entries = 1 if y is not None: self.n_entries = 2 if z is not None: self.n_entries = 3 if w is not None: self.n_entries = 4 self.reset(n=self.n_entries) self.set(x=x, y=y, z=z, w=w) self.update() def __str__(self): return f"{self.value}" def __add__(self, x:'Vector'): result = self.get_copy() result.add(x) return result def __sub__(self, x:'Vector'): result = self.get_copy() result.subtract(x) return result def __mul__(self, x:'Vector'): result = self.get_copy() result.multiply(x) return result def __truediv__(self, x:'Vector'): result = self.get_copy() result.divide(x) return result def __floordiv__(self, x:'Vector'): result = self.get_copy() result.floor_divide(x) return result def __radd__(self, x:'Vector'): if isinstance(x, numbers.Number): return self.__add__(x) def __rsub__(self, x:'Vector'): if isinstance(x, numbers.Number): result = self.get_copy() result.multiply(-1) result.add(x) return result def __rmul__(self, x:'Vector'): if isinstance(x, numbers.Number): return self.__mul__(x) def size(self) -> int: """Get the number of vector elements. Returns ------- n_entries : int Number of vector elements. """ return self.n_entries def reset(self, n:int): """Set vector to given size and initialize all values to zero. Parameters ---------- n : int Number of vector elements. """ self.n_entries = n self.value = numpy.zeros(n, dtype=numpy.float64) def same_size(self, x:'Vector') -> bool: """Check if this vector has the same size (i.e., number of vector elements, not length!) as the given vector `x`. Returns ------- same_dim : bool `True` if number of vector elements matches, `False` otherwise. """ if self.n_entries == x.n_entries: return True return False def update(self): """Called when vector is changed. Invalidates private member variables for length and unit vector, so they will be re-calculated the next time their public getter functions are called. """ self._unit_vector = None self._length = None def x(self) -> float: """Get first vector element. Returns ------- x : float First vector element. """ return self.value[0] def y(self) -> float: """Get second vector element. Returns ------- y : float Second vector element. """ return self.value[1] def z(self) -> float: """Get third vector element. Returns ------- z : float Third vector element. """ return self.value[2] def w(self) -> float: """Get fourth vector element. Returns ------- w : float Fourth vector element. """ return self.value[3] def get(self, i:int) -> float: """Get value at vector index `i`. Indices start at `0`. Parameters ---------- i : int Element index of value. Returns ------- value : float Element value at index position `i`. """ return self.value[i] def get_copy(self) -> 'Vector': """Get a copy of this `Vector` object. Returns ------- v : Vector Copy of this vector object. """ new_values = numpy.copy(self.value) return Vector(numpy_data=new_values) def set_x(self, value:float): """Set vector's x value (i.e., value of first vector element). Parameters ---------- value : float Value for the first vector element. """ self.value[0] = float(value) self.update() def set_y(self, value:float): """Set vector's y value (i.e., value of second vector element). Parameters ---------- value : float Value for the second vector element. """ self.value[1] = float(value) self.update() def set_z(self, value:float): """Set vector's z value (i.e., value of third vector element). Parameters ---------- value : float Value for the third vector element. """ self.value[2] = float(value) self.update() def set_w(self, value:float): """Set vector's w value (i.e., value of fourth vector element). Parameters ---------- value : float Value for the fourth vector element. """ self.value[3] = float(value) self.update() def set_x_y(self, x:float=0, y:float=0): """Set x and y component (relevant for 2D computations). Parameters ---------- x : float Value for the first vector element. y : float Value for the second vector element. """ self.value[0] = float(x) self.value[1] = float(y) self.update() def set(self, x:float=0, y:float=0, z:float=0, w:float=None): """ Set all vector components. Non-existing vector components (such as `w` for a 3D vector) should be set to `None`. Parameters ---------- x : float Value for the first vector element. y : float, optional Value for the second vector element. z : float, optional Value for the third vector element. w : float, optional Value for the fourth vector element. """ if self.n_entries > 0 and x is not None: self.value[0] = float(x) if self.n_entries > 1 and y is not None: self.value[1] = float(y) if self.n_entries > 2 and z is not None: self.value[2] = float(z) if self.n_entries > 3 and w is not None: self.value[3] = float(w) self.update() def set_value_for_index(self, i:int, value:float): """Set value for element at vector index `i`. Parameters ---------- i : int Index of the vector element. value : float New value for the vector element. """ self.value[i] = float(value) self.update() def set_numpy_data_array(self, numpy_data): """Provide a one-dimensional NumPy array for the vector data. The new vector size will be the same as the size of the NumPy array. Parameters ---------- numpy_data : numpy.ndarray NumPy array for the new vector values. """ self.n_entries = len(numpy_data) self.value = numpy_data.astype(float) self.update() def min(self) -> float: """Minimum value of all vector components.""" return self.value.min() def max(self) -> float: """Maximum value of all vector components.""" return self.value.min() def absmin(self) -> float: """Minimum value of absolute of all vector components.""" return numpy.absolute(self.value).min() def absmax(self) -> float: """Maximum value of absolute of all vector components.""" return numpy.absolute(self.value).max() def absmin_nonzero(self) -> float: """Minimum non-zero value of absolute of all vector components.""" nonzeros = self.value[numpy.nonzero(self.value)] if len(nonzeros) > 0: return numpy.absolute(nonzeros).min() else: return None def absmax_nonzero(self) -> float: """Maximum non-zero value of absolute of all vector components.""" nonzeros = self.value[numpy.nonzero(self.value)] if len(nonzeros) > 0: return numpy.absolute(nonzeros).max() else: return None def make_crystal_vector(self): """Scale this vector into a crystal direction vector, using Miller indices.""" absmin_number = self.absmin_nonzero() if absmin_number is not None: if absmin_number > 0: self.scale(1.0 / absmin_number) def crystal_direction(self) -> 'Vector': """Create a vector using Miller indices.""" miller = self.get_copy() miller.make_crystal_vector() return miller def length(self) -> float: """Get the length of the vector. Returns ------- length : float Length of the vector. """ if self._length is None: self._length = numpy.linalg.norm(self.value) return self._length def angle(self, x:'Vector') -> float: """Calculate angle between this vector and the given vector `x`. Parameters ---------- x : Vector Second vector for angle calculation. Returns ------- angle : float Angle (in radians) between this vector and given vector `x`. """ dotProd = self.dot(x) l1 = self.length() l2 = x.length() lp = l1 * l2 if lp > 0: cs = dotProd / lp alpha = 0 # Avoid out-of-domain due to rounding errors: if cs >= 1.0: alpha = 0 elif cs <= -1.0: alpha = math.pi else: alpha = math.acos(cs) return alpha else: return 0 def make_unit_vector(self): """ Normalize vector length to 1. """ vector_length = self.length() if vector_length != 0: if vector_length != 1.0: self.value /= vector_length self.update() else: raise Exception("Unit vector: a zero length vector cannot be converted into a unit vector.") def unit_vector(self) -> 'Vector': """ Get a unit vector that points in the same direction as this vector. Returns ------- unit_vector : Vector Unit vector for this vector. Note ---- If this vector changes, the returned unit vector object will be invalidated. To store the unit vector permanently, get a copy of the unit vector (using `get_copy()`) so it won't change: ```python unitv = v.unit_vector().get_copy() ``` """ if self._unit_vector is None: self._unit_vector = self.get_copy() self._unit_vector.make_unit_vector() return self._unit_vector def add(self, x): """Add a scalar to all vector elements, or add another compatible vector. Parameters ---------- x : Vector or float Vector of same size to be added (element-wise) or scalar to be added to all vector elements. """ if isinstance(x, Vector): # 'x' is another vector: if self.same_size(x): self.value = numpy.add(self.value, x.value) else: raise Exception(f"Incompatible vector sizes. Can only add vectors of same size. Vectors: {self} ({self.n_entries}) and {x} ({x.n_entries})") elif isinstance(x, numbers.Number): # 'x' is a scalar: self.value = numpy.add(self.value, x) else: raise Exception(f"Vector addition failed: M+A is only supported when A is of type 'Vector' or a scalar. The given type is not supported: {type(x)}.") def subtract(self, x): """Subtract a scalar from all vector elements, or subtract another compatible vector. Parameters ---------- x : Vector or float Vector of same size to be subtracted (element-wise) or scalar to be subtracted from all vector elements. """ if isinstance(x, Vector): # 'x' is another vector: if self.same_size(x): self.value = numpy.subtract(self.value, x.value) else: raise Exception("Incompatible vector sizes. Can only subtract vectors of same size.") elif isinstance(x, numbers.Number): # 'x' is a scalar: self.value = numpy.subtract(self.value, x) else: raise Exception(f"Vector subtraction failed: M-A is only supported when A is of type 'Vector' or a scalar. The given type is not supported: {type(x)}.") def multiply(self, x): """Multiply a scalar to all vector elements, or perform vector multiplication with a compatible vector. Parameters ---------- x : Vector or float Vector of same size to be multiplied (element-wise) or scalar to be multiplied to all vector elements. """ if isinstance(x, Vector): # 'x' is another vector: self.value = numpy.matmul(self.value, x.value) elif isinstance(x, numbers.Number): # 'x' is a scalar: self.value = numpy.multiply(self.value, x) else: raise Exception(f"Vector multiplication failed: M*A is only supported when A is of type 'Vector' or 'Vector' or a scalar. The given type is not supported: {type(x)}.") def divide(self, x): """Divide all vector elements by a scalar, or perform element-wise division with a vector of the same size. Parameters ---------- x : Vector or float Vector of same size to divide this vector (element-wise) or scalar to divide all vector elements. """ if isinstance(x, Vector): # 'x' is another vector... element-wise division: if self.same_size(x): self.value = numpy.divide(self.value, x.value) else: raise Exception("Incompatible vector sizes. Can only run an element-wise division for vectors of same size.") elif isinstance(x, numbers.Number): # 'x' is a scalar: self.value = numpy.divide(self.value, x) else: raise Exception(f"Vector division failed: M/A is only supported when A is of type 'Vector' or a scalar. The given type is not supported: {type(x)}.") def floor_divide(self, x): """Divide all vector elements by a scalar, or perform element-wise division with a vector of the same size. Parameters ---------- x : Vector or float Vector of same size to floor-divide this vector (element-wise) or scalar to floor-divide all vector elements. """ if isinstance(x, Vector): # 'x' is another vector... element-wise division: if self.same_size(x): self.value = numpy.floor_divide(self.value, x.value) else: raise Exception("Incompatible vector sizes. Can only run an element-wise division for vectors of same size.") elif isinstance(x, numbers.Number): # 'x' is a scalar: self.value = numpy.floor_divide(self.value, x) else: raise Exception(f"Vector division failed: M/A is only supported when A is of type 'Vector' or a scalar. The given type is not supported: {type(x)}.") def scale(self, factor:float): """Scale vector by a scalar factor. Parameters ---------- factor : float Factor that scales all vector elements. """ self.value = numpy.multiply(self.value, factor) self.update() def scaled(self, factor:float) -> 'Vector': """ Get a copy of this vector, scaled by the given `factor`. Parameters ---------- factor : float Factor that scales all vector elements. Returns ------- result : Vector Scaled copy of this vector. """ result = self.get_copy() result.scale(factor) return result def square(self): """ Square all elements of this vector. """ self.value = numpy.square(self.value) self.update() def squared(self) -> 'Vector': """ Get a squared copy of this vector. Returns ------- result : Vector Squared copy of this vector. """ result = self.get_copy() result.square() return result def sqrt(self): """ Set all elements of this vector to their square roots. """ self.value = numpy.sqrt(self.value) self.update() def distance(self, p:'Vector') -> float: """ Distance between target points of this and another vector. Parameters ---------- p : Vector Second vector. Returns ------- distance : float Distance between the target point of this vector and the target point of the second vector `p`. """ return (self-p).length() def dot(self, x:'Vector') -> float: """ Calculate vector dot product. Parameters ---------- x : Vector Another vector to calculate the dot product with this vector. Returns ------- dotp : float Dot product of this vector with the vector `x`. """ return numpy.dot(self.value, x.value) def cross_z(self, x:'Vector') -> float: """ Calculate the z component of the 3D cross product. Parameters ---------- x : Vector The second vector to calculate the cross product. Must contain three elements. Returns ------- crossz : float z component of 3D cross product of this vector with the second vector `x`. """ return self.x()*x.y() - self.y()*x.x() def cross(self, x:'Vector') -> 'Vector': """ Calculate 3D vector cross product. Parameters ---------- x : Vector The second vector to calculate the cross product. Must contain three elements. Returns ------- crossp : Vector 3D cross product of this vector with the second vector `x`. """ cp = numpy.cross(self.value, x.value) return Vector(numpy_data=cp) def sum(self) -> float: """ Get the sum of all vector elements. Returns ------- sum : float Sum of all vector elements. """ return numpy.sum(self.value) def invert(self): """ Invert this vector: v to -v. """ self.value = -self.value self.update() def inverse(self) -> 'Vector': """ Get the inverse of this vector: -v. Returns ------- inv : Vector Inverse of this vector, i.e. vector of same length pointing in the opposite direction. """ result = self.get_copy() result.invert() return result def rotate_2D_xy(self, angle:float): """ Rotate vector in xy plane. Parameters ---------- angle : float Rotation angle (in rad). """ cs = math.cos(angle) sn = math.sin(angle) self.set_x(self.x()*cs - self.y()*sn) self.set_y(self.x()*sn + self.y()*cs) def rotate(self, axis:'Vector', angle:float): """ Rotate vector around given axis by given angle (in rad). Parameters ---------- axis : Vector Rotation axis. angle : float Rotation angle (in rad). """ # Implementing a general rotation matrix. cs = math.cos(angle) sn = math.sin(angle) vx = self.x() vy = self.y() vz = self.z() nx = axis.unit_vector().x() ny = axis.unit_vector().y() nz = axis.unit_vector().z() rx = vx*(nx*nx*(1.0-cs) + cs) + vy*(nx*ny*(1.0-cs) - nz*sn) + vz*(nx*nz*(1.0-cs) + ny*sn) ry = vx*(ny*nx*(1.0-cs) + nz*sn) + vy*(ny*ny*(1.0-cs) + cs) + vz*(ny*nz*(1.0-cs) - nx*sn) rz = vx*(nz*nx*(1.0-cs) - ny*sn) + vy*(nz*ny*(1.0-cs) + nx*sn) + vz*(nz*nz*(1.0-cs) + cs) self.set(x=rx, y=ry, z=rz) def transform(self, M:'Matrix'): """Apply the transformation given by matrix `M` to this vector. Parameters ---------- M : Matrix Transformation matrix. """ result = M*self self.n_entries = result.n_entries self.value = result.value self.update() def to(self, x:'Vector') -> 'Vector': """ Get a vector that points from this location to the given location `x`. Parameters ---------- x : Vector Second location vector. Returns ------- pointer : Vector Vector that points from this vector's target point to the target point of the given vector `x`. """ return self.connection(self, x) @staticmethod def connection(p0:'Vector', p1:'Vector') -> 'Vector': """ Connection vector between two points (represented by vectors). Parameters ---------- p0 : Vector Origin point of the connection. p1 : Vector Target point of the connection. Returns ------- connecting_vector : Vector Vector pointing from the origin `p0` to the target point `p1`. """ return p1 - p0
Static methods
def connection(p0: Vector, p1: Vector) ‑> Vector
Methods
def absmax(self) ‑> float
-
Maximum value of absolute of all vector components.
def absmax_nonzero(self) ‑> float
-
Maximum non-zero value of absolute of all vector components.
def absmin(self) ‑> float
-
Minimum value of absolute of all vector components.
def absmin_nonzero(self) ‑> float
-
Minimum non-zero value of absolute of all vector components.
def add(self, x)
-
Add a scalar to all vector elements, or add another compatible vector.
Parameters
x
:Vector
orfloat
- Vector of same size to be added (element-wise) or scalar to be added to all vector elements.
def angle(self, x: Vector) ‑> float
-
Calculate angle between this vector and the given vector
x
.Parameters
x
:Vector
- Second vector for angle calculation.
Returns
angle
:float
- Angle (in radians) between this vector and given vector
x
.
def cross(self, x: Vector) ‑> Vector
def cross_z(self, x: Vector) ‑> float
-
Calculate the z component of the 3D cross product.
Parameters
x
:Vector
- The second vector to calculate the cross product. Must contain three elements.
Returns
crossz
:float
- z component of 3D cross product of this vector with the second vector
x
.
def crystal_direction(self) ‑> Vector
-
Create a vector using Miller indices.
def distance(self, p: Vector) ‑> float
-
Distance between target points of this and another vector.
Parameters
p
:Vector
- Second vector.
Returns
distance
:float
- Distance between the target point of this vector and the target point of the second vector
p
.
def divide(self, x)
-
Divide all vector elements by a scalar, or perform element-wise division with a vector of the same size.
Parameters
x
:Vector
orfloat
- Vector of same size to divide this vector (element-wise) or scalar to divide all vector elements.
def dot(self, x: Vector) ‑> float
-
Calculate vector dot product.
Parameters
x
:Vector
- Another vector to calculate the dot product with this vector.
Returns
dotp
:float
- Dot product of this vector with the vector
x
.
def floor_divide(self, x)
-
Divide all vector elements by a scalar, or perform element-wise division with a vector of the same size.
Parameters
x
:Vector
orfloat
- Vector of same size to floor-divide this vector (element-wise) or scalar to floor-divide all vector elements.
def get(self, i: int) ‑> float
-
Get value at vector index
i
. Indices start at0
.Parameters
i
:int
- Element index of value.
Returns
value
:float
- Element value at index position
i
.
def get_copy(self) ‑> Vector
def inverse(self) ‑> Vector
-
Get the inverse of this vector: -v.
Returns
inv
:Vector
- Inverse of this vector, i.e. vector of same length pointing in the opposite direction.
def invert(self)
-
Invert this vector: v to -v.
def length(self) ‑> float
-
Get the length of the vector.
Returns
length
:float
- Length of the vector.
def make_crystal_vector(self)
-
Scale this vector into a crystal direction vector, using Miller indices.
def make_unit_vector(self)
-
Normalize vector length to 1.
def max(self) ‑> float
-
Maximum value of all vector components.
def min(self) ‑> float
-
Minimum value of all vector components.
def multiply(self, x)
-
Multiply a scalar to all vector elements, or perform vector multiplication with a compatible vector.
Parameters
x
:Vector
orfloat
- Vector of same size to be multiplied (element-wise) or scalar to be multiplied to all vector elements.
def reset(self, n: int)
-
Set vector to given size and initialize all values to zero.
Parameters
n
:int
- Number of vector elements.
def rotate(self, axis: Vector, angle: float)
-
Rotate vector around given axis by given angle (in rad).
Parameters
axis
:Vector
- Rotation axis.
angle
:float
- Rotation angle (in rad).
def rotate_2D_xy(self, angle: float)
-
Rotate vector in xy plane.
Parameters
angle
:float
- Rotation angle (in rad).
def same_size(self, x: Vector) ‑> bool
-
Check if this vector has the same size (i.e., number of vector elements, not length!) as the given vector
x
.Returns
same_dim
:bool
True
if number of vector elements matches,False
otherwise.
def scale(self, factor: float)
-
Scale vector by a scalar factor.
Parameters
factor
:float
- Factor that scales all vector elements.
def scaled(self, factor: float) ‑> Vector
-
Get a copy of this vector, scaled by the given
factor
.Parameters
factor
:float
- Factor that scales all vector elements.
Returns
result
:Vector
- Scaled copy of this vector.
def set(self, x: float = 0, y: float = 0, z: float = 0, w: float = None)
-
Set all vector components.
Non-existing vector components (such as
w
for a 3D vector) should be set toNone
.Parameters
x
:float
- Value for the first vector element.
y
:float
, optional- Value for the second vector element.
z
:float
, optional- Value for the third vector element.
w
:float
, optional- Value for the fourth vector element.
def set_numpy_data_array(self, numpy_data)
-
Provide a one-dimensional NumPy array for the vector data. The new vector size will be the same as the size of the NumPy array.
Parameters
numpy_data
:numpy.ndarray
- NumPy array for the new vector values.
def set_value_for_index(self, i: int, value: float)
-
Set value for element at vector index
i
.Parameters
i
:int
- Index of the vector element.
value
:float
- New value for the vector element.
def set_w(self, value: float)
-
Set vector's w value (i.e., value of fourth vector element).
Parameters
value
:float
- Value for the fourth vector element.
def set_x(self, value: float)
-
Set vector's x value (i.e., value of first vector element).
Parameters
value
:float
- Value for the first vector element.
def set_x_y(self, x: float = 0, y: float = 0)
-
Set x and y component (relevant for 2D computations).
Parameters
x
:float
- Value for the first vector element.
y
:float
- Value for the second vector element.
def set_y(self, value: float)
-
Set vector's y value (i.e., value of second vector element).
Parameters
value
:float
- Value for the second vector element.
def set_z(self, value: float)
-
Set vector's z value (i.e., value of third vector element).
Parameters
value
:float
- Value for the third vector element.
def size(self) ‑> int
-
Get the number of vector elements.
Returns
n_entries
:int
- Number of vector elements.
def sqrt(self)
-
Set all elements of this vector to their square roots.
def square(self)
-
Square all elements of this vector.
def squared(self) ‑> Vector
def subtract(self, x)
-
Subtract a scalar from all vector elements, or subtract another compatible vector.
Parameters
x
:Vector
orfloat
- Vector of same size to be subtracted (element-wise) or scalar to be subtracted from all vector elements.
def sum(self) ‑> float
-
Get the sum of all vector elements.
Returns
sum
:float
- Sum of all vector elements.
def to(self, x: Vector) ‑> Vector
def transform(self, M: Matrix)
-
Apply the transformation given by matrix
M
to this vector.Parameters
M
:Matrix
- Transformation matrix.
def unit_vector(self) ‑> Vector
-
Get a unit vector that points in the same direction as this vector.
Returns
unit_vector
:Vector
- Unit vector for this vector.
Note
If this vector changes, the returned unit vector object will be invalidated. To store the unit vector permanently, get a copy of the unit vector (using
get_copy()
) so it won't change:unitv = v.unit_vector().get_copy()
def update(self)
-
Called when vector is changed.
Invalidates private member variables for length and unit vector, so they will be re-calculated the next time their public getter functions are called.
def w(self) ‑> float
-
Get fourth vector element.
Returns
w
:float
- Fourth vector element.
def x(self) ‑> float
-
Get first vector element.
Returns
x
:float
- First vector element.
def y(self) ‑> float
-
Get second vector element.
Returns
y
:float
- Second vector element.
def z(self) ‑> float
-
Get third vector element.
Returns
z
:float
- Third vector element.