P5 library - Math tidbits
Use proportional values
...
# GOOD - Will adjust to the canvas size
from p5 import *
# Change the width here and see what happens.
WIDTH = 300
def setup():
size(WIDTH, WIDTH/1.6)
no_loop()
def draw():
background("grey")
no_stroke()
fill("red")
rect(0, 0, width/3, height)
fill("white")
rect(1/3*width, 0, width/3, height)
fill("blue")
rect(2/3*width, 0, width/3, height)
run()
# BAD - Will NOT adjust to the canvas size
from p5 import *
# Change the width here and see what happens.
WIDTH = 300
def setup():
size(WIDTH, WIDTH/1.6)
no_loop()
def draw():
background("grey")
no_stroke()
fill("red")
rect(0, 0, 100, 188)
fill("white")
rect(100, 0, 100, 188)
fill("blue")
rect(200, 0, 100, 188)
run()
Constrain a value to a range
constrain(value, start, stop) returns a value which is guaranteed to be in the [start..stop] range.
constrain(1, 3, 7) # returns 3.0 (because 1 is below 3)
constrain(2, 3, 7) # returns 3.0 (because 2 is below 3)
constrain(3, 3, 7) # returns 3.0 (because 3 is in the range)
constrain(4, 3, 7) # returns 4.0 (because 4 is in the range)
constrain(5, 3, 7) # returns 5.0 (because 5 is in the range)
constrain(6, 3, 7) # returns 6.0 (because 6 is in the range)
constrain(7, 3, 7) # returns 7.0 (because 7 is in the range)
constrain(8, 3, 7) # returns 7.0 (because 8 is above 7)
constrain(9, 3, 7) # returns 7.0 (because 9 is above 7)
Here is a program that draws a red dot at the mouse position constrained to the white area:
from p5 import *
def setup():
size(200, 200)
rect_mode(CORNERS)
def draw():
x1 = width * 0.2
y1 = height * 0.2
x2 = width * 0.8
y2 = height * 0.8
mx = constrain(mouse_x, x1, x2) # constrain horizontally
my = constrain(mouse_y, y1, y2) # constrain vertically
push_style()
background(240)
text(f"mouse_x: {mouse_x:.0f} => mx: {mx:.0f}\nmouse_y: {mouse_y:.0f} => my: {my:.0f}", 10, 10)
rect(x1, y1, x2, y2)
stroke("red")
stroke_weight(10)
point(mx, my)
pop_style()
run()
Normalize a value
norm(value, start, stop) returns a value which represents where value is relatively to the [start..stop] range.
- equal to
start: returns 0.0
- equal to
stop: returns 1.0
- in the middle: returns a value between 0 and 1
- below
start: returns a negative value
- above
stop: returns a value greater than 1
norm(1, 3, 7) # returns -0.5 (because 1 is below 3)
norm(2, 3, 7) # returns -0.25 (because 2 is below 3)
norm(3, 3, 7) # returns 0.0 (because 3 is the start)
norm(4, 3, 7) # returns 0.25 (because 4 is in the middle)
norm(5, 3, 7) # returns 0.5 (because 5 is exactly in the middle)
norm(6, 3, 7) # returns 0.75 (because 6 is in the middle)
norm(7, 3, 7) # returns 1.0 (because 7 is the stop)
norm(8, 3, 7) # returns 1.25 (because 8 is above 7)
norm(9, 3, 7) # returns 1.5 (because 9 is above 7)
Map a value from one range to another
map(value, start1, stop1, start2, stop2) converts a value from the [start1..stop1] range into the [start2..stop2] range.
map(1, 3, 7, 10, 50) # returns -10.0
map(2, 3, 7, 10, 50) # returns 0.0
map(3, 3, 7, 10, 50) # returns 10.0
map(4, 3, 7, 10, 50) # returns 20.0
map(5, 3, 7, 10, 50) # returns 30.0
map(6, 3, 7, 10, 50) # returns 40.0
map(7, 3, 7, 10, 50) # returns 50.0
map(8, 3, 7, 10, 50) # returns 60.0
map(9, 3, 7, 10, 50) # returns 70.0
Here is a program that changes the background color according to the x-coordinate of the mouse:
from p5 import *
def setup():
size(400, 400)
def draw():
hue = map(mouse_x, 0, width, 0, 255) # map mouse_x from [0..width] to [0..255]
diameter = map(mouse_y, 0, height, 10, height * 0.8) # map mouse_y from [0..height] to [10..height*0.8]
push_style()
background(240)
fill(0)
text(f"mouse_x: {mouse_x:.0f} => hue: {hue:.0f}\nmouse_y: {mouse_y:.0f} => diameter: {diameter:.0f}", 10, 10)
color_mode(HSB)
fill(hue, 255, 255)
no_stroke()
circle(width / 2, height / 2, diameter)
pop_style()
run()
Calculate the distance between two points
dist(x1, y1, x2, y2) calculates the distance between two points (x1,y1) and (x2,y2).
dist(1, 2, 4, 6) # returns 5.0
mag(x, y) is a shortcut for dist(0, 0, x, y), the distance between the origin (0,0) and point (x,y).
Here is a program that shows the distance from the canvas center to the mouse:
from p5 import *
def setup():
size(200, 200)
def draw():
cx = width / 2
cy = height / 2
d = dist(cx, cy, mouse_x, mouse_y)
background(240)
line(cx, cy, mouse_x, mouse_y)
text(f"Distance: {d:.0f}", 10, 10)
run()
Calculate a value between two other values
lerp is an abbreviation for linear interpolation and is used for easing (= changing progressively) from a start value into a stop value.
It is convenient for creating motion along a straight path and for drawing dotted lines.
lerp(start, stop, amt) calculates start + (stop - start) * amt.
- when
amt is 0: returns start
- when
amt is 0.1: returns a little bit more than start, etc
- when
amt is 0.5: returns the average of start and stop
- when
amt is 0.9: returns a little bit less than stop, etc
- when
amt is 1: returns stop
lerp(1, 5, 0) # returns 1.0 - the start value
lerp(1, 5, 0.1) # returns 1.4
lerp(1, 5, 0.2) # returns 1.8
lerp(1, 5, 0.3) # returns 2.2
lerp(1, 5, 0.4) # returns 2.6
lerp(1, 5, 0.5) # returns 3.0 - the middle (average)
lerp(1, 5, 0.6) # returns 3.4
lerp(1, 5, 0.7) # returns 3.8
lerp(1, 5, 0.8) # returns 4.2
lerp(1, 5, 0.9) # returns 4.6
lerp(1, 5, 1) # returns 5.0 - the stop value
Here is a program that draws a dotted line:
from p5 import *
def setup():
size(100, 100)
no_loop()
def draw():
x1, y1 = 10, 10
x2, y2 = 60, 60
n = 20
stroke("red")
for i in range(0, n, 2): # the step of 2 is what makes the line dotted
amt1 = i * 1 / n # dash start amount
amt2 = (i + 1) * 1 / n # dash stop amount
line(
lerp(x1, x2, amt1), lerp(y1, y2, amt1),
lerp(x1, x2, amt2), lerp(y1, y2, amt2)
)
run()
Angles
P5 uses radians instead of degrees.
One full turn is 2π (= 360°).
The angles are measured clockwise with zero being east.
You can convert degrees to radians with radians(degrees) and radians to degrees with degrees(radians).
Besides the usual PI, P5 also defines TWO_PI, HALF_PI, QUARTER_PI, and TAU which is 2π.
| Degrees | Radians |
| 0° | 0 |
| 45° | PI/4 = QUARTER_PI |
| 90° | PI/2 = HALF_PI |
| 135° | 3*PI/4 = 3*QUARTER_PI |
| 180° | PI |
| 225° | 5*PI/4 = 5*QUARTER_PI |
| 270° | 3*PI/2 = 3*HALF_PI |
| 315° | 7*PI/4 = 7*QUARTER_PI |
| 360° | 2*PI = TWO_PI = TAU |
Calculate the coordinates from an angle
...
...
Example #1 - Polygons
Here is a program that draws a regular polygon for a given number of sides and radius.
from p5 import *
from math import cos, sin
NPOINTS = 5 # this is the same as the number of sides
RADIUS = 120
def setup():
size(300, 300)
no_loop()
def draw():
background(240) # light grey
translate(width/2, height/2)
fill(200, 255, 200) # light green
stroke_weight(3)
begin_shape()
for i in range(NPOINTS):
x = RADIUS * cos(radians(i * 360 / NPOINTS))
y = RADIUS * sin(radians(i * 360 / NPOINTS))
vertex(x, y)
end_shape(CLOSE)
run()
Challenge #1: Modify the code to start drawing from the top (North) instead of from the right (East).
Challenge #2: Modify the code to draw stars instead of polygons.
Example #2 - Pulsating circle
Here is a program that draws a pulsating circle with changing color.
from p5 import *
from math import cos, sin
def setup():
size(300, 300)
def draw():
# frame_count increases by one every time
angle_rad = radians(frame_count)
# Calculate the new diameter.
freq = 1 # change to adjust the speed
diameter = map(sin(freq * angle_rad), -1, 1, height*0.1, height*0.9)
# Calculate the new color.
freq = 0.2 # change to adjust the speed
hue = map(cos(freq * angle_rad), -1, 1, 0, 255)
# Update the canvas.
color_mode(RGB)
background(240) # lightgrey
translate(width/2, height/2)
color_mode(HSB)
fill(hue, 255, 255)
stroke_weight(3)
circle(0, 0, diameter)
run()
Calculate the angle from some coordinates
...
from p5 import *
from math import atan2
def setup():
size(300, 300)
def draw():
length = 1.8 * dist(width/2, height/2, mouse_x, mouse_y)
dx = mouse_x - width/2
dy = mouse_y - height/2
angle_rad = atan2(dy, dx)
background(240)
push_matrix()
translate(width/2, height/2)
rotate(angle_rad)
line(-length/2, 0, length/2, 0)
translate(length/2, 0)
triangle(0, 0, -15, -10, -15, 10)
pop_matrix()
run()