Ollin

Where a point is

A drawing call measures every number from the top-left corner of the canvas, in logical points. The x axis grows to the right, and the y axis grows down.

The canvas coordinate system: origin at the top left, x right, y down, with the point (380, 240) marked

So drawCircle(380, 240, 60) sits 380 across and 240 down from that corner. Inside draw(), width and height hold the canvas size. That is why drawCircle(width / 2, height / 2, 200) lands in the center of any canvas.

y grows downward, where school graphs grow upward. Screens have been addressed this way since text terminals, and p5.js, Processing, and OPENRNDR all count the same. One consequence is worth keeping in mind. A positive angle, which the math convention calls counter-clockwise, turns clockwise on screen.

Points are not pixels

The canvas is measured in logical points, so the numbers in a sketch never change with the display. A Retina screen draws the same canvas at its own native pixel density, and there is nothing to switch on. An export renders at canvasSize exactly, so a 1080-square canvas writes a 1080 by 1080 image. The --render-scale flag draws each frame more finely than that, then averages the extra samples back into those same pixels.

Two things really are in pixels, and both say so. An Image read through its subscript starts at the top-left texel, (0, 0). A depth map is a row of numbers counted from that same corner.

The canvas is not the window

The canvas is the surface you draw on. The window is a view of that canvas, scaled to fit the screen (see windowMode). So resizing the window does not change width, and an export ignores the window completely. The mouse arrives already converted into canvas points, as mouseX / mouseY.

The desk around the window has its own frame. It is measured in screen points from the top-left corner of the main screen. Call canvasOnScreen to find where this canvas sits on that desk, which is how several windows can look into one world.

Fractions travel better than fixed numbers

A sketch written in fixed numbers is tied to one canvas size. Three tools state the same intent as a fraction instead. Use uv(u, v) for a position, so uv(0.5, 0.5) is the center of any canvas. Use scale for a size, and fractions of width / height / shortSide for layout.

Moving the frame instead of the numbers

Transforms move the coordinate system, not the marks. After translate(100, 100), (0, 0) means what (100, 100) meant before, so every call after that line is measured in the new frame. Marks you already recorded do not move, because a call records rather than paints. withState { } scopes a change to its block and puts the frame back afterward.

The other frames a sketch meets

A sketch that only draws stays in one frame. Bring in a camera, a 3D scene, or a machine, and a second frame arrives with the data. Each frame is named where it enters, and each one has a conversion back to the canvas:

FrameOriginyUnitsCrossing back to the canvas
The canvastop-leftdownpointsit is the destination
uv in a shader or filtertop-leftdown0…1 of the layermultiply by the layer size
Image and depth pixelstop-leftdownpixelsscale into the rectangle you drew it in
Vision resultslower-leftup0…1 of the frameVisionSpace.point(_:_:in:), or a tracker's own bounds(in:)
The 3D worldwherever you put itupworld unitsproject(_:)
The desktop-left of the main screendownscreen pointscanvasOnScreen
A laser fieldcenterup-1…1ProjectorSpace.transform(from:)

One pattern runs through the table. Anything that draws counts y down, and anything that measures the world tends to count y up. So when a point lands in the wrong half of the picture, y is nearly always the reason.

  • Canvas - the canvas size, the window, uv, scale, and pixel density in full.
  • Geometry - Vector2 and the other value types, plus what y-down does to angles.
  • Drawing - translate / rotate / scale and the state stack.
  • The frame - why a transform cannot move something already drawn.
  • Guide, Chapter 1 - the same ground taught from the first sketch.
  • Guide, Appendix B - every frame above, with the arithmetic for crossing between them.

↑↓ move ↩ open