The Vector
The Simple Definition
Think of a Vector as an arrow.
This arrow has two distinct properties:
- Direction: Which way it is pointing (e.g., "Up", "North-East", "Towards the Sun").
- Magnitude: How long it is (e.g., "5 units long", "10 meters", "1 pixel").
Direction & Magnitude
If a Point is an absolute location in space, a Vector is a movement through space. It is the architectural arrow that tells you which way to go, and exactly how far.
While a Point is tethered to the global coordinate system, a Vector is completely untethered. It floats freely. It represents pure direction and magnitude (length), but it has no fixed position.
A More Generalized View
A vector at its basic is just a list of values:
(X, Y, Z)is a vector of 3 values(X, Y)is a vector of 2 values(X, Y, Z, W)is a vector of 4 values- and the list goes on.
The Data Structure
In AECO, the most common data structure of a vector is an ordered list of three double-precision floating-point numbers (X, Y, Z).
1class Vector3d:2 """3 Represents a magnitude and direction in 3D space.4 """5 def __init__(self, x: float, y: float, z: float):6 self.X = float(x)7 self.Y = float(y)8 self.Z = float(z)910 def __str__(self):11 return f"Vector3d({self.X}, {self.Y}, {self.Z})"1213 @classmethod14 def ZAxis(cls):15 """Gets a unit vector pointing straight up."""16 return cls(0.0, 0.0, 1.0)
There's nothing more to it, all that follows are methods, functions and formulas using addition, subtraction, multiplication, division, dot products, cross products, normalization, and other mathematical operations that can be performed on vectors.
Geometry wise, everything else is just an abstraction over this simple data structure.
Length():
1import math23class Vector3d:4 @property5 def Length(self) -> float:6 """Returns the magnitude of the vector."""7 return math.sqrt(self.X**2 + self.Y**2 + self.Z**2)
IsUnitVector():
LengthSquared():
1class Vector3d:2 def LengthSquared(self) -> float:3 """Returns the squared magnitude of the vector, skipping sqrt."""4 return self.X**2 + self.Y**2 + self.Z**2
math.sqrt, which is computationally expensive. For distance comparisons (e.g. "is this point closer than 5 units?"), we can compare LengthSquared() to 25.0 (5**2) instead, saving CPU cycles in large geometry sets.Unitize():
Reverse():
1class Vector3d:2 def Reverse(self) -> None:3 """Flips the vector mathematically."""4 self.X = -self.X5 self.Y = -self.Y6 self.Z = -self.Z
CrossProduct(Vector3d):
1class Vector3d:2 def CrossProduct(self, other: 'Vector3d') -> 'Vector3d':3 """Returns the cross product of two vectors."""4 return Vector3d(5 self.Y * other.Z - self.Z * other.Y,6 self.Z * other.X - self.X * other.Z,7 self.X * other.Y - self.Y * other.X,8 )
- Compute slopes, determining which side of a wall is exterior/interior, lighting and rendering, solar analysis, CNC fabrication.
DotProduct(Vector3d):
1class Vector3d:2 def DotProduct(self, other: 'Vector3d') -> float:3 """Returns the dot product of two vectors."""4 return self.X * other.X + self.Y * other.Y + self.Z * other.Z
- Alignment, projection, similarity, angle, or how much one direction contributes to another.
VectorAngle(Vector3d, Vector3d):
1import math23class Vector3d:4 def VectorAngle(self, other: 'Vector3d') -> float:5 """Returns the angle between two vectors in radians."""6 dot = self.X * other.X + self.Y * other.Y + self.Z * other.Z78 # Calculate magnitudes9 mag_self = math.sqrt(self.X**2 + self.Y**2 + self.Z**2)10 mag_other = math.sqrt(other.X**2 + other.Y**2 + other.Z**2)1112 # Protect against division by zero13 if mag_self == 0 or mag_other == 0:14 return 0.01516 # Cosine of the angle17 cos_theta = dot / (mag_self * mag_other)1819 # Clamp to handle precision errors20 cos_theta = max(-1.0, min(1.0, cos_theta))2122 return math.acos(cos_theta)
SignedAngleTo(Vector3d, Vector3d):
1def SignedAngle(v1: Vector3d, v2: Vector3d, normal: Vector3d) -> float:2 """Calculates the signed angle between v1 and v2 around a normal."""3 # Cross product gives the axis of rotation4 cross = Vector3d.CrossProduct(v1, v2)5 angle = math.atan2(cross.Length, v1 * v2)67 # If the cross product points opposite to our reference normal,8 # it means the rotation is clockwise (negative).9 if normal * cross < 0:10 angle = -angle1112 return angle
IsParallelTo(Vector3d):
1import math23class Vector3d:4 def IsParallelTo(self, other: 'Vector3d', tolerance: float = 1e-6) -> int:5 """6 Checks if two vectors are parallel.7 Returns:8 +1 if parallel and in the same direction.9 -1 if parallel and in opposite directions.10 0 if not parallel.11 """12 # Cross product of parallel vectors is zero (or near zero)13 cross_x = self.Y * other.Z - self.Z * other.Y14 cross_y = self.Z * other.X - self.X * other.Z15 cross_z = self.X * other.Y - self.Y * other.X1617 cross_mag_sq = cross_x**2 + cross_y**2 + cross_z**21819 if cross_mag_sq <= tolerance**2:20 # Check dot product to see if they point in the same direction21 dot = self.X * other.X + self.Y * other.Y + self.Z * other.Z22 return 1 if dot > 0 else -12324 return 0
Because floating-point math is imprecise, we rarely check if two values are exactly equal. Instead, we use a small tolerance value to check if they are "close enough".
IsPerpendicularTo(Vector3d):
1class Vector3d:2 def IsPerpendicularTo(self, other: 'Vector3d', tolerance: float = 1e-6) -> bool:3 """4 Checks if two vectors are perpendicular (orthogonal).5 Returns:6 True if the vectors are perpendicular, False otherwise.7 """8 # Two vectors are perpendicular if their dot product is zero.9 # We normalize the check using their lengths to support scaled vectors.10 mag_self = self.Length11 mag_other = other.Length1213 if mag_self == 0 or mag_other == 0:14 return False1516 dot = self.X * other.X + self.Y * other.Y + self.Z * other.Z1718 # Check if dot product is close to zero relative to lengths19 return abs(dot) / (mag_self * mag_other) <= tolerance
Perpendicularity means the angle between the vectors is exactly 90° (or 270°). The dot product of two perpendicular unit vectors is always exactly 0.0.
Orthogonal():
1class Vector3d:2 def Orthogonal(self) -> 'Vector3d':3 """4 Generates a deterministic vector that is perpendicular (orthogonal) to this vector.5 """6 # Pick helper vector based on which coordinate is smallest7 # to avoid parallel cross-products (which result in zero-length vectors)8 x = abs(self.X)9 y = abs(self.Y)10 z = abs(self.Z)1112 if x <= y and x <= z:13 helper = Vector3d(1.0, 0.0, 0.0)14 elif y <= x and y <= z:15 helper = Vector3d(0.0, 1.0, 0.0)16 else:17 helper = Vector3d(0.0, 0.0, 1.0)1819 # Cross product generates a perpendicular vector20 perp = Vector3d.CrossProduct(self, helper)21 perp.Unitize()22 return perp
Because a single vector has infinite perpendicular vectors in 3D space, this method picks a stable helper axis to generate a single consistent perpendicular vector.
Addition(Vector3d):
1class Vector3d:2 def Add(self, other: 'Vector3d') -> 'Vector3d':3 """Returns the addition of two vectors."""4 return Vector3d(self.X + other.X, self.Y + other.Y, self.Z + other.Z)
Subtraction(Vector3d):
1class Vector3d:2 def Subtraction(self, other: 'Vector3d') -> 'Vector3d':3 """Returns the result of subtracting vector 'other' from 'self'."""4 return Vector3d(5 self.X - other.X,6 self.Y - other.Y,7 self.Z - other.Z8 )910# Often used with points:11# Vector = TargetPoint - SourcePoint
ProjectOnVector(Vector3d):
1class Vector3d:2 def ProjectOnVector(self, other: 'Vector3d') -> 'Vector3d':3 """Projects this vector onto another vector."""4 # Find unit vector of the target5 mag_other = (other.X**2 + other.Y**2 + other.Z**2)**0.56 if mag_other == 0:7 return Vector3d(0, 0, 0)89 unit_other = Vector3d(other.X / mag_other, other.Y / mag_other, other.Z / mag_other)1011 # Calculate dot product12 dot = self.X * unit_other.X + self.Y * unit_other.Y + self.Z * unit_other.Z1314 # Scale unit vector by dot product15 return Vector3d(unit_other.X * dot, unit_other.Y * dot, unit_other.Z * dot)
Lerp(Vector3d, float):
1class Vector3d:2 def Lerp(self, other: 'Vector3d', t: float) -> 'Vector3d':3 """Linearly interpolates between two vectors based on parameter t (0.0 to 1.0)."""4 # Clamping t between 0 and 15 t = max(0.0, min(1.0, t))67 return Vector3d(8 self.X + (other.X - self.X) * t,9 self.Y + (other.Y - self.Y) * t,10 self.Z + (other.Z - self.Z) * t11 )
Vector Slerp (Spherical Interpolation):
1def VectorSlerp(vecA: 'Vector3d', vecB: 'Vector3d', t: float) -> 'Vector3d':2 """Spherical Linear Interpolation between two vectors."""3 # Find rotation axis and angle4 angle = Vector3d.VectorAngle(vecA, vecB)5 axis = Vector3d.CrossProduct(vecA, vecB)67 # Rotate vecA towards vecB by a fraction 't' of the angle8 result = vecA.Clone()9 result.Rotate(angle * t, axis)10 return result
Move(Vector3d):
1# This is semantically vector addition, but usually labeled as Move2def Translate(self, delta: 'Vector3d') -> 'Vector3d':3 return Vector3d(self.X + delta.X, self.Y + delta.Y, self.Z + delta.Z)
Scale(float):
Length (magnitude). If you have a vector representing the wind blowing North at 5mph, scaling it by 2.0 results in a vector blowing North at 10mph.1class Vector3d:2 def Scale(self, s: float) -> 'Vector3d':3 """Scales the vector by a factor."""4 return Vector3d(self.X * s, self.Y * s, self.Z * s)
Rotate(Vector3d, float):
1import math23class Vector3d:4 def Rotate(self, axis: 'Vector3d', angle_degrees: float) -> 'Vector3d':5 """Rotates the vector around the given axis using Rodrigues' Formula."""67 # Ensure axis is unitized8 k = Vector3d(axis)9 k.Unitize()1011 theta = math.radians(angle_degrees)12 cos_t = math.cos(theta)13 sin_t = math.sin(theta)1415 # Formula: V_rot = V*cos(θ) + (K × V)*sin(θ) + K*(K · V)*(1 - cos(θ))16 cross_kv = Vector3d.CrossProduct(k, self)17 dot_kv = k * self1819 return self * cos_t + cross_kv * sin_t + k * (dot_kv * (1 - cos_t))
Reflect(Vector3d, Vector3d):
1def Reflect(incident: Vector3d, normal: Vector3d) -> Vector3d:2 """Calculates the reflection vector off a surface."""3 # Ensure normal is unitized4 n = Vector3d(normal)5 n.Unitize()67 # Mathematical reflection formula: R = V - 2(V·N)N8 # (Where V is pointing TOWARDS the surface)9 dot_product = incident * n10 reflection = incident - 2 * dot_product * n1112 return reflection
Rejection(Vector3d, Vector3d):
1def Rejection(v: Vector3d, axis: Vector3d) -> Vector3d:2 """Calculates the rejection vector of v relative to axis."""3 projection = Projection(v, axis)4 return v - projection