Hand drawn lines
I'm experimenting with drawing hand-drawn lines in p5.js. I want basic lines that kinda "wobble" due to human imperfection. The idea is to use a noise function and draw the line point by point with a slight deviation from the straight path.
Drawing a circle
If we have a way to draw a line, then I assume we can draw a circle using small segments. Let's try it out:
draw(300, 300, (p5) => {
p5.noFill()
p5.textAlign(p5.CENTER)
p5.textStyle(p5.NORMAL)
p5.background("#F9F0EA")
const samples = [0, 0.1, 0.4, 0.7]
samples.forEach((wobbliness, idx) => {
const centerX = 150
const centerY = 150
const r = 20 + idx * 30
const totalPoints = 100
const angleStep = p5.TWO_PI / totalPoints
for (let i = 0; i <= totalPoints; i++) {
const angle = i * angleStep
const x1 = centerX + p5.cos(angle) * r
const y1 = centerY + p5.sin(angle) * r
const x2 = centerX + p5.cos(angle + angleStep) * r
const y2 = centerY + p5.sin(angle + angleStep) * r
p5.stroke(50)
p5.fill(100)
new Line(p5, x1, y1, x2, y2, { wobbliness }).draw()
p5.noStroke()
p5.text(wobbliness, centerX, centerY - r - 10)
}
})
})
Implementation
Here is the Line
class that draws the wobbly lines. The update
method calculates the next point in the line and the draw
method renders the line.
By default the lines are drawn in one go, but you can pass an animated
option to draw the line point by point, which will require calling the update
method in a loop.
class Line {
static PROGRESS_INC = 0.01
static WOBBLE_SCALE = 20
constructor(p5, x1, y1, x2, y2, opts = {}) {
this.p5 = p5
this.progress = 0
this.wobbliness = p5.constrain(opts.wobbliness ?? 0.3, 0, 1)
this.source = p5.createVector(x1, y1)
this.target = p5.createVector(x2, y2)
this.prev = this.source.copy()
this.points = [this.source]
while (!opts.animated && this.progress < 1) {
this.update()
}
}
update() {
if (this.progress >= 1) return
this.progress = this.p5.constrain(this.progress + Line.PROGRESS_INC, 0, 1)
const prev = this.points[this.points.length - 1]
const noise = this.p5.noise(prev.x / 100, prev.y / 100)
const wobble =
this.p5.map(noise, 0, 1, -1, 1) * this.wobbliness * Line.WOBBLE_SCALE
const nextPoint = P5.Vector.lerp(this.source, this.target, this.progress)
const ortho = P5.Vector.sub(nextPoint, this.source)
.rotate(this.p5.HALF_PI)
.normalize()
this.points.push(P5.Vector.add(nextPoint, ortho.mult(wobble)))
}
draw() {
this.p5.beginShape()
this.points.forEach((p) => this.p5.curveVertex(p.x, p.y))
this.p5.endShape()
}
}