We have used X and Y as if their meaning were fixed. On our diagrams, one unit of X points right and one unit of Y points up. That is a useful choice, but it is still a choice. A coordinate pair needs a pair of reference axes before it describes a position.
Start with familiar axes
Take the point (0.6,0.5). In the usual coordinate system, we reach it by moving 0.6 units right and 0.5 units up. Write those two unit steps as vectors:
vec2 axisX = vec2(1.0, 0.0);
vec2 axisY = vec2(0.0, 1.0);
vec2 local = vec2(0.6, 0.5);
vec2 world = local.x * axisX + local.y * axisY;
// (0.6, 0.0) + (0.0, 0.5) = (0.6, 0.5)
This looks like extra work because the result has the same numbers as the input. But it exposes what the coordinates mean: each number scales one chosen step, then we add the resulting displacements.
We will call the fixed coordinates of the drawing world coordinates. The coefficients attached to our movable axes are local coordinates. Both systems share the origin in this chapter. We are changing the axes, not moving the origin.
Choose different steps
Now give each local axis a new direction and length, measured in world coordinates:
vec2 axisX = vec2(0.7, 0.1);
vec2 axisY = vec2(0.2, 0.6);
One local X step now moves 0.7 world units right and 0.1 up. One local Y step moves 0.2 right and 0.6 up. The axes do not have to be perpendicular, and their lengths do not have to match.
These two independent directions form a basis for the plane. They give us two building blocks for describing any displacement. Their order matters: the first belongs to local.x, the second to local.y.
Do not normalize these vectors. Their lengths are part of the coordinate system. If we double axisX, one local X unit covers twice as much world distance. Normalizing would discard that choice.
Add the two parts
Keep the local coordinates at (0.6,0.5). Scale each axis separately:
vec2 firstPart = local.x * axisX; // (0.42, 0.06)
vec2 secondPart = local.y * axisY; // (0.10, 0.30)
vec2 world = firstPart + secondPart; // (0.52, 0.36)
Draw the first contribution from the origin. From its endpoint, draw the second contribution without rotating it. Their combined route ends at the world point. Reversing the order of the two additions reaches the same place; the four endpoints form a parallelogram.
This is vector scaling and addition from chapter 02, used as a coordinate system. The local numbers are coefficients. The axis vectors carry the world displacement for each local unit.
A negative coefficient reverses its contribution. For local = (-0.6,0.5), the first part is (-0.42,-0.06), so the world result becomes (-0.32,0.24). A zero coefficient removes one part; it does not remove that axis from the coordinate system.
The same rule builds the grid. Hold one local coordinate fixed and vary the other: the world points trace a straight line parallel to the corresponding axis. Repeat for several fixed values and the two families of lines make a tilted grid.
Try three test points
Before working with an arbitrary coordinate pair, test three simple inputs. Each one tells us something directly about the chosen axes:
| Local coordinates | World expression | World result |
|---|---|---|
| (1,0) | axisX | (0.7,0.1) |
| (0,1) | axisY | (0.2,0.6) |
| (1,1) | axisX + axisY | (0.9,0.7) |
The first two inputs land at the axis endpoints. The third reaches the opposite corner of their unit parallelogram. The local origin (0,0) always stays at the world origin because both contributions are zero.
These small tests also catch swapped axes. If (1,0) lands on the endpoint meant for Y, the code has attached the first coordinate to the wrong vector. We will reuse this check when we put the axes into a matrix.
Keep the point, change the numbers
Changing the basis while keeping the local coordinates fixed usually moves the point. Keeping the world point fixed requires a different question: which local coefficients reach it using the new steps?
Use the gold target at world position (0.5,0.5). The ordinary axes reach it with local coordinates (0.5,0.5). Now try:
vec2 axisX = vec2(0.5, 0.0);
vec2 axisY = vec2(0.25, 0.5);
Only the Y step contributes to world height. We need one whole Y step to reach height 0.5. That also moves us 0.25 right. The remaining 0.25 of horizontal distance needs half an X step. Our new coordinates are therefore (0.5,1.0):
0.5 * vec2(0.5, 0.0) + 1.0 * vec2(0.25, 0.5)
// (0.25, 0.0) + (0.25, 0.5) = (0.5, 0.5)
The world point has not changed. Its description has. This is why a pair of coordinates without its basis is incomplete information.
Do not assume that dotting the world vector with each new axis recovers its local coordinates. The projection from chapter 06 measures a component along a direction. Our axes can overlap in direction and have different lengths. For the default example, the two raw dots are (0.4,0.32), not (0.6,0.5). General recovery needs a separate calculation, which we will develop later.
When the axes collapse
Suppose both axes point along the same line. Any sum of their multiples remains on that line. We can still run the formula, but we no longer have a basis for the whole two-dimensional plane.
For axisX=(0.6,0.3) and axisY=(1,0.5), local (1,0) and (0,0.6) both reach (0.6,0.3). Coordinates are no longer unique, and a point off that line cannot be reached at all.
A zero axis also removes a direction; two zero axes send every input to the origin. Nothing requires division in our forward formula, so these cases remain safe to draw. Nearly parallel axes still form a basis if they are independent, though recovering coordinates becomes sensitive. We will examine that distinction in chapter 15.