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.

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 dist

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.

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() lerp-dotted

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

DegreesRadians
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()