Initializing 3D Canvas...

The Vector

1 min read•1 page

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").
2 min read•1 page

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).

python
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)
9
10 def __str__(self):
11 return f"Vector3d({self.X}, {self.Y}, {self.Z})"
12
13 @classmethod
14 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.

1 min read•1 page

Magnitude():

If a Point is an absolute location in space, a Vector is a movement through space. While a Point is tethered to the global coordinate system, a Vector represents pure direction and magnitude (length) without a fixed position.
python
1import math
2
3class Vector3d:
4 def Magnitude(self) -> float:
5 """Calculates the Euclidean length of the vector."""
6 return math.sqrt(self.X**2 + self.Y**2 + self.Z**2)
The Illusion of Location: You can move a vector anywhere in your 3D scene, and as long as its length and direction remain the same, it is mathematically the exact same vector.

At its core, a vector is simply an ordered list of values:

  • (X, Y) is a 2D vector
  • (X, Y, Z) is a 3D vector
  • (X, Y, Z, W) is a 4D vector
Vector X
4.00
Vector Y
3.00
While a vector may have different interpretations across physics, machine learning, and geometry, the underlying linear algebra is always the same.
1 min read•1 page

LengthSquared():

Returns the squared length of the vector. This is equivalent to X² + Y² + Z².
python
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
Vector Length
2.00
Why use it? Calculating the actual length requires 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.
2 min read•1 page

IsUnitVector(tolerance):

Checks whether the vector's magnitude is equal to 1.0 within a given floating-point tolerance. A vector of length 1.0 is called a unit vector (or normalized vector) and represents pure direction.
python
1import math
2
3class Vector3d:
4 @property
5 def Length(self) -> float:
6 """Returns the magnitude of the vector."""
7 return math.sqrt(self.X**2 + self.Y**2 + self.Z**2)
8
9 def IsUnitVector(self, tolerance: float = 1e-6) -> bool:
10 """Returns True if the vector's length is within tolerance of 1.0."""
11 return abs(self.Length - 1.0) <= tolerance
Length
1.00
Unit vectors are essential whenever you want to measure an angle or project geometry along a direction without accidentally scaling the result by the vector's length.
2 min read•1 page

Unitize() / Reverse():

Unitize() divides each component by the vector's length so its magnitude becomes exactly 1.0 while preserving direction. Reverse() negates all components to flip the vector 180°.
python
1import math
2
3class Vector3d:
4 def Unitize(self) -> bool:
5 """Normalizes the vector in-place to length 1.0."""
6 length = math.sqrt(self.X**2 + self.Y**2 + self.Z**2)
7 if length <= 1e-12:
8 return False
9 self.X /= length
10 self.Y /= length
11 self.Z /= length
12 return True
13
14 def Reverse(self) -> None:
15 """Flips the vector to point in the exact opposite direction."""
16 self.X = -self.X
17 self.Y = -self.Y
18 self.Z = -self.Z
Always guard against zero-length vectors before dividing by length in Unitize() to avoid ZeroDivisionError or NaN coordinates.
X
2.00
Y
-2.00
1 min read•1 page

Addition(Vector3d):

Adding two vectors together is known as head-to-tail addition. Placing the tail of Vector B at the tip of Vector A leads directly to the tip of the resultant sum Vector A + B.
python
1class Vector3d:
2 def Add(self, other: 'Vector3d') -> 'Vector3d':
3 """Returns the head-to-tail sum of two vectors."""
4 return Vector3d(self.X + other.X, self.Y + other.Y, self.Z + other.Z)
X
-2.30
Y
0.90
Z
2.00
1 min read•1 page

Subtract(Vector3d):

Subtracts vector B from vector A (A - B). Geometrically, when both vectors start at the origin, A - B is the vector pointing from the tip of B to the tip of A.
python
1class Vector3d:
2 def Subtract(self, other: 'Vector3d') -> 'Vector3d':
3 """Returns self - other (vector from tip of 'other' to tip of 'self')."""
4 return Vector3d(
5 self.X - other.X,
6 self.Y - other.Y,
7 self.Z - other.Z
8 )
Vector B (X)
-2.00
Vector B (Y)
3.00
1 min read•1 page

Scale(float):

Scaling a vector is extremely common. It directly multiplies the vector's 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.
python
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)
Scale
1.50
1 min read•1 page

Translation Invariance:

You cannot translate a vector! Because a vector only represents direction and magnitude, shifting the anchor point where it is drawn in space does nothing to its underlying (X, Y, Z) components.
python
1# Shifting both start and end points by delta leaves the vector unchanged:
2start_shifted = start_point + delta
3end_shifted = end_point + delta
4
5# (end_point + delta) - (start_point + delta) == end_point - start_point
6vector = end_shifted - start_shifted
Anchor X
0.00
Anchor Y
0.00
In homogeneous coordinates (which we will explore in the Matrix article), points have W = 1 (affected by translation) whereas vectors have W = 0 (immune to translation).
1 min read•1 page

DotProduct(Vector3d):

Calculates a single scalar number that represents how aligned two vectors are: positive when pointing in a similar direction, 0 when perpendicular, and negative when pointing in opposite directions.
python
1class Vector3d:
2 def DotProduct(self, other: 'Vector3d') -> float:
3 """Returns the scalar dot product of two vectors."""
4 return self.X * other.X + self.Y * other.Y + self.Z * other.Z
X
4.00
Y
-3.00
Z
2.00
Applications:
  • Alignment, orthogonal projection, similarity, lighting (Lambertian shading), and angle checks.
2 min read•1 page

VectorAngle(Vector3d, Vector3d):

Calculates the shortest angle between two vectors in radians. This formula uses the dot product and the magnitudes of both vectors.
python
1import math
2
3class 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.Z
7
8 # Calculate magnitudes
9 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)
11
12 # Protect against division by zero
13 if mag_self == 0 or mag_other == 0:
14 return 0.0
15
16 # Cosine of the angle
17 cos_theta = dot / (mag_self * mag_other)
18
19 # Clamp to handle precision errors
20 cos_theta = max(-1.0, min(1.0, cos_theta))
21
22 return math.acos(cos_theta)
Vector A (X)
4.00
Vector A (Y)
4.00
Vector A (Z)
0.00
2 min read•1 page

SignedAngleTo(Vector3d, Vector3d):

Calculates the angle from one vector to another, providing a full +/- 180° range.
Standard vector angle functions (AngleTo) only return the absolute shortest angle (0 to 180°). They don't tell you if the rotation is left or right. To know the direction (clockwise vs counter-clockwise), you must provide a reference "normal" vector to look down.
python
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 rotation
4 cross = Vector3d.CrossProduct(v1, v2)
5 angle = math.atan2(cross.Length, v1 * v2)
6
7 # 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 = -angle
11
12 return angle
Vector A Angle Z (deg)
45.00
3 min read•1 page

IsParallelTo(Vector3d):

Determines if another vector has the exact same or exact opposite direction, within a given tolerance.
python
1import math
2
3class 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.Y
14 cross_y = self.Z * other.X - self.X * other.Z
15 cross_z = self.X * other.Y - self.Y * other.X
16
17 cross_mag_sq = cross_x**2 + cross_y**2 + cross_z**2
18
19 if cross_mag_sq <= tolerance**2:
20 # Check dot product to see if they point in the same direction
21 dot = self.X * other.X + self.Y * other.Y + self.Z * other.Z
22 return 1 if dot > 0 else -1
23
24 return 0
Scale Vector A
1.50
Offset Vector A (Y)
0.00

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".

3 min read•1 page

IsPerpendicularTo(Vector3d):

Determines if another vector is perpendicular (orthogonal) to this vector within a given tolerance.
python
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.Length
11 mag_other = other.Length
12
13 if mag_self == 0 or mag_other == 0:
14 return False
15
16 dot = self.X * other.X + self.Y * other.Y + self.Z * other.Z
17
18 # Check if dot product is close to zero relative to lengths
19 return abs(dot) / (mag_self * mag_other) <= tolerance
Angle (deg)
90.00

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.

2 min read•1 page

ProjectOnVector(Vector3d):

Projects a vector onto another vector. This effectively finds the "shadow" or component of the vector that lies perfectly along the target direction.
python
1class Vector3d:
2 def ProjectOnVector(self, other: 'Vector3d') -> 'Vector3d':
3 """Projects this vector onto another vector."""
4 # Find unit vector of the target
5 mag_other = (other.X**2 + other.Y**2 + other.Z**2)**0.5
6 if mag_other == 0:
7 return Vector3d(0, 0, 0)
8
9 unit_other = Vector3d(other.X / mag_other, other.Y / mag_other, other.Z / mag_other)
10
11 # Calculate dot product
12 dot = self.X * unit_other.X + self.Y * unit_other.Y + self.Z * unit_other.Z
13
14 # Scale unit vector by dot product
15 return Vector3d(unit_other.X * dot, unit_other.Y * dot, unit_other.Z * dot)
Rotate Vector A
1.00
1 min read•1 page

Rejection(Vector3d, Vector3d):

Extracts the component of a vector that is perfectly perpendicular to a reference axis.
python
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
Rejection is the mathematical complement to Projection. Together, they perfectly decompose any vector into a parallel component and a perpendicular component. This is critical for structural force decomposition and physics simulations.
Vector X
3.00
Vector Y
4.00
1 min read•1 page

CrossProduct(Vector3d):

Calculates a completely new vector that is perpendicular (90°) to both input vectors, with a magnitude equal to the area of the parallelogram they span.
python
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 )
X
4.10
Y
-1.80
Z
2.60
Applications:
  • Computing face normals, determining interior/exterior orientation, lighting and shading, solar analysis, and CNC toolpath orientation.
3 min read•1 page

Orthogonal():

Generates a vector that is guaranteed to be perpendicular (orthogonal) to the given vector.
python
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 smallest
7 # 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)
11
12 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)
18
19 # Cross product generates a perpendicular vector
20 perp = Vector3d.CrossProduct(self, helper)
21 perp.Unitize()
22 return perp
Vector X
2.00
Vector Y
1.50

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.

2 min read•1 page

Lerp(Vector3d, float):

Linearly interpolates between two vectors. It takes a parameter t (typically 0.0 to 1.0) and blends the two directions and magnitudes.
python
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 1
5 t = max(0.0, min(1.0, t))
6
7 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) * t
11 )
Parameter (t)
0.50
3 min read•1 page

Vector Slerp (Spherical Interpolation):

Unlike standard Linear Interpolation (Lerp) which cuts across a straight chord between two vectors, Slerp rotates smoothly at constant angular velocity along a spherical arc while interpolating the magnitude from A to B.
python
1import math
2
3def VectorSlerp(a: 'Vector3d', b: 'Vector3d', t: float) -> 'Vector3d':
4 """Spherical Linear Interpolation between two vectors."""
5 len_a, len_b = a.Length, b.Length
6 ua, ub = a / len_a, b / len_b
7 dot = max(-1.0, min(1.0, ua.DotProduct(ub)))
8 theta = math.acos(dot)
9
10 if abs(theta) < 1e-6:
11 return a * (1.0 - t) + b * t
12
13 sin_theta = math.sin(theta)
14 w_a = math.sin((1.0 - t) * theta) / sin_theta
15 w_b = math.sin(t * theta) / sin_theta
16
17 # Interpolate unit direction on the sphere, then blend length
18 unit_dir = ua * w_a + ub * w_b
19 length = len_a + (len_b - len_a) * t
20 return unit_dir * length
Interpolate (t)
0.50
2 min read•1 page

Rotate(Vector3d, float):

Rotating a vector is the primary way of altering its direction. By rotating a vector around an axis, its magnitude stays exactly the same, but it points somewhere entirely new.
python
1import math
2
3class Vector3d:
4 def Rotate(self, axis: 'Vector3d', angle_degrees: float) -> 'Vector3d':
5 """Rotates the vector around the given axis using Rodrigues' Formula."""
6
7 # Ensure axis is unitized
8 k = Vector3d(axis)
9 k.Unitize()
10
11 theta = math.radians(angle_degrees)
12 cos_t = math.cos(theta)
13 sin_t = math.sin(theta)
14
15 # Formula: V_rot = V*cos(θ) + (K × V)*sin(θ) + K*(K · V)*(1 - cos(θ))
16 cross_kv = Vector3d.CrossProduct(k, self)
17 dot_kv = k * self
18
19 return self * cos_t + cross_kv * sin_t + k * (dot_kv * (1 - cos_t))
For smooth rotations free from gimbal lock, see the Quaternion article.
Angle
60.00
2 min read•1 page

Reflect(Vector3d, Vector3d):

Calculates the reflection vector when an incident ray hits a surface with a given normal.
python
1def Reflect(incident: Vector3d, normal: Vector3d) -> Vector3d:
2 """Calculates the reflection vector off a surface."""
3 # Ensure normal is unitized
4 n = Vector3d(normal)
5 n.Unitize()
6
7 # Mathematical reflection formula: R = V - 2(V·N)N
8 # (Where V is pointing TOWARDS the surface)
9 dot_product = incident * n
10 reflection = incident - 2 * dot_product * n
11
12 return reflection
This operation is the core of ray tracing, acoustic simulation, and facade glare analysis. It takes an incoming vector (incident) and a surface orientation (normal) to compute the outgoing bounce.
Surface Tilt (deg)
15.00

Related Concepts